@cyning/harness 2.7.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 +32 -0
- package/docs/ONBOARDING.md +12 -3
- package/docs/USER_GUIDE_v1.0_zh.md +14 -3
- package/docs/spec/SPEC-spec-reviews-retention-gate_v1.md +192 -0
- package/docs/spec/SPEC-verify-full-reviews-gate_v1.md +183 -0
- package/harness/lifecycle.yaml +18 -3
- package/harness/prompts/20-spec-audit.md +1 -0
- package/lib/cli.js +57 -27
- package/lib/task-meta.js +118 -9
- package/lib/verify.js +139 -5
- package/package.json +1 -1
- package/wizard/gate-check.sh +34 -20
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,37 @@
|
|
|
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
|
+
|
|
22
|
+
## [2.8.0] - 2026-07-25
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **SPEC 审查文留档闸(N3 · G2 姊妹)**:`verify --spec FILE` 检查 `docs/harness/reviews/` 下 SPEC 审查文存在性(推荐 `spec_<slug>_audit_R*` · 兼容 `*_ACCEPT_*` / `task_*_spec_ACCEPT_*`)。缺失 → `VERIFY: BLOCKED · missing SPEC R<n> review` exit 2。
|
|
27
|
+
- 豁免:`--allow-no-spec-review`;元信息 `track: bugfix` / `skip_spec_audit: true`。
|
|
28
|
+
- `findSpecReview`(支持 `--workspace-root` 分仓);handoff:`may_start_00` · `spec_review_found` · `spec_review_latest`。
|
|
29
|
+
- `lifecycle.yaml` 转移 `to_00` + guard `spec_reviews_retention`(只登记)。
|
|
30
|
+
|
|
31
|
+
### Notes
|
|
32
|
+
|
|
33
|
+
- 与 `--task` 互斥;**不**改 task 侧 verify/close。
|
|
34
|
+
- SPEC:`docs/spec/SPEC-spec-reviews-retention-gate_v1.md`
|
|
35
|
+
- minor · 行为新增(新模式)
|
|
36
|
+
- **已发布**:`@cyning/harness@2.8.0`(npm `latest` · 2026-07-25)· tag `v2.8.0` 已推送
|
|
37
|
+
|
|
7
38
|
## [2.7.0] - 2026-07-24
|
|
8
39
|
|
|
9
40
|
### Added
|
|
@@ -17,6 +48,7 @@
|
|
|
17
48
|
- 背景:Post-G4 方案 N1+N2 · rethink 方向二骨架;dogfood active lint FAIL 率高故本波不做 block。
|
|
18
49
|
- SPEC:`docs/spec/SPEC-lifecycle-and-verify-lint_v1.md`
|
|
19
50
|
- minor · `npm test` 含 lifecycle + verify-lint-warn
|
|
51
|
+
- **已发布**:`@cyning/harness@2.7.0`(npm `latest` · 2026-07-24)· tag `v2.7.0`
|
|
20
52
|
|
|
21
53
|
## [2.6.0] - 2026-07-24
|
|
22
54
|
|
package/docs/ONBOARDING.md
CHANGED
|
@@ -45,13 +45,20 @@ cd your-project
|
|
|
45
45
|
`npx @cyning/harness verify` 在 30 执行前聚合扫描人工闸与测试声明,确保 ICVO 公理可机械检查:
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
|
-
# 30 前聚合验证(gate-check +
|
|
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
|
|
|
57
|
+
# SPEC→00 前:审查文存在性(v2.8+ · 与 --task 互斥)
|
|
58
|
+
npx @cyning/harness verify --spec docs/spec/SPEC-xxx_v1.md \
|
|
59
|
+
--workspace-root /path/to/Projects
|
|
60
|
+
# 豁免:--allow-no-spec-review · 或 SPEC track=bugfix / skip_spec_audit
|
|
61
|
+
|
|
55
62
|
# 只读生命周期登记(方向二 · 非引擎)
|
|
56
63
|
npx @cyning/harness lifecycle show
|
|
57
64
|
npx @cyning/harness lifecycle show --json
|
|
@@ -71,7 +78,9 @@ npx @cyning/harness sync index --target /path/to/your-repo
|
|
|
71
78
|
| 公理 | 检查项 | 行为 |
|
|
72
79
|
|------|--------|------|
|
|
73
80
|
| **D3** | 30 前置人闸 | 复用 `gate-check.sh`,HG-AUDIT-R1 非 approved 时 verify 非 0 |
|
|
74
|
-
| **D5** | 改码任务测试声明 |
|
|
81
|
+
| **D5** | 改码任务测试声明 | 仅 `--task`:`test_strategy=required` 但无测试/CI 引用时 verify 非 0 |
|
|
82
|
+
| **reviews** | R<n> 审查文存在 | `--task` 与全量(v2.9+)· 缺文 BLOCKED · `--allow-no-review` |
|
|
83
|
+
| **active** | 任务发现 | 双路径 `docs/tasks/active` ∪ `docs/harness/tasks/active`(v2.9+) |
|
|
75
84
|
| **S5** | Git 工作区干净 | dirty 时 warn(不直接 fail verify,但 apply 须 `--force`) |
|
|
76
85
|
| **lint** | task 结构(仅 `--task`) | v2.7+ E 级 → `WARN: task lint`(不改 exit / `may_start_30`) |
|
|
77
86
|
|
|
@@ -111,10 +111,19 @@ 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
|
-
#
|
|
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
|
|
|
122
|
+
# SPEC→00:审查文存在性(v2.8+ · 与 --task 互斥)
|
|
123
|
+
npx @cyning/harness verify --spec docs/spec/SPEC-xxx_v1.md \
|
|
124
|
+
--workspace-root /path/to/Projects
|
|
125
|
+
# 豁免:--allow-no-spec-review
|
|
126
|
+
|
|
118
127
|
# Agent handoff(v2.0.2+):JSON 路由 + 下一帽提示
|
|
119
128
|
npx @cyning/harness verify --target . \
|
|
120
129
|
--task docs/tasks/active/task_xxx.md \
|
|
@@ -141,6 +150,7 @@ npx @cyning/harness gate-check --graph --target . # Inform 图谱闸
|
|
|
141
150
|
| `HG-GRAPH-MODULES` | 架构模块表人签 | pending → 拒改码 30 |
|
|
142
151
|
| `HG-RELEASE` | 发版闸(产品仓) | 一般业务仓不涉及 |
|
|
143
152
|
| `task_lint`(v2.7+) | `verify --task` 结构检查 | **仅 WARN** · 不改 `may_start_30` |
|
|
153
|
+
| `reviews`(全量 v2.9+) | 裸 verify 亦查 R<n> 文 | 缺文 → BLOCKED · `--allow-no-review` |
|
|
144
154
|
|
|
145
155
|
### 5.1 Agent handoff(v2.0.2+)
|
|
146
156
|
|
|
@@ -148,7 +158,8 @@ npx @cyning/harness gate-check --graph --target . # Inform 图谱闸
|
|
|
148
158
|
|
|
149
159
|
| JSON 字段 | 含义 |
|
|
150
160
|
| --- | --- |
|
|
151
|
-
| `may_start_30` |
|
|
161
|
+
| `may_start_30` | 闸表可 30 **且** `review_found`(或豁免) |
|
|
162
|
+
| `review_found` | R<n> 审查文是否存在(全量与 `--task` 同构 · v2.9+) |
|
|
152
163
|
| `blocked_reason` | 阻塞时 D2/D3 文案 |
|
|
153
164
|
| `review_path` | 最新 `*_audit_R1_*.md`(相对 target) |
|
|
154
165
|
| `entry_invoke_30` | task 表原始路径 |
|
|
@@ -172,7 +183,7 @@ Schema:[`schema/verify_result.v1.schema.json`](../schema/verify_result.v1.sche
|
|
|
172
183
|
| `npx @cyning/harness init` | 首次安装模板与 manifest(可选 `--with-scripts`) |
|
|
173
184
|
| `npx @cyning/harness upgrade` | 同步产品包更新(可加 `--gate-check` 先 audit) |
|
|
174
185
|
| `npx @cyning/harness check` | 检查是否有新版本 |
|
|
175
|
-
| `npx @cyning/harness verify` |
|
|
186
|
+
| `npx @cyning/harness verify` | 全量:双路径 + reviews(v2.9+);`--task`:30 前聚合;`--spec`:SPEC→00(v2.8+ · 互斥) |
|
|
176
187
|
| `npx @cyning/harness lifecycle show` | 只读展示 `harness/lifecycle.yaml`(状态/转移/守卫 · v2.7+ · 非引擎) |
|
|
177
188
|
| `npx @cyning/harness gate-check` | 仅人工闸(`--graph` / `--json`) |
|
|
178
189
|
| `npx @cyning/harness audit` | ICVO 机械审计(D3/D5/S5) |
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# SPEC:SPEC 审查文留档闸(20-spec-audit / HG-SPEC-SIGNOFF 存在性检查)(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) · N3 · G2 姊妹
|
|
7
|
+
> **前置**:G2 `@cyning/harness@2.5.0`(`findReview`)· N1 `@2.7.0`(`lifecycle.yaml`)
|
|
8
|
+
> **下游**:00 已起草 task → 20-task-audit → HG-AUDIT-R1 → 30(目标版本 **v2.8.0**)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Harness 元信息
|
|
13
|
+
|
|
14
|
+
| 字段 | 值 |
|
|
15
|
+
|------|-----|
|
|
16
|
+
| **spec_slug** | `spec-reviews-retention-gate` |
|
|
17
|
+
| **test_strategy** | `required` |
|
|
18
|
+
| **test_strategy_note** | `findSpecReview` 命名变体 + `verify --spec` 三态(pass/block/豁免)+ bugfix 豁免;既有 `verify --task` 回归不改 |
|
|
19
|
+
| **entry_invoke_10_spec** | `Projects/docs/harness/invokes/by-task/cyning-harness-spec-reviews-retention-gate/invoke_20260725_10_spec_spec_reviews_retention_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)已把 **task** 侧「R<n> 审查文存在」机械化为 `verify --task` / `task close` 检查 6。功能轨上游对称缺口仍在:
|
|
27
|
+
|
|
28
|
+
- [`20-spec-audit.md`](../../harness/prompts/20-spec-audit.md) 要求落盘 `reviews/spec_<slug>_audit_R<n>_*.md`(或既有 `*_spec_ACCEPT_R*` 惯例);
|
|
29
|
+
- **`HG-SPEC-SIGNOFF` 可由对话「签收」完成,签署依据(SPEC 审查文)存在性零机械**——与 G2 补闸前的 task 侧同构。
|
|
30
|
+
|
|
31
|
+
**dogfood(2026-07-25)**:产品仓 `docs/spec/SPEC-*.md` **5** 份近期 SPEC(含 G1–G4 / N1+N2)**均无**对应 `spec_*_audit_R*` / `*_spec_ACCEPT_R*` 文;工作区仅有少量历史 `spec_*_ACCEPT_*` / `*_spec_ACCEPT_*`。说明当前主流是「对话签收 + 跳过 20-spec-audit 落盘」。
|
|
32
|
+
|
|
33
|
+
**目标**:补上 SPEC→task 纸链的对称闸——机器只查**审查文存在性**;审查结论仍由人签 `HG-SPEC-SIGNOFF` 覆盖。
|
|
34
|
+
|
|
35
|
+
**职责切分(同 G2)**:机器 = 形式(文件在);人 = 实质(通过/条件通过)。
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 2. 范围
|
|
40
|
+
|
|
41
|
+
### D1 · `findSpecReview`(`lib/task-meta.js` 或邻近模块)
|
|
42
|
+
|
|
43
|
+
- 输入:`specFile` + `target`(reviews 根所在仓)+ 可选 `workspaceRoot`(SPEC 在产品仓、reviews 在工作区时)
|
|
44
|
+
- 匹配(任一命中即 `found`;取最新轮):
|
|
45
|
+
1. **推荐**:`spec_<slug>_audit_R<n>_*.md`
|
|
46
|
+
2. **兼容**:`spec_<slug>_ACCEPT_R<n>_*.md`
|
|
47
|
+
3. **兼容**:`task_<slug>_spec_ACCEPT_R<n>_*.md`(工作区历史惯例)
|
|
48
|
+
- slug 来源:优先 SPEC 表 `spec_slug`;否则自文件名剥离 `SPEC-` / `_v\d+` / 扩展名后 `normalizeSlug`
|
|
49
|
+
- 文件名与 slug:**两侧**下划线/连字符等价;版本后缀 `_v\d+` 双侧剥离(复用 G2 R1-B1 思路)
|
|
50
|
+
- 返回:`{ found, latest, rounds[], matched_pattern? }`
|
|
51
|
+
|
|
52
|
+
### D2 · CLI 挂点:`verify --spec FILE`
|
|
53
|
+
|
|
54
|
+
- **新模式**(与 `--task` 互斥):`npx @cyning/harness verify --spec PATH [--target PATH] [--workspace-root PATH] [--json] [--allow-no-spec-review]`
|
|
55
|
+
- 行为:
|
|
56
|
+
- 审查文存在 → `VERIFY: PASS`(或等价摘要)· exit 0
|
|
57
|
+
- 缺失 → `VERIFY: BLOCKED · missing SPEC R<n> review` · exit 2
|
|
58
|
+
- `--allow-no-spec-review` → warn 放行 · 留痕
|
|
59
|
+
- `--json`:`may_start_00`(或 `spec_review_ok`)· `spec_review_found` · `spec_review_latest` · `blocked_reason`
|
|
60
|
+
- **不**改动既有 `verify --task` / 无参全量模式语义(N4 仍另议)
|
|
61
|
+
- **不**把本闸挂进 `verify --task`(挂点错误:30 查的是 task 审查文,不是 SPEC)
|
|
62
|
+
|
|
63
|
+
### D3 · bugfix / 跳过 10-spec 豁免
|
|
64
|
+
|
|
65
|
+
满足任一则 **不**要求 SPEC 审查文(exit 0 + 可选 info 行):
|
|
66
|
+
|
|
67
|
+
- SPEC / 元信息显式:`track: bugfix` 或 `skip_spec_audit: true`(字段名以实现为准 · 文档冻结)
|
|
68
|
+
- CLI:`--allow-no-spec-review`(通用泄压,含历史对话签收存量)
|
|
69
|
+
|
|
70
|
+
### D4 · lifecycle 登记
|
|
71
|
+
|
|
72
|
+
- `harness/lifecycle.yaml` 新增转移(建议 id:`to_00` 或 `spec_signoff`):
|
|
73
|
+
- from:spec draft / signed 相关状态(可用注释说明别名;本波允许最小 states 扩展或仅在 transitions 注释)
|
|
74
|
+
- to:可起草 task
|
|
75
|
+
- guard:`spec_reviews_retention` · severity=`block` · `allow_flag: --allow-no-spec-review`
|
|
76
|
+
- **只登记 · 不实现引擎**(同 N1)
|
|
77
|
+
|
|
78
|
+
### D5 · 文档 / 版本
|
|
79
|
+
|
|
80
|
+
- `20-spec-audit.md` / ONBOARDING 或 USER_GUIDE:注明 v2.8+ `verify --spec` 机械强制存在性
|
|
81
|
+
- CHANGELOG **v2.8.0**(行为新增 · minor;醒目说明 + 豁免指引)
|
|
82
|
+
- PLAN / rethink 矩阵回链本 SPEC
|
|
83
|
+
|
|
84
|
+
### D6 · 测试
|
|
85
|
+
|
|
86
|
+
- 命名三变体命中;slug 连字符/下划线;版本后缀
|
|
87
|
+
- verify `--spec` 三态 + bugfix 豁免 + 与 `--task` 互斥用法
|
|
88
|
+
- 既有 verify/close/lifecycle 回归绿
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 3. 非范围
|
|
93
|
+
|
|
94
|
+
- 审查文**内容**判定(pass / conditional_pass / fail)——人/20-spec-audit 职责
|
|
95
|
+
- 强制给**存量** 5 份已对话签收的产品 SPEC 补审(可用豁免;另开 hygiene 可选)
|
|
96
|
+
- 把 SPEC 审查闸挂进 `verify --task` 或 `task close`
|
|
97
|
+
- N4:verify 无 `--task` 全量模式纳入 task reviews
|
|
98
|
+
- 自动代签 `HG-SPEC-SIGNOFF`;状态机引擎执行 `to_00`
|
|
99
|
+
- 统一历史所有命名为单一范式(本波只兼容读取)
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 4. 验收标准
|
|
104
|
+
|
|
105
|
+
- [ ] `findSpecReview`:推荐名 / `ACCEPT` / `task_*_spec_ACCEPT_*` 均可 `found`;多轮取最新
|
|
106
|
+
- [ ] `verify --spec`:有文 → PASS;无文 → BLOCKED exit 2;`--allow-no-spec-review` → warn 放行
|
|
107
|
+
- [ ] bugfix / `skip_spec_audit` → 不要求审查文
|
|
108
|
+
- [ ] `--json` 含 `spec_review_found`(或等价)且 **不**破坏 `--task` handoff 字段
|
|
109
|
+
- [ ] lifecycle.yaml 已登记 `to_00`(或等价)+ `spec_reviews_retention` guard
|
|
110
|
+
- [ ] `npm test` 全绿;CHANGELOG v2.8.0;文档可链到
|
|
111
|
+
- [ ] dogfood:对本 SPEC 走通「20-spec-audit 落盘 → `verify --spec` PASS」(实现波自检)
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 5. failure_paths
|
|
116
|
+
|
|
117
|
+
| 触发条件 | 系统行为 | 可重试 |
|
|
118
|
+
|----------|----------|--------|
|
|
119
|
+
| 00 前无 SPEC 审查文 | `VERIFY: BLOCKED · missing SPEC R<n> review` | 跑 20-spec-audit 落盘;或 `--allow-no-spec-review` |
|
|
120
|
+
| SPEC 在产品仓、reviews 在工作区 | 未传 `--workspace-root` / 错 `--target` → 误报缺失 | 按文档传参后重跑 |
|
|
121
|
+
| 命名不在兼容列表 | 视为缺失 | 按推荐名重命名或补一份推荐名 |
|
|
122
|
+
| bugfix 轨被误挡 | 应走 D3 豁免;若未标 track → 用 allow 旗 | 补元信息 |
|
|
123
|
+
| 业务仓未升级 2.8.0 | 无 `--spec` 模式 · 行为不变 | upgrade |
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 6. 依赖与引用
|
|
128
|
+
|
|
129
|
+
- G2:`findReview` / `--allow-no-review` 模式;本波 **独立** flag `--allow-no-spec-review`(审计痕迹不混)
|
|
130
|
+
- N1:`lifecycle.yaml` 挂点登记
|
|
131
|
+
- 纪律:`harness/prompts/20-spec-audit.md` · `10-spec-requirements.md`(bugfix 跳过)
|
|
132
|
+
- PLAN N3;G2 SPEC 非范围「SPEC 审查文——下一轮」本 SPEC 关闭该句
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 7. 思考轮(10-spec 回填 · R0–R5)
|
|
137
|
+
|
|
138
|
+
### R0 · 读入与约束
|
|
139
|
+
|
|
140
|
+
读入:PLAN N3;G2 SPEC(非范围点名本缺口);`20-spec-audit` 命名双惯例;`findReview` 实现;lifecycle.yaml 仅有 task 转移;dogfood:5/5 产品 SPEC 无 spec 审查文。约束:挂点时刻须晚于审查文应存在时刻;不误伤 bugfix;不把闸错挂到 `--task`。
|
|
141
|
+
|
|
142
|
+
### R1 · 范围 / 非范围 / 场景
|
|
143
|
+
|
|
144
|
+
**场景**:① 维护者/Agent 在 00 起草 task 前跑 `verify --spec`,挡「对话签了但无审查文」;② 20-spec-audit 多轮 R1→R2 取最新;③ 历史 `*_ACCEPT_*` 命名仍认;④ bugfix 无 10-spec 不挡。
|
|
145
|
+
**同构 G2、异挂点**:共享「存在性闸」模式,但消费者是 **SPEC→00**,不是 **task→30**。
|
|
146
|
+
**非范围**:内容判定、存量强制补审、挂进 task verify/close——避免双重计费与挂点错位。
|
|
147
|
+
|
|
148
|
+
### R2 · 方案对比
|
|
149
|
+
|
|
150
|
+
| 决策点 | 选项 | 裁定 | 理由 |
|
|
151
|
+
|---|---|---|---|
|
|
152
|
+
| 严格度 | warn 起步 / block+allow | **block + `--allow-no-spec-review`** | 与 G2 同构;warn 等于再留自觉层。dogfood 存量用豁免,不降级整闸 |
|
|
153
|
+
| CLI 形态 | 独立 `spec check` / 并入 `verify --spec` | **`verify --spec`** | 与 G2 叙事一致(「verify = 下一转移前置」);少一个动词 |
|
|
154
|
+
| 挂点 | `--task` 附带查 SPEC / 仅 `--spec` / close | **仅 `--spec`** | `--task` 时 SPEC 链路可能已结束;close 查 SPEC 无审计价值 |
|
|
155
|
+
| 命名 | 只认推荐 / 兼容 ACCEPT | **推荐 + 两兼容** | 20-spec-audit 已写双惯例;工作区有 ACCEPT 实档 |
|
|
156
|
+
| reviews 根 | 仅 SPEC 所在仓 / 可 workspace-root | **支持 `--workspace-root`(或 `--target` 指 reviews 仓)** | 产品 SPEC × 工作区 reviews 是本仓常态 |
|
|
157
|
+
| 豁免旗 | 复用 `--allow-no-review` / 独立 | **独立 `--allow-no-spec-review`** | 与 task 审查豁免分迹 |
|
|
158
|
+
| 版本 | 2.7.x patch / 2.8.0 | **2.8.0** | 新 blocking 模式 · minor |
|
|
159
|
+
|
|
160
|
+
### R3 · 边界 / 失败语义 / 安全
|
|
161
|
+
|
|
162
|
+
- **存量**:5 份已对话签收 SPEC 不自动被扫;仅在有人执行 `verify --spec` 时暴露 → 豁免或补 20-spec-audit。
|
|
163
|
+
- **误报**:形式宽容止于「slug 匹配 + 兼容模式名」;不接受任意含 spec 字样的文件。
|
|
164
|
+
- **安全**:只读检查;不写盘、不代签人闸。
|
|
165
|
+
- **兼容**:无 `--spec` 的旧调用链行为不变。
|
|
166
|
+
|
|
167
|
+
### R4 · 验收 / 可测性 / test_strategy
|
|
168
|
+
|
|
169
|
+
`test_strategy: required`。fixture:三命名 · 互斥参数 · block/pass/allow · bugfix 跳过;回归 verify-task / lifecycle show。实现波 dogfood:本 SPEC 的 20-spec-audit 文 + `verify --spec` PASS。
|
|
170
|
+
|
|
171
|
+
### R5 · SPEC 签收就绪 · 是否可交 00 出 task
|
|
172
|
+
|
|
173
|
+
自足:挂点、命名、豁免、lifecycle 登记、版本均已裁定。**可交 00**:建议单 task `cyning-harness-spec-reviews-retention-gate`。图谱无需 bootstrap。
|
|
174
|
+
|
|
175
|
+
### 思考轮控制
|
|
176
|
+
|
|
177
|
+
| 字段 | 值 |
|
|
178
|
+
|------|-----|
|
|
179
|
+
| `actual_last_round` | `R5` |
|
|
180
|
+
| `early_stop` | `no` |
|
|
181
|
+
| `early_stop_reason` | — |
|
|
182
|
+
| `residual_risks` | ① 对话签收文化可能继续依赖豁免旗——须文档把「00 前 verify --spec」写成默认;② SPEC 路径与 reviews 仓分裂时 Agent 漏传 workspace-root → 误 BLOCKED;③ 是否在 00 帽 Prompt 增加「开工前 GATE_VERIFY_SPEC」句,属文档同步,实现时对照 10-spec/00 模板 |
|
|
183
|
+
| `round_extension_note` | — |
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 修订记录
|
|
188
|
+
|
|
189
|
+
| 日期 | 摘要 |
|
|
190
|
+
|------|------|
|
|
191
|
+
| 2026-07-25 | 10-spec R0–R5(维护者点菜「继续 N3」)· dogfood 5/5 无 spec 审查文 |
|
|
192
|
+
| 2026-07-25 | 维护者签收(对话「签收」)· → 00 起草 task |
|
|
@@ -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<n> 审查文存在」挂在 **`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<n> 审查文 | **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<n> 文 → 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 |
|
package/harness/lifecycle.yaml
CHANGED
|
@@ -8,7 +8,9 @@ version: "1"
|
|
|
8
8
|
|
|
9
9
|
states:
|
|
10
10
|
- id: draft
|
|
11
|
-
note: "含 pending / active 等词表别名 · 见 KNOWN_STATUS_TOKENS"
|
|
11
|
+
note: "含 pending / active 等词表别名 · 见 KNOWN_STATUS_TOKENS;SPEC 侧亦用 draft"
|
|
12
|
+
- id: signed
|
|
13
|
+
note: "SPEC 人签 HG-SPEC-SIGNOFF 后 · 可起草 task"
|
|
12
14
|
- id: in_progress
|
|
13
15
|
- id: done
|
|
14
16
|
note: "close 可接受 completed 别名"
|
|
@@ -16,11 +18,23 @@ states:
|
|
|
16
18
|
note: "task close 归档后 · 物理上在 done/ 目录"
|
|
17
19
|
|
|
18
20
|
transitions:
|
|
21
|
+
- id: to_00
|
|
22
|
+
from: ["draft", "signed"]
|
|
23
|
+
to: signed
|
|
24
|
+
hat: "00"
|
|
25
|
+
description: "SPEC 签收后起草 task(verify --spec 聚合 · v2.8+)"
|
|
26
|
+
guards:
|
|
27
|
+
- id: spec_reviews_retention
|
|
28
|
+
command_or_check: "verify --spec · findSpecReview(v2.8+)"
|
|
29
|
+
severity: block
|
|
30
|
+
allow_flag: "--allow-no-spec-review"
|
|
31
|
+
note: "bugfix / skip_spec_audit 元信息豁免 · 不挂 verify --task"
|
|
32
|
+
|
|
19
33
|
- id: to_30
|
|
20
34
|
from: ["draft", "in_progress"]
|
|
21
35
|
to: in_progress
|
|
22
36
|
hat: "30"
|
|
23
|
-
description: "执行帽开工(verify --task
|
|
37
|
+
description: "执行帽开工(verify --task 与全量模式聚合 · v2.9+)"
|
|
24
38
|
guards:
|
|
25
39
|
- id: HG-AUDIT-R1
|
|
26
40
|
command_or_check: "gate-check · human_gate HG-AUDIT-R1=approved"
|
|
@@ -32,9 +46,10 @@ transitions:
|
|
|
32
46
|
command_or_check: "verify/audit · test_strategy vs 测试文件存在"
|
|
33
47
|
severity: block
|
|
34
48
|
- id: reviews_retention
|
|
35
|
-
command_or_check: "verify · findReview R<n> 存在性(v2.5+)"
|
|
49
|
+
command_or_check: "verify --task 与全量 · findReview R<n> 存在性(v2.5+ · 全量 v2.9+)"
|
|
36
50
|
severity: block
|
|
37
51
|
allow_flag: "--allow-no-review"
|
|
52
|
+
note: "全量模式扫描双路径 active;不跑 lint / D5"
|
|
38
53
|
- id: task_lint
|
|
39
54
|
command_or_check: "verify --task · lintTaskFile(v2.7+)"
|
|
40
55
|
severity: warn
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
- 判定:`pass` | `conditional_pass` | `fail` · 建议 **HG-SPEC-SIGNOFF**(**不**代签)
|
|
18
18
|
- **轻量路径**:10-spec 思考轮已充分时,单轮 R1 即可
|
|
19
19
|
- 通过 → 建议 00 起草 task;**不**附 30 Prompt
|
|
20
|
+
- **v2.8+ 机械闸**:00 前跑 `npx @cyning/harness verify --spec <SPEC路径> [--workspace-root <Projects>]`——机器只查审查文**存在性**;缺失 exit 2。豁免:`--allow-no-spec-review` · bugfix / `skip_spec_audit`
|
|
20
21
|
|
|
21
22
|
## 禁止什么
|
|
22
23
|
|
package/lib/cli.js
CHANGED
|
@@ -41,7 +41,7 @@ function usage(version = 'unknown') {
|
|
|
41
41
|
npx @cyning/harness check [--target PATH]
|
|
42
42
|
npx @cyning/harness audit [--target PATH] [--task FILE]
|
|
43
43
|
npx @cyning/harness gate-check [--target PATH] [--task FILE] [--graph] [--json]
|
|
44
|
-
npx @cyning/harness verify [--target PATH] [--task FILE] [--graph] [--json] [--agent-hint] [--workspace-root PATH]
|
|
44
|
+
npx @cyning/harness verify [--target PATH] [--task FILE | --spec FILE] [--graph] [--json] [--agent-hint] [--workspace-root PATH]
|
|
45
45
|
npx @cyning/harness lifecycle show [--json]
|
|
46
46
|
npx @cyning/harness sync index [--target PATH]
|
|
47
47
|
npx @cyning/harness task check --file PATH [--no-circular] [--registry DIR]...
|
|
@@ -62,7 +62,7 @@ function usage(version = 'unknown') {
|
|
|
62
62
|
check 读 manifest.json · 对比当前包版本是否有可升级版本
|
|
63
63
|
audit ICVO 机械审计:复用 gate-check · D5 测试声明检查 · S5 git-clean 提醒
|
|
64
64
|
gate-check 调用 wizard/gate-check.sh(业务仓无需 clone 即可用)
|
|
65
|
-
verify 30
|
|
65
|
+
verify 30 前聚合(--task)或 SPEC→00 审查文闸(--spec · v2.8+)
|
|
66
66
|
lifecycle 只读展示 harness/lifecycle.yaml(状态/转移/守卫 · v2.7+)
|
|
67
67
|
sync index 生成 .cyning-harness/invoke_index.json
|
|
68
68
|
task check 校验 task.harness.v1.json sidecar · --no-circular 检测 depends_on 环
|
|
@@ -390,16 +390,22 @@ async function cmdGateCheck(args) {
|
|
|
390
390
|
|
|
391
391
|
async function cmdVerify(args) {
|
|
392
392
|
// D3 · implemented in lib/verify.js
|
|
393
|
-
const {
|
|
393
|
+
const {
|
|
394
|
+
verifyTarget,
|
|
395
|
+
verifySpecTarget,
|
|
396
|
+
buildVerifyHandoff,
|
|
397
|
+
buildSpecVerifyHandoff,
|
|
398
|
+
formatAgentHint,
|
|
399
|
+
} = await import('./verify.js');
|
|
394
400
|
if (args.includes('--help') || args.includes('-h')) {
|
|
395
|
-
console.log(`用法: npx @cyning/harness verify [--target PATH] [--task FILE] [--graph] [--json] [--agent-hint] [--workspace-root PATH] [--allow-no-review] [--allow-lint-fail]
|
|
396
|
-
|
|
397
|
-
无 --task
|
|
398
|
-
|
|
399
|
-
--task
|
|
400
|
-
--
|
|
401
|
-
--
|
|
402
|
-
--
|
|
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
|
+
|
|
403
|
+
无 --task/--spec:扫描 docs/tasks/active ∪ docs/harness/tasks/active(同 basename 优先 Starter);
|
|
404
|
+
闸表 + 全量 reviews 存在性(v2.9+)· 不跑 task lint / D5 · --allow-no-review。
|
|
405
|
+
--task:30 前聚合(gate + audit + reviews + lint WARN)· --allow-no-review / --allow-lint-fail。
|
|
406
|
+
--spec:SPEC→00 前查审查文存在性(v2.8+ · 与 --task 互斥)· --allow-no-spec-review。
|
|
407
|
+
推荐:--spec <产品仓 SPEC> --workspace-root <含 docs/harness/reviews 的工作区根>
|
|
408
|
+
--json / --agent-hint:Agent handoff。
|
|
403
409
|
`);
|
|
404
410
|
return;
|
|
405
411
|
}
|
|
@@ -409,20 +415,24 @@ async function cmdVerify(args) {
|
|
|
409
415
|
rest = r1;
|
|
410
416
|
const { value: taskFile, rest: r2 } = takeOption(rest, '--task');
|
|
411
417
|
rest = r2;
|
|
412
|
-
const { value:
|
|
418
|
+
const { value: specFile, rest: r3 } = takeOption(rest, '--spec');
|
|
413
419
|
rest = r3;
|
|
420
|
+
const { value: workspaceRoot, rest: r4 } = takeOption(rest, '--workspace-root');
|
|
421
|
+
rest = r4;
|
|
414
422
|
const graph = rest.includes('--graph');
|
|
415
423
|
const json = rest.includes('--json');
|
|
416
424
|
const agentHint = rest.includes('--agent-hint');
|
|
417
425
|
const allowNoReview = rest.includes('--allow-no-review');
|
|
418
426
|
const allowLintFail = rest.includes('--allow-lint-fail');
|
|
427
|
+
const allowNoSpecReview = rest.includes('--allow-no-spec-review');
|
|
419
428
|
rest = rest.filter(
|
|
420
429
|
(a) =>
|
|
421
430
|
a !== '--graph' &&
|
|
422
431
|
a !== '--json' &&
|
|
423
432
|
a !== '--agent-hint' &&
|
|
424
433
|
a !== '--allow-no-review' &&
|
|
425
|
-
a !== '--allow-lint-fail'
|
|
434
|
+
a !== '--allow-lint-fail' &&
|
|
435
|
+
a !== '--allow-no-spec-review',
|
|
426
436
|
);
|
|
427
437
|
|
|
428
438
|
if (rest.length > 0) {
|
|
@@ -431,8 +441,37 @@ async function cmdVerify(args) {
|
|
|
431
441
|
throw err;
|
|
432
442
|
}
|
|
433
443
|
|
|
444
|
+
if (taskFile && specFile) {
|
|
445
|
+
const err = new Error('verify:--task 与 --spec 互斥');
|
|
446
|
+
err.exitCode = 1;
|
|
447
|
+
throw err;
|
|
448
|
+
}
|
|
449
|
+
|
|
434
450
|
const target = resolveTarget(process.cwd(), targetArg);
|
|
435
|
-
const
|
|
451
|
+
const ws = workspaceRoot ? path.resolve(workspaceRoot) : undefined;
|
|
452
|
+
|
|
453
|
+
let result;
|
|
454
|
+
let handoff;
|
|
455
|
+
let label;
|
|
456
|
+
|
|
457
|
+
if (specFile) {
|
|
458
|
+
result = verifySpecTarget(target, { specFile, workspaceRoot: ws, allowNoSpecReview });
|
|
459
|
+
handoff = buildSpecVerifyHandoff(target, {
|
|
460
|
+
specFile,
|
|
461
|
+
workspaceRoot: ws,
|
|
462
|
+
allowNoSpecReview,
|
|
463
|
+
});
|
|
464
|
+
label = path.basename(specFile);
|
|
465
|
+
} else {
|
|
466
|
+
result = verifyTarget(target, { taskFile, graph, allowNoReview, allowLintFail });
|
|
467
|
+
handoff = buildVerifyHandoff(target, {
|
|
468
|
+
taskFile,
|
|
469
|
+
workspaceRoot: ws,
|
|
470
|
+
allowNoReview,
|
|
471
|
+
allowLintFail,
|
|
472
|
+
});
|
|
473
|
+
label = taskFile ? path.basename(taskFile) : null;
|
|
474
|
+
}
|
|
436
475
|
|
|
437
476
|
// forward gate-check/audit/graph stdout if any(--json 时仍透传机械闸表)
|
|
438
477
|
if (result.stdout && !json) {
|
|
@@ -442,13 +481,6 @@ async function cmdVerify(args) {
|
|
|
442
481
|
process.stderr.write(result.stdout);
|
|
443
482
|
}
|
|
444
483
|
|
|
445
|
-
const handoff = buildVerifyHandoff(target, {
|
|
446
|
-
taskFile,
|
|
447
|
-
workspaceRoot: workspaceRoot ? path.resolve(workspaceRoot) : undefined,
|
|
448
|
-
allowNoReview,
|
|
449
|
-
allowLintFail,
|
|
450
|
-
});
|
|
451
|
-
|
|
452
484
|
if (json) {
|
|
453
485
|
console.log(JSON.stringify(handoff, null, 2));
|
|
454
486
|
}
|
|
@@ -456,15 +488,13 @@ async function cmdVerify(args) {
|
|
|
456
488
|
console.log(formatAgentHint(handoff));
|
|
457
489
|
}
|
|
458
490
|
|
|
491
|
+
const summary = label
|
|
492
|
+
? `VERIFY: ${result.ok ? 'PASS' : 'BLOCKED'}${result.reason ? ` · ${result.reason}` : ''} · ${label}`
|
|
493
|
+
: `VERIFY: ${result.ok ? 'PASS' : 'BLOCKED'}${result.reason ? ` · ${result.reason}` : ''}`;
|
|
494
|
+
|
|
459
495
|
if (!json && !agentHint) {
|
|
460
|
-
const summary = taskFile
|
|
461
|
-
? `VERIFY: ${result.ok ? 'PASS' : 'BLOCKED'}${result.reason ? ` · ${result.reason}` : ''} · ${path.basename(taskFile)}`
|
|
462
|
-
: `VERIFY: ${result.ok ? 'PASS' : 'BLOCKED'}${result.reason ? ` · ${result.reason}` : ''}`;
|
|
463
496
|
console.log(summary);
|
|
464
497
|
} else if (json || agentHint) {
|
|
465
|
-
const summary = taskFile
|
|
466
|
-
? `VERIFY: ${result.ok ? 'PASS' : 'BLOCKED'}${result.reason ? ` · ${result.reason}` : ''} · ${path.basename(taskFile)}`
|
|
467
|
-
: `VERIFY: ${result.ok ? 'PASS' : 'BLOCKED'}${result.reason ? ` · ${result.reason}` : ''}`;
|
|
468
498
|
console.error(summary);
|
|
469
499
|
}
|
|
470
500
|
|
package/lib/task-meta.js
CHANGED
|
@@ -157,6 +157,107 @@ export function findReviewPath(target, taskFile) {
|
|
|
157
157
|
return findReview(target, taskFile).latest;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
/** 剥离文件名末尾 `_v\d+` */
|
|
161
|
+
function stripVerSuffix(s) {
|
|
162
|
+
return String(s).replace(/_v\d+$/, '');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 自 SPEC 路径/正文解析 slug(优先表 `spec_slug`)。
|
|
167
|
+
*/
|
|
168
|
+
export function extractSpecSlug(specFile, content) {
|
|
169
|
+
const meta = content ? parseHarnessMeta(content) : {};
|
|
170
|
+
if (meta.spec_slug) return meta.spec_slug;
|
|
171
|
+
|
|
172
|
+
let base = path.basename(specFile, '.md');
|
|
173
|
+
base = base.replace(/^SPEC[-_]/i, '');
|
|
174
|
+
base = stripVerSuffix(base);
|
|
175
|
+
return base;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* bugfix / 显式跳过 10-spec 审计:不要求 SPEC 审查文。
|
|
180
|
+
* 只认元信息表 / 文首 track 行——避免命中正文里对规则的说明文字。
|
|
181
|
+
*/
|
|
182
|
+
export function shouldSkipSpecAudit(content) {
|
|
183
|
+
if (!content) return false;
|
|
184
|
+
const meta = parseHarnessMeta(content);
|
|
185
|
+
if (String(meta.skip_spec_audit || '').toLowerCase() === 'true') return true;
|
|
186
|
+
if (String(meta.track || '').toLowerCase() === 'bugfix') return true;
|
|
187
|
+
if (/^>\s*\*\*track\*\*[::]\s*`?bugfix\b/im.test(content)) return true;
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 查找 SPEC 的 R<n> 审查文(verify --spec · N3)。
|
|
193
|
+
* 兼容命名:
|
|
194
|
+
* 1. spec_<slug>_audit_R<n>_*.md(推荐)
|
|
195
|
+
* 2. spec_<slug>_ACCEPT_R<n>_*.md
|
|
196
|
+
* 3. task_<slug>_spec_ACCEPT_R<n>_*.md
|
|
197
|
+
* 在 target 与可选 workspaceRoot 的 docs/harness/reviews/ 下搜索。
|
|
198
|
+
*/
|
|
199
|
+
export function findSpecReview(specFile, options = {}) {
|
|
200
|
+
const { target = process.cwd(), workspaceRoot } = options;
|
|
201
|
+
const empty = { found: false, latest: null, rounds: [], matched_pattern: null };
|
|
202
|
+
|
|
203
|
+
const absSpec = path.isAbsolute(specFile)
|
|
204
|
+
? specFile
|
|
205
|
+
: path.resolve(target, specFile);
|
|
206
|
+
if (!fs.existsSync(absSpec)) return empty;
|
|
207
|
+
|
|
208
|
+
const content = fs.readFileSync(absSpec, 'utf8');
|
|
209
|
+
const slugNorm = normalizeSlug(stripVerSuffix(extractSpecSlug(absSpec, content)));
|
|
210
|
+
|
|
211
|
+
const roots = [];
|
|
212
|
+
const pushRoot = (root) => {
|
|
213
|
+
if (!root) return;
|
|
214
|
+
const abs = path.resolve(root);
|
|
215
|
+
if (!roots.includes(abs)) roots.push(abs);
|
|
216
|
+
};
|
|
217
|
+
pushRoot(target);
|
|
218
|
+
pushRoot(workspaceRoot);
|
|
219
|
+
|
|
220
|
+
const PATTERNS = [
|
|
221
|
+
{ id: 'spec_audit', re: /^spec_(.+?)_audit_R(\d+)_.*\.md$/i },
|
|
222
|
+
{ id: 'spec_ACCEPT', re: /^spec_(.+?)_ACCEPT_R(\d+)_.*\.md$/i },
|
|
223
|
+
{ id: 'task_spec_ACCEPT', re: /^task_(.+?)_spec_ACCEPT_R(\d+)_.*\.md$/i },
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
const matches = [];
|
|
227
|
+
for (const root of roots) {
|
|
228
|
+
const reviewsDir = path.join(root, 'docs/harness/reviews');
|
|
229
|
+
if (!fs.existsSync(reviewsDir)) continue;
|
|
230
|
+
for (const name of fs.readdirSync(reviewsDir)) {
|
|
231
|
+
for (const pat of PATTERNS) {
|
|
232
|
+
const m = name.match(pat.re);
|
|
233
|
+
if (!m) continue;
|
|
234
|
+
if (normalizeSlug(stripVerSuffix(m[1])) !== slugNorm) continue;
|
|
235
|
+
matches.push({
|
|
236
|
+
name,
|
|
237
|
+
round: parseInt(m[2], 10),
|
|
238
|
+
mtime: fs.statSync(path.join(reviewsDir, name)).mtimeMs,
|
|
239
|
+
pattern: pat.id,
|
|
240
|
+
root,
|
|
241
|
+
rel: path.join('docs/harness/reviews', name).replace(/\\/g, '/'),
|
|
242
|
+
});
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (matches.length === 0) return empty;
|
|
249
|
+
|
|
250
|
+
matches.sort((a, b) => b.round - a.round || b.mtime - a.mtime);
|
|
251
|
+
const latest = matches[0];
|
|
252
|
+
return {
|
|
253
|
+
found: true,
|
|
254
|
+
latest: latest.rel,
|
|
255
|
+
rounds: [...new Set(matches.map((x) => x.round))].sort((a, b) => a - b),
|
|
256
|
+
matched_pattern: latest.pattern,
|
|
257
|
+
reviews_root: latest.root,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
160
261
|
/**
|
|
161
262
|
* 聚合单 task 的 Agent handoff 结果。
|
|
162
263
|
*/
|
|
@@ -209,17 +310,25 @@ export function buildTaskHandoff(target, taskFile, options = {}) {
|
|
|
209
310
|
}
|
|
210
311
|
|
|
211
312
|
/**
|
|
212
|
-
* 扫描 target 下 active task
|
|
313
|
+
* 扫描 target 下 active task 列表(N4 · v2.9+)。
|
|
314
|
+
* 双路径:docs/tasks/active ∪ docs/harness/tasks/active;
|
|
315
|
+
* 同 basename 优先 Starter;排序稳定。
|
|
213
316
|
*/
|
|
214
317
|
export function listActiveTasks(target) {
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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();
|
|
223
332
|
}
|
|
224
333
|
|
|
225
334
|
function escapeRegExp(s) {
|
package/lib/verify.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { resolveHarnessRootForTarget, wizardScript } from './paths.js';
|
|
4
5
|
import { auditTarget } from './audit.js';
|
|
5
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
buildTaskHandoff,
|
|
8
|
+
extractSpecSlug,
|
|
9
|
+
findReview,
|
|
10
|
+
findSpecReview,
|
|
11
|
+
listActiveTasks,
|
|
12
|
+
shouldSkipSpecAudit,
|
|
13
|
+
} from './task-meta.js';
|
|
6
14
|
import { lintTaskFile } from './task-lint.js';
|
|
7
15
|
|
|
8
16
|
/**
|
|
9
|
-
* 30 前聚合验证:gate-check + audit D5(仅 --task)+ reviews
|
|
10
|
-
|
|
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
|
|
11
20
|
*/
|
|
12
21
|
export function verifyTarget(target, options = {}) {
|
|
13
22
|
const { taskFile, graph, allowNoReview = false, allowLintFail = false } = options;
|
|
@@ -61,6 +70,26 @@ export function verifyTarget(target, options = {}) {
|
|
|
61
70
|
gateResult.stdout += `WARN: task lint FAIL · ${rules || 'errors'}(不挡 may_start_30 · 修结构或 --allow-lint-fail)\n`;
|
|
62
71
|
}
|
|
63
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
|
+
}
|
|
64
93
|
}
|
|
65
94
|
|
|
66
95
|
// 4. S5 git-clean:warn 不挡
|
|
@@ -89,6 +118,96 @@ function runTaskLintForVerify(target, taskFile) {
|
|
|
89
118
|
}
|
|
90
119
|
}
|
|
91
120
|
|
|
121
|
+
/**
|
|
122
|
+
* SPEC→00 前验证(N3 · v2.8+):仅查 SPEC 审查文存在性。
|
|
123
|
+
* 不跑 gate-check / audit D5 / task lint(与 --task 模式分离)。
|
|
124
|
+
*/
|
|
125
|
+
export function verifySpecTarget(target, options = {}) {
|
|
126
|
+
const { specFile, workspaceRoot, allowNoSpecReview = false } = options;
|
|
127
|
+
let stdout = '';
|
|
128
|
+
|
|
129
|
+
if (!specFile) {
|
|
130
|
+
return {
|
|
131
|
+
ok: false,
|
|
132
|
+
exitCode: 1,
|
|
133
|
+
reason: 'verify --spec 须指定 SPEC 文件路径',
|
|
134
|
+
stdout,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const absSpec = path.isAbsolute(specFile)
|
|
139
|
+
? specFile
|
|
140
|
+
: path.resolve(target, specFile);
|
|
141
|
+
if (!fs.existsSync(absSpec)) {
|
|
142
|
+
return {
|
|
143
|
+
ok: false,
|
|
144
|
+
exitCode: 1,
|
|
145
|
+
reason: `SPEC 文件不存在: ${specFile}`,
|
|
146
|
+
stdout,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const content = fs.readFileSync(absSpec, 'utf8');
|
|
151
|
+
if (shouldSkipSpecAudit(content)) {
|
|
152
|
+
stdout += `INFO: skip SPEC review gate(bugfix / skip_spec_audit)\n`;
|
|
153
|
+
return { ok: true, exitCode: 0, stdout, skipped: true };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const review = findSpecReview(absSpec, { target, workspaceRoot });
|
|
157
|
+
if (!review.found) {
|
|
158
|
+
if (allowNoSpecReview) {
|
|
159
|
+
stdout += `WARN: missing SPEC R<n> review(--allow-no-spec-review 豁免 · 留痕)\n`;
|
|
160
|
+
return { ok: true, exitCode: 0, stdout, skipped: false };
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
ok: false,
|
|
164
|
+
exitCode: 2,
|
|
165
|
+
reason:
|
|
166
|
+
'missing SPEC R<n> review(docs/harness/reviews/ 无 spec_*_audit_R / *_ACCEPT_R · 20-spec-audit 或 --allow-no-spec-review)',
|
|
167
|
+
stdout,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return { ok: true, exitCode: 0, stdout, skipped: false };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** SPEC 模式 handoff(verify --spec --json) */
|
|
175
|
+
export function buildSpecVerifyHandoff(target, options = {}) {
|
|
176
|
+
const { specFile, workspaceRoot, allowNoSpecReview = false } = options;
|
|
177
|
+
const absSpec = path.isAbsolute(specFile)
|
|
178
|
+
? specFile
|
|
179
|
+
: path.resolve(target, specFile);
|
|
180
|
+
const content = fs.existsSync(absSpec) ? fs.readFileSync(absSpec, 'utf8') : '';
|
|
181
|
+
const skipped = shouldSkipSpecAudit(content);
|
|
182
|
+
const review = skipped
|
|
183
|
+
? { found: false, latest: null, rounds: [], matched_pattern: null }
|
|
184
|
+
: findSpecReview(absSpec, { target, workspaceRoot });
|
|
185
|
+
|
|
186
|
+
const reviewOk = skipped || review.found || allowNoSpecReview;
|
|
187
|
+
let blocked_reason = null;
|
|
188
|
+
if (!reviewOk) {
|
|
189
|
+
blocked_reason = 'missing SPEC R<n> review';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
schema_version: '1',
|
|
194
|
+
mode: 'spec',
|
|
195
|
+
verify_ok: reviewOk,
|
|
196
|
+
may_start_00: reviewOk,
|
|
197
|
+
spec: path.basename(specFile),
|
|
198
|
+
spec_path: specFile.replace(/\\/g, '/'),
|
|
199
|
+
spec_slug: content ? extractSpecSlug(absSpec, content) : null,
|
|
200
|
+
spec_review_found: Boolean(review.found),
|
|
201
|
+
spec_review_latest: review.latest,
|
|
202
|
+
spec_review_rounds: review.rounds ?? [],
|
|
203
|
+
spec_review_skipped: skipped,
|
|
204
|
+
spec_review_suppressed: Boolean(allowNoSpecReview && !review.found && !skipped),
|
|
205
|
+
matched_pattern: review.matched_pattern ?? null,
|
|
206
|
+
blocked_reason,
|
|
207
|
+
next_hat: reviewOk ? '00' : null,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
92
211
|
/** 规范化 lint 结果供 handoff */
|
|
93
212
|
export function lintSnapshotForHandoff(target, taskFile, { allowLintFail = false } = {}) {
|
|
94
213
|
if (!taskFile) return null;
|
|
@@ -237,14 +356,19 @@ export function buildVerifyHandoff(target, options = {}) {
|
|
|
237
356
|
const taskFiles = listActiveTasks(target);
|
|
238
357
|
const tasks = taskFiles.map((tf) => {
|
|
239
358
|
const handoff = buildTaskHandoff(target, tf, handoffOpts);
|
|
359
|
+
const reviewOk = handoff.review_found || allowNoReview;
|
|
360
|
+
const verifyOk = handoff.may_start_30 && reviewOk;
|
|
240
361
|
return {
|
|
241
362
|
schema_version: '1',
|
|
242
|
-
verify_ok:
|
|
363
|
+
verify_ok: verifyOk,
|
|
243
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,
|
|
244
368
|
};
|
|
245
369
|
});
|
|
246
370
|
|
|
247
|
-
const verifyOk = tasks.length > 0 && tasks.every((t) => t.
|
|
371
|
+
const verifyOk = tasks.length > 0 && tasks.every((t) => t.verify_ok);
|
|
248
372
|
return {
|
|
249
373
|
schema_version: '1',
|
|
250
374
|
verify_ok: verifyOk,
|
|
@@ -258,6 +382,16 @@ export function buildVerifyHandoff(target, options = {}) {
|
|
|
258
382
|
export function formatAgentHint(payload) {
|
|
259
383
|
const lines = ['=== Harness verify · agent-hint ==='];
|
|
260
384
|
|
|
385
|
+
if (payload.mode === 'spec') {
|
|
386
|
+
lines.push(`spec: ${payload.spec}`);
|
|
387
|
+
lines.push(` may_start_00: ${payload.may_start_00}`);
|
|
388
|
+
if (payload.blocked_reason) lines.push(` blocked: ${payload.blocked_reason}`);
|
|
389
|
+
if (payload.spec_review_latest) lines.push(` review: ${payload.spec_review_latest}`);
|
|
390
|
+
if (payload.spec_review_skipped) lines.push(` review: skipped`);
|
|
391
|
+
if (payload.next_hat) lines.push(` next_hat: ${payload.next_hat}`);
|
|
392
|
+
return lines.join('\n').trimEnd();
|
|
393
|
+
}
|
|
394
|
+
|
|
261
395
|
const items = payload.tasks ?? [payload];
|
|
262
396
|
for (const item of items) {
|
|
263
397
|
lines.push(`task: ${item.task}`);
|
package/package.json
CHANGED
package/wizard/gate-check.sh
CHANGED
|
@@ -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
|
-
|
|
195
|
-
if [[
|
|
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 "
|
|
222
|
+
echo "无 active task_*.md(docs/tasks/active ∪ docs/harness/tasks/active)"
|
|
200
223
|
fi
|
|
201
224
|
exit 0
|
|
202
225
|
fi
|
|
203
|
-
|
|
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
|
-
|
|
238
|
-
[[ -
|
|
239
|
-
|
|
240
|
-
|
|
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 "$
|
|
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
|