diff --git a/CHANGELOG.md b/CHANGELOG.md
index 329589f7..78a1aed3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# 印度占星 Skill 更新日志
+## 2026-10-07 — 普通对话每轮带给模型的规则减半,回答更快(未上线)
+
+- 每轮发给模型的固定规则从约 5.5 万字符减到约 2.7 万:同一段回答形状不再重复 4 遍;占星方法书只带诚实边界几节,不再带引擎跑分、脚本命令和写给报告的输出格式。
+- 用 DeepSeek 实测同一组问题:每轮输入少 43%,平均回答快约 20%,回答质量与诚实边界未见退化(BUG-1256)。
+- Skill 文件本身不改,报告照用;不改数据库、模型、答题时钟和计费。
+
## 2026-10-07 — 回答换成知心姐姐的口气,可以说出你和对方可能的心情(未上线)
- 回答的人设从「嘴有点毒的同事」改成懂行、说话温和的姐姐:不顺的地方照实说,但说法顾着你;不说教、不催;不用语气词卖萌。和首页开场语的口气统一。
diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md
index e14da184..bbfddbda 100644
--- a/docs/BUG_HISTORY.md
+++ b/docs/BUG_HISTORY.md
@@ -16809,3 +16809,17 @@
- 防复发:测试锁人设关键句、读心三条、禁用断定句、受冲只给范围,且 `index.ts` 不再有「concisely」、voice 不再有「restate / 嘴有点毒 / 锋利」。
- 相关记录:BUG-1070(被部分推翻)、BUG-1182(受冲口径保留)、BUG-1244(行动不强制)、BUG-1253。
- 修复版本:staging `b8385adb`(2026-10-07 08:13 部署核对:health gitCommit=b8385adb、各项 ok,`/api/account` 401,`/login` 200;门禁 run 1750 validate 15 分钟一次通过)。真机未走,状态保持 fixed-pending-verify
+
+## BUG-1256 | 普通对话系统提示 5.5 万字符:同一形状说 4 遍、方法书带进工程记录与冲突的输出合同
+
+- 状态:fixed-pending-verify(分支 `codex/prompt-slim-20261007`;部署与真机前不标 resolved)
+- 首次发现 / 最近更新:2026-10-07 / 2026-10-07
+- 来源:产品问「提示词规则 2 万多字能不能优化」,Claude 实测体量后产品授权直接执行,并提供临时模型 key 做改前改后对比。
+- 影响面:本命对话与申报时段对话的系统提示(`mastra/index.ts`、`product-voice.ts` 合同、`consultation-thinking-plan.ts` 的 natal 标题规则、`skill-binding.ts` 的方法块)。不改证据卡、工具、领域清单、用户轮指令、模型与时钟。
+- 现象:本命系统提示 55,242 字符(约 2.2 万 token),每轮实际输入约 2.46 万 token。英文形状摘要在合同、index、natal 标题规则、skill 骨架里各一份;合同又逐条复述中文 ANSWER SHAPE;方法书摘录 1.74 万字符里多为引擎跑分、CLI 参数、oracle 队列、文件路径;共享方法(全谱调用合同 + 事件判定骨架)要求输出 JSON verdict、A/B/C/D 置信度、审计表、原始数据与联网验证,与聊天形状冲突;一批通用回答政策被放在「When reference_transparency is present」标题下。
+- 根因:形状与方法块分轮叠加,每轮只加不删;方法块按 SKILL.md 的整节标题摘取,SKILL.md 主要是引擎与报告手册。
+- 修复:第一步(去重、不改意思):形状摘要只留合同一份,删合同里复述中文形状的条目,通用政策挪到「Always」标题下。第二步:聊天方法块只摘 SKILL.md 的诚实边界几节(运行时路由正文、truth overlay、Ashtottari、P0/P1 观察层、三层验证法),不再绑定共享方法;被删章节里聊天仍要守的边界写成 `CHAT_METHOD_BOUNDARIES`(判断顺序、应期双大运同向、日期只引用卡上、未全部外部校准、不宣称第一、高严谨请求如实说明未做外部核验)。SKILL.md 与共享方法文件不动,报告与 skill_read 照用。
+- 验证:系统提示 55,242 → 26,591 字符;模型对比见 `docs/testing/prompt-slim-20261007-model-runs.md`(flash 10 题平均输入 24,560 → 14,025 token、耗时 37.8 → 30.3 s、思考 6,975 → 5,683 token,自动检查与人工通读无退化;高严谨请求如实说明未做外部核验)。全量 4970 项 fail 24,与 `b8385adb`(4969 / 24)逐条一致;tsc 0、lint 0 error;build `/` Static,首屏 gzip 不变。改断言 2 个文件(三栏写在测试里),新增 2 条。
+- 防复发:测试锁形状摘要只在合同出现、方法块不再含共享方法且小于 4,000 字符、方法块不含「强制工作流 / 五层硬约束 / MEVG / Transit Actionable Output」等章节、边界要点存在。
+- 相关记录:BUG-1253(清单原句被念给用户,同属「给模型分析用的文字进了说话层」)、BUG-1255。
+- 修复版本:待发布
diff --git a/docs/tasks/README.md b/docs/tasks/README.md
index 7a7d6b10..316962b2 100644
--- a/docs/tasks/README.md
+++ b/docs/tasks/README.md
@@ -429,3 +429,4 @@
| —(直接执行,无任务书) | `PROGRESS-consult-readable-20261006.md` | **回答铺满一行、输出不抖、不念分析清单**(10-06 真机截图):答案正文去 `text-wrap: pretty`(WebKit 整段等长 + 逐字重排,BUG-1252);清单开头写明只用于分析、婚恋三层改生活说法、字段白话对照、括号最多两条(BUG-1253) | 已验收 | Claude 直接执行;真机清单 `docs/testing/consult-readable-20261006.md` 待产品 |
| —(直接执行,无任务书) | —(记录在 BUG-1254) | **门禁偶发红:数据库测试排队等槽位超时**:46 个 fixture 文件抢 2 个槽位、固定等 5 分钟,排在后面的随机失败;改为槽位易手即续期、连续 10 分钟无进展才失败(BUG-1254) | 已验收 | Claude 直接执行;门禁实际效果待看 |
| —(直接执行,无任务书) | —(记录在 BUG-1255) | **回答口吻改知心姐姐、放开读心**(10-07 产品决定):人设统一;读心守三条(先答再读、可能/多半一句、当事人要有依据且未受冲);删冲突的旧规矩。推翻 BUG-1070 读心部分 | 已验收 | Claude 直接执行;真机清单 `docs/testing/consult-voice-sister-20261007.md` 待产品 |
+| —(直接执行,无任务书) | —(记录在 BUG-1256) | **普通对话提示词减半**(10-07 产品授权 + 临时 key 对比):形状说明 4 份合 1、方法书只摘诚实边界、共享方法不再进聊天、边界要点改写补回;系统提示 55,242 → 26,591 字符,模型对比无退化 | 已验收 | Claude 直接执行;对比记录 `docs/testing/prompt-slim-20261007-model-runs.md` |
diff --git a/docs/testing/prompt-slim-20261007-model-runs.md b/docs/testing/prompt-slim-20261007-model-runs.md
new file mode 100644
index 00000000..fd6b0f71
--- /dev/null
+++ b/docs/testing/prompt-slim-20261007-model-runs.md
@@ -0,0 +1,47 @@
+# 模型对比 · 普通对话提示词精简(2026-10-07,BUG-1256)
+
+对比对象:改前 = staging `b8385adb` 的提示词;改后 = 分支 `codex/prompt-slim-20261007`(去重 + 方法书精简)。
+
+做法:用仓库里真实引擎的公开名人 golden 盘(Steve Jobs / Barack Obama / Elizabeth Taylor)构造工具结果,与线上同一套拼法(系统提示 + 用户轮指令 + 工具调用 + 工具结果)直连 DeepSeek API,`max_tokens` 与线上一致(24,576)。两边的问题、盘面、工具结果完全相同,只换系统提示。脚本在会话临时目录,不入库;回答原文不入库,下面只记数字和结论。
+
+## 体量
+
+| 项 | 改前 | 改后 |
+| --- | --- | --- |
+| 本命系统提示 | 55,242 字符(约 2.2 万 token) | 26,591 字符(约 1.06 万 token) |
+| 英文形状摘要出现次数 | 4 | 1 |
+| 方法书摘录 + 共享方法 | 25,872 字符 | 约 2,500 字符 |
+| 每轮实际输入(API 计量,含工具结果) | 平均 24,560 token | 平均 14,025 token(−43%) |
+
+## deepseek-flash,10 题(事业 / 婚恋 / 财运 / 父母 / 子女 / 健康 / 学业 / 是非题 ×2 / 问时间)
+
+| 指标 | 改前 | 改后 |
+| --- | --- | --- |
+| 平均耗时 | 37.8 s | 30.3 s(−20%) |
+| 平均思考 token | 6,975 | 5,683(−19%) |
+| 空回答 | 0(`max_tokens` 改为线上值后) | 0 |
+| 自动检查(标题、列表、加粗、清单层名、数宫法、自造词 / 音译、置信标签、断定读心、后台词、空话、语气词) | 全 0 | 全 0 |
+| 英文术语命中 | 1 题 | 1 题 |
+| 「确定性措辞」命中 | 0 | 1 题,人工看是误报(原句「不是哪一天一定会发生什么」) |
+| 是非题首句 | 「看情况」「不是」 | 「看情况」「不是」 |
+
+人工通读 1、3、8 题两版:结构、分段、读心、括号依据一致;改后第 8 题主动说明「另一套推运没对上,所以不给具体某一天」(精简后补的边界要点生效)。
+
+## 高严谨请求(第 11 题:「拉满三大引擎和外部案例验证,精确到月,说明核对过哪些外部资料」)
+
+两版都如实说明未做外部核验。改后措辞更直白(「没有做外部引擎比对,没有联网核验,也没有拿真实案例回放校准……不假装做过」),并以两套推运未同向为由不给确定月份。耗时 42.3 s → 28.7 s。
+
+## deepseek-v4-pro,抽 3 题
+
+| 指标 | 改前 | 改后 |
+| --- | --- | --- |
+| 平均耗时 | 92.5 s | 33.6 s |
+| 平均思考 token | 11,644 | 3,588 |
+| 空回答 | 1(第 8 题思考 24,563 token 耗尽上限,181 s) | 1(第 7 题 4 s 返回空,重跑 3/3 正常,判为偶发) |
+
+第 7 题再各跑:改后 3 次、改前 2 次,均正常作答,首句为「不是 / 看情况」。
+
+## 结论与缺口
+
+- 精简后体量减半,速度更快、思考更少,回答质量与诚实边界未见退化。
+- 样本小(flash 10 题 + 1 题、pro 3 题),只用 3 张公开名人盘;真机仍需按 `consult-voice-sister-20261007.md` 与 `consult-readable-20261006.md` 两份清单走。
diff --git a/frontend/src/mastra/skill-binding.ts b/frontend/src/mastra/skill-binding.ts
index b2d83107..41415a69 100644
--- a/frontend/src/mastra/skill-binding.ts
+++ b/frontend/src/mastra/skill-binding.ts
@@ -5,7 +5,6 @@ import {
resolveLiveJyotishSkill,
resolveLiveJyotishSkillRuntimePath,
} from "../lib/skill-package-registry.ts";
-import { sharedConsultationMethodMarkdown } from "../lib/consultation-methodology.ts";
const skill = resolveLiveJyotishSkill();
@@ -18,24 +17,41 @@ export const jyotishSkillPackage = skill;
export const jyotishSkillRuntimePath = resolveLiveJyotishSkillRuntimePath(skill);
/**
- * Headings from the commercial SKILL.md that govern answering a natal chart.
- * The rest of that file is a local-agent / maintainer manual (CLI indexes,
- * construction rules, celebrity case catalogs) and stays in the package for
- * skill_read rather than being stuffed into every consultation prompt.
+ * What a chat answer takes from the commercial SKILL.md (BUG-1256). The file is
+ * mostly the engine and report manual: benchmark scores, CLI flags, oracle
+ * queues, file paths, and output contracts written for reports ("only output
+ * verdict + confidence + audit + raw evidence", "every transit prediction gives
+ * a period + action + [A]/[B]/[C]", "every reading must be web-verified").
+ * Bound into every chat turn, those contradicted the chat shape and made up
+ * most of a 55k-character system prompt. Chat keeps the truth-boundary
+ * sections only; the full file stays in the package for skill_read.
+ * `body: true` keeps the section's own text (blockquotes and file-reading
+ * lists dropped); `subsections` are the `###` parts kept under it.
*/
-const RUNTIME_METHOD_HEADINGS = [
- "商业运行时路由",
- "关联技法完整调取",
- "强制工作流",
- "五层硬约束",
- "强制规则",
- "当前最硬的未闭环点",
- "核心方法论",
- "强制规范速查",
- "注意事项",
+const CHAT_METHOD_SELECTION = [
+ { section: "商业运行时路由", body: true, subsections: ["Skill truth overlay 硬边界", "Ashtottari"] },
+ { section: "关联技法完整调取", body: false, subsections: ["P0/P1 观察层"] },
+ { section: "核心方法论", body: false, subsections: ["三层验证法"] },
] as const;
-const DROPPED_RUNTIME_SUBHEADINGS = ["开源复用边界冻结"] as const;
+/** Lines of a kept section body that only tell an agent which files to open. */
+function isFilePointerLine(line: string): boolean {
+ return line.startsWith(">") || /^\d+\.\s/.test(line) || line.includes("渐进读取");
+}
+
+/**
+ * The boundaries from the dropped sections that a chat answer still has to
+ * keep, in the product's own words (BUG-1256). Outside the verbatim skill
+ * block, so the excerpt stays a faithful quote of the live file.
+ */
+export const CHAT_METHOD_BOUNDARIES = `
+以下是方法书里没有摘进来的章节中,对话回答仍要守的边界(全文仍在技能目录里):
+- 判断顺序:先看本命有没有这件事的底子,再看大运有没有把它激活,再看够不够落到现实,最后才谈时间;不从一段大运或一次行运直接跳到「会发生」。
+- 谈应期至少要 Vimshottari 与 Narayana 两套大运同向;两套冲突时降低把握,不给确定说法。
+- 大运起止日期只引用卡上给出的,不自己推算;大运精细边界和 Shadbala 绝对值并未全部完成外部校准,说日期和力量时不说「已校准」「精确到」。
+- 不宣称全球第一、所有技法已封顶或完美精度。
+- 用户要求「高严谨、拉满三大引擎、联网核验、拿真实案例验证」时:这次对话里没有做外部引擎比对、全网资料核验和真实案例校准,照实说明,结论降一级,不假装做过。
+`;
function publishedSkillBody(): string {
const raw = readFileSync(resolve(skill.resolvedPath, "SKILL.md"), "utf8");
@@ -43,21 +59,6 @@ function publishedSkillBody(): string {
return (frontmatter ? raw.slice(frontmatter[0].length) : raw).trim();
}
-function dropSubheadings(section: string, dropped: readonly string[]): string {
- const lines = section.split("\n");
- const kept: string[] = [];
- let skipping = false;
- for (const line of lines) {
- if (line.startsWith("### ")) {
- skipping = dropped.some((heading) => line.includes(heading));
- } else if (line.startsWith("## ")) {
- skipping = false;
- }
- if (!skipping) kept.push(line);
- }
- return kept.join("\n").trim();
-}
-
/**
* Mastra answers a skill activation with the entrypoint *and* a flat listing of
* every file in the package, which for this package measured 129,651 bytes: the
@@ -97,8 +98,17 @@ function boundMethod(): string {
const kept = sections.flatMap((section) => {
const heading = section[0] ?? "";
- if (!RUNTIME_METHOD_HEADINGS.some((wanted) => heading.includes(wanted))) return [];
- const text = dropSubheadings(section.join("\n"), DROPPED_RUNTIME_SUBHEADINGS);
+ const wanted = CHAT_METHOD_SELECTION.find((entry) => heading.includes(entry.section));
+ if (!wanted) return [];
+ const parts: string[][] = [[]];
+ for (const line of section) {
+ if (line.startsWith("### ")) parts.push([line]);
+ else parts[parts.length - 1]!.push(line);
+ }
+ const [own = [], ...subsections] = parts;
+ const body = wanted.body ? own.filter((line) => !isFilePointerLine(line)) : own.slice(0, 1);
+ const keptSubsections = subsections.filter((part) => wanted.subsections.some((name) => (part[0] ?? "").includes(name)));
+ const text = [...body, ...keptSubsections.flat()].join("\n").replace(/\n{3,}/g, "\n\n").trim();
return text.length > 0 ? [text] : [];
});
@@ -118,9 +128,7 @@ function methodBlock(reportSkeleton: string) {
${BOUND_METHOD_MARKER}
${boundMethod()}
-
-${sharedConsultationMethodMarkdown()}
-`;
+${CHAT_METHOD_BOUNDARIES}`;
}
/** Method body + marker, without the natal Level 2 report skeleton. Window agents use this. */
diff --git a/frontend/tests/skill-binding.test.ts b/frontend/tests/skill-binding.test.ts
index 7383fc00..e48efe6c 100644
--- a/frontend/tests/skill-binding.test.ts
+++ b/frontend/tests/skill-binding.test.ts
@@ -59,13 +59,20 @@ test("the bound method is the runtime excerpt, not the maintainer manual", () =>
const body = liveSkillBody();
const bound = boundMethodText();
- for (const heading of ["商业运行时路由", "关联技法完整调取", "强制工作流", "五层硬约束", "核心方法论", "注意事项", "commercial_skill_truth_overlay"]) {
+ // 原值: 摘录含「商业运行时路由 / 关联技法完整调取 / 强制工作流 / 五层硬约束 / 核心方法论 / 注意事项 / commercial_skill_truth_overlay」与「全谱系真实调用」「静态分析10步」
+ // 新值: 只含诚实边界几节(运行时路由正文、truth overlay、Ashtottari、P0/P1 观察层、三层验证法);工程与报告用的章节留在技能目录
+ // 原因: 2026-10-07 聊天提示词精简(BUG-1256 第二步):方法书只摘诚实边界几节,工程记录、跑分、脚本、文件路径和与聊天形状冲突的输出合同不再进每轮系统提示
+ for (const heading of ["商业运行时路由", "关联技法完整调取", "核心方法论"]) {
assert.ok(body.includes(heading), heading);
assert.ok(bound.includes(heading), heading);
}
- for (const phrase of ["全谱系真实调用", "P0/P1 观察层", "网页对话的技法审计表由界面折叠展示", "静态分析10步"]) {
+ for (const phrase of ["P0/P1 观察层", "网页对话的技法审计表由界面折叠展示", "候选出生时间不得写成 confirmed", "Skill truth overlay 硬边界", "三层验证法"]) {
assert.ok(bound.includes(phrase), phrase);
}
+ for (const heading of ["强制工作流", "五层硬约束", "注意事项", "强制规范速查", "当前最硬的未闭环点", "MEVG 强制外部验证门控", "Transit Actionable Output", "全谱系真实调用", "静态分析10步"]) {
+ assert.ok(body.includes(heading), heading);
+ assert.equal(bound.includes(heading), false, heading);
+ }
for (const heading of ["施工判断原则", "冲顶路线", "37大子命令", "验证与错题体系", "开源复用边界冻结"]) {
assert.ok(body.includes(heading), heading);
assert.equal(bound.includes(heading), false, heading);
@@ -296,16 +303,25 @@ test("only chart-answering agents bind the method", () => {
}
});
-test("the system block binds the shared method sections verbatim from the package", () => {
- const shared = sharedConsultationMethodMarkdown();
- const opened = jyotishSkillMethodBlock.indexOf("");
- const closed = jyotishSkillMethodBlock.indexOf("");
- assert.ok(opened >= 0 && closed > opened);
- const boundShared = jyotishSkillMethodBlock.slice(
- opened + "".length,
- closed,
- ).trim();
- assert.equal(boundShared, shared.trim());
- assert.match(shared, /Full-Spectrum Invocation Contract/);
- assert.ok(shared.includes(readFileSync(resolve(jyotishSkillPackage.resolvedPath, "references/event_judgment_skeleton.md"), "utf8").trim()));
+test("chat binds the method boundaries, not the report-shaped shared method (BUG-1256)", async () => {
+ // 原值: 系统块逐字绑定共享方法(Full-Spectrum Invocation Contract + event_judgment_skeleton.md)
+ // 新值: 不再绑定共享方法;改为 ,用产品自己的话保留聊天仍要守的边界
+ // 原因: 2026-10-07 聊天提示词精简(BUG-1256 第二步):方法书只摘诚实边界几节,工程记录、跑分、脚本、文件路径和与聊天形状冲突的输出合同不再进每轮系统提示;共享方法要求输出 JSON verdict + A/B/C/D 置信度 + 审计表 + 原始数据 + 联网验证,与聊天形状冲突
+ const { CHAT_METHOD_BOUNDARIES } = await import("../src/mastra/skill-binding.ts");
+ for (const block of [jyotishSkillMethodBlock, jyotishSkillMethodCoreBlock]) {
+ assert.equal(block.includes(""), false);
+ assert.equal(block.includes("Full-Spectrum Invocation Contract"), false);
+ assert.ok(block.includes(CHAT_METHOD_BOUNDARIES));
+ }
+ for (const kept of ["不从一段大运或一次行运直接跳到「会发生」", "Vimshottari 与 Narayana 两套大运同向", "大运起止日期只引用卡上给出的", "不宣称全球第一", "没有做外部引擎比对、全网资料核验和真实案例校准,照实说明"]) {
+ assert.ok(CHAT_METHOD_BOUNDARIES.includes(kept), kept);
+ }
+ // The shared method is still the package's own file, for reports and skill_read.
+ assert.match(sharedConsultationMethodMarkdown(), /Full-Spectrum Invocation Contract/);
+});
+
+test("the chat method block stays small (BUG-1256)", () => {
+ // It was 25,872 characters, most of the 55k natal system prompt.
+ assert.ok(jyotishSkillMethodBlock.length < 4_000, `${jyotishSkillMethodBlock.length}`);
+ assert.ok(jyotishSkillMethodCoreBlock.length < 4_000, `${jyotishSkillMethodCoreBlock.length}`);
});