@cyning/harness 2.8.0 → 2.9.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,21 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.9.0] - 2026-07-25
8
+
9
+ ### Changed
10
+
11
+ - **⚠ 行为变更 · 裸 `verify`(无 `--task`/`--spec`)纳入 reviews 留档闸(N4 · G2 residual)**:gate-check 通过后对每个 active 跑 `findReview`;任一缺失 → `VERIFY: BLOCKED · missing R<n> review(n/m tasks · basename…)` exit 2。豁免:`--allow-no-review`(WARN 放行)。
12
+ - **active 发现双路径**:`listActiveTasks` 与 `gate-check.sh` 同步扫描 `docs/tasks/active` ∪ `docs/harness/tasks/active`(同 basename 优先 Starter)。
13
+ - 全量 `--json`:每 task 的 `may_start_30` / `verify_ok` 纳入 `review_found`(与 `--task` 同构)。
14
+ - `lifecycle.yaml` `to_30` / `reviews_retention`:描述覆盖「`--task` 与全量」。
15
+
16
+ ### Notes
17
+
18
+ - 全量模式仍**不**跑 task lint / audit D5(维持 N2)。
19
+ - SPEC:`docs/spec/SPEC-verify-full-reviews-gate_v1.md`
20
+ - minor · 行为更严 · 存量「闸表可 30 但无审查文」将被挡(可补审或 `--allow-no-review`)
21
+
7
22
  ## [2.8.0] - 2026-07-25
8
23
 
9
24
  ### Added
@@ -18,6 +33,7 @@
18
33
  - 与 `--task` 互斥;**不**改 task 侧 verify/close。
19
34
  - SPEC:`docs/spec/SPEC-spec-reviews-retention-gate_v1.md`
20
35
  - minor · 行为新增(新模式)
36
+ - **已发布**:`@cyning/harness@2.8.0`(npm `latest` · 2026-07-25)· tag `v2.8.0` 已推送
21
37
 
22
38
  ## [2.7.0] - 2026-07-24
23
39
 
@@ -45,11 +45,13 @@ cd your-project
45
45
  `npx @cyning/harness verify` 在 30 执行前聚合扫描人工闸与测试声明,确保 ICVO 公理可机械检查:
46
46
 
47
47
  ```bash
48
- # 30 前聚合验证(gate-check + audit D5 + reviews + S5 warn + 可选 --graph)
48
+ # 30 前聚合验证(gate-check + reviews + S5 warn + 可选 --graph)
49
+ # 无 --task:双路径 active(docs/tasks/active ∪ docs/harness/tasks/active)+ 全量 reviews(v2.9+)
49
50
  npx @cyning/harness verify --target /path/to/your-repo
51
+ # 缺审查文豁免:--allow-no-review
50
52
  npx @cyning/harness verify --target /path/to/your-repo \
51
53
  --task docs/tasks/active/task_xxx.md
52
- # --task 另跑 task lint(v2.7+ · E 级仅 WARN,不挡 may_start_30)
54
+ # --task 另跑 audit D5 + task lint(v2.7+ · E 级仅 WARN,不挡 may_start_30)
53
55
  # 抑制 lint WARN:加 --allow-lint-fail
54
56
 
55
57
  # SPEC→00 前:审查文存在性(v2.8+ · 与 --task 互斥)
@@ -76,7 +78,9 @@ npx @cyning/harness sync index --target /path/to/your-repo
76
78
  | 公理 | 检查项 | 行为 |
77
79
  |------|--------|------|
78
80
  | **D3** | 30 前置人闸 | 复用 `gate-check.sh`,HG-AUDIT-R1 非 approved 时 verify 非 0 |
79
- | **D5** | 改码任务测试声明 | `test_strategy=required` 但无测试/CI 引用时 verify 非 0 |
81
+ | **D5** | 改码任务测试声明 | 仅 `--task`:`test_strategy=required` 但无测试/CI 引用时 verify 非 0 |
82
+ | **reviews** | R&lt;n&gt; 审查文存在 | `--task` 与全量(v2.9+)· 缺文 BLOCKED · `--allow-no-review` |
83
+ | **active** | 任务发现 | 双路径 `docs/tasks/active` ∪ `docs/harness/tasks/active`(v2.9+) |
80
84
  | **S5** | Git 工作区干净 | dirty 时 warn(不直接 fail verify,但 apply 须 `--force`) |
81
85
  | **lint** | task 结构(仅 `--task`) | v2.7+ E 级 → `WARN: task lint`(不改 exit / `may_start_30`) |
82
86
 
@@ -111,7 +111,11 @@ npx @cyning/harness@1.0.1 init --preset harness-only --ide cursor,agents --yes
111
111
  **原则**:Agent 可以写 task 和 review,**人工闸只有维护者能签**。
112
112
 
113
113
  ```bash
