@tea-agent/loop-agent 0.36.4-beta.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/CHANGELOG.md +50 -68
  2. package/dist/build-stamp.json +3 -3
  3. package/dist/infrastructure/harness/atomic-write.js +1 -2
  4. package/dist/task/contract/project.js +4 -0
  5. package/dist/task/frontend-project-capability.js +12 -162
  6. package/dist/task/source-prepare/completeness.js +36 -0
  7. package/dist/task/source-prepare/parse-intent.js +67 -11
  8. package/dist/task/source-prepare/prepare.js +102 -5
  9. package/dist/worker/console/chat/chat-event-store.js +91 -0
  10. package/dist/worker/console/chat/pi-runtime.js +25 -0
  11. package/dist/worker/console/chat/routes.js +7 -2
  12. package/dist/worker/console/chat/semantic-activity.js +7 -1
  13. package/dist/worker/console/chat/session-store.js +6 -1
  14. package/dist/worker/console/chat/turn-process.js +119 -12
  15. package/dist/worker/console/operator-actions.js +18 -1
  16. package/dist/worker/console/static/assets/abnfDiagram-N423BO3Z-DSvF6RXQ.js +1 -0
  17. package/dist/worker/console/static/assets/arc-DuI4ogtJ.js +1 -0
  18. package/dist/worker/console/static/assets/architectureDiagram-T3A2C74G-DdBbgD4y.js +36 -0
  19. package/dist/worker/console/static/assets/blockDiagram-VBNYF7ZC-xQC2HELm.js +132 -0
  20. package/dist/worker/console/static/assets/c4Diagram-5PPSVZJV-DPMSgwIZ.js +10 -0
  21. package/dist/worker/console/static/assets/channel-DLVz9TYW.js +1 -0
  22. package/dist/worker/console/static/assets/chunk-2GRJ4B5K-WTEKTxin.js +1 -0
  23. package/dist/worker/console/static/assets/chunk-2Q5K7J3B-B6vZ0Pv_.js +1 -0
  24. package/dist/worker/console/static/assets/chunk-5RXB4S5H-BrerNAix.js +231 -0
  25. package/dist/worker/console/static/assets/chunk-5VM5RSS4-D_ch2aaI.js +15 -0
  26. package/dist/worker/console/static/assets/chunk-6Q2QTUOP-VvfacNrq.js +88 -0
  27. package/dist/worker/console/static/assets/chunk-GF5L2VYU-B-uGFIg2.js +206 -0
  28. package/dist/worker/console/static/assets/chunk-JWPE2WC7-BlysCtod.js +1 -0
  29. package/dist/worker/console/static/assets/chunk-KBJHAD2P-BjAUAfcT.js +1 -0
  30. package/dist/worker/console/static/assets/chunk-RYQCIY6F-BeZnBmb-.js +1 -0
  31. package/dist/worker/console/static/assets/chunk-XXDRQBXY-Bbfe3UJG.js +1 -0
  32. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +1 -0
  33. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +1 -0
  34. package/dist/worker/console/static/assets/cose-bilkent-JH36ORCC-BKJQnYVM.js +1 -0
  35. package/dist/worker/console/static/assets/cynefin-VYW2F7L2-B1wtI3hU.js +166 -0
  36. package/dist/worker/console/static/assets/cynefinDiagram-MW4NZA55-DCF6DBSu.js +62 -0
  37. package/dist/worker/console/static/assets/cytoscape.esm-yzknjiTM.js +321 -0
  38. package/dist/worker/console/static/assets/dagre-VZM6K2ZE-BJyNGRlP.js +4 -0
  39. package/dist/worker/console/static/assets/defaultLocale-DX6XiGOO.js +1 -0
  40. package/dist/worker/console/static/assets/diagram-7IWD3JNH-B7j1-VsZ.js +30 -0
  41. package/dist/worker/console/static/assets/diagram-B4RE2ZJO-BREJ140a.js +3 -0
  42. package/dist/worker/console/static/assets/diagram-LBJQPF4R-CDPHmHGH.js +24 -0
  43. package/dist/worker/console/static/assets/diagram-Q27KOJAE-BtZLrez1.js +24 -0
  44. package/dist/worker/console/static/assets/diagram-UB23O5K3-mzybu8oU.js +41 -0
  45. package/dist/worker/console/static/assets/ebnfDiagram-BXEA7PRR-Dk3I2uO6.js +1 -0
  46. package/dist/worker/console/static/assets/erDiagram-JOGREHBK-BT97u_tQ.js +85 -0
  47. package/dist/worker/console/static/assets/flowDiagram-UKHOOZJN-CMOtYzui.js +156 -0
  48. package/dist/worker/console/static/assets/ganttDiagram-PKOTCBZU-DiNydkuY.js +292 -0
  49. package/dist/worker/console/static/assets/gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js +106 -0
  50. package/dist/worker/console/static/assets/graph-DOmOIIwC.js +1 -0
  51. package/dist/worker/console/static/assets/index-DQxiso1w.js +325 -0
  52. package/dist/worker/console/static/assets/index-_j0Hzo69.css +1 -0
  53. package/dist/worker/console/static/assets/infoDiagram-6WML65LV-AsGIGKKB.js +2 -0
  54. package/dist/worker/console/static/assets/init-Gi6I4Gst.js +1 -0
  55. package/dist/worker/console/static/assets/ishikawaDiagram-WSZJBQD7-BL8ITZh3.js +70 -0
  56. package/dist/worker/console/static/assets/journeyDiagram-NVQOT4AX-VPKAUX8M.js +139 -0
  57. package/dist/worker/console/static/assets/kanban-definition-27J2QSJJ-BL56hfGp.js +89 -0
  58. package/dist/worker/console/static/assets/layout-D-LzfAck.js +1 -0
  59. package/dist/worker/console/static/assets/linear-BmSG3nIZ.js +1 -0
  60. package/dist/worker/console/static/assets/map-DxJ2ADlA.js +1 -0
  61. package/dist/worker/console/static/assets/mermaid.core-DWGK9kwP.js +308 -0
  62. package/dist/worker/console/static/assets/mindmap-definition-FAOFIHXS--vvKLjo0.js +96 -0
  63. package/dist/worker/console/static/assets/ordinal-Cboi1Yqb.js +1 -0
  64. package/dist/worker/console/static/assets/pegDiagram-VL7TDLO6-B41mxb2-.js +1 -0
  65. package/dist/worker/console/static/assets/pieDiagram-7S7Q4E2Y-DTlu77sw.js +39 -0
  66. package/dist/worker/console/static/assets/quadrantDiagram-CIZ2JOQS-Bk5A7alv.js +7 -0
  67. package/dist/worker/console/static/assets/railroadDiagram-AXF67PYL-Csf9jzQy.js +1 -0
  68. package/dist/worker/console/static/assets/requirementDiagram-LRYGKXZP-CeptoSNx.js +84 -0
  69. package/dist/worker/console/static/assets/sankeyDiagram-W5VNT64P-A-DLCLer.js +40 -0
  70. package/dist/worker/console/static/assets/sequenceDiagram-SI44F4Z6-22VbPdOI.js +162 -0
  71. package/dist/worker/console/static/assets/sizeCapture-X5ZJPWSS-DWvAyBC9.js +1 -0
  72. package/dist/worker/console/static/assets/stateDiagram-OKZ733FA-qICZ75yT.js +1 -0
  73. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +1 -0
  74. package/dist/worker/console/static/assets/swimlanes-SLNWSIFB-xi7lcMyp.js +2 -0
  75. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +8 -0
  76. package/dist/worker/console/static/assets/timeline-definition-Z64GVDOM-Bu6Nl6O2.js +120 -0
  77. package/dist/worker/console/static/assets/vennDiagram-T6HMQDX7-BzMz4Bn9.js +34 -0
  78. package/dist/worker/console/static/assets/wardleyDiagram-T6FBY63Y-CxaJnzc5.js +78 -0
  79. package/dist/worker/console/static/assets/xychartDiagram-ELKLHX3M-5JK-omQK.js +7 -0
  80. package/dist/worker/console/static/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
  81. package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
  82. package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
  83. package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
  84. package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
  85. package/dist/worker/console/static/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
  86. package/dist/worker/console/static/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
  87. package/dist/worker/console/static/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
  88. package/dist/worker/console/static/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
  89. package/dist/worker/console/static/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
  90. package/dist/worker/console/static/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
  91. package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
  92. package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
  93. package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
  94. package/dist/worker/console/static/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
  95. package/dist/worker/console/static/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
  96. package/dist/worker/console/static/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
  97. package/dist/worker/console/static/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
  98. package/dist/worker/console/static/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
  99. package/dist/worker/console/static/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
  100. package/dist/worker/console/static/index.html +2 -2
  101. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +25 -0
  102. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +37 -2
  103. package/dist/worker/observe/static/operator-chrome.js +4 -2
  104. package/dist/workflows/dag/failure-routing.js +4 -9
  105. package/dist/workflows/dag/frontend-prewrite-gate.js +8 -37
  106. package/dist/workflows/dag/init-hybrid.js +4 -73
  107. package/dist/workflows/dag/lifecycle.js +0 -4
  108. package/dist/workflows/dag/node-execution.js +6 -16
  109. package/dist/workflows/dag/report.js +0 -6
  110. package/dist/workflows/dag/retry-policy.js +0 -11
  111. package/dist/workflows/dag/runner.js +19 -3
  112. package/dist/workflows/dag/types.js +0 -2
  113. package/dist/workflows/dag/validate.js +2 -7
  114. package/docs/templates/frontend-design-contract.md +14 -34
  115. package/docs/templates/frontend-task-constraints.md +13 -33
  116. package/docs/templates/frontend-task-requirement.md +20 -54
  117. package/package.json +6 -1
  118. package/skills/loop-agent/references/command-reference.md +1 -0
  119. package/dist/worker/console/static/assets/index-D83DYAFG.css +0 -1
  120. package/dist/worker/console/static/assets/index-IXm7oYjL.js +0 -59
