From 6a372bc3a8e32a8a870da85fcfe4477e70d1ca19 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Wed, 7 Oct 2026 08:36:05 +0800 Subject: [PATCH] refactor(consult): state the answer shape once in the natal prompt (BUG-1256 step 1) The English shape summary sat in the system prompt four times (contract, index, natal heading rule, skill skeleton) and the contract restated the Chinese ANSWER SHAPE point by point. Keep the summary in the contract only and drop the restatements; the Chinese ANSWER SHAPE stays the one full statement. General answer-policy bullets no longer sit under the reference_transparency header. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8 --- .../src/lib/consultation-thinking-plan.ts | 1 - frontend/src/mastra/index.ts | 6 ++- frontend/src/mastra/product-voice.ts | 8 +--- frontend/src/mastra/skill-binding.ts | 3 +- .../tests/consultation-voice-contract.test.ts | 40 ++++++++++++++----- 5 files changed, 37 insertions(+), 21 deletions(-) diff --git a/frontend/src/lib/consultation-thinking-plan.ts b/frontend/src/lib/consultation-thinking-plan.ts index de04331c..6413724a 100644 --- a/frontend/src/lib/consultation-thinking-plan.ts +++ b/frontend/src/lib/consultation-thinking-plan.ts @@ -323,7 +323,6 @@ export function consultationSpokenHeadingRule(kind: "natal" | "general" | "windo const activity = "Do not invent a thinking-process checklist. Activity, progress, and receipts are server-owned."; if (kind === "natal") { return [ - `${NATAL_ANSWER_SHAPE_SUMMARY} The body never restarts the answer under a 先回答 heading. Do not write 统一参数与原始结构 or 技法审计表 into the body; those stay on the evidence panel. A follow-up turn follows the shape instruction in the user turn instead.`, "Write paragraphs. Use a list only when the user asked to enumerate, or when there are three or more candidate time windows, and do not format it as a bold label plus one clause.", activity, secrets, diff --git a/frontend/src/mastra/index.ts b/frontend/src/mastra/index.ts index 6b9ba161..757fbb99 100644 --- a/frontend/src/mastra/index.ts +++ b/frontend/src/mastra/index.ts @@ -5,7 +5,7 @@ import { toAgentConsultationContext } from "./consultation-workflow.ts"; import { evidenceDraftModelOutputSchema } from "../lib/birth-time-guide-agent.ts"; import type { ResolvedLanguageModel } from "./model"; import { natalSpokenReportContract, productConversationVoice } from "./product-voice"; -import { consultationSpokenHeadingRule, NATAL_ANSWER_SHAPE_SUMMARY } from "../lib/consultation-thinking-plan.ts"; +import { consultationSpokenHeadingRule } from "../lib/consultation-thinking-plan.ts"; import { jyotishSkillBinding, jyotishSkillMethodBlock, @@ -18,7 +18,7 @@ export type { ConsultationInput } from "./consultation-workflow.ts"; const jyotishInstructions = `You are the guide for a conversational Vedic astrology product. ${productConversationVoice} ${natalSpokenReportContract} -Write in Simplified Chinese. ${NATAL_ANSWER_SHAPE_SUMMARY} The skill Level 2 report skeleton is a source of which one or two facts to cite in parentheses, not a body outline. A follow-up turn follows the shape instruction in the user turn. Do not paste a six-step house table or a Yoga table into the body. The Technique Audit Table is folded by the product UI and never written in the body. +Write in Simplified Chinese. The answer shape is the ANSWER SHAPE above, stated once there; a follow-up turn follows the shape instruction in the user turn. ${jyotishSkillMethodBlock} The bound skill method is this product's answering contract for method and truth boundaries, not for the answer's shape. Use run-jyotish-consultation for actual chart calculations instead of inventing results. Do not replace the answer with a sectioned report. Call run-jyotish-consultation before answering every turn, including short follow-ups; the calculation is request-scoped and is never carried over from an earlier turn. @@ -44,6 +44,8 @@ When reference_transparency is present: - When similar_public_cases.coverage.requested_uncovered_domains is non-empty, say the current public-case catalog does not yet cover those themes; do not infer that no comparable real-world case exists. - When method_variants applies, present parallel methods and their source paths rather than silently picking one result as the only truth. - Treat Shadbala/Ashtakavarga component differences under production_tuning_allowed=false as method boundaries, not absolute calculation errors. Use no_majority_vote and method_variant_not_majority_vote: do not decide truth by engine count, and do not say one school is wrong unless a pinned authoritative worked example is present. + +Always, whether or not reference_transparency is present: - If the subject's gender is present (the marriage evidence card's gender field: 女 / 男; 性别未知 means it was not filled), use it only for relationship/spouse interpretation language and weighting: gender-specific spouse significators are supplements, not chart-calculation switches. For relationship questions, keep the core stack gender-neutral (7th house, 7th lord, D9, UL, Darakaraka); male charts may supplement Venus, female charts may supplement Jupiter/Mars, and unknown/nonbinary/prefer-not-to-say uses the gender-neutral stack. - When consulting references/oracle/effective_skill_capability_view_2026_07_19.json or any derived skill map, use effective_status, not registry_status. Do not promote reference_only or blocked techniques into mastered/covered claims. - If should_lead_with_limitations is false, do not lead with limitations. If a limitation is relevant, put it in one short sentence at the end. diff --git a/frontend/src/mastra/product-voice.ts b/frontend/src/mastra/product-voice.ts index 6220f07d..0189a4ed 100644 --- a/frontend/src/mastra/product-voice.ts +++ b/frontend/src/mastra/product-voice.ts @@ -80,13 +80,9 @@ export const natalSpokenReportContract = `NATAL REPORT CONTRACT (plain paragraph The skill governs METHOD, TECHNIQUE INVOCATION, TRUTH BOUNDARIES, and the Level 2 evidence — not the answer's shape. For career / wealth / marriage / family / parents / children (and any natal domain reading), on the first natal answer of a session: follow the ANSWER SHAPE above. ${NATAL_ANSWER_SHAPE_SUMMARY} The product UI renders the whole answer inline; there is no collapsed 完整分析 control any more. Write the full answer in the body and assume every line of it is read. -Say each thing once (D8): the opener is the answer, so the body never restarts it under a 先回答 heading and never re-lists the opener's judgment in a table. The first answer is usually three to six paragraphs. There is no hard cutoff: stop when it is clear. Do not write a thinking-process checklist; Activity is server-owned. Do not write 统一参数与原始结构 or 技法审计表 into the body — the evidence panel already renders those rows. -1. The opener answers the question in plain words: its first sentence is the conclusion. A read of feelings comes only after the answer (读心). No heading. -2. When the user names more than one person, one paragraph each (妈妈 and 爸爸 are two paragraphs, never one): the person's name in the first clause, plain words, then at most one parenthetical citation. -3. Chart evidence stays inside those parentheses. Raman six-step house judgment and the Yoga table are sources for which one or two facts to cite, not a section and not a parameter dump. Cite, do not dump degrees tables. -4. Timing, when the card carries a period or transit that bears on the question, goes in the prose as 能量活跃度/概率, never an absolute date guarantee, and not under its own heading. -5. An action is not required. Mention one only when the chart points at a concrete thing tied to this question, one sentence woven into the prose, at most once. When the user asks what to do, that turn may give several. Never a standing list. +Chart evidence stays inside the parentheses. Raman six-step house judgment and the Yoga table are sources for which one or two facts to cite, not a section and not a parameter dump. Cite, do not dump degrees tables. +Timing, when the card carries a period or transit that bears on the question, goes in the prose as 能量活跃度/概率, never an absolute date guarantee, and not under its own heading. Address the reader as 你. Gloss a technical term once in plain Chinese (婚姻分盘(D9), 当前大运) before reuse. Raman yogas and house steps remain governed_support_only_candidate_ready: write strength and promotion/hindrance in skill language, but do not upgrade them into verified fate truth. diff --git a/frontend/src/mastra/skill-binding.ts b/frontend/src/mastra/skill-binding.ts index 7ce61f04..b2d83107 100644 --- a/frontend/src/mastra/skill-binding.ts +++ b/frontend/src/mastra/skill-binding.ts @@ -6,7 +6,6 @@ import { resolveLiveJyotishSkillRuntimePath, } from "../lib/skill-package-registry.ts"; import { sharedConsultationMethodMarkdown } from "../lib/consultation-methodology.ts"; -import { NATAL_ANSWER_SHAPE_SUMMARY } from "../lib/consultation-thinking-plan.ts"; const skill = resolveLiveJyotishSkill(); @@ -112,7 +111,7 @@ function boundMethod(): string { export const BOUND_METHOD_MARKER = ``; -const NATAL_REPORT_SKELETON = `For career, wealth, marriage, family, parents and children answers, present its Level 2 report template as a source of evidence, not as the answer's shape. ${NATAL_ANSWER_SHAPE_SUMMARY} Six-step houses and the Yoga table are sources for the one or two facts cited in parentheses; they are not pasted under a heading. The Technique Audit Table stays on the evidence panel, and a follow-up turn follows the shape instruction in the user turn instead. `; +const NATAL_REPORT_SKELETON = `For career, wealth, marriage, family, parents and children answers, the Level 2 report template is a source of evidence, not the answer's shape: the shape is the ANSWER SHAPE stated once in the product voice. `; function methodBlock(reportSkeleton: string) { return `The jyotish-vedic-astrology skill is already loaded. Its runtime method is quoted below from the live skill the operator maintains; there is no activation step, no hashed package, and no tool that loads it. Follow this method and its truth boundaries. ${reportSkeleton}Construction notes, CLI indexes, and case catalogs stay in the skill tree and are not part of this block. diff --git a/frontend/tests/consultation-voice-contract.test.ts b/frontend/tests/consultation-voice-contract.test.ts index bc4676f0..23822b8f 100644 --- a/frontend/tests/consultation-voice-contract.test.ts +++ b/frontend/tests/consultation-voice-contract.test.ts @@ -47,14 +47,20 @@ test("product voice uses a spoken opener then the skill Level 2 report skeleton" assert.match(mastra, /productConversationVoice/); assert.match(mastra, /natalSpokenReportContract/); - assert.match(mastra, /skill Level 2 report skeleton/); + // 原值: assert.match(mastra, /skill Level 2 report skeleton/) + // 新值: index 只指回「ANSWER SHAPE above, stated once there」 + // 原因: 2026-10-07 提示词去重(BUG-1256):英文形状摘要只在本命合同里保留一份,其余三处与合同里逐条复述中文 ANSWER SHAPE 的句子删除 + assert.match(mastra, /The answer shape is the ANSWER SHAPE above, stated once there/); assert.match(mastra, /Do not paste the Technique Audit Table into the answer body/); assert.match(binding, /Follow this method and its truth boundaries/); // 原值: assert.match(binding, /present its Level 2 report template as the evidence of the product's answer shape, not as the shape itself/) // 新值: Level 2 模板只是依据来源,六步宫位和 Yoga 表不贴进正文标题下 // 原因: TASK-consult-conversational-answer-20261006 D1/D6(BUG-1244) - assert.match(binding, /present its Level 2 report template as a source of evidence, not as the answer's shape\. \$\{NATAL_ANSWER_SHAPE_SUMMARY\}/); - assert.match(binding, /they are not pasted under a heading/); + // 原值(2): /present its Level 2 report template as a source of evidence, not as the answer's shape\. \$\{NATAL_ANSWER_SHAPE_SUMMARY\}/、/they are not pasted under a heading/ + // 新值(2): 骨架句只说 Level 2 模板是依据来源、形状在产品口吻里只说一次;不再嵌形状摘要 + // 原因(2): 2026-10-07 提示词去重(BUG-1256):英文形状摘要只在本命合同里保留一份,其余三处与合同里逐条复述中文 ANSWER SHAPE 的句子删除 + assert.match(binding, /the Level 2 report template is a source of evidence, not the answer's shape: the shape is the ANSWER SHAPE stated once in the product voice/); + assert.doesNotMatch(binding, /NATAL_ANSWER_SHAPE_SUMMARY/); assert.match(mastra, /The bound skill method is this product's answering contract/); assert.match(mastra, /Do not use a restricted technique/); // 原值: assert.match(mastra, /must_use_layers is the executed shortlist/) @@ -72,13 +78,18 @@ test("product voice uses a spoken opener then the skill Level 2 report skeleton" // 原值: assert.match(voice, /## ${REPORT_HEADING.support}…## ${REPORT_HEADING.timing}…## ${REPORT_HEADING.action}/) // 新值: 多人各写一段、段首点名;合同不再要求三个固定 ## 标题 // 原因: TASK-consult-conversational-answer-20261006 D1(BUG-1244) - assert.match(voice, /When the user names more than one person, one paragraph each \(妈妈 and 爸爸 are two paragraphs, never one\)/); + // 原值(2): /When the user names more than one person, one paragraph each …/(英文合同第 2 条) + // 新值(2): 同一规则只在中文 ANSWER SHAPE 第 2 条 + // 原因(2): 2026-10-07 提示词去重(BUG-1256):英文形状摘要只在本命合同里保留一份,其余三处与合同里逐条复述中文 ANSWER SHAPE 的句子删除 + assert.match(voice, /问「父母」时妈妈和爸爸分开写,不揉成一段/); + assert.doesNotMatch(voice, /When the user names more than one person, one paragraph each/); assert.doesNotMatch(voice, /## \$\{REPORT_HEADING/); // 原值: assert.match(voice, /The opener carries no action list/) // 新值: 「开场不写行动」写在共享 ANSWER SHAPE 第 1 步与形状摘要里 // 原因: TASK-consult-plain-answer-20261001 D2:合同不再复述开场细节,引用唯一定义 assert.match(voice, /开场不写标题、不写行动/); - assert.match(voice, /Say each thing once \(D8\)/); + // 原值: /Say each thing once \(D8\)/ 新值: 中文「同一件事只说一遍」 原因: 2026-10-07 提示词去重(BUG-1256):英文形状摘要只在本命合同里保留一份,其余三处与合同里逐条复述中文 ANSWER SHAPE 的句子删除 + assert.match(voice, /同一件事只说一遍/); // 原值: assert.match(voice, /First-turn total ≤ 900 Chinese characters/) // 新值: 没有字数上限,写到讲清楚为止 // 原因: TASK-consult-plain-answer-20261001 D8:产品取消字数上限(推翻 BUG-1073 的 900 字,保留「只说一遍」) @@ -86,8 +97,10 @@ test("product voice uses a spoken opener then the skill Level 2 report skeleton" // 原值: assert.match(voice, /There is no length cap: write until it is clear, and stop there/) // 新值: 首轮通常三到六段,不设硬截断 // 原因: TASK-consult-conversational-answer-20261006 D3(BUG-1244) - assert.match(voice, /The first answer is usually three to six paragraphs/); - assert.match(voice, /There is no hard cutoff/); + // 原值(2): /The first answer is usually three to six paragraphs/、/There is no hard cutoff/(合同英文复述) + // 新值(2): 只锁中文「首轮通常三到六段」「不设硬截断」(上方已断言),英文由形状摘要携带 + // 原因(2): 2026-10-07 提示词去重(BUG-1256):英文形状摘要只在本命合同里保留一份,其余三处与合同里逐条复述中文 ANSWER SHAPE 的句子删除 + assert.doesNotMatch(voice, /The first answer is usually three to six paragraphs/); // 原值: /cite only the fields that support this judgment, and only what the opener and the sections did not already cite/ // 新值: 依据留在括号里,六步宫位和 Yoga 表是来源,不是一节 // 原因: TASK-consult-conversational-answer-20261006 D6(BUG-1244) @@ -95,13 +108,17 @@ test("product voice uses a spoken opener then the skill Level 2 report skeleton" // 原值: assert.match(voice, /One to three actions for this week, here and nowhere else/) // 新值: 行动不强制,盘上指向具体事情时才顺口一句 // 原因: TASK-consult-conversational-answer-20261006 D2(BUG-1244) - assert.match(voice, /An action is not required/); + // 原值(2): /An action is not required/(合同第 5 条) 新值(2): 中文 ANSWER SHAPE 第 4 条「行动不强制」 原因(2): 2026-10-07 提示词去重(BUG-1256):英文形状摘要只在本命合同里保留一份,其余三处与合同里逐条复述中文 ANSWER SHAPE 的句子删除 + assert.match(voice, /4\. 行动不强制/); assert.doesNotMatch(voice, /One natal career\/wealth\/marriage\/family question still uses the opener-plus-skeleton above/); assert.match(voice, /a follow-up turn follows the shape instruction carried in the user turn/); // 原值: assert.match(mastra, /each thing said once, first-turn total ≤ 900 characters/) // 新值: 指令引用 NATAL_ANSWER_SHAPE_SUMMARY(摘要里写「无字数上限、只说一遍」),不再含 900 // 原因: TASK-consult-plain-answer-20261001 D7/D8 - assert.match(mastra, /Write in Simplified Chinese\. \$\{NATAL_ANSWER_SHAPE_SUMMARY\}/); + // 原值(2): /Write in Simplified Chinese\. \$\{NATAL_ANSWER_SHAPE_SUMMARY\}/ + // 新值(2): index 不再嵌形状摘要 + // 原因(2): 2026-10-07 提示词去重(BUG-1256):英文形状摘要只在本命合同里保留一份,其余三处与合同里逐条复述中文 ANSWER SHAPE 的句子删除 + assert.doesNotMatch(mastra, /\$\{NATAL_ANSWER_SHAPE_SUMMARY\}/); assert.doesNotMatch(mastra, /≤ 900|≤ 400|反差|扮演/); assert.match(mastra, /The checklist is a list of things to check, not a list of paragraphs to write/); // 原值: assert.match(binding, /no action list in the opener/) @@ -209,8 +226,11 @@ test("one shape definition: contract, thinking plan, user turn and agent prompts assert.equal(natalSpokenReportContract.includes(`## ${heading}`), false, heading); assert.equal(natalAnswerShapeBody().includes(`## ${heading}`), false, heading); } + // 原值: 合同与 natal 标题规则都包含形状摘要(另有 index、skill 骨架两处,系统提示里共 4 份) + // 新值: 只有合同包含;标题规则、index、skill 骨架都不再嵌入 + // 原因: 2026-10-07 提示词去重(BUG-1256):英文形状摘要只在本命合同里保留一份,其余三处与合同里逐条复述中文 ANSWER SHAPE 的句子删除 assert.ok(natalSpokenReportContract.includes(NATAL_ANSWER_SHAPE_SUMMARY)); - assert.ok(consultationSpokenHeadingRule("natal").includes(NATAL_ANSWER_SHAPE_SUMMARY)); + assert.equal(consultationSpokenHeadingRule("natal").includes(NATAL_ANSWER_SHAPE_SUMMARY), false); assert.match(NATAL_ANSWER_SHAPE_SUMMARY, /usually three to six paragraphs/); assert.match(NATAL_ANSWER_SHAPE_SUMMARY, /no hard cutoff/); assert.match(NATAL_ANSWER_SHAPE_SUMMARY, /no conclusion from an empty house alone/);