@cyning/harness 2.4.0 → 2.6.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,41 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.6.0] - 2026-07-24
8
+
9
+ ### Added
10
+
11
+ - **task lint · 思考轮结构规则组(G4)**:条件触发(行首 `### R0` 或 `##/###` 标题含「思考轮」)后检查:
12
+ - **E8** R0–R5 槽位标题齐全(`^### R\d` 前缀宽容)
13
+ - **E9** 控制表字段 `actual_last_round` / `early_stop` / `residual_risks`
14
+ - **E10** `early_stop=yes` 时 `early_stop_reason` 非空
15
+ - **W4** 无思考轮节(SPEC 承载 / bugfix 合法豁免提示)
16
+ - **W5** 存在 `### R6+` 但缺 `round_extension_note`
17
+ - 槽内容 `(待填)` / `(跳过 · 见思考轮控制)` 合法;**不**查思考内容质量(20 帽职责)。
18
+ - SPEC:`docs/spec/SPEC-task-lint-thinking-rounds_v1.md`
19
+
20
+ ### Notes
21
+
22
+ - 背景:机械化率审计 G4(rethink 矩阵 P1 · 第三波);与 G1 同命令、分开交付。
23
+ - minor · 无 verify 行为变更 · `npm test` 含既有回归全绿
24
+ - **待发布**:`@cyning/harness@2.6.0`(维护者 CLOSE 后 npm publish + tag)
25
+
26
+ ## [2.5.0] - 2026-07-24
27
+
28
+ ### Changed
29
+
30
+ - **⚠ 行为变更 · reviews 留档闸(G2)**:`verify --task` 新增 blocking 条件——`docs/harness/reviews/task_<base>_audit_R<n>_*.md`(n≥1)不存在时 `VERIFY: BLOCKED · missing R<n> review` exit 2(即使人工闸表全 approved)。职责切分:机器只查**存在性**,审查结论仍由维护者签 `HG-AUDIT-R1` 覆盖。**豁免**:`--allow-no-review`(warn 放行 · 留痕)。
31
+ - `task close` 新增**检查 6**:归档时 R<n> 审查文应存在(纸链完整);同 `--allow-no-review` 豁免。
32
+ - `findReviewPath` 升级 `findReview`:glob `_audit_R1_` → `_audit_R` 多轮取最新;**匹配两侧剥离版本后缀**(合法变体 `task_x_v1.md` ↔ `task_x_audit_R1_*.md` · R1 审查实测案例);`review_path` 字段兼容。
33
+ - verify `--json` handoff 新增 `review_found` / `review_latest` / `review_rounds`;`may_start_30` 纳入审查文条件。
34
+
35
+ ### Notes
36
+
37
+ - 背景:机械化率审计 G2(rethink 矩阵 P0 最后一块);22/20 帽自此脱离「全帽零机械」。
38
+ - dogfood:工作区 approved 任务全过(含版本后缀变体 ops_clarify · 恰好是 R1 审查抓出的案例)。
39
+ - minor · verify 行为变更(本版核心 · 见上)· `npm test` 118 全绿 · SPEC `docs/spec/SPEC-reviews-retention-gate_v1.md`
40
+ - **已发布**:`@cyning/harness@2.5.0`(npm `latest` · 2026-07-24)· tag `v2.5.0` 已推送
41
+
7
42
  ## [2.4.0] - 2026-07-24
8
43
 
9
44
  ### Changed
@@ -29,6 +64,7 @@
29
64
  - **升级指引**:业务仓 `upgrade` 后若出现 `warn: obsolete`,按提示人工删除旧帽文件即可(sync 不会自动删)。
30
65
  - 背景:三方对比(后端仓=V1 手工快照 · 包内=半途状态 · V2 真值仅在工作区)· task `cyning-harness-hat-v2-split-sync`。
31
66
  - minor · 无 CLI breaking · `npm test` 111 全绿
67
+ - **已发布**:`@cyning/harness@2.4.0`(npm `latest` · 2026-07-24)· tag `v2.4.0` 已推送
32
68
 
33
69
  ## [2.3.0] - 2026-07-24
34
70
 
@@ -35,9 +35,9 @@ description: 机械化率统计矩阵 + 缺口聚类 + 候选闸优先级排序
35
35
  | 优先级 | 缺口 | 风险 | 成本 | 候选闸形态 | 挂点 |
36
36
  |---|---|---|---|---|---|
37
37
  | ~~P0~~ ✅ | **G1 · task md 结构 lint** | 高(下游全部闸的输入假设) | 低(解析与 M5 共享) | **已落地 v2.3.0**:`harness task lint`(E1–E7/W1–W3)· 不接入 verify(dogfood 后另议) | 10 产出时 |
38
- | **P0** | **G2 · reviews 留档闸** | 高(与 invoke 失守同构,且是 HG-AUDIT-R1 的签署依据) | 低(glob 命名模式) | `verify` 增检查:`reviews/task_<slug>_audit_R1_*.md` 存在(may_start_30 加条件);可选 `close` 增第 6 | verify(30 前)· close(归档前) |
38
+ | ~~P0~~ | **G2 · reviews 留档闸** | 高(与 invoke 失守同构,且是 HG-AUDIT-R1 的签署依据) | 低(glob 命名模式) | **已落地 v2.5.0**:verify blocking(`missing R<n> review`)+ close 检查 6 · 豁免 `--allow-no-review` · 匹配两侧剥离版本后缀 | verify(30 前)· close(归档前) |
39
39
  | ~~P1~~ ✅(task 部分) | **G3 · 文本纪律 grep 闸** | 中 | 极低(正则) | **已落地 v2.3.0**(E6 绝对路径);invoke 预写 approved grep 缓做(SPEC R2:误报不可控) | 同 G1 |
40
- | **P1** | **G4 · 思考轮结构检查** | 中(阶段 C 纪律,漏槽=思考轮形同虚设) | 中(格式变体多,须宽容:只查槽位存在与控制表字段,不查内容质量) | 并入 `task lint`:R0R5 + `actual_last_round`/`early_stop`/`residual_risks` 字段存在性 | 10 产出时 |
40
+ | ~~P1~~ | **G4 · 思考轮结构检查** | 中(阶段 C 纪律,漏槽=思考轮形同虚设) | 中(格式变体多,须宽容:只查槽位存在与控制表字段,不查内容质量) | **已落地 v2.6.0**:`task lint` 条件触发 E8E10 / W4–W5(有节才查 · 形态 B/C 仅 W4) | 10 产出时 |
41
41
  | **P2** | **G7 · 执行证据** | 高但难(「真跑过」本质要 runner 见证) | 高 | `harness run -- <验证命令>`:包装执行、留 exit code + 输出摘要到 `.cyning-harness/runs/`,40 回填引用 run id | 方向二状态机内再做 |
