@cyning/harness 2.4.0 → 2.5.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 +16 -0
- package/docs/rethink/2026-07-mechanization-rate/03_coverage_matrix.md +1 -1
- package/docs/rethink/2026-07-mechanization-rate/README.md +1 -0
- package/docs/spec/SPEC-reviews-retention-gate_v1.md +141 -0
- package/harness/prompts/FRAGMENT_30_gate_verify_v1_zh.md +1 -1
- package/lib/cli.js +7 -3
- package/lib/task-close.js +16 -0
- package/lib/task-meta.js +34 -18
- package/lib/verify.js +26 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.5.0] - 2026-07-24
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **⚠ 行为变更 · 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 放行 · 留痕)。
|
|
12
|
+
- `task close` 新增**检查 6**:归档时 R<n> 审查文应存在(纸链完整);同 `--allow-no-review` 豁免。
|
|
13
|
+
- `findReviewPath` 升级 `findReview`:glob `_audit_R1_` → `_audit_R` 多轮取最新;**匹配两侧剥离版本后缀**(合法变体 `task_x_v1.md` ↔ `task_x_audit_R1_*.md` · R1 审查实测案例);`review_path` 字段兼容。
|
|
14
|
+
- verify `--json` handoff 新增 `review_found` / `review_latest` / `review_rounds`;`may_start_30` 纳入审查文条件。
|
|
15
|
+
|
|
16
|
+
### Notes
|
|
17
|
+
|
|
18
|
+
- 背景:机械化率审计 G2(rethink 矩阵 P0 最后一块);22/20 帽自此脱离「全帽零机械」。
|
|
19
|
+
- dogfood:工作区 approved 任务全过(含版本后缀变体 ops_clarify · 恰好是 R1 审查抓出的案例)。
|
|
20
|
+
- minor · verify 行为变更(本版核心 · 见上)· `npm test` 118 全绿 · SPEC `docs/spec/SPEC-reviews-retention-gate_v1.md`
|
|
21
|
+
|
|
7
22
|
## [2.4.0] - 2026-07-24
|
|
8
23
|
|
|
9
24
|
### Changed
|
|
@@ -29,6 +44,7 @@
|
|
|
29
44
|
- **升级指引**:业务仓 `upgrade` 后若出现 `warn: obsolete`,按提示人工删除旧帽文件即可(sync 不会自动删)。
|
|
30
45
|
- 背景:三方对比(后端仓=V1 手工快照 · 包内=半途状态 · V2 真值仅在工作区)· task `cyning-harness-hat-v2-split-sync`。
|
|
31
46
|
- minor · 无 CLI breaking · `npm test` 111 全绿
|
|
47
|
+
- **已发布**:`@cyning/harness@2.4.0`(npm `latest` · 2026-07-24)· tag `v2.4.0` 已推送
|
|
32
48
|
|
|
33
49
|
## [2.3.0] - 2026-07-24
|
|
34
50
|
|
|
@@ -35,7 +35,7 @@ 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
|
-
|
|
|
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
40
|
| **P1** | **G4 · 思考轮结构检查** | 中(阶段 C 纪律,漏槽=思考轮形同虚设) | 中(格式变体多,须宽容:只查槽位存在与控制表字段,不查内容质量) | 并入 `task lint`:R0–R5 槽 + `actual_last_round`/`early_stop`/`residual_risks` 字段存在性 | 10 产出时 |
|
|
41
41
|
| **P2** | **G7 · 执行证据** | 高但难(「真跑过」本质要 runner 见证) | 高 | `harness run -- <验证命令>`:包装执行、留 exit code + 输出摘要到 `.cyning-harness/runs/`,40 回填引用 run id | 方向二状态机内再做 |
|
|
@@ -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<n> 审查文存在性检查)(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<n> 审查文存在」从填表项升级为机械闸,双挂点:
|
|
30
|
+
|
|
31
|
+
1. **verify(30 前)**:`may_start_30` 增加前置条件——`docs/harness/reviews/task_<base>_audit_R<n>_*.md`(n≥1)至少一份存在;
|
|
32
|
+
2. **close(归档前)**:新增第 6 项检查——归档时 R<n> 审查文应存在(纸链完整性)。
|
|
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<n> 审查文存在;缺失 → `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<n> / 要求结论 pass | **存在 ≥1 份 R<n>,信息位报最新轮** | 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<n> 审查文」的在途 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 |
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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-meta.js
CHANGED
|
@@ -119,29 +119,42 @@ export function resolveInvokePath(rawPath, { target, workspaceRoot } = {}) {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
/**
|
|
122
|
-
*
|
|
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
|
|
126
|
+
export function findReview(target, taskFile) {
|
|
125
127
|
const reviewsDir = path.join(target, 'docs/harness/reviews');
|
|
126
|
-
|
|
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
|
-
|
|
132
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
//
|
|
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:
|
|
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
|
|