package/CHANGELOG.md CHANGED
@@ -1,32 +1,35 @@
1
1
  # 更新日志
2
2
 
3
- ## [0.36.4-beta.0] - 2026-08-16
3
+ ## [Unreleased]
4
4
 
5
- > 前端 DAG 规范上下文确定性落地(beta,发布到 `beta` dist-tag)。
5
+ ## [0.37.0] - 2026-08-16
6
6
 
7
7
  ### 新增
8
8
 
9
- - `discoverFrontendProjectCapability` 产出 `designEvidence.classified` 语义归类:按通用目录约定(`openspec/schemas/**`、`openspec/project-specs/templates/**`、`openspec/project-specs/rules/**`、`openspec/project-specs/ui/**`、`ai_workspace/**`)把规范路径分入稳定键 `schemas` / `codeTemplate` / `rule.*` / `theme` / `component` / `uiOther` / `advisoryOther`,并进入 zod schema 校验(分区恒等式:归类值集合 == `normativePaths` 集合),重复探测稳定,不硬编码具体项目文件名
10
- - 前端 contract/scout/plan/design-review 节点的 `subtask_prompt` 注入归类后的规范路径(含角色语义)并明确要求报告适用规则、命中路径/章节/行号、冲突与缺失
9
+ - markdown syntax highlight, math formulas and mermaid diagrams; operate entry lands on chat
10
+ - label rerun titles with round number from -rN id suffix
11
+ - enter-DAG action, artifact rail projection, zh changelog
12
+ - surface compaction progress and timeline summary
13
+ - floating auto-dismiss toasts replace in-flow error alerts
14
+ - add running spinners to session titles and stop button
15
+ - show context ratio, semantic cache icons, align process entry typography
16
+ - show context usage and semantic cache labels in usage bar
17
+ - stream live turn process in strict order
11
18
 