42
42
  | **P3** | **G6 · git 行为层** | 中 | 高(需 hook/平台集成) | 暂留 prompt 层;远期 Agent 契约(方向三)的行为审计范畴 | 方向三 |
43
43
 
@@ -27,7 +27,7 @@ description: 结论与立项建议 · 缺口到 SPEC/task 的转化路径 + 与
27
27
  - `close` 可选增第 6 项检查(归档时 R1 审查文应仍在)
28
28
  - 注意与现有流程的兼容:`findReviewPath` 已被 `verify --json` 消费(handoff.review_path),本次是把它从「信息」升级为「闸」
29
29
 
30
- ### 第三波 · 思考轮结构检查(G4)· 建议 v2.4.0
30
+ ### 第三波 · 思考轮结构检查(G4)· v2.6.0
31
31
 
32
32
  - 并入 `task lint` 但作为独立规则组;宽容度设计需 dogfood 一轮(工作区 task 的槽位写法变体多)
33
33
  - 只查结构(槽位存在、控制表字段),**不查**内容质量——质量判定永远留在 22 帽
@@ -31,6 +31,7 @@ description: 索引与背景 · 2026-07 机械化率审计思考系列 · 何时
31
31
  | 2026-07-24 | 矩阵聚合 + 缺口聚类 | 03 |
32
32
  | 2026-07-24 | 结论与立项建议 | 04 |
33
33
  | 2026-07-24 | **第一波落地**:G1+G3(task 部分)→ `task lint` v2.3.0(03 已勾 ✅);dogfood 数据:17 个 active task 中 15 个存在真实结构缺口 | 03 更新 |
34
+ | 2026-07-24 | **第二波落地**:G2 → verify/close reviews 闸 v2.5.0(03 已勾 ✅ · **P0 队列清零**);期间帽定义 V2 拆分收编(v2.4.0) | 03 更新 |
34
35
 
35
36
  **机制编号约定**(02/03 引用):M1 gate-check.sh · M2 verify · M3 audit · M4 task check · M5 task close · M6 task lint-done · M7 graph yaml · M8 graph HGM · M9 harness-sync · M10 package-scripts
36
37
 
