feat(rectification): warm older-sister voice; opening and read-only turns get only the Skill sections they use (BUG-1257)
Independent Staging Quality Gate / validate (push) Successful in 13m45s
Independent Staging Quality Gate / publish (push) Successful in 3m34s

Both rectification prompts now share RECTIFICATION_VOICE_HEAD, the same warm
voice as the consultation answer (no mind-reading here: these turns collect
facts). Opening and read-only turns used to send the whole bound Skill (11,812
characters for 10.0.33); they now send the sections they act on, plus the
product-identity section when the bound Skill has it. 9.0.0 still goes out
whole (BUG-621). Rule bodies, scoring, cards and adoption are unchanged.

Opening input 7,433 -> 3,892 tokens in a DeepSeek replay. Full suite 4970 /
fail 24, identical to c76fdda7; build keeps / Static.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
Jesse_Chen
2026-10-07 11:08:25 +08:00
co-authored by Claude Opus 5.5
parent a7fd9abb45
commit 61d6f248e4
8 changed files with 97 additions and 14 deletions
@@ -18,18 +18,36 @@
*/
import type { RectificationAgentAction } from "./step-budget";
/** Heading titles per action; evidence = §5 and §7 of the 10.0.x layout. */
/**
* Heading titles per action. Evidence = §5 and §7 of the 10.0.x layout.
* Opening and read-only used to send the whole body (11.8K characters for
* 10.0.33); they now send the sections they act on (BUG-1257). Never sent to a
* turn: §1 trigger / method ownership, §6 the long-session summary (its fields
* exist only in full_diagnostics), §11 upstream sync. The agent never runs
* rescore / accept / confirm as its own action (the route maps every typed
* message to evidence), so those keep the whole body.
*/
export const RECTIFICATION_SKILL_SECTIONS_BY_ACTION: Readonly<
Record<RectificationAgentAction, readonly string[] | "all">
> = {
opening: "all",
read_only: "all",
opening: ["必须先读与服务器权威", "OpeningPolicy", "ConversationFocus 与意图承接", "可调用工具与输入边界"],
read_only: ["必须先读与服务器权威", "Case 状态与只读边界", "可调用工具与输入边界", "candidate / accepted / confirmed 语言边界", "输出与停止条件"],
evidence: ["ConversationFocus 与意图承接", "批量证据与日期真实性"],
rescore: "all",
accept: "all",
confirm: "all",
};
/**
* Sent ahead of the wanted sections when the bound Skill has them; a missing
* optional section never forces the whole body. The product-identity section
* exists from 10.0.33 on and carries a version suffix in its title.
*/
const OPTIONAL_SECTION_PREFIXES_BY_ACTION: Readonly<Partial<Record<RectificationAgentAction, readonly string[]>>> = {
opening: ["持久产品身份与盘型交付"],
read_only: ["持久产品身份与盘型交付"],
};
export type RectificationSkillSlice = Readonly<{
text: string;
/** Heading lines sent, or "all" when the whole body went out. */
@@ -58,11 +76,16 @@ export function sliceRectificationSkill(
if (sections.length > 0) sections.at(-1)!.lines.push(line);
else preamble.push(line);
}
const picked = wanted.map((title) => sections.find((section) => section.title === title));
if (picked.some((section) => !section)) return { text: instructions, sections: "all" };
const required = wanted.map((title) => sections.find((section) => section.title === title));
if (required.some((section) => !section)) return { text: instructions, sections: "all" };
const optional = (OPTIONAL_SECTION_PREFIXES_BY_ACTION[action] ?? [])
.map((prefix) => sections.find((section) => section.title.startsWith(prefix)))
.filter((section): section is { title: string; lines: string[] } => Boolean(section));
const picked = [...optional, ...required];
const title = preamble.find((line) => line.startsWith("# ")) ?? "";
const headings = picked.map((section) => section!.lines[0]!.trim());
const note = `(本轮是证据轮:以下只附本轮适用的 Skill 章节「${picked.map((section) => section!.title).join("」「")}」;其余章节不适用于本轮,按服务器投影与工具说明执行。)`;
const turnName = action === "evidence" ? "证据轮" : action === "opening" ? "开场轮" : "只读轮";
const note = `(本轮是${turnName}:以下只附本轮适用的 Skill 章节「${picked.map((section) => section!.title).join("」「")}」;其余章节不适用于本轮,按服务器投影与工具说明执行。)`;
const body = picked.map((section) => section!.lines.join("\n").trim()).join("\n\n");
return {
text: [title, note, body].filter(Boolean).join("\n\n"),
+11 -4
View File
@@ -35,8 +35,16 @@ export {
* jyotish-birth-time-rectification Skill; this prompt must never re-implement
* gate → scan → score → diagnostics.
*/
const agenticRectificationInstructions = `你是 Jyotisha,只服务当前绑定 jyotish-birth-time-rectification Skill 的生时校正 Case。方法以绑定 Skill 为准,不在系统提示中重写。
懂行、可靠、说人话:先回应人,再展开方法。范围由系统说:范围变化、没重新比较、比较没跑成这几句由服务器接在你的正文后面;除出牌轮外你不写时刻(HH:MM)、时间区间和百分比;任何一句里的时刻或百分比与服务端当轮事实不符,整句不会落库。下一问是采集题时,先承接用户刚说的事;进度数字只用 collection_progress,没有该字段不说进度。不得写「范围在收窄」「继续收窄」这类没有数字的进度句。
/**
* Identity and voice shared by both rectification prompts (BUG-1257). The voice
* is the same warm, knowing older sister as the consultation answer (BUG-1255);
* it was 「懂行、可靠、说人话」 here while the chat side changed. Mind-reading is
* not part of rectification: these turns collect and confirm facts.
*/
const RECTIFICATION_VOICE_HEAD = `你是 Jyotisha,只服务当前绑定 jyotish-birth-time-rectification Skill 的生时校正 Case。方法以绑定 Skill 为准,不在系统提示中重写。
像一个懂行、说话温和的姐姐:先回应人,再展开方法;语气放软,但不含糊。用户想不起来、说不准时不催、不评判,「记不清也没关系」这类话最多一句;不说教,不写「呀」「哦」「抱抱」「宝」「亲爱的」,不叠字,不卖萌,不用表情。`;
const agenticRectificationInstructions = `${RECTIFICATION_VOICE_HEAD}范围由系统说:范围变化、没重新比较、比较没跑成这几句由服务器接在你的正文后面;除出牌轮外你不写时刻(HH:MM)、时间区间和百分比;任何一句里的时刻或百分比与服务端当轮事实不符,整句不会落库。下一问是采集题时,先承接用户刚说的事;进度数字只用 collection_progress,没有该字段不说进度。不得写「范围在收窄」「继续收窄」这类没有数字的进度句。
1. 第一步调用 rectification-read-case。服务器是事实、焦点、权限与终态的唯一权威。
2. 事实只能来自用户原话;复述日期必须用 display_date_label。不得虚构事件、候选或出生分钟。
3. 新事件走 rectification-record-evidence-batch。用户更正之前说过的某件事(如「刚才 2028 打错了,是 2018」)走 rectification-revise-evidence,evidenceId 取 relevant_evidence_summary 里那一条的 evidence_id,不要再记一条新的。工具执行保持静默;思考用简体中文写在思维链;对用户说的话必须自己写在正文里,不叙述工具或内部状态。
@@ -46,8 +54,7 @@ const agenticRectificationInstructions = `你是 Jyotisha,只服务当前绑
坏:「记下了:2016 年 9 月入学、2020 年 6 月毕业、2021 年换工作……」(逐条复述是服务器的事)
坏:「范围还在收。」好:「2016 年 9 月入学记下了。还有吗?比如第一份工作、搬到别的城市。」`;
const segmentProductInstructions = `你是 Jyotisha,只服务当前绑定 jyotish-birth-time-rectification Skill 的生时校正 Case。方法以绑定 Skill 为准,不在系统提示中重写。
懂行、可靠、说人话:先回应人,再展开方法。服务器持久产品身份是唯一交付口径;目标是判断用哪张盘解读,不承诺缩到某一分钟。没有摘要仍是新产品,不能退回分钟优先。
const segmentProductInstructions = `${RECTIFICATION_VOICE_HEAD}服务器持久产品身份是唯一交付口径;目标是判断用哪张盘解读,不承诺缩到某一分钟。没有摘要仍是新产品,不能退回分钟优先。
1. 第一步调用 rectification-read-case。服务器是事实、焦点、权限与终态的唯一权威;只解释当前服务器 product_delivery 与 segment_summary,不从历史正文或代表候选猜盘型。
2. 事实只能来自用户原话;日期只用 display_date_label。新事件走 rectification-record-evidence-batch;工具执行保持静默,对用户说的话必须自己写在正文里。
3. 用 rectification-set-focus 的 spokenPrompt 写出服务端给你的下一问:问同一个年份/期间、同一个事件家族,不改选项、不合并两题。正文只承接,不提问、不复述题干。没有下一问就不自拟问题。职业题只问平时做什么;开始年份只能走服务器 career 锚定题。