114
- # 30 前聚合验证(gate-check + audit D5 + reviews + task lint WARN + S5 · 可选 --graph
114
+ # 全量(无 --task):双路径 active + 闸表 + reviews(v2.9+ · 不跑 lint/D5
115
+ npx @cyning/harness verify --target .
116
+ # 缺审查文豁免:--allow-no-review
117
+
118
+ # 单 task:gate-check + audit D5 + reviews + task lint WARN + S5 · 可选 --graph
115
119
  npx @cyning/harness verify --target . --task docs/tasks/active/task_xxx.md
116
120
  # lint FAIL 仅 WARN(v2.7+ · 不挡 30);抑制:--allow-lint-fail
117
121
 
@@ -146,6 +150,7 @@ npx @cyning/harness gate-check --graph --target . # Inform 图谱闸
146
150
  | `HG-GRAPH-MODULES` | 架构模块表人签 | pending → 拒改码 30 |
147
151
  | `HG-RELEASE` | 发版闸(产品仓) | 一般业务仓不涉及 |
148
152
  | `task_lint`(v2.7+) | `verify --task` 结构检查 | **仅 WARN** · 不改 `may_start_30` |
153
+ | `reviews`(全量 v2.9+) | 裸 verify 亦查 R&lt;n&gt; 文 | 缺文 → BLOCKED · `--allow-no-review` |
149
154
 
150
155
  ### 5.1 Agent handoff(v2.0.2+)
151
156
 
@@ -153,7 +158,8 @@ npx @cyning/harness gate-check --graph --target . # Inform 图谱闸
153
158
 
154
159
  | JSON 字段 | 含义 |
155
160
  | --- | --- |
156
- | `may_start_30` | 等价 gate-check「可 30 |
161
+ | `may_start_30` | 闸表可 30 **且** `review_found`(或豁免) |
162
+ | `review_found` | R&lt;n&gt; 审查文是否存在(全量与 `--task` 同构 · v2.9+) |
157
163
  | `blocked_reason` | 阻塞时 D2/D3 文案 |
158
164
  | `review_path` | 最新 `*_audit_R1_*.md`(相对 target) |
159
165
  | `entry_invoke_30` | task 表原始路径 |
@@ -177,7 +183,7 @@ Schema:[`schema/verify_result.v1.schema.json`](../schema/verify_result.v1.sche
177
183
  | `npx @cyning/harness init` | 首次安装模板与 manifest(可选 `--with-scripts`) |
178
184
  | `npx @cyning/harness upgrade` | 同步产品包更新(可加 `--gate-check` 先 audit) |
179
185
  | `npx @cyning/harness check` | 检查是否有新版本 |
180
- | `npx @cyning/harness verify` | `--task`:30 前聚合;`--spec`:SPEC→00 审查文闸(v2.8+ · 互斥)· `--allow-no-spec-review` |
186
+ | `npx @cyning/harness verify` | 全量:双路径 + reviews(v2.9+);`--task`:30 前聚合;`--spec`:SPEC→00v2.8+ · 互斥) |
181
187
  | `npx @cyning/harness lifecycle show` | 只读展示 `harness/lifecycle.yaml`(状态/转移/守卫 · v2.7+ · 非引擎) |
182
188
  | `npx @cyning/harness gate-check` | 仅人工闸(`--graph` / `--json`) |