@@ -0,0 +1,141 @@
1
+ # SPEC:reviews 留档闸(verify + close 的 R&lt;n&gt; 审查文存在性检查)(v1)
2
+
3
+ > **状态**:`done`(10-spec R0–R5 已回填 · **维护者已签收 2026-07-24** · → 00 起草 task)
4
+ > **track**:`feature`
5
+ > **关联图谱**:无(纯 Harness 工具链)
6
+ > **上游思考**:[`docs/rethink/2026-07-mechanization-rate/`](../rethink/2026-07-mechanization-rate/)(缺口 G2 · P0 最后一块)
7
+ > **下游**:SPEC 签收 → 00 起草 task → 10-task
8
+
9
+ ---
10
+
11
+ ## Harness 元信息
12
+
13
+ | 字段 | 值 |
14
+ |------|-----|
15
+ | **spec_slug** | `reviews-retention-gate` |
16
+ | **test_strategy** | `required` |
17
+ | **test_strategy_note** | verify/close 双挂点的 block/pass/豁免路径均可 fixture 驱动 |
18
+ | **entry_invoke_10_spec** | `Projects/docs/harness/invokes/by-task/cyning-harness-reviews-retention-gate/invoke_20260724_10_spec_reviews_retention_gate.md` |
19
+ | **entry_invoke_00_draft** | 工作区 `docs/harness/prompts/PROMPT_00_draft_spec_or_task_v1_zh.md` |
20
+
21
+ ---
22
+
23
+ ## 1. 背景与目标
24
+
25
+ 机械化率审计(rethink 02 C1–C4):**20-task-audit(原 22)全帽零机械**——「必须落盘 `reviews/task_<slug>_audit_R<n>_<date>.md`」是纯 Prompt 纪律,与 invoke 失守同构。`FRAGMENT_30_gate_verify` 闸扫描表里本有一行「reviews:`task_*_audit_R1_*.md` 存在且 R1 通过?」,但它是**手工填表项**,无任何代码消费。
26
+
27
+ **关键事实**:`HG-AUDIT-R1` 是 30 的硬闸且由人签,但**签署依据(R1 审查文)的存在性本身没有闸**——人可以对着一份不存在的审查文签 approved,机械层面无法发现。
28
+
29
+ **目标**:把「R&lt;n&gt; 审查文存在」从填表项升级为机械闸,双挂点:
30
+
31
+ 1. **verify(30 前)**:`may_start_30` 增加前置条件——`docs/harness/reviews/task_<base>_audit_R<n>_*.md`(n≥1)至少一份存在;
32
+ 2. **close(归档前)**:新增第 6 项检查——归档时 R&lt;n&gt; 审查文应存在(纸链完整性)。
33
+
34
+ **职责切分(核心设计)**:机器只查**存在性**(形式);审查**结论是否通过**由维护者签 `HG-AUDIT-R1` 覆盖(实质)。闸不做内容判定。
35
+
36
+ ## 2. 范围
37
+
38
+ - **D1 · verify 挂点(`lib/verify.js` + `lib/task-meta.js`)**:
39
+ - `findReviewPath` 扩展:glob 从 `_audit_R1_` 放宽为 `_audit_R`(取最新轮),返回 `{found, latest, rounds[]}`
40
+ - `verifyTarget`:taskFile 模式下审查文缺失 → `VERIFY: BLOCKED · missing R<n> review` · exit 2
41
+ - 豁免:`--allow-no-review`(warn 放行 · 留痕);handoff JSON 增加 `review_found` / `review_latest`(`review_path` 保留兼容)
42
+ - **D2 · close 挂点(`lib/task-close.js`)**:新增检查 6——R&lt;n&gt; 审查文存在;缺失 → `CLOSE: BLOCKED`;同一 `--allow-no-review` 豁免(warn)
43
+ - **D3 · 测试**:verify 三态(存在 pass / 缺失 block / 豁免 warn)+ close 检查 6 三态;`npm test` 全绿(含既有回归)
44
+ - **D4 · dogfood**:工作区 active tasks 跑 verify 报告「approved 但无审查文」分布(只报告)
45
+ - **D5 · 文档**:`FRAGMENT_30_gate_verify_v1_zh.md` 该行标注「v2.5+ verify 机械强制存在性」;CHANGELOG + 版本 **v2.5.0**
46
+
47
+ ## 3. 非范围
48
+
49
+ - 审查文**内容**判定(pass/fail 结论、核对项质量)——永远人/20 帽职责
50
+ - SPEC 审查文(20-spec-audit · HG-SPEC-SIGNOFF)留档闸——下一轮
51
+ - `task lint-done` 的 reviews 集合 diff(done↔reviews)——有需求再议
52
+ - 存量 done 任务的 reviews 补录;R 轮次与 task 回填一致性的交叉校验
53
+
54
+ ---
55
+
56
+ ## 4. 验收标准
57
+
58
+ - [ ] verify:task 有 `reviews/task_<base>_audit_R<n>_*.md`(n≥1)→ 行为不变;**缺失 → `VERIFY: BLOCKED · missing R<n> review`** exit 2(即使闸表全 approved)
59
+ - [ ] verify:`--allow-no-review` → warn + 放行;`--json` handoff 含 `review_found` / `review_latest`
60
+ - [ ] verify:多轮审查文(R1+R2)取最新轮为 `review_latest`
61
+ - [ ] close:检查 6 缺失审查文 → exit 2 不 mv;`--allow-no-review` warn 放行
62
+ - [ ] `npm test` 全绿(新增用例覆盖上述三态 · 既有 111 例回归)
63
+ - [ ] dogfood:工作区 active tasks「approved 无审查文」分布落自检结论
64
+ - [ ] FRAGMENT_30 + CHANGELOG(v2.5.0 · 行为变更醒目说明 + 豁免指引)
65
+
66
+ ---
67
+
68
+ ## 5. failure_paths
69
+
70
+ | 触发条件 | 系统行为 | 可重试 |
71
+ |----------|----------|--------|
72
+ | 30 前审查文缺失 | `VERIFY: BLOCKED · missing R<n> review` · exit 2 | 20 补审落盘后重跑;或维护者授意 `--allow-no-review` |
73
+ | 归档时审查文缺失 | `CLOSE: BLOCKED` · 不 mv | 补审 / 豁免后重跑 |
74
+ | 存量 approved 无审查文任务被挡 | 属**预期拦截**(闸的目的);豁免阀 `--allow-no-review` 留痕放行 | 同上 |
75
+ | 审查文命名不规范(无 `_audit_R` 段) | 视为缺失(形式宽容止于轮次段,文件名主体必须匹配 task basename) | 重命名后重跑 |
76
+ | 业务仓未升级 2.5.0 | 旧 verify 无此检查 · 行为不变 | upgrade |
77
+
78
+ ---
79
+
80
+ ## 6. 依赖与引用
81
+
82
+ - 复用:`lib/task-meta.js` `findReviewPath`(现 glob `_audit_R1_` · 已服务 verify --json 的 `review_path` 信息位)
83
+ - 挂点:`lib/verify.js` `verifyTarget`(taskFile 分支 · gate-check 之后);`lib/task-close.js` 检查表第 6 项
84
+ - 纪律原文:`harness/prompts/20-task-audit.md`(必须落盘 reviews)· `harness/prompts/FRAGMENT_30_gate_verify_v1_zh.md`(reviews 填表行)
85
+ - 缺口出处:rethink 02(C1–C4)· 03(G2 行)
86
+
87
+ ---
88
+
89
+ ## 7. 思考轮(10-spec 回填 · R0–R5)
90
+
91
+ ### R0 · 读入与约束
92
+
93
+ 读入:rethink 02/03(G2=P0 队列最后一块);`findReviewPath` 现实现(只 glob R1 · 只作信息位);FRAGMENT_30 的 reviews 填表行(纪律原文);v2.3 教训(lint 不接入 verify = 不误伤存量)。约束:verify 新增 blocking 条件是**行为变更**,豁免阀与 CHANGELOG 醒目说明是硬要求。
94
+
95
+ ### R1 · 范围 / 非范围 / 场景
96
+
97
+ 场景:① 30 前 verify 挡「闸表 approved 但审查文不存在」(主);② 归档时 close 保证纸链完整;③ 存量任务被挡时维护者有豁免阀。**职责切分**是本 SPEC 最重要的边界:机器查存在性(形式),人签覆盖结论(实质)——不做审查文内容判定,那是 20 帽与维护者的职责,机械判定会重蹈 slug 式误报。双挂点同波做:机制共享(同一个 finder),分开做反而要两次发版说明。
98
+
99
+ ### R2 · 方案对比
100
+
101
+ | 决策点 | 选项 | 裁定 | 理由 |
102
+ |---|---|---|---|
103
+ | 严格度 | block / warn 起步 | **block + `--allow-no-review`** | 这是 verify 的语义本职(「30 可开工?」应含签署依据存在);warn 起步等于又把纪律留在自觉层。豁免阀保灵活 |
104
+ | 挂点 | verify only / close only / 双挂点 | **双挂点同波** | finder 共享 · 一份发版说明;close 保证归档纸链(done 任务的未来审计入口) |
105
+ | 轮次语义 | 只认 R1 / 最新 R&lt;n&gt; / 要求结论 pass | **存在 ≥1 份 R&lt;n&gt;,信息位报最新轮** | R1 有阻塞 → 回填 → R2 是合法流程;结论 pass 由 HG-AUDIT-R1 人签覆盖,机器不判内容 |
106
+ | 与 `--allow-unchecked` 合并豁免阀 | 合并 / 独立 flag | **独立 `--allow-no-review`** | 语义不同(勾选豁免 vs 审查文豁免),合并会模糊审计痕迹 |
107
+ | verify 无 --task 全量模式 | 也查 reviews / 只查闸表 | **本波只查闸表(不变)** | 全量模式的存量面太大(dogfood 前先探);taskFile 模式是 30 开工的正式路径 |
108
+
109
+ ### R3 · 边界 / 失败语义 / 安全
110
+
111
+ - **存量误伤面**:受影响 = 「HG-AUDIT-R1 approved 且无 R&lt;n&gt; 审查文」的在途 task。工作区近三波 task 全有 R1 文(实测);业务仓手工快照可能有缺口 → 豁免阀 + dogfood 报告(D4)先行量化。
112
+ - **文件名形式宽容边界**:宽容到「轮次数字任意」,不宽容到「文件名主体必须 = task basename」——否则张冠李戴的审查文也能过闸,闸就失去意义。
113
+ - **兼容**:`review_path` 字段保留(handoff 既有消费者);新增字段不删旧字段。
114
+ - **安全**:verify/close 均为只读检查(close 的 mv 语义不变);无 git 动作。
115
+
116
+ ### R4 · 验收 / 可测性 / test_strategy
117
+
118
+ `test_strategy: required`。fixture:verify 三态 + 多轮取最新 + close 检查 6 三态;复用 verify.test.js / task-close.test.js 的 mkdtemp 模式。dogfood 命令与分布落自检结论(同 v2.3 模式)。
119
+
120
+ ### R5 · SPEC 签收就绪 · 是否可交 00 出 task
121
+
122
+ SPEC 自足:双挂点行为、豁免阀、职责切分、存量缓解均有明确规则。**可交 00 起草 task**。图谱:纯工具链无需 bootstrap。版本 v2.5.0(行为变更但属闸语义本职 · CHANGELOG 醒目 + 豁免指引)。落地后 rethink 矩阵 ❌ 8 → 6(C1/C2 族 ✅),22/20 帽脱离「全帽零机械」。
123
+
124
+ ### 思考轮控制
125
+
126
+ | 字段 | 值 |
127
+ |------|-----|
128
+ | `actual_last_round` | `R5` |
129
+ | `early_stop` | `no` |
130
+ | `early_stop_reason` | — |
131
+ | `residual_risks` | ① 存量仓 verify 行为变更可能挡在途 task(豁免阀 + CHANGELOG 醒目 + dogfood 量化缓解);② 「审查文命名不规范视为缺失」可能对历史命名变体过严(宽容边界已声明 · 误报可重命名或豁免);③ verify 无 --task 全量模式本波不查 reviews,遗留到 dogfood 后决策 |
132
+ | `round_extension_note` | — |
133
+
134
+ ---
135
+
136
+ ## 修订记录
137
+
138
+ | 日期 | 摘要 |
139
+ |------|------|
140
+ | 2026-07-24 | 10-spec R0–R5 同会话回填(维护者委派「开始」)· 基于 rethink G2 + findReviewPath 资产核查 |
141
+ | 2026-07-24 | **维护者签收** · 进入 00 起草 task |
@@ -0,0 +1,144 @@
1
+ # SPEC:task lint 思考轮结构规则组(G4)(v1)
2
+
3
+ > **状态**:`signed`(维护者签收 2026-07-24 · 对话「签收,继续」)
4
+ > **track**:`feature`
5
+ > **关联图谱**:无(纯 Harness 工具链)
6
+ > **上游思考**:[`docs/rethink/2026-07-mechanization-rate/`](../rethink/2026-07-mechanization-rate/)(缺口 G4 · P1)
7
+ > **前置**:v2.3.0 `task lint`(G1+G3)· 本 SPEC 为其规则组扩展
8
+ > **下游**:00 已起草 task → 20-task-audit → HG-AUDIT-R1 → 30
9
+
10
+ ---
11
+
12
+ ## Harness 元信息
13
+
14
+ | 字段 | 值 |
15
+ |------|-----|
16
+ | **spec_slug** | `task-lint-thinking-rounds` |
17
+ | **test_strategy** | `required` |
18
+ | **test_strategy_note** | 条件触发三形态 + 每条 E/W 规则的 fail/pass fixture;`npm test` 全绿含回归 |
19
+ | **entry_invoke_10_spec** | `Projects/docs/harness/invokes/by-task/cyning-harness-task-lint-thinking-rounds/invoke_20260724_10_spec_task_lint_thinking_rounds.md` |
20
+ | **entry_invoke_00_draft** | 工作区 `docs/harness/prompts/PROMPT_00_draft_spec_or_task_v1_zh.md` |
21
+
22
+ ---
23
+
24
+ ## 1. 背景与目标
25
+
26
+ rethink 02 D3:10 帽「预置 R0–R5 思考轮槽 + 思考轮控制表」是纯 Prompt 纪律(零机械)。v2.3.0 `task lint` 落地时本项被显式推迟(SPEC R1:写法变体多 · 宽容度需独立 dogfood)——本 SPEC 即第三波。
27
+
28
+ **变体实测(2026-07-24 · SPEC 设计依据)**:
29
+
30
+ | 形态 | 实例 | 占比(active 16) |
31
+ |---|---|---|
32
+ | **A · task 内嵌**(### R0–R5 + 控制表) | hat_chain_assessment · v0_2_d3_ide · ops_desk_kimi_code_spec_10_refine | ~2-3/16 |
33
+ | **B · SPEC 承载**(task 无槽位 · 链 SPEC) | 近三波 task(lint/reviews-gate 等)· 3 份 SPEC 均有完整结构 | ~2/16 |
34
+ | **C · 无思考轮**(bugfix 轨 / Issue≈SPEC) | hat_v2_split 等 | ~12/16 |
35
+
36
+ **目标**:`task lint` 新增思考轮规则组——**条件触发**(有思考轮节才查结构完整性),只查结构不查内容质量;形态 B/C 不挡(W 级提示)。
37
+
38
+ ## 2. 范围
39
+
40
+ - **D1 · `lib/task-lint.js` 规则组扩展**:
41
+ - 触发条件:文件含思考轮节(标题含「思考轮」或存在 `### R0` 槽标题)
42
+ - **E 级**(触发后):E8 R0–R5 槽位标题齐全 · E9 控制表必填字段齐全(`actual_last_round` / `early_stop` / `residual_risks`)· E10 `early_stop=yes` 时 `early_stop_reason` 非空
43
+ - **W 级**:W4 无思考轮节(提示 · 注明 SPEC 承载/bugfix 轨合法豁免)· W5 存在 `### R6`+ 但 `round_extension_note` 缺失或为空
44
+ - 槽位内容为 `(待填)`/`(跳过 · 见思考轮控制)` **合法**(draft 期语义 · 同自检结论占位原则)
45
+ - **D2 · 测试**:三形态(A 完整 pass / A 缺槽或缺字段 fail / B·C 仅 W4)+ E10 逻辑用例 + W5 用例;`npm test` 全绿含 118 例回归
46
+ - **D3 · dogfood**:工作区 active + done tasks 全量 lint,三形态分布与规则命中落自检结论(只报告)
47
+ - **D4 · 文档**:`harness/prompts/10-task-requirements.md` 思考轮摘要节补「v2.6+ task lint 机械检查结构」;CHANGELOG + 版本 **v2.6.0**;rethink 03 G4 行 ❌→✅
48
+
49
+ ## 3. 非范围
50
+
51
+ - 思考轮**内容质量**判定(是否「真思考」)—— 20 帽职责,永不在 lint
52
+ - SPEC 文件的 lint(E1 元信息字段不同 · 规则组设计为可复用,SPEC lint 下一轮)
53
+ - `verify` 接入 lint(维持 v2.3 裁定 · 独立命令)
54
+ - 强制所有 task 必须有思考轮(形态 B/C 合法 · 本闸不消灭变体)
55
+
56
+ ---
57
+
58
+ ## 4. 验收标准
59
+
60
+ - [ ] 形态 A 完整(R0–R5 + 三字段 + early_stop 逻辑一致)→ 无 E · exit 0
61
+ - [ ] 形态 A 缺槽(如缺 `### R3`)→ E8 exit 2;缺控制表字段 → E9;`early_stop=yes` 无 reason → E10
62
+ - [ ] 形态 B/C(无思考轮节)→ 仅 W4 · exit 0
63
+ - [ ] `### R6` 存在且 `round_extension_note` 空 → W5 · exit 0
64
+ - [ ] 槽位 `(待填)`/`(跳过 · 见思考轮控制)` 不触发任何 E
65
+ - [ ] `npm test` 全绿(新增用例 + 118 例回归)
66
+ - [ ] dogfood 三形态分布落自检结论
67
+ - [ ] 10-task Prompt + CHANGELOG(v2.6.0);rethink G4 行 ❌→✅
68
+
69
+ ---
70
+
71
+ ## 5. failure_paths
72
+
73
+ | 触发条件 | 系统行为 | 可重试 |
74
+ |----------|----------|--------|
75
+ | 有思考轮节但结构不全 | exit 2 · 逐条列出(E8/E9/E10)· 不改文件 | 补齐后重跑 |
76
+ | 形态 B/C | W4 提示 · exit 0 | — |
77
+ | 槽标题变体(如 `### R3 · 边界`) | 槽匹配按 `^### R\d` 前缀(形式宽容)| 误报则 dogfood 期调整 |
78
+ | 既有 task 大量 W4 | 不影响 exit · dogfood 记录分布 | — |
79
+ | 业务仓未升级 2.6.0 | 旧 lint 无本规则组 · 行为不变 | upgrade |
80
+
81
+ ---
82
+
83
+ ## 6. 依赖与引用
84
+
85
+ - 复用:`lib/task-lint.js`(规则组框架 · E/W 分级)· `lib/task-meta.js`(`extractSection` 行首锚定)
86
+ - 纪律原文:`harness/prompts/10-task-requirements.md`「OSS 阶段 C · 思考轮」节;工作区 `FRAGMENT_rethink_backfill_spec_v1_zh.md`(控制表字段定义)
87
+ - 变体实测:2026-07-24 扫描(§1 表)
88
+ - 缺口出处:rethink 02(D3)· 03(G4 行)· SPEC-task-lint-structure-gate R1(推迟裁定)
89
+
90
+ ---
91
+
92
+ ## 7. 思考轮(10-spec 回填 · R0–R5)
93
+
94
+ ### R0 · 读入与约束
95
+
96
+ 读入:rethink 02 D3 / 03 G4 / 04 第三波定义;v2.3 SPEC 的推迟裁定;变体实测数据(§1);10-task 新帽思考轮摘要节(v2.4.0)。约束:只查结构不查内容是 04 定下的硬边界;不消灭形态 B/C(它们是 R1 审查已接受的合法轨)。
97
+
98
+ ### R1 · 范围 / 非范围 / 场景
99
+
100
+ 变体实测驱动设计:active 16 个中仅 ~3 个形态 A——**无条件强制槽位 = 误伤 80% 存量**,故核心设计是**条件触发**(有节才查)。场景:① 形态 A task 的 10 产出自查(主);② 22/20 审查时辅助核对控制表;③ 维护者批量体检。SPEC 文件的结构与本规则组同构,但 E1 元信息字段不同(spec_slug ≠ task_slug),SPEC lint 不在本波(非范围已列)。
101
+
102
+ ### R2 · 方案对比
103
+
104
+ | 决策点 | 选项 | 裁定 | 理由 |
105
+ |---|---|---|---|
106
+ | 触发方式 | 全员强制 / 条件触发 / 只对阶段 C 标记 task | **条件触发(有节才查)** | 变体实测:强制 = 80% 误伤;「阶段 C 标记」本身无机械标记可锚 |
107
+ | 无思考轮节 | 沉默 / W4 提示 / E 级 | **W4** | 形态 B/C 合法但值得可见(提示里也写明豁免路径,教育新用户) |
108
+ | 槽内容 | 查非占位 / 不查 | **不查**(`(待填)`/`(跳过)` 合法) | draft 期语义与自检结论占位原则一致;填没填的思考质量是 20 帽职责 |
109
+ | early_stop 逻辑 | 查 / 不查 | **查(E10)** | `yes` 必须配 reason 是控制表自身的逻辑完整性,属结构非内容 |
110
+ | R6+ | 不查 / W5 / E | **W5** | 扩展理由缺失值得提示,但 R6 使用率低,E 级过重 |
111
+ | 槽标题匹配 | 精确 `### R0` | **`^### R\d` 前缀宽容**(允许 `### R3 · 边界` 后缀) | 实测槽标题带主题后缀是惯例 |
112
+
113
+ ### R3 · 边界 / 失败语义 / 安全
114
+
115
+ - **E/W 分界原则**:触发后结构残缺 = E(有节就等于承诺了结构);未触发 = 最多 W(不承诺不查)。
116
+ - **触发条件假阳性**:文件正文提到「思考轮」三字但无槽位 → 触发条件要求「标题含思考轮 **或** 存在 `### R0` 槽标题」——纯文本提及(如本段)不触发,因为 `### R0` 必须行首锚定(extractSection 同款锚定)。「标题含思考轮」单独触发会不会误伤?一个写着「## 思考轮说明」但无槽的文件 → E8 fail。裁定:触发 = 存在 `^### R0` 槽标题 **或** `^#{2,3}.*思考轮` 标题;两者任一即按形态 A 要求。dogfood 验证误伤面(D3)。
117
+ - **安全**:纯只读;无 git 动作;不影响既有 E1–E7 行为(118 例回归兜底)。
118
+
119
+ ### R4 · 验收 / 可测性 / test_strategy
120
+
121
+ `test_strategy: required`。fixture:形态 A 完整 / 缺槽 / 缺字段 / early_stop 无 reason / R6 无扩展理由 / 形态 B / 形态 C / 槽标题带后缀宽容 / 正文提及「思考轮」不触发。dogfood 落三形态分布与 W4 计数。
122
+
123
+ ### R5 · SPEC 签收就绪 · 是否可交 00 出 task
124
+
125
+ SPEC 自足:条件触发模型有三形态实测支撑,E/W 边界清晰,宽容条款逐条在案。**可交 00 起草 task**。图谱:纯工具链。版本 v2.6.0(lint 规则组扩展 · 无 verify 行为变更)。落地后 rethink 矩阵 ❌ 8 → 7;P1 队列余 SPEC 审查闸与 verify 全量模式决策。
126
+
127
+ ### 思考轮控制
128
+
129
+ | 字段 | 值 |
130
+ |------|-----|
131
+ | `actual_last_round` | `R5` |
132
+ | `early_stop` | `no` |
133
+ | `early_stop_reason` | — |
134
+ | `residual_risks` | ① 触发条件的假阳性(文件有「思考轮」标题但槽在别处)——dogfood D3 实测,必要时触发条件收紧为仅 `### R0`;② 历史形态 A 文件可能结构不全被 E 挡(存量仅 ~3 个 · 影响面小且可豁免豁免阀之外直接修复);③ SPEC lint 复用需求可能提前到来(规则组已按可复用设计) |
135
+ | `round_extension_note` | — |
136
+
137
+ ---
138
+
139
+ ## 修订记录
140
+
141
+ | 日期 | 摘要 |
142
+ |------|------|
143
+ | 2026-07-24 | 10-spec R0–R5 同会话回填(维护者点菜「1」)· 基于 rethink G4 + 变体实测(形态 A/B/C 分布) |
144
+ | 2026-07-24 | 维护者签收(对话「签收,继续」)· 下游 00 起草 task |
@@ -40,12 +40,14 @@
40
40
  - 10-task 草稿 **须预置 R0 + R1–R5** 五槽 + **思考轮控制** 表(见 `wizard/templates/TASK_TEMPLATE_upstream_pr_v1.md`)。
41
41
  - **可提前停 / 可增 R6+**;须填 reason · residual_risks(无则 `none`)。
42
42
  - **20-task-audit 审思考是否充分**;不通过 → **退回 10-task** 补轮后再审 R+1。
43
+ - **v2.6+ 机械检查结构**(条件触发):`task lint` 在存在 `### R0` 或思考轮标题时查 E8–E10(槽位/控制表/`early_stop` 逻辑);无节仅 W4(SPEC 承载 / bugfix 合法);不查内容质量。
43
44
 
44
45
  ## 修订记录
45
46
 
46
47
  | 日期 | 摘要 |
47
48
  |------|------|
48
49
  | 2026-07-24 | V2 拆分收编:自 `10-requirements.md` 改名为 `10-task-requirements.md`(来源:工作区 `10-task-requirements.md` · 2026-06-12 v2) |
50
+ | 2026-07-24 | G4:思考轮摘要节补 v2.6+ `task lint` 条件触发结构检查 |
49
51
 
50
52
  ## 给 Cursor
51
53
 
@@ -14,7 +14,7 @@
14
14
  | HG-AUDIT-R1 | {读表} | {若有} | {Y/N} | Y | … |
15
15
  | HG-GRAPH-MODULES | {01_struct或—} | — | — | … | … |
16
16
 
17
- reviews:task_*_audit_R1_*.md 存在且 R1 通过? {是/否}
17
+ reviews:task_*_audit_R1_*.md 存在且 R1 通过? {是/否}(**v2.5+**:`verify` 对 R<n> 审查文**存在性**机械强制 · 缺失即 `VERIFY: BLOCKED · missing R<n> review`;结论通过与否仍由维护者签 `HG-AUDIT-R1` 覆盖)
18
18
 
19
19
  冲突规则:task表 pending 且用户/invoke 称 approved → **STOP** · 以 task 表为准
20
20
 
package/lib/cli.js CHANGED
@@ -407,7 +407,8 @@ async function cmdVerify(args) {
407
407
  const graph = rest.includes('--graph');
408
408
  const json = rest.includes('--json');
409
409
  const agentHint = rest.includes('--agent-hint');
410
- rest = rest.filter((a) => a !== '--graph' && a !== '--json' && a !== '--agent-hint');
410
+ const allowNoReview = rest.includes('--allow-no-review');
411
+ rest = rest.filter((a) => a !== '--graph' && a !== '--json' && a !== '--agent-hint' && a !== '--allow-no-review');
411
412
 
412
413
  if (rest.length > 0) {
413
414
  const err = new Error(`verify 未知参数: ${rest.join(' ')}`);
@@ -416,7 +417,7 @@ async function cmdVerify(args) {
416
417
  }
417
418
 
418
419
  const target = resolveTarget(process.cwd(), targetArg);
419
- const result = verifyTarget(target, { taskFile, graph });
420
+ const result = verifyTarget(target, { taskFile, graph, allowNoReview });
420
421
 
421
422
  // forward gate-check/audit/graph stdout if any(--json 时仍透传机械闸表)
422
423
  if (result.stdout && !json) {
@@ -429,6 +430,7 @@ async function cmdVerify(args) {
429
430
  const handoff = buildVerifyHandoff(target, {
430
431
  taskFile,
431
432
  workspaceRoot: workspaceRoot ? path.resolve(workspaceRoot) : undefined,
433
+ allowNoReview,
432
434
  });
433
435
 
434
436
  if (json) {
@@ -536,7 +538,8 @@ async function cmdTaskClose(args) {
536
538
 
537
539
  const yes = args.includes('--yes');
538
540
  const allowUnchecked = args.includes('--allow-unchecked');
539
- let rest = args.filter((a) => a !== '--yes' && a !== '--allow-unchecked');
541
+ const allowNoReview = args.includes('--allow-no-review');
542
+ let rest = args.filter((a) => a !== '--yes' && a !== '--allow-unchecked' && a !== '--allow-no-review');
540
543
  const { value: fileArg, rest: r1 } = takeOption(rest, '--file');
541
544
  rest = r1;
542
545
  const { value: targetArg, rest: r2 } = takeOption(rest, '--target');
@@ -557,6 +560,7 @@ async function cmdTaskClose(args) {
557
560
  yes,
558
561
  target: targetArg,
559
562
  allowUnchecked,
563
+ allowNoReview,
560
564
  cwd: process.cwd(),
561
565
  });
562
566
 
@@ -626,10 +630,12 @@ async function cmdTaskLint(args) {
626
630
  if (args.includes('--help') || args.includes('-h')) {
627
631
  console.log(`用法: npx @cyning/harness task lint --file PATH [--json]
628
632
 
629
- task md 结构闸(G1)+ 文本规则包(G3)· 纯只读:
633
+ task md 结构闸(G1)+ 文本规则包(G3)+ 思考轮结构(G4)· 纯只读:
630
634
  E 级(exit 2):E1 元信息/task_slug · E2 状态行 · E3 验收标准含勾选项
631
635
  E4 失败路径节 · E5 自检结论节 · E6 绝对本机路径 · E7 slug 一致
636
+ E8–E10(条件触发:有 ### R0 或思考轮标题):槽位齐全 · 控制表字段 · early_stop 逻辑
632
637
  W 级(仅 warn):W1 状态 token 词表外 · W2 缺人工闸节 · W3 自检结论占位
638
+ W4 无思考轮节(形态 B/C 豁免提示)· W5 R6+ 缺 round_extension_note
633
639
  末行协议:LINT: PASS · <file> / LINT: FAIL · <file>
634
640
  --json:{ok, errors[], warnings[], file, slug}(errors 元素 {rule, message, line?})
635
641
  `);
package/lib/task-close.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  UNCHECKED_RE,
8
8
  extractSection,
9
9
  extractTaskSlug,
10
+ findReview,
10
11
  normalizeSlug,
11
12
  parseHarnessMeta,
12
13
  } from './task-meta.js';
@@ -21,6 +22,7 @@ export function closeTaskFile(filePath, options = {}) {
21
22
  yes = false,
22
23
  target: targetArg,
23
24
  allowUnchecked = false,
25
+ allowNoReview = false,
24
26
  cwd = process.cwd(),
25
27
  } = options;
26
28
 
@@ -121,6 +123,20 @@ export function closeTaskFile(filePath, options = {}) {
121
123
  blockers.push(`状态非 done/completed(当前: ${status})`);
122
124
  }
123
125
 
126
+ // 检查 6 · R<n> 审查文留档(G2 · v2.5+)
127
+ if (repoRoot) {
128
+ const review = findReview(repoRoot, abs);
129
+ if (!review.found) {
130
+ if (allowNoReview) {
131
+ warnings.push('warn: missing R<n> review(--allow-no-review 豁免 · 留痕)');
132
+ } else {
133
+ blockers.push(
134
+ 'missing R<n> review(docs/harness/reviews/ 无该 task 的 _audit_R 文件 · 或 --allow-no-review)',
135
+ );
136
+ }
137
+ }
138
+ }
139
+
124
140
  // 归档目标
125
141
  let dest = null;
126
142
  if (targetArg) {
package/lib/task-lint.js CHANGED
@@ -17,6 +17,14 @@ import {
17
17
  */
18
18
  const ABS_PATH_RE = /(\/(?:Users|home|root)\/[^\s/`\\]|[A-Za-z]:\\Users\\[^\s`\\])/;
19
19
  const FAILURE_HEADING_RE = /^#{2,4}\s.*(失败路径|failure_paths)/i;
20
+ /** G4 触发:行首 ### R0(可带后缀) */
21
+ const R0_HEADING_RE = /^###\s+R0(\b|[^\d]|$)/m;
22
+ /** G4 触发(叠加):行首 ##/### 标题含「思考轮」 */
23
+ const THINK_HEADING_RE = /^#{2,3}\s+.*思考轮/m;
24
+ /** 槽位标题:### Rn(前缀宽容) */
25
+ const RN_HEADING_RE = /^###\s+R(\d+)\b/gm;
26
+ /** 控制表字段行:| `field` | / | **field** | / | field | */
27
+ const CTRL_FIELD_RE = /^\|\s*`?\*{0,2}([a-z_]+)\*{0,2}`?\s*\|\s*(.*?)\s*\|/i;
20
28
 
21
29
  function err(rule, message, line) {
22
30
  return line ? { rule, message, line } : { rule, message };
@@ -25,8 +33,79 @@ function warn(rule, message, line) {
25
33
  return line ? { rule, message, line } : { rule, message };
26
34
  }
27
35
 
36
+ function parseControlFields(lines) {
37
+ const fields = {};
38
+ for (const l of lines) {
39
+ const m = l.match(CTRL_FIELD_RE);
40
+ if (!m) continue;
41
+ const key = m[1].toLowerCase();
42
+ let val = m[2].trim();
43
+ if (val.startsWith('`') && val.endsWith('`')) val = val.slice(1, -1).trim();
44
+ fields[key] = val;
45
+ }
46
+ return fields;
47
+ }
48
+
49
+ function isEmptyCtrlValue(val) {
50
+ if (val == null) return true;
51
+ const v = String(val).trim();
52
+ return v === '' || v === '—' || v === '-' || v === '–';
53
+ }
54
+
55
+ /**
56
+ * G4 · 思考轮结构规则组(条件触发)。
57
+ * 触发优先:存在 ### R0;叠加:行首 ##/### 含「思考轮」。
58
+ * 正文提及「思考轮」不触发。
59
+ */
60
+ function lintThinkingRounds(content, lines, errors, warnings) {
61
+ const hasR0 = R0_HEADING_RE.test(content);
62
+ const hasThinkHeading = THINK_HEADING_RE.test(content);
63
+ const triggered = hasR0 || hasThinkHeading;
64
+
65
+ if (!triggered) {
66
+ warnings.push(
67
+ warn('W4', '无思考轮节(SPEC 承载 / bugfix 轨合法豁免 · 有节则查 R0–R5 与控制表)'),
68
+ );
69
+ return;
70
+ }
71
+
72
+ // E8 · R0–R5 槽位齐全
73
+ const found = new Set();
74
+ const rnRe = new RegExp(RN_HEADING_RE.source, 'gm');
75
+ let m;
76
+ while ((m = rnRe.exec(content)) !== null) {
77
+ found.add(Number(m[1]));
78
+ }
79
+ const missing = [];
80
+ for (let i = 0; i <= 5; i += 1) {
81
+ if (!found.has(i)) missing.push(`R${i}`);
82
+ }
83
+ if (missing.length) {
84
+ errors.push(err('E8', `思考轮槽位不全,缺:${missing.join(', ')}`));
85
+ }
86
+
87
+ // E9 / E10 · 控制表字段
88
+ const fields = parseControlFields(lines);
89
+ const required = ['actual_last_round', 'early_stop', 'residual_risks'];
90
+ const missFields = required.filter((k) => !(k in fields));
91
+ if (missFields.length) {
92
+ errors.push(err('E9', `思考轮控制表缺字段:${missFields.join(', ')}`));
93
+ }
94
+
95
+ const early = (fields.early_stop || '').toLowerCase();
96
+ if (early === 'yes' && isEmptyCtrlValue(fields.early_stop_reason)) {
97
+ errors.push(err('E10', 'early_stop=yes 时 early_stop_reason 须非空'));
98
+ }
99
+
100
+ // W5 · R6+ 须有 round_extension_note
101
+ const hasR6Plus = [...found].some((n) => n >= 6);
102
+ if (hasR6Plus && isEmptyCtrlValue(fields.round_extension_note)) {
103
+ warnings.push(warn('W5', '存在 ### R6+ 但 round_extension_note 缺失或为空'));
104
+ }
105
+ }
106
+
28
107
  /**
29
- * task lint · task md 结构闸(G1)+ 文本规则包(G3)。
108
+ * task lint · task md 结构闸(G1)+ 文本规则包(G3)+ 思考轮结构(G4)。
30
109
  * 纯只读;E 级 → ok:false(CLI exit 2),W 级仅提醒。
31
110
  */
32
111
  export function lintTaskFile(filePath, options = {}) {
@@ -114,6 +193,9 @@ export function lintTaskFile(filePath, options = {}) {
114
193
  warnings.push(warn('W2', '缺 ### 人工闸 节(轻量 task 可忽略本提醒)'));
115
194
  }
116
195
 
196
+ // G4 · 思考轮结构(条件触发)
197
+ lintThinkingRounds(content, lines, errors, warnings);
198
+
117
199
  return {
118
200
  ok: errors.length === 0,
119
201
  errors,
package/lib/task-meta.js CHANGED
@@ -119,29 +119,42 @@ export function resolveInvokePath(rawPath, { target, workspaceRoot } = {}) {
119
119
  }
120
120
 
121
121
  /**
122
- * 查找最新 R1 审查文(相对 target)。
122
+ * 查找 task R<n> 审查文(verify/close 共用)。
123
+ * 匹配规则(G2 · R1-B1):task basename 与审查文文件名均剥离 `_v\d+$` 后前缀比较;
124
+ * 文件名须含 `_audit_R<n>_` 段。返回 {found, latest, rounds}。
123
125
  */
124
- export function findReviewPath(target, taskFile) {
126
+ export function findReview(target, taskFile) {
125
127
  const reviewsDir = path.join(target, 'docs/harness/reviews');
126
- if (!fs.existsSync(reviewsDir)) return null;
127
-
128
- const taskBase = path.basename(taskFile, '.md');
129
- const prefix = `${taskBase}_audit_R1_`;
128
+ const empty = { found: false, latest: null, rounds: [] };
129
+ if (!fs.existsSync(reviewsDir)) return empty;
130
130
 
131
- let best = null;
132
- let bestMtime = 0;
131
+ const stripVer = (s) => s.replace(/_v\d+$/, '');
132
+ const base = stripVer(path.basename(taskFile, '.md'));
133
+ const REVIEW_RE = /^(task_.+?)_audit_R(\d+)_.*\.md$/;
133
134
 
135
+ const matches = [];
134
136
  for (const name of fs.readdirSync(reviewsDir)) {
135
- if (!name.startsWith(prefix) || !name.endsWith('.md')) continue;
136
- const full = path.join(reviewsDir, name);
137
- const mtime = fs.statSync(full).mtimeMs;
138
- if (mtime > bestMtime) {
139
- bestMtime = mtime;
140
- best = path.relative(target, full).replace(/\\/g, '/');
141
- }
137
+ const m = name.match(REVIEW_RE);
138
+ if (!m) continue;
139
+ if (stripVer(m[1]) !== base) continue;
140
+ matches.push({ name, round: parseInt(m[2], 10), mtime: fs.statSync(path.join(reviewsDir, name)).mtimeMs });
142
141
  }
142
+ if (matches.length === 0) return empty;
143
+
144
+ matches.sort((a, b) => b.round - a.round || b.mtime - a.mtime);
145
+ const latest = matches[0];
146
+ return {
147
+ found: true,
148
+ latest: path.join('docs/harness/reviews', latest.name).replace(/\\/g, '/'),
149
+ rounds: [...new Set(matches.map((x) => x.round))].sort((a, b) => a - b),
150
+ };
151
+ }
143
152
 
144
- return best;
153
+ /**
154
+ * 查找最新 R1+ 审查文(相对 target)· 兼容旧签名:返回路径或 null。
155
+ */
156
+ export function findReviewPath(target, taskFile) {
157
+ return findReview(target, taskFile).latest;
145
158
  }
146
159
 
147
160
  /**
@@ -166,7 +179,7 @@ export function buildTaskHandoff(target, taskFile, options = {}) {
166
179
  const entry20 = resolveInvokePath(entry20Raw, { target, workspaceRoot });
167
180
  warnings.push(...entry30.warnings, ...entry20.warnings);
168
181
 
169
- const reviewPath = findReviewPath(target, taskFile);
182
+ const review = findReview(target, taskFile);
170
183
 
171
184
  return {
172
185
  task: path.basename(taskFile),
@@ -174,7 +187,10 @@ export function buildTaskHandoff(target, taskFile, options = {}) {
174
187
  task_slug: meta.task_slug ?? null,
175
188
  may_start_30: gateEval.may_start_30,
176
189
  blocked_reason: gateEval.blocked_reason,
177
- review_path: reviewPath,
190
+ review_path: review.latest,
191
+ review_found: review.found,
192
+ review_latest: review.latest,
193
+ review_rounds: review.rounds,
178
194
  entry_invoke_20: entry20Raw,
179
195
  entry_invoke_20_resolved: entry20.resolved,
180
196
  entry_invoke_30: entry30Raw,
package/lib/verify.js CHANGED
@@ -3,13 +3,13 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { resolveHarnessRootForTarget, wizardScript } from './paths.js';
5
5
  import { auditTarget } from './audit.js';
6
- import { buildTaskHandoff, listActiveTasks } from './task-meta.js';
6
+ import { buildTaskHandoff, findReview, listActiveTasks } from './task-meta.js';
7
7
 
8
8
  /**
9
- * 30 前聚合验证:gate-check + audit D5(仅 --task)+ S5 git-clean warn + 可选 --graph
9
+ * 30 前聚合验证:gate-check + audit D5(仅 --task)+ reviews 留档闸(仅 --task)+ S5 git-clean warn + 可选 --graph
10
10
  */
11
11
  export function verifyTarget(target, options = {}) {
12
- const { taskFile, graph } = options;
12
+ const { taskFile, graph, allowNoReview = false } = options;
13
13
  const harnessRoot = resolveHarnessRootForTarget(target);
14
14
 
15
15
  // 1. gate-check(含 --graph)
@@ -34,9 +34,24 @@ export function verifyTarget(target, options = {}) {
34
34
  stdout: gateResult.stdout,
35
35
  };
36
36
  }
37
+
38
+ // 3. reviews 留档闸(G2 · v2.5+):R<n> 审查文存在性(存在性=机器 · 结论=人签)
39
+ const review = findReview(target, taskFile);
40
+ if (!review.found) {
41
+ if (allowNoReview) {
42
+ gateResult.stdout += `WARN: missing R<n> review(--allow-no-review 豁免 · 留痕)\n`;
43
+ } else {
44
+ return {
45
+ ok: false,
46
+ exitCode: 2,
47
+ reason: 'missing R<n> review(docs/harness/reviews/ 无该 task 的 _audit_R 文件 · 20 补审或 --allow-no-review)',
48
+ stdout: gateResult.stdout,
49
+ };
50
+ }
51
+ }
37
52
  }
38
53
 
39
- // 3. S5 git-clean:warn 不挡
54
+ // 4. S5 git-clean:warn 不挡
40
55
  const gitResult = runGitCleanCheck(target);
41
56
  if (gitResult.dirty) {
42
57
  // 把 warn 拼到 stdout 里让 CLI 透传
@@ -156,15 +171,20 @@ function runGitCleanCheck(target) {
156
171
  * 构建 verify --json / --agent-hint 的 Agent handoff 载荷。
157
172
  */
158
173
  export function buildVerifyHandoff(target, options = {}) {
159
- const { taskFile, workspaceRoot } = options;
174
+ const { taskFile, workspaceRoot, allowNoReview = false } = options;
160
175
  const handoffOpts = { workspaceRoot };
161
176
 
162
177
  if (taskFile) {
163
178
  const handoff = buildTaskHandoff(target, taskFile, handoffOpts);
179
+ const reviewOk = handoff.review_found || allowNoReview;
180
+ const verifyOk = handoff.may_start_30 && reviewOk;
164
181
  return {
165
182
  schema_version: '1',
166
- verify_ok: handoff.may_start_30,
183
+ verify_ok: verifyOk,
167
184
  ...handoff,
185
+ may_start_30: verifyOk,
186
+ blocked_reason: handoff.blocked_reason ?? (reviewOk ? null : 'missing R<n> review'),
187
+ next_hat: verifyOk ? '30' : null,
168
188
  };
169
189
  }
170
190
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyning/harness",
3
- "version": "2.4.0",
3
+ "version": "2.6.0",
4
4
  "description": "cyning-harness discipline package · init / upgrade / check CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",