12
19
  ### 修复
13
20
 
14
- - `frontend-prewrite-gate` `openspecCandidatePaths` 非空时强制生效 plan/review 节点实际读取候选路径:未读 → `retryable-invalid` + 新增 `openspec-not-read` failureCode,错误信息列出未读路径;空候选(greenfield)不阻断。此前读取证据仅记录不阻断(advisory),本次真正 fail-closed
21
+ - isolate interrupt kernel behind worker adapter
22
+ - constrain session title width so hover tooltip engages
23
+ - backfill operation link for semantic activity refs
24
+ - fail fast on non-canonical newTask ids with suggested fix
25
+ - accept projected references and normalize PRD headings
15
26
 
16
- ### 文档
27
+ ## [0.36.2] - 2026-08-16
17
28
 
18
- - `docs/templates/frontend-task-constraints.md` 把裸 `TODO` 改写为「探索协议 + 结果 schema + 缺失/冲突 fail-closed」,不再出现具体业务组件名或项目专属路径/命令
19
- - `docs/templates/frontend-task-requirement.md` 与 `docs/templates/frontend-design-contract.md` 把裸 `TODO` 改写为「编写协议」三要素(要写什么 / DAG 如何消费 / 缺失/冲突时 fail-closed),并新增 `test/frontend-template-no-todo.test.ts` 扫描三个前端模板断言无裸 `TODO`
20
- - `docs/runtime/frontend-implementation-workflow.md` 的项目规范归类清单改为通用目录约定 + 归类规则,删除写死的示例项目文件名
21
-
22
- ## [0.36.3-beta.0] - 2026-08-16
23
-
24
- > 前端 DAG Mock 治理收紧(beta,发布到 `beta` dist-tag)。
25
-
26
- ### 改进
29
+ ### 修复
27
30
 
28
- - 前端 DAG Mock 策略约束注入设计评审链:`mockContextBlock`(含冻结 allowlist 的「HARD CONSTRAINT」与修复路径)现在也注入 `frontend-design-review-pi` / `frontend-plan-revision-pi` / `frontend-final-design-review-pi`,消除「design-review 按项目规范要求 native、但生成期 allowlist 冻结为 not-needed、prewrite 硬阻断」的策略断环——评审链会明确提示这是生成期契约缺口(需声明 `frontendMock.verifyCommands` 或 `policy: "required"` 后重新生成 DAG)
29
- - 前端任务生成期 Mock 预警:新增 `DagSpec.advisories`,任务源提到接口/API/Mock auto 模式把策略收窄为 `not-needed` 时,`task advance` 的 `warnings` 会给出可操作提示(声明 `frontendMock.verifyCommands` 或 `policy: required` 后重新生成 DAG),把契约缺口提前到生成期暴露
31
+ - exempt changelog history from task-pool-root scan
32
+ - exempt gitignore managed-block doc references in task-pool-root check
30
33
 
31
34
  ## [0.36.1] - 2026-08-16
32
35
 
@@ -39,73 +42,52 @@
39
42
  - move interrupt eligibility to observability to honor kernel import boundary
40
43
  - retry semantic intake across Pi tiers with 300s timeout
41
44
 
42
- ## [0.36.1-beta.0] - 2026-08-16
43
-
44
- > 标准 DAG planner 输出超长紧凑重试(beta,发布到 `beta` dist-tag)。
45
-
46
- ### 改进
47
-
48
- - 标准 DAG 的 planner 节点(`contract-pi` / `plan-pi`)输出超长(`output-too-large`)时自动紧凑重试:新增 `PLANNER_OUTPUT_LIMIT_RETRY_POLICY`(默认重试集合 + `output-too-large`),紧凑重试指令按节点类型区分——结构化契约节点只返回单个紧凑 JSON,普通计划节点返回最小计划(有序步骤、writeSet 边界、验证命令),并丢弃上游原文引用与长证据摘录
49
-
50
- ## [0.35.4-beta.0] - 2026-08-15
51
-
52
- > frontend prewrite 阻断误路由修正 + Mock 契约文档落地(beta,发布到 `beta` dist-tag)。
53
-
54
- ### 改进
55
-
56
- - prewrite 阻断导致的 writer `SKIPPED`(`skippedReason=frontend-prewrite-not-authorized`)失败路由从 `DependencyFailure`/`unblock dependency` 修正为 `ContractMismatch`/`fix-frontend-task-contract-and-regenerate-dag`;`dag report` / `dag doctor` 透传 `skippedReason`,对「Mock 策略契约缺陷需改 task.json + 重新生成 DAG」这类场景给出可操作建议,而非误导性的「解除依赖」
57
-
58
- ### 文档
59
-
60
- - `docs/templates/frontend-task-constraints.md` 的 Mock 约束从 TODO 改为可落地契约:要求从项目**探索**实际存在的确定性 Mock 验证命令(不硬编码命令名)并声明到 `task.json.frontendMock.verifyCommands`,说明生成期 `allowedMockStrategies` 来源、`mock-strategy-outside-allowed` 是生成期契约问题(修复 = 补契约 + 重新生成 DAG,plan-revision 无法修复)
61
- - `docs/runtime/frontend-implementation-workflow.md` 的 task.json 输入标准补充 Mock 契约声明、命令探索与失败恢复指引
62
-
63
45
  ## [0.36.0] - 2026-08-15