183
189
  | `npx @cyning/harness audit` | ICVO 机械审计(D3/D5/S5) |
@@ -0,0 +1,183 @@
1
+ # SPEC:verify 全量模式纳入 reviews 留档闸(N4)(v1)
2
+
3
+ > **状态**:`signed`(维护者签收 2026-07-25 · 对话「签收」)
4
+ > **track**:`feature`
5
+ > **关联图谱**:无(纯 Harness 工具链)
6
+ > **上游**:[`PLAN_post_g4_next_mechanization_v1_zh.md`](../../../docs/harness/guides/PLAN_post_g4_next_mechanization_v1_zh.md) · N4 · G2 residual
7
+ > **前置**:G2 `@cyning/harness@2.5.0`(`--task` 已查 reviews)· N3 `@2.8.0`
8
+ > **下游**:30/40 已完成(分支 `task/cyning-harness-verify-full-reviews-gate` · package **2.9.0** · 待 publish)
9
+
10
+ ---
11
+
12
+ ## Harness 元信息
13
+
14
+ | 字段 | 值 |
15
+ |------|-----|
16
+ | **spec_slug** | `verify-full-reviews-gate` |
17
+ | **test_strategy** | `required` |
18
+ | **test_strategy_note** | 全量模式:有文 PASS / 缺文 BLOCKED / `--allow-no-review`;双路径发现;既有 `--task` 回归不变 |
19
+ | **entry_invoke_10_spec** | `Projects/docs/harness/invokes/by-task/cyning-harness-verify-full-reviews-gate/invoke_20260725_10_spec_verify_full_reviews_gate.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
+ G2(v2.5.0)把「R&lt;n&gt; 审查文存在」挂在 **`verify --task`** 与 `task close`,但 R2 明确:**无 `--task` 全量模式本波只查闸表**。后果:
27
+
28
+ 1. **语义裂缝**:`verify --task X` 会因缺审查文 BLOCKED,但裸 `verify`(全量)在闸表全 approved 时仍可能 **PASS**——「仓库可否 30」答案因是否传 `--task` 而分裂。
29
+ 2. **发现路径裂缝**:`gate-check` / `listActiveTasks` 只扫 `docs/tasks/active`;工作区 Extended 真值在 `docs/harness/tasks/active`。对 Projects 根跑裸 `verify` →「无 active」vacuous PASS,与人工闸表现实脱节。
30
+
31
+ **dogfood(2026-07-25 · `findReview` + `evaluateMayStart30`)**:`docs/harness/tasks/active` **15** 个 task:
32
+
33
+ | 指标 | 值 |
34
+ |------|-----|
35
+ | `may_start_30=true` | **4** |
36
+ | 其中缺 R&lt;n&gt; 审查文 | **3**(`a5_cli_verify` · `hat_chain_pointer_sync` · `task_validate_human_gate_ci_gate`) |
37
+ | 有审查文且可 30 | **1**(`ops_clarify_single_run_resume`) |
38
+
39
+ → 若仅扩展 reviews 检查而不扩路径,N4 对工作区几乎无效果;若扩路径 + 查 reviews,裸 `verify` 将暴露上述 3 个「闸表可 30 但无审查文」任务(预期拦截 · 可用 `--allow-no-review`)。
40
+
41
+ **目标**:闭合 G2 residual——全量模式与 `--task` 在 **reviews 存在性** 上同构;并让全量发现覆盖 Starter(`docs/tasks/active`)与 Extended(`docs/harness/tasks/active`)。
42
+
43
+ **职责切分(同 G2)**:机器 = 存在性;人 = `HG-AUDIT-R1` 结论。
44
+
45
+ ---
46
+
47
+ ## 2. 范围
48
+
49
+ ### D1 · 全量模式 reviews 闸(`lib/verify.js`)
50
+
51
+ - 当**无** `--task` 且**无** `--spec`:在 gate-check 通过后,对发现的每个 active task 调用 `findReview`:
52
+ - 任一缺失 → `VERIFY: BLOCKED · missing R<n> review`(reason 列出 basename,如 `3/15 tasks · a.md, b.md, …`)· exit 2
53
+ - `--allow-no-review` → 对缺失者 WARN 放行(与 `--task` 同旗)
54
+ - **不**在全量模式跑 task lint(维持 N2:仅 `--task`)
55
+ - **不**在全量模式跑 audit D5 逐 task(本波不变;另议)
56
+
57
+ ### D2 · active 发现双路径
58
+
59
+ - `listActiveTasks(target)`:返回
60
+ `docs/tasks/active/task_*.md` ∪ `docs/harness/tasks/active/task_*.md`
61
+ (去重:同 basename 优先 Starter 路径;排序稳定)
62
+ - `wizard/gate-check.sh`:**同步**双路径扫描(无 `--task` 时),避免闸表集合 ≠ reviews 集合
63
+ - 文档:ONBOARDING/USER_GUIDE 注明双路径
64
+
65
+ ### D3 · handoff 对齐
66
+
67
+ - 全量 `--json`:每个 task 的 `may_start_30` / `verify_ok` **纳入** `review_found`(与单 `--task` 一致);根级 `verify_ok` = 全部可 30(含审查文)
68
+ - `--agent-hint` 可显示缺审查文的 blocked 行
69
+
70
+ ### D4 · lifecycle 登记
71
+
72
+ - `harness/lifecycle.yaml`:`to_30` 的 `reviews_retention` 注释/描述改为覆盖「`--task` 与全量模式」;不新增转移 id(同守卫 · 两入口)
73
+
74
+ ### D5 · 测试 / 文档 / 版本
75
+
76
+ - 测试:全量三态(全有文 PASS / 一缺 BLOCKED / allow 豁免)+ 双路径夹具(仅 harness 路径也能被扫到)+ 既有 `--task` 回归
77
+ - CHANGELOG **v2.9.0**(⚠ 行为变更:裸 verify 更严)+ 豁免指引
78
+ - PLAN / G2 SPEC residual 回链关闭
79
+
80
+ ### D6 · dogfood(实现波)
81
+
82
+ - 对工作区根或含 `docs/harness/tasks/active` 的 target 跑裸 `verify`,报告 BLOCKED 列表与豁免后结果;落自检结论(可修审查文或 `--allow-no-review`,不强制本波修齐 3 个存量)
83
+
84
+ ---
85
+
86
+ ## 3. 非范围
87
+
88
+ - 全量模式接入 **task lint**(N2-C / 另 SPEC)
89
+ - 全量模式逐 task **audit D5**
90
+ - SPEC 侧全量扫描(已有 `verify --spec` 单文件)
91
+ - 强制补齐 dogfood 3 个缺审查文的存量 task
92
+ - 审查文内容判定;状态机引擎
93
+
94
+ ---
95
+
96
+ ## 4. 验收标准
97
+
98
+ - [ ] 无 `--task`:active 均有 R&lt;n&gt; 文 → PASS(闸表亦过)
99
+ - [ ] 无 `--task`:至少一 task 缺审查文 → BLOCKED exit 2 · reason 含该 basename
100
+ - [ ] `--allow-no-review`:全量缺失 → WARN + PASS
101
+ - [ ] 仅存在 `docs/harness/tasks/active` 时,gate-check / listActiveTasks 仍能发现 task
102
+ - [ ] `--task` 行为与 v2.8 一致(回归)
103
+ - [ ] 全量 `--json`:`may_start_30` 含审查文条件
104
+ - [ ] `npm test` 全绿;CHANGELOG v2.9.0 行为变更醒目
105
+ - [ ] dogfood 分布落自检结论
106
+
107
+ ---
108
+
109
+ ## 5. failure_paths
110
+
111
+ | 触发条件 | 系统行为 | 可重试 |
112
+ |----------|----------|--------|
113
+ | 全量扫描发现缺审查文 | BLOCKED · 列出 task | 补 20 审 / `--allow-no-review` |
114
+ | 双路径同 basename 重复 | 去重后只检一次 | — |
115
+ | 业务仓只有 Starter 路径 | 行为 = 只扫 `docs/tasks/active`(与今同构 + reviews) | — |
116
+ | 未升级 2.9.0 | 全量仍不查 reviews | upgrade |
117
+ | 存量 3 个可 30 无审查文被挡 | **预期** | 补审或豁免 |
118
+
119
+ ---
120
+
121
+ ## 6. 依赖与引用
122
+
123
+ - G2:`findReview` · `--allow-no-review` · SPEC residual ③
124
+ - N2:全量不跑 lint(本 SPEC 维持)
125
+ - `listActiveTasks` · `gate-check.sh` ACTIVE_DIR
126
+ - PLAN N4
127
+
128
+ ---
129
+
130
+ ## 7. 思考轮(10-spec 回填 · R0–R5)
131
+
132
+ ### R0 · 读入与约束
133
+
134
+ 读入:PLAN N4;G2 R2「全量本波不变」+ residual ③;`verify.js` 仅 `taskFile` 分支查 reviews;`listActiveTasks` / gate-check 仅 `docs/tasks/active`;dogfood 15/4/3。约束:行为变更须豁免阀 + CHANGELOG;挂点须与检查物同时刻(审查文应在 R1 后已存在)。
135
+
136
+ ### R1 · 范围 / 非范围 / 场景
137
+
138
+ **场景**:① 维护者/Agent 裸 `verify` 做仓库健康检查,应与逐 task `--task` 对「可 30」同答案;② Extended 工作区 active 在 harness 路径下也要被扫到;③ 存量缺文用豁免,不强制本波修齐。
139
+ **非范围**:lint/D5 全量化、SPEC 全量、内容判定——避免 N4 膨胀成「verify 大一统」。
140
+
141
+ ### R2 · 方案对比
142
+
143
+ | 决策点 | 选项 | 裁定 | 理由 |
144
+ |---|---|---|---|
145
+ | 是否纳入 reviews | 否(关账 N4)/ warn / block | **block + `--allow-no-review`** | 与 G2 `--task` 同构;warn 再留自觉层;dogfood 已能量化误伤面(3 task) |
146
+ | 检查哪些 task | 全部 active / 仅 may_start_30 | **全部发现的 active**(与 gate-check 集合对齐) | 闸表未 approved 者 gate-check 已挡;对已过闸者补审查文洞;实现简单 |
147
+ | 双路径 | 仅 Starter / Starter∪Extended | **∪ 双路径**(listActiveTasks + gate-check 同步) | 否则 N4 对 Projects dogfood 无效 |
148
+ | 全量 lint | 顺带做 / 不做 | **不做** | N2 已拒;FAIL 率仍高 |
149
+ | 版本 | 2.8.x / 2.9.0 | **2.9.0** | 裸 verify 行为变更 · minor |
150
+
151
+ ### R3 · 边界 / 失败语义 / 安全
152
+
153
+ - **误伤**:主要是「闸表可 30 且无审查文」——正是 G2 要抓的类;豁免旗复用。
154
+ - **空 active**:两路径皆无 → 维持现状(gate-check 提示无 active · exit 0)。
155
+ - **安全**:只读。
156
+ - **兼容**:`--task` / `--spec` 不变。
157
+
158
+ ### R4 · 验收 / 可测性 / test_strategy
159
+
160
+ `test_strategy: required`。夹具:双路径目录、全量缺一文、allow 豁免;回归 verify-review-gate / verify-lint-warn。dogfood 命令写进 40 自检。
161
+
162
+ ### R5 · SPEC 签收就绪 · 是否可交 00 出 task
163
+
164
+ 自足:裁定、路径、豁免、版本清晰。**可交 00**:单 task `cyning-harness-verify-full-reviews-gate`。图谱无需。关闭 G2 residual ③。
165
+
166
+ ### 思考轮控制
167
+
168
+ | 字段 | 值 |
169
+ |------|-----|
170
+ | `actual_last_round` | `R5` |
171
+ | `early_stop` | `no` |
172
+ | `early_stop_reason` | — |
173
+ | `residual_risks` | ① gate-check 双路径改 bash 须防破既有单测路径假设;② 去重规则若两目录同 slug 不同内容属数据错误——本波只去重不深比较;③ 全量 D5 仍空缺,健康检查仍非「可 30」充分条件 |
174
+ | `round_extension_note` | — |
175
+
176
+ ---
177
+
178
+ ## 修订记录
179
+
180
+ | 日期 | 摘要 |
181
+ |------|------|
182
+ | 2026-07-25 | 10-spec R0–R5(维护者点菜「继续 N4」)· dogfood 15/4/3 |
183
+ | 2026-07-25 | 维护者签收(对话「签收」)· → 00 起草 task |
@@ -34,7 +34,7 @@ transitions:
34
34
  from: ["draft", "in_progress"]
35
35
  to: in_progress
36
36
  hat: "30"
37
- description: "执行帽开工(verify --task 聚合)"
37
+ description: "执行帽开工(verify --task 与全量模式聚合 · v2.9+)"
38
38
  guards:
39
39
  - id: HG-AUDIT-R1
40
40
  command_or_check: "gate-check · human_gate HG-AUDIT-R1=approved"
@@ -46,9 +46,10 @@ transitions:
46
46
  command_or_check: "verify/audit · test_strategy vs 测试文件存在"
47
47
  severity: block
48
48
  - id: reviews_retention
49
- command_or_check: "verify · findReview R<n> 存在性(v2.5+)"
49
+ command_or_check: "verify --task 与全量 · findReview R<n> 存在性(v2.5+ · 全量 v2.9+)"
50
50
  severity: block
51
51
  allow_flag: "--allow-no-review"
52
+ note: "全量模式扫描双路径 active;不跑 lint / D5"
52
53
  - id: task_lint
53
54
  command_or_check: "verify --task · lintTaskFile(v2.7+)"
54
55
  severity: warn
package/lib/cli.js CHANGED
@@ -400,7 +400,8 @@ async function cmdVerify(args) {
400
400
  if (args.includes('--help') || args.includes('-h')) {
401
401
  console.log(`用法: npx @cyning/harness verify [--target PATH] [--task FILE | --spec FILE] [--graph] [--json] [--agent-hint] [--workspace-root PATH] [--allow-no-review] [--allow-lint-fail] [--allow-no-spec-review]
