@cyning/harness 2.7.0 → 2.8.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/ONBOARDING.md +5 -0
- package/docs/USER_GUIDE_v1.0_zh.md +6 -1
- package/docs/spec/SPEC-spec-reviews-retention-gate_v1.md +192 -0
- package/harness/lifecycle.yaml +15 -1
- package/harness/prompts/20-spec-audit.md +1 -0
- package/lib/cli.js +56 -27
- package/lib/task-meta.js +101 -0
- package/lib/verify.js +109 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.8.0] - 2026-07-25
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **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。
|
|
12
|
+
- 豁免:`--allow-no-spec-review`;元信息 `track: bugfix` / `skip_spec_audit: true`。
|
|
13
|
+
- `findSpecReview`(支持 `--workspace-root` 分仓);handoff:`may_start_00` · `spec_review_found` · `spec_review_latest`。
|
|
14
|
+
- `lifecycle.yaml` 转移 `to_00` + guard `spec_reviews_retention`(只登记)。
|
|
15
|
+
|
|
16
|
+
### Notes
|
|
17
|
+
|
|
18
|
+
- 与 `--task` 互斥;**不**改 task 侧 verify/close。
|
|
19
|
+
- SPEC:`docs/spec/SPEC-spec-reviews-retention-gate_v1.md`
|
|
20
|
+
- minor · 行为新增(新模式)
|
|
21
|
+
|
|
7
22
|
## [2.7.0] - 2026-07-24
|
|
8
23
|
|
|
9
24
|
### Added
|
|
@@ -17,6 +32,7 @@
|
|
|
17
32
|
- 背景:Post-G4 方案 N1+N2 · rethink 方向二骨架;dogfood active lint FAIL 率高故本波不做 block。
|
|
18
33
|
- SPEC:`docs/spec/SPEC-lifecycle-and-verify-lint_v1.md`
|
|
19
34
|
- minor · `npm test` 含 lifecycle + verify-lint-warn
|
|
35
|
+
- **已发布**:`@cyning/harness@2.7.0`(npm `latest` · 2026-07-24)· tag `v2.7.0`
|
|
20
36
|
|
|
21
37
|
## [2.6.0] - 2026-07-24
|
|
22
38
|
|
package/docs/ONBOARDING.md
CHANGED
|
@@ -52,6 +52,11 @@ npx @cyning/harness verify --target /path/to/your-repo \
|
|
|
52
52
|
# --task 另跑 task lint(v2.7+ · E 级仅 WARN,不挡 may_start_30)
|
|
53
53
|
# 抑制 lint WARN:加 --allow-lint-fail
|
|
54
54
|
|
|
55
|
+
# SPEC→00 前:审查文存在性(v2.8+ · 与 --task 互斥)
|
|
56
|
+
npx @cyning/harness verify --spec docs/spec/SPEC-xxx_v1.md \
|
|
57
|
+
--workspace-root /path/to/Projects
|
|
58
|
+
# 豁免:--allow-no-spec-review · 或 SPEC track=bugfix / skip_spec_audit
|
|
59
|
+
|
|
55
60
|
# 只读生命周期登记(方向二 · 非引擎)
|
|
56
61
|
npx @cyning/harness lifecycle show
|
|
57
62
|
npx @cyning/harness lifecycle show --json
|
|
@@ -115,6 +115,11 @@ npx @cyning/harness@1.0.1 init --preset harness-only --ide cursor,agents --yes
|
|
|
115
115
|
npx @cyning/harness verify --target . --task docs/tasks/active/task_xxx.md
|
|
116
116
|
# lint FAIL 仅 WARN(v2.7+ · 不挡 30);抑制:--allow-lint-fail
|
|
117
117
|
|
|
118
|
+
# SPEC→00:审查文存在性(v2.8+ · 与 --task 互斥)
|
|
119
|
+
npx @cyning/harness verify --spec docs/spec/SPEC-xxx_v1.md \
|
|
120
|
+
--workspace-root /path/to/Projects
|
|
121
|
+
# 豁免:--allow-no-spec-review
|
|
122
|
+
|
|
118
123
|
# Agent handoff(v2.0.2+):JSON 路由 + 下一帽提示
|
|
119
124
|
npx @cyning/harness verify --target . \
|
|
120
125
|
--task docs/tasks/active/task_xxx.md \
|
|
@@ -172,7 +177,7 @@ Schema:[`schema/verify_result.v1.schema.json`](../schema/verify_result.v1.sche
|
|
|
172
177
|
| `npx @cyning/harness init` | 首次安装模板与 manifest(可选 `--with-scripts`) |
|
|
173
178
|
| `npx @cyning/harness upgrade` | 同步产品包更新(可加 `--gate-check` 先 audit) |
|
|
174
179
|
| `npx @cyning/harness check` | 检查是否有新版本 |
|
|
175
|
-
| `npx @cyning/harness verify` | 30
|
|
180
|
+
| `npx @cyning/harness verify` | `--task`:30 前聚合;`--spec`:SPEC→00 审查文闸(v2.8+ · 互斥)· `--allow-no-spec-review` |
|
|
176
181
|
| `npx @cyning/harness lifecycle show` | 只读展示 `harness/lifecycle.yaml`(状态/转移/守卫 · v2.7+ · 非引擎) |
|
|
177
182
|
| `npx @cyning/harness gate-check` | 仅人工闸(`--graph` / `--json`) |
|
|
178
183
|
| `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 |
|
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,6 +18,18 @@ 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
|
|
@@ -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,21 @@ 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
|
-
--
|
|
400
|
-
|
|
401
|
-
--agent-hint
|
|
402
|
-
--workspace-root:解析 task 中 Projects/ 前缀的 entry_invoke 路径。
|
|
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/task_*.md 并逐项列出闸表(同 gate-check);不跑 task lint。
|
|
404
|
+
--task:30 前聚合(gate + audit + reviews + lint WARN)· --allow-no-review / --allow-lint-fail。
|
|
405
|
+
--spec:SPEC→00 前查审查文存在性(v2.8+ · 与 --task 互斥)· --allow-no-spec-review。
|
|
406
|
+
推荐:--spec <产品仓 SPEC> --workspace-root <含 docs/harness/reviews 的工作区根>
|
|
407
|
+
--json / --agent-hint:Agent handoff。
|
|
403
408
|
`);
|
|
404
409
|
return;
|
|
405
410
|
}
|
|
@@ -409,20 +414,24 @@ async function cmdVerify(args) {
|
|
|
409
414
|
rest = r1;
|
|
410
415
|
const { value: taskFile, rest: r2 } = takeOption(rest, '--task');
|
|
411
416
|
rest = r2;
|
|
412
|
-
const { value:
|
|
417
|
+
const { value: specFile, rest: r3 } = takeOption(rest, '--spec');
|
|
413
418
|
rest = r3;
|
|
419
|
+
const { value: workspaceRoot, rest: r4 } = takeOption(rest, '--workspace-root');
|
|
420
|
+
rest = r4;
|
|
414
421
|
const graph = rest.includes('--graph');
|
|
415
422
|
const json = rest.includes('--json');
|
|
416
423
|
const agentHint = rest.includes('--agent-hint');
|
|
417
424
|
const allowNoReview = rest.includes('--allow-no-review');
|
|
418
425
|
const allowLintFail = rest.includes('--allow-lint-fail');
|
|
426
|
+
const allowNoSpecReview = rest.includes('--allow-no-spec-review');
|
|
419
427
|
rest = rest.filter(
|
|
420
428
|
(a) =>
|
|
421
429
|
a !== '--graph' &&
|
|
422
430
|
a !== '--json' &&
|
|
423
431
|
a !== '--agent-hint' &&
|
|
424
432
|
a !== '--allow-no-review' &&
|
|
425
|
-
a !== '--allow-lint-fail'
|
|
433
|
+
a !== '--allow-lint-fail' &&
|
|
434
|
+
a !== '--allow-no-spec-review',
|
|
426
435
|
);
|
|
427
436
|
|
|
428
437
|
if (rest.length > 0) {
|
|
@@ -431,8 +440,37 @@ async function cmdVerify(args) {
|
|
|
431
440
|
throw err;
|
|
432
441
|
}
|
|
433
442
|
|
|
443
|
+
if (taskFile && specFile) {
|
|
444
|
+
const err = new Error('verify:--task 与 --spec 互斥');
|
|
445
|
+
err.exitCode = 1;
|
|
446
|
+
throw err;
|
|
447
|
+
}
|
|
448
|
+
|
|
434
449
|
const target = resolveTarget(process.cwd(), targetArg);
|
|
435
|
-
const
|
|
450
|
+
const ws = workspaceRoot ? path.resolve(workspaceRoot) : undefined;
|
|
451
|
+
|
|
452
|
+
let result;
|
|
453
|
+
let handoff;
|
|
454
|
+
let label;
|
|
455
|
+
|
|
456
|
+
if (specFile) {
|
|
457
|
+
result = verifySpecTarget(target, { specFile, workspaceRoot: ws, allowNoSpecReview });
|
|
458
|
+
handoff = buildSpecVerifyHandoff(target, {
|
|
459
|
+
specFile,
|
|
460
|
+
workspaceRoot: ws,
|
|
461
|
+
allowNoSpecReview,
|
|
462
|
+
});
|
|
463
|
+
label = path.basename(specFile);
|
|
464
|
+
} else {
|
|
465
|
+
result = verifyTarget(target, { taskFile, graph, allowNoReview, allowLintFail });
|
|
466
|
+
handoff = buildVerifyHandoff(target, {
|
|
467
|
+
taskFile,
|
|
468
|
+
workspaceRoot: ws,
|
|
469
|
+
allowNoReview,
|
|
470
|
+
allowLintFail,
|
|
471
|
+
});
|
|
472
|
+
label = taskFile ? path.basename(taskFile) : null;
|
|
473
|
+
}
|
|
436
474
|
|
|
437
475
|
// forward gate-check/audit/graph stdout if any(--json 时仍透传机械闸表)
|
|
438
476
|
if (result.stdout && !json) {
|
|
@@ -442,13 +480,6 @@ async function cmdVerify(args) {
|
|
|
442
480
|
process.stderr.write(result.stdout);
|
|
443
481
|
}
|
|
444
482
|
|
|
445
|
-
const handoff = buildVerifyHandoff(target, {
|
|
446
|
-
taskFile,
|
|
447
|
-
workspaceRoot: workspaceRoot ? path.resolve(workspaceRoot) : undefined,
|
|
448
|
-
allowNoReview,
|
|
449
|
-
allowLintFail,
|
|
450
|
-
});
|
|
451
|
-
|
|
452
483
|
if (json) {
|
|
453
484
|
console.log(JSON.stringify(handoff, null, 2));
|
|
454
485
|
}
|
|
@@ -456,15 +487,13 @@ async function cmdVerify(args) {
|
|
|
456
487
|
console.log(formatAgentHint(handoff));
|
|
457
488
|
}
|
|
458
489
|
|
|
490
|
+
const summary = label
|
|
491
|
+
? `VERIFY: ${result.ok ? 'PASS' : 'BLOCKED'}${result.reason ? ` · ${result.reason}` : ''} · ${label}`
|
|
492
|
+
: `VERIFY: ${result.ok ? 'PASS' : 'BLOCKED'}${result.reason ? ` · ${result.reason}` : ''}`;
|
|
493
|
+
|
|
459
494
|
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
495
|
console.log(summary);
|
|
464
496
|
} 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
497
|
console.error(summary);
|
|
469
498
|
}
|
|
470
499
|
|
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
|
*/
|
package/lib/verify.js
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
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
|
/**
|
|
@@ -89,6 +97,96 @@ function runTaskLintForVerify(target, taskFile) {
|
|
|
89
97
|
}
|
|
90
98
|
}
|
|
91
99
|
|
|
100
|
+
/**
|
|
101
|
+
* SPEC→00 前验证(N3 · v2.8+):仅查 SPEC 审查文存在性。
|
|
102
|
+
* 不跑 gate-check / audit D5 / task lint(与 --task 模式分离)。
|
|
103
|
+
*/
|
|
104
|
+
export function verifySpecTarget(target, options = {}) {
|
|
105
|
+
const { specFile, workspaceRoot, allowNoSpecReview = false } = options;
|
|
106
|
+
let stdout = '';
|
|
107
|
+
|
|
108
|
+
if (!specFile) {
|
|
109
|
+
return {
|
|
110
|
+
ok: false,
|
|
111
|
+
exitCode: 1,
|
|
112
|
+
reason: 'verify --spec 须指定 SPEC 文件路径',
|
|
113
|
+
stdout,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const absSpec = path.isAbsolute(specFile)
|
|
118
|
+
? specFile
|
|
119
|
+
: path.resolve(target, specFile);
|
|
120
|
+
if (!fs.existsSync(absSpec)) {
|
|
121
|
+
return {
|
|
122
|
+
ok: false,
|
|
123
|
+
exitCode: 1,
|
|
124
|
+
reason: `SPEC 文件不存在: ${specFile}`,
|
|
125
|
+
stdout,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const content = fs.readFileSync(absSpec, 'utf8');
|
|
130
|
+
if (shouldSkipSpecAudit(content)) {
|
|
131
|
+
stdout += `INFO: skip SPEC review gate(bugfix / skip_spec_audit)\n`;
|
|
132
|
+
return { ok: true, exitCode: 0, stdout, skipped: true };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const review = findSpecReview(absSpec, { target, workspaceRoot });
|
|
136
|
+
if (!review.found) {
|
|
137
|
+
if (allowNoSpecReview) {
|
|
138
|
+
stdout += `WARN: missing SPEC R<n> review(--allow-no-spec-review 豁免 · 留痕)\n`;
|
|
139
|
+
return { ok: true, exitCode: 0, stdout, skipped: false };
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
ok: false,
|
|
143
|
+
exitCode: 2,
|
|
144
|
+
reason:
|
|
145
|
+
'missing SPEC R<n> review(docs/harness/reviews/ 无 spec_*_audit_R / *_ACCEPT_R · 20-spec-audit 或 --allow-no-spec-review)',
|
|
146
|
+
stdout,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return { ok: true, exitCode: 0, stdout, skipped: false };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** SPEC 模式 handoff(verify --spec --json) */
|
|
154
|
+
export function buildSpecVerifyHandoff(target, options = {}) {
|
|
155
|
+
const { specFile, workspaceRoot, allowNoSpecReview = false } = options;
|
|
156
|
+
const absSpec = path.isAbsolute(specFile)
|
|
157
|
+
? specFile
|
|
158
|
+
: path.resolve(target, specFile);
|
|
159
|
+
const content = fs.existsSync(absSpec) ? fs.readFileSync(absSpec, 'utf8') : '';
|
|
160
|
+
const skipped = shouldSkipSpecAudit(content);
|
|
161
|
+
const review = skipped
|
|
162
|
+
? { found: false, latest: null, rounds: [], matched_pattern: null }
|
|
163
|
+
: findSpecReview(absSpec, { target, workspaceRoot });
|
|
164
|
+
|
|
165
|
+
const reviewOk = skipped || review.found || allowNoSpecReview;
|
|
166
|
+
let blocked_reason = null;
|
|
167
|
+
if (!reviewOk) {
|
|
168
|
+
blocked_reason = 'missing SPEC R<n> review';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
schema_version: '1',
|
|
173
|
+
mode: 'spec',
|
|
174
|
+
verify_ok: reviewOk,
|
|
175
|
+
may_start_00: reviewOk,
|
|
176
|
+
spec: path.basename(specFile),
|
|
177
|
+
spec_path: specFile.replace(/\\/g, '/'),
|
|
178
|
+
spec_slug: content ? extractSpecSlug(absSpec, content) : null,
|
|
179
|
+
spec_review_found: Boolean(review.found),
|
|
180
|
+
spec_review_latest: review.latest,
|
|
181
|
+
spec_review_rounds: review.rounds ?? [],
|
|
182
|
+
spec_review_skipped: skipped,
|
|
183
|
+
spec_review_suppressed: Boolean(allowNoSpecReview && !review.found && !skipped),
|
|
184
|
+
matched_pattern: review.matched_pattern ?? null,
|
|
185
|
+
blocked_reason,
|
|
186
|
+
next_hat: reviewOk ? '00' : null,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
92
190
|
/** 规范化 lint 结果供 handoff */
|
|
93
191
|
export function lintSnapshotForHandoff(target, taskFile, { allowLintFail = false } = {}) {
|
|
94
192
|
if (!taskFile) return null;
|
|
@@ -258,6 +356,16 @@ export function buildVerifyHandoff(target, options = {}) {
|
|
|
258
356
|
export function formatAgentHint(payload) {
|
|
259
357
|
const lines = ['=== Harness verify · agent-hint ==='];
|
|
260
358
|
|
|
359
|
+
if (payload.mode === 'spec') {
|
|
360
|
+
lines.push(`spec: ${payload.spec}`);
|
|
361
|
+
lines.push(` may_start_00: ${payload.may_start_00}`);
|
|
362
|
+
if (payload.blocked_reason) lines.push(` blocked: ${payload.blocked_reason}`);
|
|
363
|
+
if (payload.spec_review_latest) lines.push(` review: ${payload.spec_review_latest}`);
|
|
364
|
+
if (payload.spec_review_skipped) lines.push(` review: skipped`);
|
|
365
|
+
if (payload.next_hat) lines.push(` next_hat: ${payload.next_hat}`);
|
|
366
|
+
return lines.join('\n').trimEnd();
|
|
367
|
+
}
|
|
368
|
+
|
|
261
369
|
const items = payload.tasks ?? [payload];
|
|
262
370
|
for (const item of items) {
|
|
263
371
|
lines.push(`task: ${item.task}`);
|