@tea-agent/loop-agent 0.36.4-beta.0 → 0.38.0-next.1
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.
- package/AGENTS.md +2 -0
- package/CHANGELOG.md +56 -67
- package/README.md +12 -0
- package/dist/build-stamp.json +3 -3
- package/dist/infrastructure/harness/atomic-write.js +1 -2
- package/dist/task/contract/project.js +4 -0
- package/dist/task/frontend-project-capability.js +12 -162
- package/dist/task/source-prepare/completeness.js +36 -0
- package/dist/task/source-prepare/parse-intent.js +67 -11
- package/dist/task/source-prepare/prepare.js +102 -5
- package/dist/worker/console/chat/chat-event-store.js +91 -0
- package/dist/worker/console/chat/pi-runtime.js +25 -0
- package/dist/worker/console/chat/routes.js +7 -2
- package/dist/worker/console/chat/semantic-activity.js +7 -1
- package/dist/worker/console/chat/session-store.js +6 -1
- package/dist/worker/console/chat/turn-process.js +119 -12
- package/dist/worker/console/operator-actions.js +18 -1
- package/dist/worker/console/static/assets/abnfDiagram-N423BO3Z-DSvF6RXQ.js +1 -0
- package/dist/worker/console/static/assets/arc-DuI4ogtJ.js +1 -0
- package/dist/worker/console/static/assets/architectureDiagram-T3A2C74G-DdBbgD4y.js +36 -0
- package/dist/worker/console/static/assets/blockDiagram-VBNYF7ZC-xQC2HELm.js +132 -0
- package/dist/worker/console/static/assets/c4Diagram-5PPSVZJV-DPMSgwIZ.js +10 -0
- package/dist/worker/console/static/assets/channel-DLVz9TYW.js +1 -0
- package/dist/worker/console/static/assets/chunk-2GRJ4B5K-WTEKTxin.js +1 -0
- package/dist/worker/console/static/assets/chunk-2Q5K7J3B-B6vZ0Pv_.js +1 -0
- package/dist/worker/console/static/assets/chunk-5RXB4S5H-BrerNAix.js +231 -0
- package/dist/worker/console/static/assets/chunk-5VM5RSS4-D_ch2aaI.js +15 -0
- package/dist/worker/console/static/assets/chunk-6Q2QTUOP-VvfacNrq.js +88 -0
- package/dist/worker/console/static/assets/chunk-GF5L2VYU-B-uGFIg2.js +206 -0
- package/dist/worker/console/static/assets/chunk-JWPE2WC7-BlysCtod.js +1 -0
- package/dist/worker/console/static/assets/chunk-KBJHAD2P-BjAUAfcT.js +1 -0
- package/dist/worker/console/static/assets/chunk-RYQCIY6F-BeZnBmb-.js +1 -0
- package/dist/worker/console/static/assets/chunk-XXDRQBXY-Bbfe3UJG.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +1 -0
- package/dist/worker/console/static/assets/cose-bilkent-JH36ORCC-BKJQnYVM.js +1 -0
- package/dist/worker/console/static/assets/cynefin-VYW2F7L2-B1wtI3hU.js +166 -0
- package/dist/worker/console/static/assets/cynefinDiagram-MW4NZA55-DCF6DBSu.js +62 -0
- package/dist/worker/console/static/assets/cytoscape.esm-yzknjiTM.js +321 -0
- package/dist/worker/console/static/assets/dagre-VZM6K2ZE-BJyNGRlP.js +4 -0
- package/dist/worker/console/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/worker/console/static/assets/diagram-7IWD3JNH-B7j1-VsZ.js +30 -0
- package/dist/worker/console/static/assets/diagram-B4RE2ZJO-BREJ140a.js +3 -0
- package/dist/worker/console/static/assets/diagram-LBJQPF4R-CDPHmHGH.js +24 -0
- package/dist/worker/console/static/assets/diagram-Q27KOJAE-BtZLrez1.js +24 -0
- package/dist/worker/console/static/assets/diagram-UB23O5K3-mzybu8oU.js +41 -0
- package/dist/worker/console/static/assets/ebnfDiagram-BXEA7PRR-Dk3I2uO6.js +1 -0
- package/dist/worker/console/static/assets/erDiagram-JOGREHBK-BT97u_tQ.js +85 -0
- package/dist/worker/console/static/assets/flowDiagram-UKHOOZJN-CMOtYzui.js +156 -0
- package/dist/worker/console/static/assets/ganttDiagram-PKOTCBZU-DiNydkuY.js +292 -0
- package/dist/worker/console/static/assets/gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js +106 -0
- package/dist/worker/console/static/assets/graph-DOmOIIwC.js +1 -0
- package/dist/worker/console/static/assets/index-DQxiso1w.js +325 -0
- package/dist/worker/console/static/assets/index-_j0Hzo69.css +1 -0
- package/dist/worker/console/static/assets/infoDiagram-6WML65LV-AsGIGKKB.js +2 -0
- package/dist/worker/console/static/assets/init-Gi6I4Gst.js +1 -0
- package/dist/worker/console/static/assets/ishikawaDiagram-WSZJBQD7-BL8ITZh3.js +70 -0
- package/dist/worker/console/static/assets/journeyDiagram-NVQOT4AX-VPKAUX8M.js +139 -0
- package/dist/worker/console/static/assets/kanban-definition-27J2QSJJ-BL56hfGp.js +89 -0
- package/dist/worker/console/static/assets/layout-D-LzfAck.js +1 -0
- package/dist/worker/console/static/assets/linear-BmSG3nIZ.js +1 -0
- package/dist/worker/console/static/assets/map-DxJ2ADlA.js +1 -0
- package/dist/worker/console/static/assets/mermaid.core-DWGK9kwP.js +308 -0
- package/dist/worker/console/static/assets/mindmap-definition-FAOFIHXS--vvKLjo0.js +96 -0
- package/dist/worker/console/static/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/worker/console/static/assets/pegDiagram-VL7TDLO6-B41mxb2-.js +1 -0
- package/dist/worker/console/static/assets/pieDiagram-7S7Q4E2Y-DTlu77sw.js +39 -0
- package/dist/worker/console/static/assets/quadrantDiagram-CIZ2JOQS-Bk5A7alv.js +7 -0
- package/dist/worker/console/static/assets/railroadDiagram-AXF67PYL-Csf9jzQy.js +1 -0
- package/dist/worker/console/static/assets/requirementDiagram-LRYGKXZP-CeptoSNx.js +84 -0
- package/dist/worker/console/static/assets/sankeyDiagram-W5VNT64P-A-DLCLer.js +40 -0
- package/dist/worker/console/static/assets/sequenceDiagram-SI44F4Z6-22VbPdOI.js +162 -0
- package/dist/worker/console/static/assets/sizeCapture-X5ZJPWSS-DWvAyBC9.js +1 -0
- package/dist/worker/console/static/assets/stateDiagram-OKZ733FA-qICZ75yT.js +1 -0
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +1 -0
- package/dist/worker/console/static/assets/swimlanes-SLNWSIFB-xi7lcMyp.js +2 -0
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +8 -0
- package/dist/worker/console/static/assets/timeline-definition-Z64GVDOM-Bu6Nl6O2.js +120 -0
- package/dist/worker/console/static/assets/vennDiagram-T6HMQDX7-BzMz4Bn9.js +34 -0
- package/dist/worker/console/static/assets/wardleyDiagram-T6FBY63Y-CxaJnzc5.js +78 -0
- package/dist/worker/console/static/assets/xychartDiagram-ELKLHX3M-5JK-omQK.js +7 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +25 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +37 -2
- package/dist/worker/observe/static/operator-chrome.js +4 -2
- package/dist/workflows/dag/failure-routing.js +4 -9
- package/dist/workflows/dag/frontend-prewrite-gate.js +8 -37
- package/dist/workflows/dag/init-hybrid.js +4 -73
- package/dist/workflows/dag/lifecycle.js +0 -4
- package/dist/workflows/dag/node-execution.js +6 -16
- package/dist/workflows/dag/report.js +0 -6
- package/dist/workflows/dag/retry-policy.js +0 -11
- package/dist/workflows/dag/runner.js +19 -3
- package/dist/workflows/dag/types.js +0 -2
- package/dist/workflows/dag/validate.js +2 -7
- package/docs/README.md +2 -0
- package/docs/operations/README.md +2 -1
- package/docs/templates/frontend-design-contract.md +14 -34
- package/docs/templates/frontend-task-constraints.md +13 -33
- package/docs/templates/frontend-task-requirement.md +20 -54
- package/package.json +6 -1
- package/skills/loop-agent/references/command-reference.md +4 -1
- package/skills/loop-agent/references/harness-policy.md +1 -1
- package/skills/loop-agent/references/task-workflow.md +1 -1
- package/dist/worker/console/static/assets/index-D83DYAFG.css +0 -1
- package/dist/worker/console/static/assets/index-IXm7oYjL.js +0 -59
package/AGENTS.md
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
6. 后端/接口/pytest → `taskKind: "backend-test"`(不是 `--profile`);知识回写 `knowledge-sync`;图谱开荒 `knowledge-graph-bootstrap`。`--profile` 仅 `auto|minimal|standard|reviewed|supervised`。
|
|
41
41
|
7. 看板/observe → `agent-worker console`(默认 repo=当前目录、port=8790;listen 成功后默认打开系统浏览器,`--no-open` 禁止)(`/inspect/` 只读);`observe serve` 已下线(`OBSERVE_SERVE_REMOVED` + exit 2);`observe snapshot` 仍可用。
|
|
42
42
|
8. 分支合并 → 先读 `docs/operations/branch-merge-guideline.md`。
|
|
43
|
+
9. 涉及版本发布、npm 渠道、release 分支、GitHub Actions 发布链或 dist-tag → 先读 `docs/operations/version-and-release-management.md`。
|
|
43
44
|
|
|
44
45
|
## 会话协议
|
|
45
46
|
|
|
@@ -62,6 +63,7 @@ DAG 执行期间,主 agent 不得修改 writeSet 外的任何工作区文件
|
|
|
62
63
|
- `skills/`:源仓库/npm 内置 skills;目标项目只生成 `.agents/skills/`
|
|
63
64
|
- `.harness/`:运行态(tasks/dag-runs/runs 等;init 会 gitignore 运行事实,保留 prompts 与占位)
|
|
64
65
|
- `docs/`:治理;`website/`:用户文档站;`scripts/`:检查与 CI
|
|
66
|
+
- 版本发布链(next / release-x.y / latest 手工 promote):`docs/operations/version-and-release-management.md`
|
|
65
67
|
|
|
66
68
|
## 工作规则(增量约束)
|
|
67
69
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,32 +1,42 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
-
## [
|
|
4
|
-
|
|
5
|
-
> 前端 DAG 规范上下文确定性落地(beta,发布到 `beta` dist-tag)。
|
|
3
|
+
## [Unreleased]
|
|
6
4
|
|
|
7
5
|
### 新增
|
|
8
6
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
7
|
+
- 版本发布改为三渠道:main 自动发布 npm `next`(`x.y.0-next.N`)、release 分支自动发布正式 patch 到 `release-x.y`、`latest` 仅由人工 GitHub Actions promotion 移动(支持 dry-run 与 CAS 校验)
|
|
8
|
+
- 新增 `next-release.yml` / `cut-release-line.yml` / `release-line-publish.yml` / `promote-npm-latest.yml`,删除旧每日自动 latest 发布入口
|
|
9
|
+
- 发布策略改为 branch + version + channel 矩阵(`scripts/release-channel-policy.mjs`),fail closed
|
|
10
|
+
- 双周 Release Train:可从选定 next 的精确 source SHA 创建版本化 release 分支,带幂等恢复与单 tarball SHA-256 绑定
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
## [0.37.0] - 2026-08-16
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
### 新增
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
- markdown syntax highlight, math formulas and mermaid diagrams; operate entry lands on chat
|
|
17
|
+
- label rerun titles with round number from -rN id suffix
|
|
18
|
+
- enter-DAG action, artifact rail projection, zh changelog
|
|
19
|
+
- surface compaction progress and timeline summary
|
|
20
|
+
- floating auto-dismiss toasts replace in-flow error alerts
|
|
21
|
+
- add running spinners to session titles and stop button
|
|
22
|
+
- show context ratio, semantic cache icons, align process entry typography
|
|
23
|
+
- show context usage and semantic cache labels in usage bar
|
|
24
|
+
- stream live turn process in strict order
|
|
17
25
|
|
|
18
|
-
|
|
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` 的项目规范归类清单改为通用目录约定 + 归类规则,删除写死的示例项目文件名
|
|
26
|
+
### 修复
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
- isolate interrupt kernel behind worker adapter
|
|
29
|
+
- constrain session title width so hover tooltip engages
|
|
30
|
+
- backfill operation link for semantic activity refs
|
|
31
|
+
- fail fast on non-canonical newTask ids with suggested fix
|
|
32
|
+
- accept projected references and normalize PRD headings
|
|
23
33
|
|
|
24
|
-
|
|
34
|
+
## [0.36.2] - 2026-08-16
|
|
25
35
|
|
|
26
|
-
###
|
|
36
|
+
### 修复
|
|
27
37
|
|
|
28
|
-
-
|
|
29
|
-
-
|
|
38
|
+
- exempt changelog history from task-pool-root scan
|
|
39
|
+
- exempt gitignore managed-block doc references in task-pool-root check
|
|
30
40
|
|
|
31
41
|
## [0.36.1] - 2026-08-16
|
|
32
42
|
|
|
@@ -39,73 +49,52 @@
|
|
|
39
49
|
- move interrupt eligibility to observability to honor kernel import boundary
|
|
40
50
|
- retry semantic intake across Pi tiers with 300s timeout
|
|
41
51
|
|
|
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
52
|
## [0.36.0] - 2026-08-15
|
|
64
53
|
|
|
65
54
|
### 新增
|
|
66
55
|
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
56
|
+
- 新增任务旅程活动栏,把任务创建、人工门禁、多次运行、失败和恢复记录聚合在同一张卡片中
|
|
57
|
+
- 强化 Operator Chat 的 DAG 恢复能力,运行失败后可以直接查看原因、恢复路径和后续动作
|
|
58
|
+
- 会话删除改为列表行内二次确认,减少误删,也不再被浏览器原生弹窗打断
|
|
59
|
+
- Operator Chat 可以持续监督长时间运行的 DAG,直到任务结束、失败或确实需要人工处理
|
|
60
|
+
- 优化斜杠命令面板的卡片布局和键盘操作,在较小窗口中也能完整浏览和选择命令
|
|
61
|
+
- 运行状态、恢复提示和关键操作文案统一为自然中文,减少内部状态码和生硬英文直接暴露给用户
|
|
73
62
|
|
|
74
63
|
### 修复
|
|
75
64
|
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
65
|
+
- 任务推进期间的监督更加可靠,减少因短暂静默或状态更新延迟造成的误判
|
|
66
|
+
- 运行器心跳不再提前唤醒 `operationWait`;Markdown 写入统一使用安全编辑工具,降低误覆盖风险
|
|
67
|
+
- 写入目录文档时必须同步更新对应索引,避免内容已经生成却无法从目录入口找到
|
|
68
|
+
- PRD 验收标准支持更多常见中文标题写法,不再因括号、破折号或措辞差异漏掉验收条件
|
|
69
|
+
- 要求执行节点真正生成计划文件;没有触碰禁写路径时,不再错误声称任务受阻
|
|
70
|
+
- 恢复重跑可以正确读取 `dag report` 返回的运行列表,不再因为响应结构差异误判为不可恢复
|
|
71
|
+
- 变更范围统一以 Git 工作区的真实差异为准,不再根据模型回复文字推测是否修改过文件
|
|
72
|
+
- Operator 自定义工具可以正常进入 Pi SDK 会话,不再被工具白名单意外过滤
|
|
73
|
+
- 斜杠命令面板会根据实际可用高度自动调整,不再被窗口边缘裁切
|
|
74
|
+
- 补齐 Operator Chat 的本地命令处理、动态命令降级和错误提示等交互缺口
|
|
86
75
|
|
|
87
76
|
## [0.35.3] - 2026-08-15
|
|
88
77
|
|
|
89
78
|
### 新增
|
|
90
79
|
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
99
|
-
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
80
|
+
- Console 默认进入「对话」页面;创建新任务时先分析需求和依赖,再进入正式任务流程
|
|
81
|
+
- 计划中的文件验证会优先落到允许写入的范围内,命令级检查则使用稳定的静态验证入口
|
|
82
|
+
- Recovery 页面新增恢复链路和最终结果展示,能够看清一次失败是如何被接管和收敛的
|
|
83
|
+
- 前端实现 DAG 增加恢复根清单,并去除重复统计,恢复报告更清晰
|
|
84
|
+
- 写入节点支持部分写入回滚和子任务恢复,局部失败不再轻易污染后续执行
|
|
85
|
+
- 写入节点遇到短暂故障时可以回退到稳定基线,再决定重试或恢复
|
|
86
|
+
- 调度器增加激活门禁和恢复触发条件,只有满足执行条件的子任务才会进入运行
|
|
87
|
+
- 新增分阶段创建子任务的基础能力,为后续恢复和分批执行提供统一入口
|
|
88
|
+
- 补齐恢复状态、恢复结果和终态恢复意图的数据结构,前后端对失败状态的理解保持一致
|
|
89
|
+
- 产出节点新增契约自检,并强化长时间运行时的稳定性和异常收敛能力
|
|
90
|
+
- 统一写入节点的公共辅助逻辑,修复判断改为结构化结果,不再依赖模糊文本
|
|
91
|
+
- 精简前端实现 DAG 的第一阶段,在正式写入前完成结果合并和写入准入检查
|
|
103
92
|
|
|
104
93
|
### 修复
|
|
105
94
|
|
|
106
95
|
- 规范证据面板不再单列 openspec,统一为「规范已读取/规范检索」
|
|
107
|
-
-
|
|
108
|
-
-
|
|
96
|
+
- 恢复全量测试稳定性:没有 `python3` 时自动使用可用 Python,统一构建版本格式,并处理 Node 弃用警告造成的干扰
|
|
97
|
+
- 修正续跑候选测试数据,使 `failureCode` 与真实失败语义一致,避免变基后出现错误判断
|
|
109
98
|
|
|
110
99
|
## [0.35.2] - 2026-08-15
|
|
111
100
|
|
package/README.md
CHANGED
|
@@ -29,6 +29,18 @@ npm install -g @tea-agent/loop-agent@latest
|
|
|
29
29
|
loop-agent --version
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
渠道说明(普通用户默认 `latest`;开发/测试用 `next`;固定某条版本线用
|
|
33
|
+
`release-x.y`;正式 dogfood 用精确版本):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install -g @tea-agent/loop-agent # 推荐稳定版本
|
|
37
|
+
npm install -g @tea-agent/loop-agent@next # main 高频开发构建
|
|
38
|
+
npm install -g @tea-agent/loop-agent@release-0.37 # 0.37 版本线最新 patch
|
|
39
|
+
npm install -g @tea-agent/loop-agent@0.37.5 # 精确、可复现
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
完整发布治理见 `docs/operations/version-and-release-management.md`。
|
|
43
|
+
|
|
32
44
|
### 初始化当前项目
|
|
33
45
|
|
|
34
46
|
```bash
|
package/dist/build-stamp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.
|
|
4
|
-
"gitSha": "
|
|
5
|
-
"builtAt": "2026-08-
|
|
3
|
+
"version": "0.37.0",
|
|
4
|
+
"gitSha": "7b0878dd8e02f0df25546a68db57ca0e4e6f58a1",
|
|
5
|
+
"builtAt": "2026-08-17T01:11:56.938Z"
|
|
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}
|
|
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 {
|
|
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 =
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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
|
|
470
|
+
label,
|
|
415
471
|
command: raw.slice(idx + 1).trim(),
|
|
416
472
|
};
|
|
417
473
|
}
|