402
402
 
403
- 无 --task/--spec:扫描 docs/tasks/active/task_*.md 并逐项列出闸表(同 gate-check);不跑 task lint。
403
+ 无 --task/--spec:扫描 docs/tasks/active ∪ docs/harness/tasks/active(同 basename 优先 Starter);
404
+ 闸表 + 全量 reviews 存在性(v2.9+)· 不跑 task lint / D5 · --allow-no-review。
404
405
  --task:30 前聚合(gate + audit + reviews + lint WARN)· --allow-no-review / --allow-lint-fail。
405
406
  --spec:SPEC→00 前查审查文存在性(v2.8+ · 与 --task 互斥)· --allow-no-spec-review。
406
407
  推荐:--spec <产品仓 SPEC> --workspace-root <含 docs/harness/reviews 的工作区根>
package/lib/task-meta.js CHANGED
@@ -310,17 +310,25 @@ export function buildTaskHandoff(target, taskFile, options = {}) {
310
310
  }
311
311
 
312
312
  /**
313
- * 扫描 target 下 active task 列表。
313
+ * 扫描 target 下 active task 列表(N4 · v2.9+)。
314
+ * 双路径:docs/tasks/active ∪ docs/harness/tasks/active;
315
+ * 同 basename 优先 Starter;排序稳定。
314
316
  */
