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/);