Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a00b069dcc | |||
| f3e7270237 |
@@ -1,5 +1,9 @@
|
||||
# 印度占星 Skill 更新日志
|
||||
|
||||
## 2026-09-07 — 区间卡能编译,采用后才说预测窗口,再补经历有下一句提示
|
||||
|
||||
生时校正交付卡不再把编译挡在门外。点采用后,第一轮核对旁白末尾会接上实际的下次事业变动窗口(有才写)。点「再补一件经历」后卡片收起,输入框上方改成收集经历的提示。Skill 版本仍是 10.0.15。
|
||||
|
||||
## 2026-09-07 — 生时校正结尾改成一张区间卡,不再让你在分钟里挑
|
||||
|
||||
问完经历后,交付轮只出一张卡:这段范围、排盘用的代表分钟、这段里稳定和会随分钟变的主题。如果几段之间还有没问过的关系盘或事业盘差别,会并排两三列对照,文案只来自类型表。动作只有「按这个范围用」和「再补一件经历」。不再显示相对支持度,也不再并排三行「候选 A 预测下次事业变动」。Skill 版本 10.0.15。
|
||||
|
||||
@@ -9030,5 +9030,21 @@
|
||||
- 复发自:BUG-558
|
||||
- 修复版本:待发布(`codex/rectification-stop-rescore-fix-20260907`)
|
||||
|
||||
## BUG-583 | 区间交付卡判空顺序使 tsc 失败,staging 无法部署
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-09-07
|
||||
- 最近更新:2026-09-07
|
||||
- 影响面:`rectification-agentic-chat.tsx` 区间卡渲染、采用后首轮旁白、`RectificationRangeDelivery`「再补一件经历」
|
||||
- 用户现象:staging 门禁与 `next build` 因 TypeScript 失败,BUG-581~582 的修复无法部署。采用后看不到承诺过的预测窗口。点「再补一件经历」后卡片消失、输入框上方没有下一句。
|
||||
- 触发条件:`ab57d03f` 合入后跑 `tsc --noEmit`;或交付轮点「再补一件经历」;或采用后进入核对。
|
||||
- 根因:渲染条件先读 `candidateResult.resultId` 再写 `candidateResult &&`,收窄发生在属性读取之后。`prospectiveWindowsNarration` 从交付旁白撤掉后没有接到采用后首轮。`onContinue` 只改本地 dismiss,不给采集提示。步骤条类名 `rectification-step-state` 已由 BUG-575 禁止,不能靠加回步骤条过关。
|
||||
- 修复:判空提到 `dismissedRangeResultId` 之前。`persistNextInterviewIfIdle` 在已采用时把 `prospective_probes` 接到首轮旁白末尾;`deferredCareerWindow` 仍留在交付旁白。收起卡片后用 `RangeDeliveryContinueHint` 显示「第 1 步·收集经历」加 `continueCollectFallback`,不用被禁的步骤条类名。
|
||||
- 验证:`frontend/tests/rectification-range-delivery-20260907.test.ts`;`tsc --noEmit` 原文见 `PROGRESS-rectification-range-delivery-fix-20260907.md`。
|
||||
- 防复发:区间卡渲染源码必须先出现 `candidateResult &&` 再读 `resultId`。采用后首轮 idle persist 必须调用 `appendPostAdoptProspectiveWindows`。聊天源码仍不得出现 `rectification-step-state`。
|
||||
- 相关记录:BUG-575、BUG-581、BUG-582
|
||||
- 复发自:无(区间卡 `ab57d03f` 引入;任务书 4.2 验收未过)
|
||||
- 修复版本:待发布(`codex/rectification-range-delivery-fix-20260907`)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# PROGRESS · 区间交付卡修复单(2026-09-07)
|
||||
|
||||
工作树:`.worktrees/rectification-range-delivery-fix-20260907`
|
||||
分支:`codex/rectification-range-delivery-fix-20260907`
|
||||
任务书:`docs/tasks/TASK-rectification-range-delivery-fix-20260907.md`
|
||||
基线:`origin/staging` @ `f3e72702`(任务书写 `ab57d03f`;其上有验收文档提交)
|
||||
|
||||
本单状态:**待验收**。未 commit、未 push。
|
||||
|
||||
未改:采用 RPC 校验、确认门、`MIN_SEPARATION_LEAD`、`_relative_support`、`page.tsx`、Skill 版本(仍 10.0.15)、聊天源码不出现 `rectification-step-state`(BUG-575)。
|
||||
|
||||
## 做了什么
|
||||
|
||||
- **3.1** `rectification-agentic-chat.tsx` 渲染条件改为 `showSelectionCards && candidateResult && dismissedRangeResultId !== candidateResult.resultId && …`。
|
||||
- **3.2** 选「接上」:`persistNextInterviewIfIdle` 在 `acceptedTime` 时把 `prospectiveWindowsNarration(receipt.prospective_probes)` 接到首轮旁白末尾。交付旁白仍留 `deferredCareerWindow`。跳过最后一道核对题的 `postAdoptVerifyDone` 不走 idle 包装,仍是原句。
|
||||
- **3.3** 「再补一件经历」收起卡片后,输入框上方 `RangeDeliveryContinueHint` 显示 `STEP_STATE_COPY.collect.headline` + `continueCollectFallback`。不用被禁的 `.rectification-step-state`。
|
||||
- **3.4** BUG-583、CHANGELOG、本文件、场景第 5 条、DESIGN / VOICE。
|
||||
|
||||
## 三栏(被触碰断言)
|
||||
|
||||
| 用例 | 原值 | 新值 | 理由 |
|
||||
| --- | --- | --- | --- |
|
||||
| 区间卡渲染条件 | 先读 `candidateResult.resultId` 再 `candidateResult &&` | 先 `candidateResult &&` | TS18047 |
|
||||
| 采用后首轮旁白 | 无 `prospectiveWindowsNarration` 调用点 | idle persist 已采用时追加 | 决策 2 接上 |
|
||||
| 「再补一件经历」 | 只 `setDismissedRangeResultId` | 另显示收集经历提示 | 决策 3;不恢复步骤条类名 |
|
||||
|
||||
## 测试
|
||||
|
||||
开工前 `frontend` `./node_modules/.bin/tsc --noEmit; echo "tsc exit $?"` 原文:
|
||||
|
||||
```
|
||||
src/components/rectification-agentic-chat.tsx(1655,67): error TS18047: 'candidateResult' is possibly 'null'.
|
||||
tsc exit 2
|
||||
```
|
||||
|
||||
修复后同一命令原文(stdout 为空,只有 echo):
|
||||
|
||||
```
|
||||
tsc exit 0
|
||||
```
|
||||
|
||||
- 任务书指定 TS 套件(`rectification-*.test.ts` + `consultation-*.test.ts` + `agent-voice-copy-contract.test.ts` + `skill-registry.test.ts`,排除 database):**1230 passed / 0 failed**(`ab57d03f` 同切片 1227;本单 +3)
|
||||
- 本单改动文件 eslint:**0 error**(chat 上 2 条既有 hooks warning)
|
||||
- Docker `database-rectification-*.test.ts`:本机未跑,记 blocked
|
||||
- 浏览器点选「再补一件经历」:无登录态 / Chrome 驱动,记环境缺口,见 `docs/testing/rectification-scenarios-20260907.md` §5
|
||||
|
||||
## 偏离
|
||||
|
||||
1. 步骤条文案用 `RangeDeliveryContinueHint`(`.rectification-continue-hint`),没有恢复 `.rectification-step-state`。BUG-575 防复发与 `rectification-step-state-20260906.test.ts` 禁止该类名。
|
||||
2. 预测窗口挂在 `persistNextInterviewIfIdle`(采用 RPC 之后的首轮),不挂 `applyRectificationChoice` 的跳过核对收尾,以免改掉 `postAdoptVerifyDone` 全等断言。
|
||||
@@ -69,8 +69,9 @@
|
||||
| `TASK-rectification-capability-fix-20260907.md` | `PROGRESS-rectification-capability-fix-20260907.md` | 能力补齐修复单:答后旁白把 `range_start/range_end`(搜索窗口)当范围比较,每题都说「范围没变」;`block_scan` 五段支持度按段内原始分求和,长时段先天占优(24/24/36/30/30 个候选);`TRACK_LABEL` 音译与产品 Vimshottari/Narayana 口径不一;BLK-001 写到 `docs/BLOCKED.md` 应回根目录 | 已验收通过(P2:block_scan 15 s 壁钟断言在门禁里可能间歇红) | `517df002`(BUG-569~570);staging 未部署,需先 Migrate Staging Database |
|
||||
| `TASK-rectification-declared-uncertainty-20260907.md` | `PROGRESS-rectification-declared-uncertainty-20260907.md` | 出生时间「有多确定」只在 intake 问一次:三档(医院记录 / 家人大概 ±15·30·60·120 / 时段或未知),校正窗口读档案(现在有钟点一律 ±15,声明值被忽略,BUG-571);吻合率 <60% 且代表分钟贴窗口边缘时出服务端一键放宽卡,放宽后重算并写回档案(BUG-572);窗口 >120 分钟先切三子段迭代到 ≤120 再进分钟(BUG-573);日级事件问一次可靠度;四个脚本化手测场景进 docs/testing | 已验收通过(2 P3 建议:可靠度正则去掉「记得」、档案写回改走 account-profile-patch) | `8e31680b`(BUG-571~573);迁移 `20260907020000` 待应用,部署前先 Migrate Staging Database |
|
||||
| `TASK-rectification-stale-compare-fix-20260907.md` | `PROGRESS-rectification-stale-compare-fix-20260907.md` | **P0**:BUG-559 把过长 `candidate_split_hash` 塞进引擎比较键,答完性格/边界卡后比较静默失败;快照过期压过用户停止;holdout 用过期领域被改写成「再说一件事」;采集轮正文双写 | 已验收通过(P2 正文双写根因未拆、P3 重算节流 Map 只增不减) | `4e0db55f`(落地编号 BUG-577~580);staging 未部署 |
|
||||
| `TASK-rectification-stop-rescore-fix-20260907.md` | `PROGRESS-rectification-stop-rescore-fix-20260907.md` | **P0**:BUG-579 的停止路径用「裸重算」(`rescoreMinuteAfterWindowChange` 不重建 `inference_state`、不重放已答探针、不写 transition),停止后范围变宽、卡片变成引擎裸支持度、采用 RPC 报 `candidate_state_inconsistent`;无框区分题经 `spokenCollectFallbackFollowup` 变成 domain=other 的采集题,再次冒出开场句 `GENERIC_COLLECT_QUESTION`;holdout 采集排在带年月采集之前 | 待验收 | `codex/rectification-stop-rescore-fix-20260907`(BUG-581~582) |
|
||||
| `TASK-rectification-range-delivery-card-20260907.md` | `PROGRESS-rectification-range-delivery-card-20260907.md` | 结尾重设计:四张分钟卡 + 相对支持度换成一张区间交付卡(范围、代表分钟、稳定/敏感主题、边界句)+ 只列未问 D9/D10/月宿分歧的 2~3 列对比面板(文案只取类型表),动作只有「按这个范围用 / 再补一件经历」;删交付旁白里三行预测窗口;Skill §9 口径改、bump 10.0.15 | 待验收 | `codex/rectification-range-delivery-card-20260907` |
|
||||
| `TASK-rectification-stop-rescore-fix-20260907.md` | `PROGRESS-rectification-stop-rescore-fix-20260907.md` | **P0**:BUG-579 的停止路径用「裸重算」(`rescoreMinuteAfterWindowChange` 不重建 `inference_state`、不重放已答探针、不写 transition),停止后范围变宽、卡片变成引擎裸支持度、采用 RPC 报 `candidate_state_inconsistent`;无框区分题经 `spokenCollectFallbackFollowup` 变成 domain=other 的采集题,再次冒出开场句 `GENERIC_COLLECT_QUESTION`;holdout 采集排在带年月采集之前 | 已验收通过(逻辑);但同提交含区间卡 tsc 错,整体不可部署 | `ab57d03f`(BUG-581~582) |
|
||||
| `TASK-rectification-range-delivery-card-20260907.md` | `PROGRESS-rectification-range-delivery-card-20260907.md` | 结尾重设计:四张分钟卡 + 相对支持度换成一张区间交付卡(范围、代表分钟、稳定/敏感主题、边界句)+ 只列未问 D9/D10/月宿分歧的 2~3 列对比面板(文案只取类型表),动作只有「按这个范围用 / 再补一件经历」;删交付旁白里三行预测窗口;Skill §9 口径改、bump 10.0.15 | **未通过**:tsc 1 错(判空顺序);P2 预测窗口句兑现不了;P3 再补经历无提示 | `ab57d03f`;修复单 `TASK-rectification-range-delivery-fix-20260907.md` |
|
||||
| `TASK-rectification-range-delivery-fix-20260907.md` | `PROGRESS-rectification-range-delivery-fix-20260907.md` | 区间卡修复单:`candidateResult` 判空提到最前(tsc 阻断);预测窗口接到采用后首轮或删句;「再补一件经历」隐藏卡后给采集提示;进度记录须贴 tsc 原文 | 待验收 | `codex/rectification-range-delivery-fix-20260907`(BUG-583) |
|
||||
| `TASK-api-not-configured-mislabel-20260904.md` | `PROGRESS-api-not-configured-mislabel-20260904.md` | 16 处路由把数据库瞬断(部署切换窗口)兜底翻译成 503「服务尚未配置」;改为仅配置错误用该文案,其余 `service_unavailable`,收敛为共享 helper | 已验收 | `5483649b`(BUG-542);2 条子进程测试留 CI Node 22 复核 |
|
||||
| `TASK-rectification-ux-20260902.md` | `PROGRESS-rectification-ux-20260903.md` | 会话面空白假死与交互摩擦 | 已验收 | `d159f08e`(09-03 在新基线重做后合入,BUG-505~509) |
|
||||
|
||||
|
||||
@@ -77,3 +77,21 @@ ln -s /workspace/Jyotisha/frontend/node_modules frontend/node_modules
|
||||
cd frontend && ls tests/rectification-*.test.ts tests/consultation-*.test.ts tests/agent-voice-copy-contract.test.ts | grep -v database | xargs npx tsx --test 2>&1 | grep -E "^# (tests|pass|fail)"
|
||||
grep -n "^version" ../skills/jyotish-birth-time-rectification/SKILL.md
|
||||
```
|
||||
|
||||
## 验收(Claude,2026-09-07,`origin/staging` @ `ab57d03f`)
|
||||
|
||||
**未通过(P1 阻断)**:`frontend/src/components/rectification-agentic-chat.tsx` L1655 把 `dismissedRangeResultId !== candidateResult.resultId` 放在 `candidateResult &&` 判空之前,`tsc --noEmit` 报 `TS18047: 'candidateResult' is possibly 'null'`。硬红线 tsc 0 错不满足,`next build` 会失败,staging 门禁过不去。修法一行:把判空提到最前面。执行方进度记录写 tsc 0,不符。
|
||||
|
||||
其余各项(在修掉这一行之后可视为通过):
|
||||
|
||||
| 项 | 结论 |
|
||||
| --- | --- |
|
||||
| 4.1 投影 `range_delivery` | 通过。`divergence-panel.ts`:活跃簇 2~3、只列未问的 D9/D10/月宿层,星座先从探针选项取、再从换升表推,文案只取 `D9_TYPE_TABLE` / `D10_TYPE_TABLE` 的 `userChoice` 与 nakshatra `traits`;代表 id 用公开 UUID |
|
||||
| 4.2 组件 | 通过。四张分钟卡、相对支持度、当前推荐徽章全部删除;两个动作;列单选,"都不像 / 说不好"清空 |
|
||||
| 4.3 旁白与 Skill | 通过。三行预测窗口删除;Skill §9 改口径、10.0.15 快照与 live 一致、五份 references 与 10.0.14 逐字相同(没有新写方法学);注册表新条目 `sourceCommit: null` 与历史条目一致 |
|
||||
| 4.4 记录 | 通过;手测场景第 5 条已加 |
|
||||
| P2 | 旁白改成"下一次事业变动的预测窗口留在采用后的核对阶段",但采用后没有任何地方再展示预测窗口(`prospectiveWindowsNarration` 已无调用点)——这是一句兑现不了的承诺,与之前"说一声我来汇总"同类。要么在采用后核对首轮真的接上,要么把这句删掉 |
|
||||
| P3 | "再补一件经历"只在本地隐藏卡片,不给用户任何提示该说什么;建议隐藏后由步骤条/旁白给一句采集提示 |
|
||||
| P3 | lint warning 从 84 涨到 103 |
|
||||
|
||||
修复单:`TASK-rectification-range-delivery-fix-20260907.md`。
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# TASK · 区间交付卡修复单:tsc 判空顺序、兑现不了的预测窗口句、再补经历无提示(2026-09-07)
|
||||
|
||||
- 基线:`origin/staging` @ `ab57d03f`
|
||||
- 分支:`codex/rectification-range-delivery-fix-20260907`
|
||||
- 执行方:coding agent;验收:Claude
|
||||
- 优先级:**P1 阻断**(tsc 不过,staging 门禁与 `next build` 都会失败,BUG-581~582 的 P0 修复被一起卡住)
|
||||
- BUG 编号起点:**BUG-583**(开工时 `grep -o "^## BUG-5[0-9][0-9]" docs/BUG_HISTORY.md | tail -1` 复核)
|
||||
|
||||
## 1. 事故实证
|
||||
|
||||
| # | 事实 | 位置 |
|
||||
| --- | --- | --- |
|
||||
| 1 | `tsc --noEmit`:`TS18047: 'candidateResult' is possibly 'null'` | `frontend/src/components/rectification-agentic-chat.tsx` L1655:`showSelectionCards && dismissedRangeResultId !== candidateResult.resultId && message.renderKey === selectionCardMessageKey && candidateResult && (…)` |
|
||||
| 2 | 交付旁白句"下一次事业变动的预测窗口留在采用后的核对阶段",但 `prospectiveWindowsNarration` 已无任何调用点,采用后从不展示 | `user-copy.ts` `deferredCareerWindow`;`grep prospectiveWindowsNarration` 只剩定义 |
|
||||
| 3 | 「再补一件经历」只 `setDismissedRangeResultId`,卡片消失后没有任何提示 | `rectification-agentic-chat.tsx` `onContinue` |
|
||||
| 4 | 执行方进度记录写"tsc 0",与 `ab57d03f` 实测不符 | `PROGRESS-rectification-range-delivery-card-20260907.md` |
|
||||
|
||||
## 2. 决策记录
|
||||
|
||||
1. 判空提到最前:`showSelectionCards && candidateResult && dismissedRangeResultId !== candidateResult.resultId && …`。
|
||||
2. 预测窗口二选一,本单选 **接上**:采用后 `postAdoptVerify` 第一轮旁白末尾追加 `prospectiveWindowsNarration(receipt.prospective_probes)`(有则加,无则不加);`deferredCareerWindow` 句保留。若执行方评估接不上,则删句并写明。
|
||||
3. 「再补一件经历」隐藏卡片后,步骤条切到"第 1 步·收集经历"文案,并在输入框上方给一句 `continueCollectFallback`(已有文案:"请继续说下一件你记得比较清楚、大概带年份的经历。")。
|
||||
4. 进度记录必须贴 `tsc --noEmit` 的真实输出行;没有复跑不得写 0。
|
||||
|
||||
## 3. 任务分解
|
||||
|
||||
- 3.1 判空修复 + 源扫描断言(`rectification-range-delivery-20260907.test.ts`:正则锁定 `candidateResult &&` 出现在 `dismissedRangeResultId` 之前)。
|
||||
- 3.2 预测窗口接到采用后首轮(`postAdoptVerify` 路径),测试:有 `prospective_probes` 的 receipt → 采用后首轮旁白含"预测下一次事业变动";无则不含。
|
||||
- 3.3 再补经历提示:组件测试——点击后卡片隐藏且出现 `continueCollectFallback`。
|
||||
- 3.4 记录:BUG-583(tsc 判空,复发自本单 4.2);CHANGELOG 一句;PROGRESS 贴 tsc 原文。
|
||||
|
||||
## 4. 让步顺序
|
||||
|
||||
3.1 必须当天合入(否则 P0 修复无法部署);3.2、3.3 其次;3.4 不可省。
|
||||
|
||||
## 5. 开工前置命令
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/rectification-range-delivery-fix-20260907 .worktrees/rectification-range-delivery-fix-20260907 origin/staging
|
||||
cd .worktrees/rectification-range-delivery-fix-20260907
|
||||
ln -s /workspace/Jyotisha/frontend/node_modules frontend/node_modules
|
||||
cd frontend && ./node_modules/.bin/tsc --noEmit; echo "tsc exit $?"
|
||||
```
|
||||
@@ -70,3 +70,22 @@ ln -s /workspace/Jyotisha/frontend/node_modules frontend/node_modules
|
||||
ln -s /workspace/Jyotisha/.venv .venv
|
||||
cd frontend && ls tests/rectification-*.test.ts tests/consultation-*.test.ts tests/agent-voice-copy-contract.test.ts | grep -v database | xargs npx tsx --test 2>&1 | grep -E "^# (tests|pass|fail)"
|
||||
```
|
||||
|
||||
## 验收(Claude,2026-09-07,`origin/staging` @ `ab57d03f`;与区间交付卡同一提交)
|
||||
|
||||
| 门 | 结果 |
|
||||
| --- | --- |
|
||||
| tsc | **1 错**(在同提交的区间交付卡改动里:`rectification-agentic-chat.tsx` L1655 先访问 `candidateResult.resultId` 再判空,`'candidateResult' is possibly 'null'`)。执行方进度记录写 tsc 0,与实测不符——应是最后一次改动后没有复跑 |
|
||||
| lint | 0 error / 103 warning(比上一轮多 19 条,分散在测试文件与 `rectification-v9-tools.ts`,未清) |
|
||||
| 前端 rectification + consultation + birth-time + skill + voice(非 DB) | 1605 / 0 |
|
||||
| Python v5_services / input_contract / growth contract | 全绿 |
|
||||
| 引擎 `minute_step=1` 字节级不变 | 全等 |
|
||||
|
||||
| 项 | 结论 |
|
||||
| --- | --- |
|
||||
| 4.1 BUG-581 | 通过。`scoreAndPersistCurrentEvidence` 抽到 `v9/score-persist.ts`,停止/闲置 `keepAnswers=true` 带 `previous` 重放;时段/放宽 `keepAnswers=false` 仍写出空答案的 `inference_state`;VedAstro 校验与对比包逻辑随之搬迁 |
|
||||
| 4.2 BUG-582 | 通过。无框区分题一律 `skipped`;工具层与 `spokenFollowupForUser` 的 GENERIC 兜底删除;`dropped_probes(frameless_distinguish)` 记录 |
|
||||
| 4.3 顺序 | 通过。holdout 排在带年月采集与职业之后 |
|
||||
| 4.4 记录 | 通过 |
|
||||
|
||||
结论:本单逻辑通过,但同一提交因区间交付卡的 tsc 错误**整体不可部署**,见 `TASK-rectification-range-delivery-card-20260907.md` 验收段。
|
||||
|
||||
@@ -88,5 +88,5 @@
|
||||
- 若有分歧面板,每列只有 1~3 行,且都是没问过的 D9 / D10 / 月宿层
|
||||
- 不见「相对支持度」「当前推荐」「采用此时间」
|
||||
- 点「按这个范围用」成功,设置页出生时间状态变为已采用
|
||||
- 点「再补一件经历」卡片收起,服务端范围不变
|
||||
- 点「再补一件经历」卡片收起,输入框上方出现「第 1 步·收集经历」和「请继续说下一件…」,服务端范围不变
|
||||
|
||||
|
||||
+1
-1
@@ -252,7 +252,7 @@ The birth-time rectification session is the consultation transcript plus a house
|
||||
- **Life-event evidence:** after deterministic questionnaire completion, render three structured event rows by default and allow up to six. Each row uses a domain select, a precision select, and a matching year/month/day control; free-form descriptions are not part of scoring.
|
||||
- **Candidate result:** keep the reported range, candidate interval, and active-time status visually separate. Delivery shows one range card (window, representative minute, stable/sensitive themes, boundary sentence) plus an optional unasked D9/D10/nakshatra panel. Support numbers stay on the house board. Low confidence keeps evidence editing open; medium offers save or add evidence; high uses a separate confirmation action and never labels the representative minute as the true birth time.
|
||||
- **Evidence accessibility:** every row keeps visible labels, validation errors use live regions, add/remove controls retain 44px targets, and scoring/confirmation loading states disable duplicate submission without hiding the existing evidence.
|
||||
- **One-question guide:** the guided journey renders only the persisted `nextAction` and one server-selected question. A deterministic question is visible immediately; Agent wording may replace it without changing the question identity, domain, precision request, progress, or permissions. The composer explicitly permits an approximate year and keeps skip and pause as secondary 44px actions. Spoken collect (`collect_spoken`) places the same `CHOICE_STOP_LABEL` (“先这样,先看当前范围”) as a 44px secondary button above `ChatComposer`. The readonly range line is a status sentence, not a stop control. Discriminator cards fold “为什么问这题” under the stem. Hovering or selecting an option does not reveal an `answer_impact` time line. The composer has no `rectification-step-state` status sentence.
|
||||
- **One-question guide:** the guided journey renders only the persisted `nextAction` and one server-selected question. A deterministic question is visible immediately; Agent wording may replace it without changing the question identity, domain, precision request, progress, or permissions. The composer explicitly permits an approximate year and keeps skip and pause as secondary 44px actions. Spoken collect (`collect_spoken`) places the same `CHOICE_STOP_LABEL` (“先这样,先看当前范围”) as a 44px secondary button above `ChatComposer`. The readonly range line is a status sentence, not a stop control. Discriminator cards fold “为什么问这题” under the stem. Hovering or selecting an option does not reveal an `answer_impact` time line. The composer has no `rectification-step-state` status sentence. After “再补一件经历” dismisses the range card, `rectification-composer-meta` shows the collect-step headline plus `continueCollectFallback`.
|
||||
- **Draft review:** natural-language answers become one inline review card. The evidence domain is read-only and uses its Chinese label; precision controls which exact year, month, or day input is available. Incomplete drafts keep edit and skip paths visible, while confirmation is disabled until the structured date is valid. Status and errors use polite or assertive live regions without clearing the persisted journey.
|
||||
- **Scoring and retry:** `score_pending` is a quiet progress surface with cancellable bounded polling and no manual compare control. `retry_scoring` preserves the confirmed evidence and exposes one explicit retry action. Refresh and device changes resume from the persisted action rather than inferring progress from copy.
|
||||
- **Guided candidate states:** low confidence presents the saved candidate range and either another evidence question or a safe finish; medium confidence can save the range but never apply a representative minute; high confidence names both “候选时间” and “当前排盘使用时间” before explicit confirmation; ready states that the current chart time changed while the original report remains preserved. No state calls a candidate the true birth minute.
|
||||
|
||||
@@ -33,7 +33,8 @@ Jyotisha 的可见文案是产品的一部分。正确性红线(真实性、
|
||||
| 剩下的题分不开当前候选。更站得住的范围是 05:15,代表分钟 05:15。 | 再问下去也分不出更准的时间了。眼下更站得住的是 05:15。 | 单分钟不要把范围和代表分钟念两遍;「当前候选」是内部词。 |
|
||||
| 可以从下面的时间里选一个采用。 / 下面的时间可以先用着。 | 我按你说的经历认真分析过了,下面是这次的结果。 | 这是认真分析后的结果,不是随便先用着;采用仍不是确认。区间交付卡必须就在这句下面。 |
|
||||
| 相对支持度 16 · 采用此时间 | 按这个范围用 / 再补一件经历 | 交付对象是区间,不是四张分钟卡上的支持度数字。 |
|
||||
| 候选 A 预测下一次事业变动更可能在 2027 年 3 月附近。 | 下一次事业变动的预测窗口留在采用后的核对阶段。 | 交付轮不并排三行预测。 |
|
||||
| 候选 A 预测下一次事业变动更可能在 2027 年 3 月附近。 | 下一次事业变动的预测窗口留在采用后的核对阶段。 | 交付轮不并排三行预测。采用后首轮核对旁白末尾才接上实际窗口句。 |
|
||||
| 「再补一件经历」后卡片消失、输入框上方空白 | 第 1 步·收集经历。请继续说下一件你记得比较清楚、大概带年份的经历。 | 收起交付卡后要告诉人下一步做什么。 |
|
||||
| 采用后会拿盘外核对来验证。 | 前事核对到这里。之后新建对话即按已采用时间排盘;对不上随时改选。 | 已采用且没有下一道核对题时用 `postAdoptVerifyDone`,不再念 `adoptCue`。 |
|
||||
| (采用后只念代表分钟)代表分钟是 05:12。 | 这 27 分钟里,事业方向、性格底色的判断是稳定的;婚恋(D9)会随分钟变,看盘时按范围读。这不是已确认的唯一出生分钟。 | 采用后先讲区间里什么稳、什么随分钟变;两句来自服务端,不是模型编的。 |
|
||||
| 当前问题已更新,请刷新后重新作答。 | 这一问刚换成新的,刷新后再答就行。 | 机器状态句改成下一步。 |
|
||||
|
||||
@@ -3091,6 +3091,16 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
|
||||
.rectification-composer-meta .rectification-collect-stop {
|
||||
margin: 0;
|
||||
}
|
||||
.rectification-continue-hint {
|
||||
margin: 0;
|
||||
color: var(--color-ink-secondary);
|
||||
font-size: var(--type-caption);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.rectification-continue-hint strong {
|
||||
color: var(--color-ink);
|
||||
font-weight: 600;
|
||||
}
|
||||
.rectification-candidates-heading { display: grid; gap: var(--space-1); }
|
||||
.rectification-candidates-heading strong { font-size: var(--type-title-sm); font-family: var(--font-display); font-weight: 600; letter-spacing: -.2px; }
|
||||
.rectification-candidates-heading span,
|
||||
|
||||
@@ -39,7 +39,7 @@ import {
|
||||
workingRectificationHouseTable,
|
||||
type RectificationCandidateResult,
|
||||
} from "@/lib/rectification-candidate-result";
|
||||
import { RectificationRangeDelivery } from "@/components/rectification-range-delivery";
|
||||
import { RangeDeliveryContinueHint, RectificationRangeDelivery } from "@/components/rectification-range-delivery";
|
||||
import {
|
||||
diffRectificationBoard,
|
||||
RECTIFICATION_BOARD_SPLIT_MIN_PX,
|
||||
@@ -1506,6 +1506,15 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
void submitStructuredChoice(STOP_ACTION, "stop");
|
||||
}
|
||||
|
||||
const rangeCardDismissed = Boolean(
|
||||
candidateResult
|
||||
&& dismissedRangeResultId === candidateResult.resultId
|
||||
&& !candidateResult.selectedTime
|
||||
&& savedStatus !== "accepted"
|
||||
&& savedStatus !== "confirmed"
|
||||
&& !readonly
|
||||
);
|
||||
const spokenCollectOpen = currentQuestion?.kind === "collect_spoken" && !readonly && !busy;
|
||||
const boardPeek = compactBoard && !boardOpen ? (
|
||||
<RectificationBoardPeek
|
||||
result={candidateResult}
|
||||
@@ -1652,7 +1661,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
onRegenerate={() => void regenerateMessage(message)}
|
||||
/>
|
||||
)}
|
||||
{showSelectionCards && dismissedRangeResultId !== candidateResult.resultId && message.renderKey === selectionCardMessageKey && candidateResult && (
|
||||
{showSelectionCards && candidateResult && dismissedRangeResultId !== candidateResult.resultId && message.renderKey === selectionCardMessageKey && (
|
||||
<RectificationRangeDelivery
|
||||
result={candidateResult}
|
||||
acceptingCandidateId={acceptingCandidateId}
|
||||
@@ -1724,8 +1733,10 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
<span className="rectification-adopt-status__hint">之后新建对话即按此时间排盘。</span>
|
||||
</div>
|
||||
)}
|
||||
{currentQuestion?.kind === "collect_spoken" && !readonly && !busy && (
|
||||
{(rangeCardDismissed || spokenCollectOpen) && (
|
||||
<div className="rectification-composer-meta">
|
||||
<RangeDeliveryContinueHint visible={rangeCardDismissed && !busy} />
|
||||
{spokenCollectOpen && (
|
||||
<button
|
||||
type="button"
|
||||
className="rectification-collect-stop"
|
||||
@@ -1733,6 +1744,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
>
|
||||
{CHOICE_STOP_LABEL}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<ChatComposer
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
rangeDeliverySensitiveCopy,
|
||||
rangeDeliveryStableCopy,
|
||||
} from "@/lib/rectification-agentic/user-copy";
|
||||
import { STEP_STATE_COPY } from "@/lib/rectification-agentic/v9/step-state";
|
||||
import type { RangeDeliveryProjection } from "@/lib/rectification-agentic/v9/divergence-panel";
|
||||
|
||||
function adoptCandidateId(
|
||||
@@ -25,6 +26,17 @@ function adoptCandidateId(
|
||||
?? null;
|
||||
}
|
||||
|
||||
export function RangeDeliveryContinueHint({ visible }: Readonly<{ visible: boolean }>) {
|
||||
if (!visible) return null;
|
||||
return (
|
||||
<p className="rectification-continue-hint" role="status">
|
||||
<strong>{STEP_STATE_COPY.collect.headline}</strong>
|
||||
{" "}
|
||||
{RECTIFICATION_USER_COPY.continueCollectFallback}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
export function RectificationRangeDelivery({
|
||||
result,
|
||||
acceptingCandidateId,
|
||||
|
||||
@@ -92,7 +92,7 @@ import {
|
||||
import { followupCaseArgs, isBlockChoiceSchema, isWidenWindowSchema } from "./block-scan.ts";
|
||||
import { mutateCaseForBlockChoice, mutateCaseForWidenWindow, rescoreStaleMinuteSnapshotIfNeeded } from "./block-scan-answer.ts";
|
||||
import type { SessionOutcomeKind } from "./confirmation-gate";
|
||||
import { refinementFromDecisionReceipt } from "./refinement-packet";
|
||||
import { prospectiveWindowsNarration, refinementFromDecisionReceipt } from "./refinement-packet";
|
||||
import { projectCurrentQuestion } from "./turn-decision";
|
||||
|
||||
const EXHAUSTION_DELIVERY_ACTIONS = new Set([
|
||||
@@ -143,6 +143,18 @@ function withDeferredCareerWindow(base: string): string {
|
||||
return `${base} ${extra}`.replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
export function appendPostAdoptProspectiveWindows(
|
||||
narration: string,
|
||||
receipt: Readonly<Record<string, unknown>> | null | undefined,
|
||||
): string {
|
||||
const extra = prospectiveWindowsNarration(
|
||||
refinementFromDecisionReceipt(receipt).prospective_probes,
|
||||
);
|
||||
if (!extra) return narration;
|
||||
if (narration.includes(extra)) return narration;
|
||||
return `${narration} ${extra}`.replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
async function withRangeReadingNarration(
|
||||
base: string,
|
||||
input: {
|
||||
@@ -1105,21 +1117,26 @@ export async function persistNextInterviewIfIdle(input: {
|
||||
caseId: input.caseId,
|
||||
askedTurnId: input.askedTurnId ?? null,
|
||||
});
|
||||
let dossier = rescored.dossier;
|
||||
const finishIdle = <T extends { hostNarration: string | null }>(result: T): T => {
|
||||
let hostNarration = result.hostNarration;
|
||||
if (dossier.case.acceptedTime && hostNarration) {
|
||||
hostNarration = appendPostAdoptProspectiveWindows(
|
||||
hostNarration,
|
||||
dossier.latestResult?.decisionReceipt,
|
||||
);
|
||||
}
|
||||
if (
|
||||
input.userStopped === true
|
||||
&& rescored.rescoreAttempted
|
||||
&& !rescored.snapshotCurrent
|
||||
&& result.hostNarration
|
||||
&& hostNarration
|
||||
) {
|
||||
return {
|
||||
...result,
|
||||
hostNarration: withLastSuccessfulCompareNotice(result.hostNarration),
|
||||
};
|
||||
hostNarration = withLastSuccessfulCompareNotice(hostNarration);
|
||||
}
|
||||
return result;
|
||||
if (hostNarration === result.hostNarration) return result;
|
||||
return { ...result, hostNarration };
|
||||
};
|
||||
let dossier = rescored.dossier;
|
||||
const staleFocus = dossier.conversationSummary.activeFocus;
|
||||
const staleFocusId = staleFocus?.id;
|
||||
if (
|
||||
|
||||
@@ -169,6 +169,7 @@ test("delivery narration defers career windows and the range card replaces minut
|
||||
assert.doesNotMatch(choice, /withProspectiveWindows/);
|
||||
assert.doesNotMatch(choice, /候选 A 预测/);
|
||||
assert.match(choice, /deferredCareerWindow/);
|
||||
assert.match(choice, /appendPostAdoptProspectiveWindows/);
|
||||
assert.match(chat, /<RectificationRangeDelivery/);
|
||||
assert.doesNotMatch(chat, /相对支持度/);
|
||||
assert.doesNotMatch(card, /相对支持度|概率|置信度/);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
import { createElement } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
import { INFERENCE_ALGORITHM_VERSION, type InferenceCandidate, type InferenceState } from "../src/lib/rectification-agentic/core/types.ts";
|
||||
import {
|
||||
@@ -10,8 +12,11 @@ import {
|
||||
d9DivergenceLabel,
|
||||
} from "../src/lib/rectification-agentic/v9/divergence-panel.ts";
|
||||
import { D10_TYPE_TABLE, D9_TYPE_TABLE } from "../src/lib/rectification-agentic/v9/varga-type-tables.ts";
|
||||
import { REPRESENTATIVE_MINUTE_DISCLAIMER } from "../src/lib/rectification-agentic/user-copy.ts";
|
||||
import { RECTIFICATION_USER_COPY, REPRESENTATIVE_MINUTE_DISCLAIMER } from "../src/lib/rectification-agentic/user-copy.ts";
|
||||
import type { NakshatraBoundary } from "../src/lib/rectification-agentic/v9/refinement-packet.ts";
|
||||
import { appendPostAdoptProspectiveWindows } from "../src/lib/rectification-agentic/v9/answer-choice.ts";
|
||||
import { RangeDeliveryContinueHint } from "../src/components/rectification-range-delivery.tsx";
|
||||
import { STEP_STATE_COPY } from "../src/lib/rectification-agentic/v9/step-state.ts";
|
||||
|
||||
const ID_A = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaa1";
|
||||
const ID_B = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbb2";
|
||||
@@ -288,3 +293,53 @@ test("the range-delivery component does not render relative support", () => {
|
||||
assert.match(source, /rangeDeliveryAdopt/);
|
||||
assert.match(source, /rangeDeliveryContinue/);
|
||||
});
|
||||
|
||||
test("chat null-checks candidateResult before reading dismissedRangeResultId", () => {
|
||||
const chat = readFileSync(new URL("../src/components/rectification-agentic-chat.tsx", import.meta.url), "utf8");
|
||||
const start = chat.indexOf("showSelectionCards && candidateResult");
|
||||
assert.ok(start >= 0);
|
||||
const slice = chat.slice(start, start + 280);
|
||||
assert.match(slice, /candidateResult && dismissedRangeResultId !== candidateResult\.resultId/);
|
||||
assert.ok(slice.indexOf("candidateResult &&") < slice.indexOf("dismissedRangeResultId"));
|
||||
});
|
||||
|
||||
const CAREER_WINDOW_PROBE = {
|
||||
candidate_label: "A",
|
||||
domain: "career",
|
||||
window_label: "2027 年 3 月附近",
|
||||
user_meaning: "候选 A 预测下一次事业变动更可能在 2027 年 3 月附近。这是预测窗口,不是承诺;下次发生时回来补一条,可进一步分辨。",
|
||||
used_for_scoring: false,
|
||||
};
|
||||
|
||||
test("post-adopt first-round narration appends prospective windows when present", () => {
|
||||
const base = "2013 年前后,钱上有没有明显变化?";
|
||||
const withWindows = appendPostAdoptProspectiveWindows(base, {
|
||||
prospective_probes: [CAREER_WINDOW_PROBE],
|
||||
});
|
||||
assert.match(withWindows, /预测下一次事业变动/);
|
||||
assert.match(withWindows, /2013 年前后/);
|
||||
const without = appendPostAdoptProspectiveWindows(base, { prospective_probes: [] });
|
||||
assert.equal(without, base);
|
||||
assert.doesNotMatch(without, /预测下一次事业变动/);
|
||||
const idle = readFileSync(
|
||||
new URL("../src/lib/rectification-agentic/v9/answer-choice.ts", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const start = idle.indexOf("export async function persistNextInterviewIfIdle");
|
||||
const slice = idle.slice(start, start + 1800);
|
||||
assert.match(slice, /appendPostAdoptProspectiveWindows/);
|
||||
assert.match(slice, /dossier\.case\.acceptedTime/);
|
||||
});
|
||||
|
||||
test("dismissing the range card hides it and shows the collect-continue hint", () => {
|
||||
const hidden = renderToStaticMarkup(createElement(RangeDeliveryContinueHint, { visible: false }));
|
||||
assert.equal(hidden, "");
|
||||
const shown = renderToStaticMarkup(createElement(RangeDeliveryContinueHint, { visible: true }));
|
||||
assert.ok(shown.includes(STEP_STATE_COPY.collect.headline));
|
||||
assert.ok(shown.includes(RECTIFICATION_USER_COPY.continueCollectFallback));
|
||||
assert.doesNotMatch(shown, /rectification-step-state/);
|
||||
const chat = readFileSync(new URL("../src/components/rectification-agentic-chat.tsx", import.meta.url), "utf8");
|
||||
assert.match(chat, /RangeDeliveryContinueHint/);
|
||||
assert.match(chat, /setDismissedRangeResultId\(candidateResult\.resultId\)/);
|
||||
assert.match(chat, /rangeCardDismissed/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user