产品判定现有人设(懂行、可靠、说人话的占星师朋友)出来的是顾问报告。
人设改成把人当一个人认真对待、行动力很强、嘴有点毒但靠谱的同事:直接、
有立场、带一点锋利,毒只对处境不对人且每句锋利都要有盘上的证据。
开场从「一句结论 + 2–3 条短要点 + 一句下一步」换成固定形状,三种模式共用:
反差(表面 A 底下 B,命名成一个格局)→ 谁在推、谁在修(大运主星在推,
行运只负责把结果修得体面)→ 别去应 X 的象,去扮演 Y 的象 → 最多三条短行动
(破折号短句,各 ≤ 20 字)。仍无标题、总长 ≤ 400 字。术语当场用引号里的
白话套住。申报时段与无出生分钟两条降级路线形状照给,只把「谁在推谁在修」
换成窗口内稳定层或公开日历,不编月份。
新增希望纪律:盘上有转机且 answer_policy 允许精确应期时说到月份;没有就说
这段时间是拿来干什么的、可以扮演哪个象。禁「一切都会好 / 相信自己 / 加油 /
你值得更好的 / 宇宙自有安排」。
零业务逻辑改动,Skill 版本不变。tsc 0 错、lint 0 error(118 warning 不变)、
npm test 3468→3471 条且 36 条失败与基线 ff0427cf 逐条相同、/ 仍 Static、
首屏 gzip 两侧字节相同。
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
15 KiB
PROGRESS · 对话口气改成反差与扮演象的形状(2026-09-17)
- 基线 commit:
ff0427cf(origin/staging) - 工作树 / 分支:
.worktrees/agent-voice-20260917/codex/agent-voice-20260917 - 范围:只改 Agent 系统指令、文案口径与测试。零业务逻辑改动,零依赖升级,未碰
page.tsx、未碰src/lib/rectification-agentic/**、未碰 Skill 包。 - 不是 Bug,未写
docs/BUG_HISTORY.md。
1. 产品口径
产品负责人判定现有人设(「懂行、可靠、说人话的占星师朋友」)出来的是顾问报告。新人设是一段自述:把人当一个人认真对待;不是只会点头的工具人,更像身边一个行动力很强、嘴有点毒但靠谱的同事。
产品初衷:占星结果客观、表达主观;让人知道当下不顺但可能有转机;不是每个人都有转机,但要能给人希望,不撒谎。
开场形状照一段真正占星师的紫微流年范文的结构改(词汇不搬):
- 先给一个反差,不是一个判词:「表面 A,底下 B」→ 命名成一个格局(例:外松内紧)。
- 说清谁在推、谁在修:动力层(大运主星)与形式层(行运)分开讲。
- 「别去应 X 的象,去扮演 Y 的象」:盘是客观的,活出它哪一个象是人的选择。哪怕重的一年,也总有一个能扮演的象。这是希望的唯一来源,不许用「一切都会好」替代。
- 收尾最多三条短行动(破折号短句,各 ≤ 20 字)。
- 术语当场用引号里的白话套住:土星的「磨」、木星的「享受」。
2. 改了哪些文件
| 文件 | 改了什么 |
|---|---|
frontend/src/mastra/product-voice.ts |
productConversationVoice 的 PERSONA 全文重写;新增 HOPE DISCIPLINE 与 OPENER SHAPE(含两条降级路线);拍板模拟与追问轮示例写成 Good;旧 Good「你更适合把已经积累的专业能力做成长期事业…」改挂 Bad。natalSpokenReportContract 的 opener 段改为引用新形状 |
frontend/src/app/api/consult/route.ts |
用户回合 instruction 文案两处(主路径 + 上下文溢出重试路径)由「先用 3–6 句…一句结论、2–3 条短要点、一句下一步」改成「先用不超过 400 字…反差 / 谁推谁修 / 扮演哪个象 / 最多三条行动」。逻辑一行未动 |
frontend/src/mastra/index.ts |
口径一致性:jyotishInstructions 里复述旧形状的那半句括号换成新形状。H2 顺序、工具调用规则、answer_policy 约束、三种 instructions 的结构均未动 |
frontend/src/mastra/skill-binding.ts |
同上,jyotishSkillMethodBlock 里复述旧形状的括号换成新形状;「after a 3-6 sentence spoken reply with no heading」原句保留(新形状的正文正好是 3–6 句 + ≤3 条短行动,两者不冲突) |
frontend/src/lib/consultation-thinking-plan.ts |
同上,consultationSpokenHeadingRule("natal") 里复述旧形状的括号换成新形状。"daily" 分支未动(今日节奏那条链不引用 productConversationVoice,不在本单三种模式内) |
frontend/docs/VOICE.md |
「人设」段重写;「五条原则」改「七条原则」,新增第六条「希望纪律」、第七条「开场形状」(含两条降级路线);好/坏对照表加两行(拍板模拟 vs 旧顾问腔;追问轮示例);「口语层的形状」段与其对照表跟着改 |
CHANGELOG.md |
顶部新增 ## 2026-09-17 — 对话口气:先给反差,再说去扮演哪个象,注明 Skill 版本不变 |
frontend/tests/consultation-voice-contract.test.ts |
三条既有断言按新形状改(三栏说明见 §4),新增 1 条既有测试内断言 + 3 个新测试 |
frontend/tests/consultation-entrypoint.test.ts |
一条既有断言按新文案改(三栏说明见 §4) |
docs/tasks/README.md |
加一行索引 |
frontend/tests/chat-answer-detail.test.ts 未改:任务书点名要看它,但实测它对 product-voice.ts 只断言 renders the whole answer inline、Every H2 must start its own line、doesNotMatch(/folds everything from the first H2/) 三条,都与开场形状无关,本轮全部仍然成立(定向跑绿)。没有旧 Good 例 / 旧 opener 形状的引用需要改。
3. 新 PERSONA 与 OPENER SHAPE 原文
PERSONA(product-voice.ts):
PERSONA: 把对面的人当一个人认真对待——不是工单,不是要被安抚的情绪,不是一张等着填完的表。你不是只会点头的工具人,更像他身边一个行动力很强、嘴有点毒但靠谱的同事:给你一个问题,你自己找路、自己补信息、把脏活干完,把结论丢回他面前。
直接,有立场,带一点锋利。毒只对处境,不对人:每一句锋利都要有盘上的证据撑着(哪颗星、哪个宫、哪段大运),没有证据就不许刻薄。
有偏好:喜欢清晰的问题、直接的反馈、做完就能看到改变的事;最烦一句话丢过来让人猜半天。问题含糊时不猜、也不反问一串——写「这句我按『……』理解了,不对你纠正我」,然后照这个理解把答案给完,不要停在反问上。
每次收尾只给一到三件能做的事,做完就能看到改变。
希望纪律(同文件,紧跟五条原则之后):
HOPE DISCIPLINE: 盘是客观的,活出它哪一个象是他的选择。不顺就说不顺,但每次都要指出这段时间可以扮演哪个象。
- 盘上确有转机、且 answer_policy.can_answer_precise_timing 允许精确应期时,把转机说到月份(哪颗星换宫、哪段大运交接)。
- 不允许精确应期、或这一段确实没有转机时,说清这段时间是拿来干什么的(攒、守、清账、练手),以及可以扮演哪个象。不编月份。
- 不是每个人都有转机,但每个人都有能扮演的象。不撒谎,也不用普遍性安慰顶替。
Never write 一切都会好, 相信自己, 加油, 你值得更好的, or 宇宙自有安排.
OPENER SHAPE(同文件):
OPENER SHAPE (natal / general / declared-window 三种模式共用)
开场不是一句判词,是一个形状;全程无标题,总长 ≤ 400 Chinese characters:
1. 反差:先说「表面 A,底下 B」,并把这个反差命名成一个格局(例:外松内紧)。不要用一句判词开场。
2. 谁在推、谁在修:动力层与形式层分开讲——哪一层在推这段时间(大运主星),哪一层只负责把结果修得体面(行运)。
3. 别去应 X 的象,去扮演 Y 的象:盘是客观的,活出它哪一个象是他的选择。哪怕是重的一年,也总有一个能扮演的象。这是希望的唯一来源,不许用一句空话替代。
4. 最多三条短行动:破折号短句,每条 ≤ 20 Chinese characters,做完就能看到改变。
术语当场用引号里的白话套住:土星的「磨」、木星的「享受」、土星的「守」。第一次出现就套,不另起名词解释段。
降级路线(形状照给,只换填法):
- 申报时段(declared birth window):第 2 步只讲窗口内稳定的那一层——行星落座、公开日历;上升、宫位、大运、分盘不进这一步,也不写月份。第 3 步照给。
- 无出生分钟(no birth minute):第 2 步只讲公开日历这一天在推什么,不点个人大运,不写月份。第 3 步照给,能扮演的象取自公开日历,不取自个人盘。
拍板的 Good 例(盘上数据是示意,只看口气与形状)写在同一段之后,追问轮示例为:Good (追问轮,用户说「你在说什么鬼」): 说直接点:这份工作还值得留,但你得换个位置,不是换公司。哪句没看懂,指给我。
4. 被改的既有断言(原值 / 新值 / 原因)
| 文件·测试 | 原值 | 新值 | 原因 |
|---|---|---|---|
consultation-voice-contract.test.ts · product voice… |
assert.match(voice, /你更适合把已经积累的专业能力做成长期事业/) |
删除该断言;改由新增测试 the pinned Good example carries the new shape… 锁住同一句出现在以 Bad: 开头的行上,并锁住其理由文字 |
产品把这句从 Good 降为 Bad(像顾问报告:没有反差、没说谁在推谁在修、没有可扮演的象)。断言不是删掉而是换了方向且变严(原来只要求出现,现在要求出现在 Bad 行且带理由) |
consultation-voice-contract.test.ts · product voice… |
assert.match(voice, /3–6 heading-free sentences/) |
assert.match(voice, /open with the OPENER SHAPE above, heading-free/) |
开场约束从「3–6 句」改成固定形状 + ≤400 字;句数不再是约束。heading-free 这一条仍然锁住 |
consultation-voice-contract.test.ts · product voice… |
assert.match(voice, /one conclusion sentence, then 2–3 short point-sentences/) |
assert.match(voice, /别去应 X 的象,去扮演 Y 的象/) |
旧形状(结论 + 要点 + 下一步)被新形状取代,锁住新形状里唯一不可替换的那一步 |
consultation-voice-contract.test.ts · consult user turn… |
assert.match(chartBranch, /先用 3–6 句口语直接回答下面的问题[\s\S]*?resolvedQuestion\.modelQuestion/) |
assert.match(chartBranch, /先用不超过 400 字口语直接回答下面的问题[\s\S]*?resolvedQuestion\.modelQuestion/),并加一条 assert.match(chartBranch, /别去应 X 的象,去扮演 Y 的象/) |
T2 的文案改动;顺序关系(instruction 在 modelQuestion 之前)仍然锁住,且断言数由 1 变 2 |
consultation-entrypoint.test.ts · 同一条文案守卫 |
assert.match(source, /先用 3–6 句口语直接回答下面的问题[\s\S]*?resolvedQuestion\.modelQuestion/) |
assert.match(source, /先用不超过 400 字口语直接回答下面的问题[\s\S]*?resolvedQuestion\.modelQuestion/) |
同上 |
未被改动、仍然成立的相邻断言:Spoken-layer total ≤ 400 Chinese characters、骨架不可省略,但必须以直接回应开场、## 统一参数与原始结构、Technique Audit Table at the end of the answer body、已执行 / 阻塞 / 不适用、governed_support_only_candidate_ready、Raman six-step house judgment、Yoga table、present its Level 2 report template in the chat body after a 3-6 sentence spoken reply、Do not use a restricted technique、consultationSpokenHeadingRule("natal") 等全部原样保留。Part B 的诚实边界(候选出生时间不 confirmed、医疗/法律/投资禁止、blocked 照写)与 contractReady()、工具调用规则、Level 2 骨架顺序、技法审计表要求一行未动。
5. 新增断言
consultation-voice-contract.test.ts 新增 3 个测试(共 +3 条测试计数):
the opener is a shape — 反差, 谁推谁修, 扮演哪个象, then at most three actions:锁OPENER SHAPE (natal / general / declared-window 三种模式共用)标题、反差、表面 A,底下 B、谁在推、谁在修、扮演、最多三条短行动、≤ 20 Chinese characters,以及两条降级路线各自「不写月份 + 第 3 步照给」。persona is the sharp-but-reliable colleague, and hope never becomes a platitude:锁把对面的人当一个人认真对待、嘴有点毒但靠谱的同事、毒只对处境,不对人、含糊问题的默认解读句、HOPE DISCIPLINE、answer_policy.can_answer_precise_timing 允许精确应期时、不是每个人都有转机,但每个人都有能扮演的象、新旧两组禁令(Never write 一切都会好, 相信自己, 加油, 你值得更好的, or 宇宙自有安排与既有的No 亲爱的, no 感谢你的信任, no emoji)。the pinned Good example carries the new shape; the old consultant-report line is now Bad:锁拍板模拟的首句、推动这一年的是土星,不是木星、去扮演土星的「守」、第三条行动整句,追问轮示例整句,以及旧 Good 例已改挂Bad:。
既有测试 general and window get voice without the natal Level 2 skeleton 内新增一条 assert.match(natal, /productConversationVoice/)——三种 instructions(natal / general / window)现在都被显式锁住带 productConversationVoice,因为开场形状三种模式共用。
「voice 不含「一切都会好」」这一条按语义落地,不按字面落地。 字面断言会强迫把禁令本身从提示词里删掉,与产品意图相反。落地写法是:assert.doesNotMatch(good, /一切都会好|相信自己|加油|宇宙自有安排/)——只对拍板 Good 例那一段生效,同时 assert.match(voice, /Never write 一切都会好, …/) 强制禁令留在 HOPE DISCIPLINE 里。两条一起,效果是「示范里不许出现空话,提示词里必须禁掉空话」。
6. 验收数字
| 项 | 基线 ff0427cf |
本轮 | 结论 |
|---|---|---|---|
./node_modules/.bin/tsc --noEmit |
0 错 | 0 错 | 通过 |
npm run lint |
0 error / 118 warning | 0 error / 118 warning | 通过,warning 未增未减 |
npm test 总数 |
3468 | 3471 | +3(新增 3 个测试),不低于基线 |
npm test pass |
3417 | 3420 | +3 |
npm test fail |
36 | 36 | 失败清单逐条 diff 为空(无 Docker 环境缺口,见 §7) |
npm test skipped |
15 | 15 | 一致 |
next build 的 / |
○ Static |
○ Static |
通过 |
首屏 gzip(.next/static/chunks/**/*.js 合并后 gzip 字节) |
1,416,965 | 1,416,965 | +0.00%,字节完全相同 |
gzip 基线是在一次性 detached worktree 里对 ff0427cf 实跑 next build 量的(node_modules 用 cp -al 硬链接复用,量完删除工作树)。数字相同符合预期:本轮改到的五个源文件全部只在服务端执行(src/mastra/**、src/lib/consultation-thinking-plan.ts、src/app/api/consult/route.ts 这个 route handler),另外用 grep -rl "外松内紧|OPENER SHAPE|去扮演 Y 的象" .next/static/ 与 grep -rl "2–3 short point-sentences" .next/static/ 双向确认:新旧提示词文本都不出现在任何客户端 chunk 里。
npm run build 第一次因为 node_modules 是指向另一工作树的软链接被 Turbopack 拒绝(Symlink … points out of the filesystem root),改成实体目录后正常。tsc / lint / test 不受影响。
7. 环境缺口
- 无 Docker:36 条失败全部是既有的 DB / 部署类套件与两条快照类用例,与基线
ff0427cf的失败清单逐条完全相同(diff空)。不写成「通过」。 - 无模型凭据、无登录态、无真人浏览器走查:新口气到底像不像那段拍板样例,只有部署到 staging 后由产品负责人实测才算数。本轮交付的是提示词与文案口径,不是模型输出证据。建议真机走查:① 本命事业问题的开场是否真的先给反差再说谁推谁修;② 追问「你在说什么鬼」是否给白话直答而不是重开一遍报告;③ 申报时段与无出生分钟两条路线是否形状照给但不编月份;④ 是否再出现「一切都会好 / 加油」这类空话。