64
46
 
65
47
  ### 新增
66
48
 
67
- - add task journey activity rail
68
- - harden Operator Chat DAG recovery
69
- - confirm session deletion inline
70
- - supervise long-running DAG operations
71
- - polish slash command card grid
72
- - localize generated runtime state
49
+ - 新增任务旅程活动栏,把任务创建、人工门禁、多次运行、失败和恢复记录聚合在同一张卡片中
50
+ - 强化 Operator Chat DAG 恢复能力,运行失败后可以直接查看原因、恢复路径和后续动作
51
+ - 会话删除改为列表行内二次确认,减少误删,也不再被浏览器原生弹窗打断
52
+ - Operator Chat 可以持续监督长时间运行的 DAG,直到任务结束、失败或确实需要人工处理
53
+ - 优化斜杠命令面板的卡片布局和键盘操作,在较小窗口中也能完整浏览和选择命令
54
+ - 运行状态、恢复提示和关键操作文案统一为自然中文,减少内部状态码和生硬英文直接暴露给用户
73
55
 
74
56
  ### 修复
75
57
 
76
- - harden task advance supervision
77
- - runner heartbeat logs no longer wake operationWait; writers must use edit tools for markdown
78
- - require writer to sync catalog index companions in writeSet
79
- - accept zh-CN acceptance heading variants in PRD parse
80
- - writer must materialize plan and not claim blocked for untouched forbidden paths
81
- - parse dag report runs[] envelope in rerun eligibility
82
- - tell writers diff is measured from git status, not response text
83
- - allow custom operator tools in SDK session allowlist
84
- - fit slash palette to available height
85
- - close operator chat command gaps
58
+ - 任务推进期间的监督更加可靠,减少因短暂静默或状态更新延迟造成的误判
59
+ - 运行器心跳不再提前唤醒 `operationWait`;Markdown 写入统一使用安全编辑工具,降低误覆盖风险
60
+ - 写入目录文档时必须同步更新对应索引,避免内容已经生成却无法从目录入口找到
61
+ - PRD 验收标准支持更多常见中文标题写法,不再因括号、破折号或措辞差异漏掉验收条件
62
+ - 要求执行节点真正生成计划文件;没有触碰禁写路径时,不再错误声称任务受阻
63
+ - 恢复重跑可以正确读取 `dag report` 返回的运行列表,不再因为响应结构差异误判为不可恢复
64
+ - 变更范围统一以 Git 工作区的真实差异为准,不再根据模型回复文字推测是否修改过文件
65
+ - Operator 自定义工具可以正常进入 Pi SDK 会话,不再被工具白名单意外过滤
66
+ - 斜杠命令面板会根据实际可用高度自动调整,不再被窗口边缘裁切
67
+ - 补齐 Operator Chat 的本地命令处理、动态命令降级和错误提示等交互缺口
86
68
 
87
69
  ## [0.35.3] - 2026-08-15
88
70
 
89
71
  ### 新增
90
72
 
91
- - chat as default landing + analyze-skills-first new-task flow
92
- - guide plan verification targets to writeSet-internal files, command-level checks to static
93
- - console recovery display — surface frontend recovery lineage + outcome
94
- - frontend-implementation DAG phase 6 — recovery root manifest + statistics dedup
95
- - frontend-implementation DAG phase 5 — writer partial-write rollback + child recovery
96
- - frontend-implementation DAG phase 4 — writer transient rollback base
97
- - frontend-implementation DAG phase 3c — scheduler activation gate + recovery trigger
98
- - frontend-implementation DAG phase 3b — staged child-creation kernel
99
- - frontend-implementation DAG phase 3a — recovery state/result types + terminal recovery intent
100
- - producing-node contract self-check and run robustness hardening
101
- - frontend-implementation DAG phase 2 — unified writer helper + structured repair assessment
102
- - slim frontend-implementation DAG phase 1 — prewrite merge + writer admission gate
73
+ - Console 默认进入「对话」页面;创建新任务时先分析需求和依赖,再进入正式任务流程
74
+ - 计划中的文件验证会优先落到允许写入的范围内,命令级检查则使用稳定的静态验证入口
75
+ - Recovery 页面新增恢复链路和最终结果展示,能够看清一次失败是如何被接管和收敛的
76
+ - 前端实现 DAG 增加恢复根清单,并去除重复统计,恢复报告更清晰
77
+ - 写入节点支持部分写入回滚和子任务恢复,局部失败不再轻易污染后续执行
78
+ - 写入节点遇到短暂故障时可以回退到稳定基线,再决定重试或恢复
79
+ - 调度器增加激活门禁和恢复触发条件,只有满足执行条件的子任务才会进入运行
80
+ - 新增分阶段创建子任务的基础能力,为后续恢复和分批执行提供统一入口
81
+ - 补齐恢复状态、恢复结果和终态恢复意图的数据结构,前后端对失败状态的理解保持一致
82
+ - 产出节点新增契约自检,并强化长时间运行时的稳定性和异常收敛能力
83
+ - 统一写入节点的公共辅助逻辑,修复判断改为结构化结果,不再依赖模糊文本
84
+ - 精简前端实现 DAG 的第一阶段,在正式写入前完成结果合并和写入准入检查
103
85
 