315
317
  export function listActiveTasks(target) {
316
- const activeDir = path.join(target, 'docs/tasks/active');
317
- if (!fs.existsSync(activeDir)) return [];
318
-
319
- return fs
320
- .readdirSync(activeDir)
321
- .filter((f) => f.startsWith('task_') && f.endsWith('.md'))
322
- .map((f) => path.join('docs/tasks/active', f).replace(/\\/g, '/'))
323
- .sort();
318
+ const dirs = ['docs/tasks/active', 'docs/harness/tasks/active'];
319
+ const byBasename = new Map();
320
+
321
+ for (const dir of dirs) {
322
+ const abs = path.join(target, dir);
323
+ if (!fs.existsSync(abs)) continue;
324
+ for (const f of fs.readdirSync(abs)) {
325
+ if (!f.startsWith('task_') || !f.endsWith('.md')) continue;
326
+ if (byBasename.has(f)) continue;
327
+ byBasename.set(f, path.join(dir, f).replace(/\\/g, '/'));
328
+ }
329
+ }
330
+
331
+ return [...byBasename.values()].sort();
324
332
  }
325
333
 
326
334
  function escapeRegExp(s) {
package/lib/verify.js CHANGED
@@ -14,8 +14,9 @@ import {
14
14
  import { lintTaskFile } from './task-lint.js';
15
15
 
16
16
  /**
17
- * 30 前聚合验证:gate-check + audit D5(仅 --task)+ reviews 留档闸(仅 --task)
18
- * + task lint WARN(仅 --task · v2.7+)+ S5 git-clean warn + 可选 --graph
17
+ * 30 前聚合验证:gate-check + audit D5(仅 --task)+ reviews 留档闸
18
+ *(--task 与全量 · N4/v2.9+)+ task lint WARN(仅 --task · v2.7+)
19
+ * + S5 git-clean warn + 可选 --graph
19
20
  */
20
21
  export function verifyTarget(target, options = {}) {
21
22
  const { taskFile, graph, allowNoReview = false, allowLintFail = false } = options;
@@ -69,6 +70,26 @@ export function verifyTarget(target, options = {}) {
69
70
  gateResult.stdout += `WARN: task lint FAIL · ${rules || 'errors'}(不挡 may_start_30 · 修结构或 --allow-lint-fail)\n`;
70
71
  }
71
72
  }
73
+ } else {
74
+ // 3c. 全量 reviews 闸(N4 · v2.9+):不跑 lint / D5
75
+ const taskFiles = listActiveTasks(target);
76
+ const missing = [];
77
+ for (const tf of taskFiles) {
78
+ if (!findReview(target, tf).found) missing.push(path.basename(tf));
79
+ }
80
+ if (missing.length > 0) {
81
+ const detail = `${missing.length}/${taskFiles.length} tasks · ${missing.join(', ')}`;
82
+ if (allowNoReview) {
83
+ gateResult.stdout += `WARN: missing R<n> review(--allow-no-review 豁免 · 留痕)· ${detail}\n`;
84
+ } else {
85
+ return {
86
+ ok: false,
87
+ exitCode: 2,
88
+ reason: `missing R<n> review(${detail})`,
89
+ stdout: gateResult.stdout,
90
+ };
91
+ }
92
+ }
72
93
  }
73
94
 
74
95
  // 4. S5 git-clean:warn 不挡
@@ -335,14 +356,19 @@ export function buildVerifyHandoff(target, options = {}) {
335
356
  const taskFiles = listActiveTasks(target);
336
357
  const tasks = taskFiles.map((tf) => {
337
358
  const handoff = buildTaskHandoff(target, tf, handoffOpts);
359
+ const reviewOk = handoff.review_found || allowNoReview;
360
+ const verifyOk = handoff.may_start_30 && reviewOk;
338
361
  return {
339
362
  schema_version: '1',
340
- verify_ok: handoff.may_start_30,
363
+ verify_ok: verifyOk,
341
364
  ...handoff,
365
+ may_start_30: verifyOk,
366
+ blocked_reason: handoff.blocked_reason ?? (reviewOk ? null : 'missing R<n> review'),
367
+ next_hat: verifyOk ? '30' : null,
342
368
  };
343
369
  });
344
370
 
345
- const verifyOk = tasks.length > 0 && tasks.every((t) => t.may_start_30);
371
+ const verifyOk = tasks.length > 0 && tasks.every((t) => t.verify_ok);
346
372
  return {
347
373
  schema_version: '1',
348
374
  verify_ok: verifyOk,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyning/harness",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "description": "cyning-harness discipline package · init / upgrade / check CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -174,6 +174,29 @@ if [[ "$JSON_MODE" != "1" ]]; then
174
174
  echo ""
175
175
  fi
176
176
 
177
+ # N4 · v2.9+:双路径发现(Starter ∪ Extended);同 basename 优先 Starter
178
+ # 输出绝对路径列表到 TASK_LIST(bash 3.2 兼容 · 无关联数组)
179
+ collect_active_tasks() {
180
+ TASK_LIST=()
181
+ local dir tf base existing already
182
+ for dir in "$TARGET/docs/tasks/active" "$TARGET/docs/harness/tasks/active"; do
183
+ [[ -d "$dir" ]] || continue
184
+ for tf in "$dir"/task_*.md; do
185
+ [[ -f "$tf" ]] || continue
186
+ base="$(basename "$tf")"
187
+ already=0
188
+ for existing in "${TASK_LIST[@]+"${TASK_LIST[@]}"}"; do
189
+ if [[ "$(basename "$existing")" == "$base" ]]; then
190
+ already=1
191
+ break
192
+ fi
193
+ done
194
+ [[ "$already" == "1" ]] && continue
195
+ TASK_LIST+=("$tf")
196
+ done
197
+ done
198
+ }
199
+
177
200
  BLOCKED=0
178
201
 
179
202
  if [[ "$GRAPH_MODE" == "1" ]]; then
@@ -191,26 +214,16 @@ if [[ "$GRAPH_MODE" == "1" ]]; then
191
214
  fi
192
215
  graph_for_task "$TASK_FILE"
193
216
  else
194
- ACTIVE_DIR="$TARGET/docs/tasks/active"
195
- if [[ ! -d "$ACTIVE_DIR" ]]; then
217
+ collect_active_tasks
218
+ if [[ ${#TASK_LIST[@]} -eq 0 ]]; then
196
219
  if [[ "$JSON_MODE" == "1" ]]; then
197
220
  printf ']\n'
198
221
  else
199
- echo "⚠️ $ACTIVE_DIR" >&2
222
+ echo "无 active task_*.md(docs/tasks/active ∪ docs/harness/tasks/active)"
200
223
  fi
201
224
  exit 0
202
225
  fi
203
- TASK_FILES=("$ACTIVE_DIR"/task_*.md)
204
- if [[ ! -e "${TASK_FILES[0]}" ]]; then
205
- if [[ "$JSON_MODE" == "1" ]]; then
206
- printf ']\n'
207
- else
208
- echo "无 active task_*.md"
209
- fi
210
- exit 0
211
- fi
212
- for tf in "$ACTIVE_DIR"/task_*.md; do
213
- [[ -f "$tf" ]] || continue
226
+ for tf in "${TASK_LIST[@]}"; do
214
227
  if [[ "$JSON_MODE" == "1" ]]; then
215
228
  [[ "$local_first" == "1" ]] || printf ','
216
229
  local_first=0
@@ -234,15 +247,16 @@ if [[ -n "$TASK_FILE" ]]; then
234
247
  fi
235
248
  check_one_task "$TASK_FILE" || BLOCKED=1
236
249
  else
237
- ACTIVE_DIR="$TARGET/docs/tasks/active"
238
- [[ -d "$ACTIVE_DIR" ]] || { echo "无 $ACTIVE_DIR" >&2; exit 1; }
239
- TASK_FILES=("$ACTIVE_DIR"/task_*.md)
240
- if [[ ! -e "${TASK_FILES[0]}" ]]; then
250
+ collect_active_tasks
251
+ if [[ ${#TASK_LIST[@]} -eq 0 ]]; then
252
+ if [[ ! -d "$TARGET/docs/tasks/active" && ! -d "$TARGET/docs/harness/tasks/active" ]]; then
253
+ echo "无 docs/tasks/active 且无 docs/harness/tasks/active" >&2
254
+ exit 1
255
+ fi
241
256
  echo "无 active task_*.md"
242
257
  exit 0
243
258
  fi
244
- for tf in "$ACTIVE_DIR"/task_*.md; do
245
- [[ -f "$tf" ]] || continue
259
+ for tf in "${TASK_LIST[@]}"; do
246
260
  check_one_task "$tf" || BLOCKED=1
247
261
  done
248
262
  fi