104
86
  ### 修复
105
87
 
106
88
  - 规范证据面板不再单列 openspec,统一为「规范已读取/规范检索」
107
- - green full test suite — python3 fallback, build-stamp version normalization, DEP0205 suppression
108
- - update candidate-continuation fixtures for failureCode (rebase semantic fix)
89
+ - 恢复全量测试稳定性:没有 `python3` 时自动使用可用 Python,统一构建版本格式,并处理 Node 弃用警告造成的干扰
90
+ - 修正续跑候选测试数据,使 `failureCode` 与真实失败语义一致,避免变基后出现错误判断
109
91
 
110
92
  ## [0.35.2] - 2026-08-15
111
93
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.36.4-beta.0",
4
- "gitSha": "b4d154e6c1e109f754c059c22588cfd0298d4764",
5
- "builtAt": "2026-08-16T17:25:17.099Z"
3
+ "version": "0.37.0",
4
+ "gitSha": "7b2fcb1072e85a7dbe0d92311d4b457cc34e7735",
5
+ "builtAt": "2026-08-16T20:27:37.113Z"
6
6
  }
@@ -1,14 +1,13 @@
1
1
  import { copyFile, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { assertHarnessWriteAllowed, } from "./completed-facts-guard.js";
4
- let atomicWriteSequence = 0;
5
4
  async function writeAtomic(targetPath, content, options) {
6
5
  assertHarnessWriteAllowed(targetPath, options);
7
6
  const resolved = options?.repoRoot && !path.isAbsolute(targetPath)
8
7
  ? path.resolve(options.repoRoot, targetPath)
9
8
  : path.resolve(targetPath);
10
9
  await mkdir(path.dirname(resolved), { recursive: true });
11
- const tempPath = `${resolved}.${process.pid}.${(atomicWriteSequence += 1)}.tmp`;
10
+ const tempPath = `${resolved}.${process.pid}.tmp`;
12
11
  try {
13
12
  await writeFile(tempPath, content, "utf-8");
14
13
  try {
@@ -46,6 +46,10 @@ export function projectRequirementMarkdown(draft) {
46
46
  lines.push(`- ${item}`);
47
47
  }
48
48
  }
49
+ // References / Source Resolutions remain projected into 需求.md (durable
50
+ // provenance for human readers); the managed read-back validator accepts
51
+ // the projector's own canonical forms instead of rejecting them
52
+ // (2026-08-16 L1: writer/reader round-trip invariant).
49
53
  if (draft.sourceResolutions && draft.sourceResolutions.length > 0) {
50
54
  lines.push("", "## Source Resolutions", "");
51
55
  for (const res of draft.sourceResolutions) {
@@ -5,73 +5,7 @@
5
5
  */
6
6
  import { readFile, readdir, stat } from "node:fs/promises";
7
7
  import path from "node:path";
8
- import { z } from "zod";
9
- import { OPENSPEC_SPEC_DIRS, OPENSPEC_SPEC_EXT_RE, isOpenspecSpecFilePath, } from "../shared/openspec-spec.js";
10
- const classifiedPathSchema = z
11
- .string()
12
- .refine((candidate) => isOpenspecSpecFilePath(candidate), "classified path must be a repo-relative supported spec file under openspec/schemas/, openspec/project-specs/, or ai_workspace/");
13
- /**
14
- * Stable semantic partition of `designEvidence.normativePaths`. Keys are a
15
- * generic directory convention (not project-specific file names); the same
16
- * repository therefore yields the same classification on repeated probes.
17
- */
18
- export const frontendDesignClassifiedSchema = z
19
- .object({
20
- schemas: z.array(classifiedPathSchema),
21
- codeTemplate: z.array(classifiedPathSchema),
22
- rule: z
23
- .object({
24
- api: z.array(classifiedPathSchema),
25
- mock: z.array(classifiedPathSchema),
26
- router: z.array(classifiedPathSchema),
27
- hooks: z.array(classifiedPathSchema),
28
- utils: z.array(classifiedPathSchema),
29
- components: z.array(classifiedPathSchema),
30
- other: z.array(classifiedPathSchema),
31
- })
32
- .strict(),
33
- theme: z.array(classifiedPathSchema),
34
- component: z.array(classifiedPathSchema),
35
- uiOther: z.array(classifiedPathSchema),
36
- advisoryOther: z.array(classifiedPathSchema),
37
- })
38
- .strict();
39
- export const frontendDesignEvidenceSchema = z
40
- .object({
41
- normativePaths: z.array(z.string()),
42
- advisoryPaths: z.array(z.string()),
43
- conflicts: z.array(z.string()),
44
- classified: frontendDesignClassifiedSchema,
45
- })
46
- .strict()
47
- .superRefine((evidence, ctx) => {
48
- const classified = [
49
- ...evidence.classified.schemas,
50
- ...evidence.classified.codeTemplate,
51
- ...evidence.classified.rule.api,
52
- ...evidence.classified.rule.mock,
53
- ...evidence.classified.rule.router,
54
- ...evidence.classified.rule.hooks,
55
- ...evidence.classified.rule.utils,
56
- ...evidence.classified.rule.components,
57
- ...evidence.classified.rule.other,
58
- ...evidence.classified.theme,
59
- ...evidence.classified.component,
60
- ...evidence.classified.uiOther,
61
- ...evidence.classified.advisoryOther,
62
- ];
63
- const classifiedSet = new Set(classified);
64
- const normativeSet = new Set(evidence.normativePaths);
65
- const onlyClassified = [...classifiedSet].filter((candidate) => !normativeSet.has(candidate));
66
- const onlyNormative = [...normativeSet].filter((candidate) => !classifiedSet.has(candidate));
67
- if (onlyClassified.length > 0 || onlyNormative.length > 0) {
68
- ctx.addIssue({
69
- code: z.ZodIssueCode.custom,
70
- message: `classified must partition normativePaths exactly; extra in classified: ${onlyClassified.join(", ") || "(none)"}; missing from classified: ${onlyNormative.join(", ") || "(none)"}`,
71
- path: ["classified"],
72
- });
73
- }
74
- });
8
+ import { OPENSPEC_SPEC_DIRS, OPENSPEC_SPEC_EXT_RE } from "../shared/openspec-spec.js";
75
9
  async function exists(filePath) {
76
10
  try {
77
11
  await stat(filePath);
@@ -107,14 +41,13 @@ async function listOpenspec(repoRoot) {
107
41
  continue;
108
42
  await walk(root, 0);
109
43
  }
110
- out.sort();
111
44
  return out.slice(0, 40);
112
45
  async function walk(dir, depth) {
113
46
  if (depth > 4)
114
47
  return;
115
48
  let entries = [];
116
49
  try {
117
- entries = (await readdir(dir)).sort();
50
+ entries = await readdir(dir);
118
51
  }
119
52
  catch {
120
53
  return;
@@ -135,97 +68,6 @@ async function listOpenspec(repoRoot) {
135
68
  }
136
69
  }
137
70
  }
138
- function emptyClassified() {
139
- return {
140
- schemas: [],
141
- codeTemplate: [],
142
- rule: { api: [], mock: [], router: [], hooks: [], utils: [], components: [], other: [] },
143
- theme: [],
144
- component: [],
145
- uiOther: [],
146
- advisoryOther: [],
147
- };
148
- }
149
- /**
150
- * Classify discovered openspec paths by generic directory convention. Only
151
- * `isOpenspecSpecFilePath` paths enter a bucket; the classifier never invents
152
- * project-specific file names. Basename stems use word-boundary matching so
153
- * e.g. `*api*` (case-insensitive) maps to `rule.api` without hardcoding names.
154
- */
155
- function classifyOpenspecPaths(paths) {
156
- const classified = emptyClassified();
157
- const ruleStem = (basename) => {
158
- const lower = basename.toLowerCase();
159
- const stems = [
160
- ["api", /\bapi\b/],
161
- ["mock", /\bmock\b/],
162
- ["router", /\brouter\b/],
163
- ["hooks", /\bhooks?\b/],
164
- ["utils", /\butils?\b/],
165
- ["components", /\bcomponents?\b/],
166
- ];
167
- for (const [key, re] of stems) {
168
- if (re.test(lower))
169
- return key;
170
- }
171
- return "other";
172
- };
173
- for (const candidate of paths) {
174
- if (!isOpenspecSpecFilePath(candidate))
175
- continue;
176
- const segments = candidate.split("/");
177
- const basename = segments[segments.length - 1] ?? "";
178
- if (candidate.startsWith("openspec/schemas/")) {
179
- classified.schemas.push(candidate);
180
- }
181
- else if (candidate.startsWith("openspec/project-specs/templates/")) {
182
- classified.codeTemplate.push(candidate);
183
- }
184
- else if (candidate.startsWith("openspec/project-specs/rules/")) {
185
- classified.rule[ruleStem(basename)].push(candidate);
186
- }
187
- else if (candidate.startsWith("openspec/project-specs/ui/")) {
188
- const lower = basename.toLowerCase();
189
- if (/\btheme\b/.test(lower))
190
- classified.theme.push(candidate);
191
- else if (/\bcomponents?\b/.test(lower))
192
- classified.component.push(candidate);
193
- else
194
- classified.uiOther.push(candidate);
195
- }
196
- else {
197
- classified.advisoryOther.push(candidate);
198
- }
199
- }
200
- const sortBucket = (bucket) => bucket.sort();
201
- classified.schemas = sortBucket(classified.schemas);
202
- classified.codeTemplate = sortBucket(classified.codeTemplate);
203
- for (const key of [
204
- "api",
205
- "mock",
206
- "router",
207
- "hooks",
208
- "utils",
209
- "components",
210
- "other",
211
- ]) {
212
- classified.rule[key] = sortBucket(classified.rule[key]);
213
- }
214
- classified.theme = sortBucket(classified.theme);
215
- classified.component = sortBucket(classified.component);
216
- classified.uiOther = sortBucket(classified.uiOther);
217
- classified.advisoryOther = sortBucket(classified.advisoryOther);
218
- return classified;
219
- }
220
- function buildDesignEvidence(openspec) {
221
- const normativePaths = [...new Set(openspec)].sort();
222
- return frontendDesignEvidenceSchema.parse({
223
- normativePaths,
224
- advisoryPaths: [],
225
- conflicts: [],
226
- classified: classifyOpenspecPaths(normativePaths),
227
- });
228
- }
229
71
  export function buildAdapterGuidance(capability) {
230
72
  const lines = [
231
73
  "## Frontend project capability (generation-time)",
@@ -279,7 +121,11 @@ export async function discoverFrontendProjectCapability(repoRoot) {
279
121
  a11y: { status: "unknown", tools: [], evidencePaths: [] },
280
122
  evidencePaths: openspec.slice(0, 5),
281
123
  reasons: ["package.json missing or unreadable"],
282
- designEvidence: buildDesignEvidence(openspec),
124
+ designEvidence: {
125
+ normativePaths: openspec,
126
+ advisoryPaths: [],
127
+ conflicts: [],
128
+ },
283
129
  };
284
130
  if (openspec.length) {
285
131
  base.reasons.push(`openspec spec files discovered: ${openspec.slice(0, 5).join(", ")}`);
@@ -438,7 +284,11 @@ export async function discoverFrontendProjectCapability(repoRoot) {
438
284
  if (hasDep(deps, "vue-router"))
439
285
  router = "vue-router";
440
286
  const openspec = await listOpenspec(repoRoot);
441
- const designEvidence = buildDesignEvidence(openspec);
287
+ const designEvidence = {
288
+ normativePaths: openspec,
289
+ advisoryPaths: [],
290
+ conflicts: [],
291
+ };
442
292
  if (openspec.length)
443
293
  evidencePaths.push(...openspec.slice(0, 5));
444
294
  const base = {
@@ -68,6 +68,15 @@ export function listPrepareGaps(input) {
68
68
  level: "blocking",
69
69
  field: "objective",
70
70
  message: "requirement.objective is required",
71
+ details: { headingAliases: ["目标", "objective", "业务目标", "需求概述"] },
72
+ selfHealing: {
73
+ action: "reimport-prd",
74
+ steps: [
75
+ "检查 PRD 是否含「## 目标」或「## Objective」章节;中文括号后缀(如「目标(概述)」)已被识别",
76
+ "若确实缺失:补全 PRD 后重新 importPrd(同 taskId),advance 会自动重投影",
77
+ "不要手改 source/需求.md(会触发 DIRTY_SOURCE);不要在 PRD 目录生成 sidecar draft JSON",
78
+ ],
79
+ },
71
80
  });
72
81
  }
73
82
  if (draft.requirement.acceptanceCriteria.length === 0) {
@@ -76,6 +85,25 @@ export function listPrepareGaps(input) {
76
85
  level: "blocking",
77
86
  field: "acceptanceCriteria",
78
87
  message: "at least one acceptance criterion is required",
88
+ details: {
89
+ recognizedHeadings: [
90
+ "验收标准",
91
+ "acceptance criteria",
92
+ "完成标准",
93
+ "完成定义",
94
+ "验收",
95
+ "dod",
96
+ ],
97
+ note: "标题含括号/破折号后缀会被自动归一;AC 需为列表项(- / 1.)或 AC-NNN 标题/行内格式",
98
+ },
99
+ selfHealing: {
100
+ action: "reimport-prd",
101
+ steps: [
102
+ "检查 PRD 验收章节标题(如「完成标准(验收)」已可识别)与列表格式:需为「- 项目」或「AC-001: 文本」",
103
+ "格式修正后重新 importPrd(同 taskId);advance 内置 semantic intake 兜底,文档语义完整时会自动结构化",
104
+ "若 advance 已返回 SEMANTIC_INTAKE_FAILED:补全 PRD 后重试,不要手改 source/ 需求.md",
105
+ ],
106
+ },
79
107
  });
80
108
  }
81
109
  if (input.acceptanceConflicts && input.acceptanceConflicts.length > 0) {
@@ -188,6 +216,14 @@ export function listPrepareGaps(input) {
188
216
  code: "DIRTY_SOURCE",
189
217
  level: "blocking",
190
218
  message: "existing non-empty or externally-modified source refuses --apply; use show/diff/adopt/recover or --force-overwrite-source",
219
+ selfHealing: {
220
+ action: "adopt-contract",
221
+ steps: [
222
+ "task status --json 读取 contract.observedCanonicalHash 与当前 revision",
223
+ "若变更确属预期:task contract adopt --task <id> --expected-revision <n> --expected-observed-hash <hash> --request-id <id> --request-payload-sha256 <sha>",
224
+ "若变更不是预期的:改 PRD 后重新 importPrd 走完整重投影;禁止手改 lifecycle.json / .transactions/ 绕过",
225
+ ],
226
+ },
191
227
  });
192
228
  risks.push({
193
229
  code: "DIRTY_SOURCE",
@@ -44,15 +44,6 @@ function stripFencedAndComments(text) {
44
44
  .replace(/~~~[\s\S]*?~~~/g, "\n")
45
45
  .replace(/<!--[\s\S]*?-->/g, "\n");
46
46
  }
47
- function normalizeHeadingText(raw) {
48
- return raw
49
- .trim()
50
- // Strip leading section numbers: "2. 业务目标" / "3.1 已确认范围" / "3.2、非目标"
51
- .replace(/^(?:\d+(?:\.\d+)*[..、]?\s*)+/u, "")
52
- .toLowerCase()
53
- .replace(/[::]\s*$/u, "")
54
- .replace(/\s+/g, " ");
55
- }
56
47
  function normalizeAcceptanceId(raw) {
57
48
  return raw
58
49
  .trim()
@@ -69,12 +60,67 @@ function parseEmbeddedAcHeading(headingText) {
69
60
  const text = match[2]?.trim() || id;
70
61
  return { id, text };
71
62
  }
63
+ function normalizeHeadingText(raw) {
64
+ return raw
65
+ .trim()
66
+ // Strip leading section numbers: "2. 业务目标" / "3.1 已确认范围" / "3.2、非目标"
67
+ .replace(/^(?:\d+(?:\.\d+)*[..、]?\s*)+/u, "")
68
+ .toLowerCase()
69
+ .replace(/[::]\s*$/u, "")
70
+ .replace(/\s+/g, " ");
71
+ }
72
+ /**
73
+ * Strip decorative suffixes that real PRD writers attach to section
74
+ * headings: "完成标准(验收)" → "完成标准", "Acceptance Criteria (DoD):" →
75
+ * "acceptance criteria", "验收标准 — 交付门槛" → "验收标准". Handles zh/en
76
+ * parens, dashes, colons and trailing separators before the suffix.
77
+ */
78
+ function stripHeadingDecorations(normalized) {
79
+ return normalized
80
+ .replace(/[((][^(())]*[))]\s*$/u, "")
81
+ .replace(/\s*[—–\-|]\s*[^—–\-|]+$/u, "")
82
+ .replace(/[::]\s*$/u, "")
83
+ .trim();
84
+ }
85
+ const HEADING_PREFIX_HINTS = [
86
+ { key: "objective", hint: "目标" },
87
+ { key: "objective", hint: "objective" },
88
+ { key: "scope", hint: "范围" },
89
+ { key: "scope", hint: "scope" },
90
+ { key: "nonGoals", hint: "非目标" },
91
+ { key: "nonGoals", hint: "out of scope" },
92
+ { key: "acceptance", hint: "验收" },
93
+ { key: "acceptance", hint: "acceptance" },
94
+ { key: "acceptance", hint: "完成标准" },
95
+ { key: "acceptance", hint: "完成定义" },
96
+ { key: "acceptance", hint: "dod" },
97
+ { key: "constraints", hint: "约束" },
98
+ { key: "constraints", hint: "constraints" },
99
+ { key: "openQuestions", hint: "待决问题" },
100
+ { key: "openQuestions", hint: "open questions" },
101
+ { key: "assumptions", hint: "假设" },
102
+ { key: "assumptions", hint: "assumptions" },
103
+ ];
72
104
  function classifyHeading(text) {
73
105
  const normalized = normalizeHeadingText(text);
74
106
  for (const [key, aliases] of Object.entries(HEADING_ALIASES)) {
75
107
  if (aliases.has(normalized))
76
108
  return key;
77
109
  }
110
+ // Decoration-stripped exact match: "完成标准(验收)" → "完成标准".
111
+ const stripped = stripHeadingDecorations(normalized);
112
+ if (stripped && stripped !== normalized) {
113
+ for (const [key, aliases] of Object.entries(HEADING_ALIASES)) {
114
+ if (aliases.has(stripped))
115
+ return key;
116
+ }
117
+ }
118
+ // Prefix-contains fallback: "验收标准与测试" starts with "验收"; keep the
119
+ // hint list narrow (whole-word/leading-substring) to avoid false hits.
120
+ for (const { key, hint } of HEADING_PREFIX_HINTS) {
121
+ if (stripped === hint || stripped.startsWith(hint))
122
+ return key;
123
+ }
78
124
  return "other";
79
125
  }
80
126
  function isAtxHeading(line) {
@@ -408,10 +454,20 @@ export function fillMissingRequirementFacts(base, supplemental) {
408
454
  export function parseVerifyFlag(raw) {
409
455
  const idx = raw.indexOf(":");
410
456
  if (idx <= 0 || idx === raw.length - 1) {
411
- throw new Error(`invalid --verify value "${raw}"; expected <label>:<command>`);
457
+ // Actionable error with concrete examples (2026-08-16 L3'-style):
458
+ // models were guessing flag names (--verify-label) and separator
459
+ // shapes; the example makes the contract self-evident.
460
+ const hint = !raw.includes(":")
461
+ ? `no label separator found; write it as <label>:<command>, e.g. --verify "typecheck:npm run typecheck"`
462
+ : `empty ${idx === 0 ? "label" : "command"}; write it as <label>:<command>, e.g. --verify "typecheck:npm run typecheck"`;
463
+ throw new Error(`invalid --verify value "${raw}"; expected <label>:<command> — ${hint}`);
464
+ }
465
+ const label = raw.slice(0, idx).trim();
466
+ if (!/^[a-z0-9][a-z0-9-]*$/i.test(label)) {
467
+ throw new Error(`invalid --verify label "${label}"; use short kebab-case letters/digits/hyphens (e.g. typecheck, unit-tests, build) — value was "${raw}"`);
412
468
  }
413
469
  return {
414
- label: raw.slice(0, idx).trim(),
470
+ label,
415
471
  command: raw.slice(idx + 1).trim(),
416
472
  };
417
473
  }