@cyning/harness 2.8.0 → 2.10.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 +35 -0
- package/docs/ONBOARDING.md +16 -5
- package/docs/USER_GUIDE_v1.0_zh.md +16 -5
- package/docs/rethink/2026-07-mechanization-rate/03_coverage_matrix.md +4 -2
- package/docs/rethink/2026-07-mechanization-rate/04_next_steps.md +4 -3
- package/docs/spec/SPEC-discipline-coverage-yaml_v1.md +204 -0
- package/docs/spec/SPEC-lifecycle-engine-min_v1.md +246 -0
- package/docs/spec/SPEC-post-g4-debt-retro_v1.md +237 -0
- package/docs/spec/SPEC-verify-full-reviews-gate_v1.md +183 -0
- package/harness/discipline-coverage.yaml +289 -0
- package/harness/lifecycle.yaml +7 -5
- package/lib/cli.js +86 -21
- package/lib/discipline-coverage.js +96 -0
- package/lib/lifecycle.js +222 -0
- package/lib/task-meta.js +17 -9
- package/lib/verify.js +30 -4
- package/package.json +1 -1
- package/schema/discipline-coverage.v1.schema.json +55 -0
- package/schema/lifecycle.v1.schema.json +1 -1
- package/wizard/gate-check.sh +34 -20
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# Discipline coverage · Starter 子集机械化率资产(方向一)
|
|
2
|
+
#
|
|
3
|
+
# 运行时真值:本文件(随 npm 包分发)。
|
|
4
|
+
# 历史叙事:docs/rethink/2026-07-mechanization-rate/03_coverage_matrix.md
|
|
5
|
+
# 维护:新闸合入时更新 statements[].status / gaps[] / as_of_package_version
|
|
6
|
+
# 禁止:把本文件当「每周全量重盘」流程;不做 audit --discipline UI(本波)
|
|
7
|
+
|
|
8
|
+
version: "1"
|
|
9
|
+
as_of_package_version: "2.10.0"
|
|
10
|
+
scope: "Starter · harness/prompts (10/22/30/40 + FRAGMENT)+ TEMPLATE_invoke"
|
|
11
|
+
source_audit: "rethink/2026-07-mechanization-rate · 02 转录 + 03 聚合 · 状态已按 G1–G4/N1–N4 刷新(非第二轮通读盘点)"
|
|
12
|
+
|
|
13
|
+
gaps:
|
|
14
|
+
- id: G1
|
|
15
|
+
title: task md 结构 lint
|
|
16
|
+
status: closed
|
|
17
|
+
closed_in: "2.3.0"
|
|
18
|
+
note: "task lint E1–E7"
|
|
19
|
+
- id: G2
|
|
20
|
+
title: reviews 留档闸
|
|
21
|
+
status: closed
|
|
22
|
+
closed_in: "2.5.0"
|
|
23
|
+
note: "verify --task + close 检查 6;N4 全量 2.9.0"
|
|
24
|
+
- id: G3
|
|
25
|
+
title: 文本纪律(绝对路径)
|
|
26
|
+
status: closed
|
|
27
|
+
closed_in: "2.3.0"
|
|
28
|
+
note: "E6;invoke 预写 approved grep 仍缓做"
|
|
29
|
+
- id: G4
|
|
30
|
+
title: 思考轮结构
|
|
31
|
+
status: closed
|
|
32
|
+
closed_in: "2.6.0"
|
|
33
|
+
note: "task lint E8–E10 / W4–W5"
|
|
34
|
+
- id: G6
|
|
35
|
+
title: git 行为层
|
|
36
|
+
status: deferred
|
|
37
|
+
closed_in: null
|
|
38
|
+
note: "方向三"
|
|
39
|
+
- id: G7
|
|
40
|
+
title: 执行证据(真跑过)
|
|
41
|
+
status: deferred
|
|
42
|
+
closed_in: null
|
|
43
|
+
note: "方向二 runner;跟 lifecycle dry-run 之后"
|
|
44
|
+
- id: N2-C
|
|
45
|
+
title: verify --task lint → block
|
|
46
|
+
status: deferred
|
|
47
|
+
closed_in: null
|
|
48
|
+
note: "现 warn-only(2.7.0)· 须 FAIL 率下降"
|
|
49
|
+
- id: SPEC-reviews
|
|
50
|
+
title: SPEC 审查文闸
|
|
51
|
+
status: closed
|
|
52
|
+
closed_in: "2.8.0"
|
|
53
|
+
note: "verify --spec"
|
|
54
|
+
- id: FULL-reviews
|
|
55
|
+
title: 裸 verify 全量 reviews + 双路径
|
|
56
|
+
status: closed
|
|
57
|
+
closed_in: "2.9.0"
|
|
58
|
+
note: "N4"
|
|
59
|
+
- id: LIFECYCLE-ENGINE
|
|
60
|
+
title: lifecycle dry-run 最小引擎
|
|
61
|
+
status: closed
|
|
62
|
+
closed_in: "2.10.0"
|
|
63
|
+
note: "Epic T2 · 旁路报告"
|
|
64
|
+
|
|
65
|
+
statements:
|
|
66
|
+
- id: A1
|
|
67
|
+
source: 30-execute-code.md
|
|
68
|
+
summary: HG pending → 拒开工、禁改码
|
|
69
|
+
status: mechanical
|
|
70
|
+
mechanism: "M1 gate-check + M2 verify"
|
|
71
|
+
gap: null
|
|
72
|
+
notes: null
|
|
73
|
+
mechanism_quality: null
|
|
74
|
+
- id: A2
|
|
75
|
+
source: 30-execute-code.md
|
|
76
|
+
summary: 开工前首输出 GATE_VERIFY 闸扫描表
|
|
77
|
+
status: partial
|
|
78
|
+
mechanism: "M2 提供判定;Agent 是否先输出表无检查"
|
|
79
|
+
gap: null
|
|
80
|
+
notes: null
|
|
81
|
+
mechanism_quality: null
|
|
82
|
+
- id: A3
|
|
83
|
+
source: 30-execute-code.md
|
|
84
|
+
summary: 真值在 task 表;声称 vs 表 pending → STOP
|
|
85
|
+
status: partial
|
|
86
|
+
mechanism: "M1/M2 读表"
|
|
87
|
+
gap: null
|
|
88
|
+
notes: "用户声称侧不可机械"
|
|
89
|
+
mechanism_quality: null
|
|
90
|
+
- id: A4
|
|
91
|
+
source: 30-execute-code.md
|
|
92
|
+
summary: test_strategy required → 先可失败测试再改实现
|
|
93
|
+
status: partial
|
|
94
|
+
mechanism: "M3 D5 探测测试文件存在"
|
|
95
|
+
gap: null
|
|
96
|
+
notes: "先红后绿顺序无机制 · mechanism_quality 预留"
|
|
97
|
+
mechanism_quality: null
|
|
98
|
+
- id: A5
|
|
99
|
+
source: 30-execute-code.md
|
|
100
|
+
summary: 运行验证命令 + 回填自检结论
|
|
101
|
+
status: mechanical
|
|
102
|
+
mechanism: "M5 非占位符检查"
|
|
103
|
+
gap: G7
|
|
104
|
+
notes: "命令真跑过仍靠 40 诚实"
|
|
105
|
+
mechanism_quality: null
|
|
106
|
+
- id: A6
|
|
107
|
+
source: 30-execute-code.md
|
|
108
|
+
summary: invoke 快照落盘 by-task/<slug>/
|
|
109
|
+
status: mechanical
|
|
110
|
+
mechanism: "M5 close + M6 lint-done"
|
|
111
|
+
gap: null
|
|
112
|
+
notes: null
|
|
113
|
+
mechanism_quality: null
|
|
114
|
+
- id: A7
|
|
115
|
+
source: 30-execute-code.md
|
|
116
|
+
summary: 归档只能 task close PASS 后进行
|
|
117
|
+
status: mechanical
|
|
118
|
+
mechanism: "M5"
|
|
119
|
+
gap: null
|
|
120
|
+
notes: "手动 mv 旁路由 M6 兜底"
|
|
121
|
+
mechanism_quality: null
|
|
122
|
+
- id: A8
|
|
123
|
+
source: 30-execute-code.md
|
|
124
|
+
summary: HG-GRAPH-MODULES pending → 禁改码
|
|
125
|
+
status: mechanical
|
|
126
|
+
mechanism: "M1 gate-check --graph"
|
|
127
|
+
gap: null
|
|
128
|
+
notes: null
|
|
129
|
+
mechanism_quality: null
|
|
130
|
+
- id: A9
|
|
131
|
+
source: 30-execute-code.md
|
|
132
|
+
summary: 缺验收/failure_paths/必读 → 仅输出阻塞清单
|
|
133
|
+
status: mechanical
|
|
134
|
+
mechanism: "task lint E1–E7(G1 · v2.3.0)"
|
|
135
|
+
gap: G1
|
|
136
|
+
notes: "02 原 ❌ · 已刷新"
|
|
137
|
+
mechanism_quality: null
|
|
138
|
+
- id: A10
|
|
139
|
+
source: 30-execute-code.md
|
|
140
|
+
summary: 交接物 commit 仅本轮路径、禁 git add -A
|
|
141
|
+
status: prompt-only
|
|
142
|
+
mechanism: null
|
|
143
|
+
gap: G6
|
|
144
|
+
notes: null
|
|
145
|
+
mechanism_quality: null
|
|
146
|
+
- id: B1
|
|
147
|
+
source: 40-self-check.md
|
|
148
|
+
summary: 逐条对照验收标准标记 pass/fail
|
|
149
|
+
status: partial
|
|
150
|
+
mechanism: "M5 查无未勾选"
|
|
151
|
+
gap: null
|
|
152
|
+
notes: null
|
|
153
|
+
mechanism_quality: null
|
|
154
|
+
- id: B2
|
|
155
|
+
source: 40-self-check.md
|
|
156
|
+
summary: 必须运行验证命令并摘要退出码
|
|
157
|
+
status: prompt-only
|
|
158
|
+
mechanism: null
|
|
159
|
+
gap: G7
|
|
160
|
+
notes: "真跑过无证据"
|
|
161
|
+
mechanism_quality: null
|
|
162
|
+
- id: B3
|
|
163
|
+
source: 40-self-check.md
|
|
164
|
+
summary: 必须回填自检结论(执行者)
|
|
165
|
+
status: mechanical
|
|
166
|
+
mechanism: "M5"
|
|
167
|
+
gap: null
|
|
168
|
+
notes: null
|
|
169
|
+
mechanism_quality: null
|
|
170
|
+
- id: B4
|
|
171
|
+
source: 40-self-check.md
|
|
172
|
+
summary: 禁止改 tasks/reviews/invokes(S2)
|
|
173
|
+
status: prompt-only
|
|
174
|
+
mechanism: null
|
|
175
|
+
gap: G6
|
|
176
|
+
notes: null
|
|
177
|
+
mechanism_quality: null
|
|
178
|
+
- id: B5
|
|
179
|
+
source: 40-self-check.md
|
|
180
|
+
summary: 不凭记忆声称测过
|
|
181
|
+
status: prompt-only
|
|
182
|
+
mechanism: null
|
|
183
|
+
gap: null
|
|
184
|
+
notes: "诚实纪律 · 设计上留 Prompt"
|
|
185
|
+
mechanism_quality: null
|
|
186
|
+
- id: C1
|
|
187
|
+
source: 22-task-audit.md
|
|
188
|
+
summary: 必须落盘 reviews task_*_audit_R*
|
|
189
|
+
status: mechanical
|
|
190
|
+
mechanism: "findReview · verify/close(G2 · v2.5+;全量 v2.9)"
|
|
191
|
+
gap: G2
|
|
192
|
+
notes: "02 原 ❌ · 已刷新;内容质量仍 prompt-only"
|
|
193
|
+
mechanism_quality: null
|
|
194
|
+
- id: C2
|
|
195
|
+
source: 22-task-audit.md
|
|
196
|
+
summary: HG-AUDIT-R1 pending 时禁止附 30 Prompt
|
|
197
|
+
status: partial
|
|
198
|
+
mechanism: "存在性闸;审查文内容无检查"
|
|
199
|
+
gap: G2
|
|
200
|
+
notes: null
|
|
201
|
+
mechanism_quality: null
|
|
202
|
+
- id: C3
|
|
203
|
+
source: 22-task-audit.md
|
|
204
|
+
summary: 思考轮审查不通过 → 退回 10
|
|
205
|
+
status: prompt-only
|
|
206
|
+
mechanism: null
|
|
207
|
+
gap: null
|
|
208
|
+
notes: "流程纪律"
|
|
209
|
+
mechanism_quality: null
|
|
210
|
+
- id: C4
|
|
211
|
+
source: 22-task-audit.md
|
|
212
|
+
summary: 零阻塞写已核对项;终轮写签收
|
|
213
|
+
status: prompt-only
|
|
214
|
+
mechanism: null
|
|
215
|
+
gap: null
|
|
216
|
+
notes: "审查文结构无 lint"
|
|
217
|
+
mechanism_quality: null
|
|
218
|
+
- id: D1
|
|
219
|
+
source: 10-requirements.md
|
|
220
|
+
summary: task 必含验收/failure_paths/非范围/依赖
|
|
221
|
+
status: mechanical
|
|
222
|
+
mechanism: "task lint(G1)"
|
|
223
|
+
gap: G1
|
|
224
|
+
notes: "02 原 ❌ · 已刷新"
|
|
225
|
+
mechanism_quality: null
|
|
226
|
+
- id: D2
|
|
227
|
+
source: 10-requirements.md
|
|
228
|
+
summary: 不写绝对本机路径
|
|
229
|
+
status: mechanical
|
|
230
|
+
mechanism: "task lint E6(G3)"
|
|
231
|
+
gap: G3
|
|
232
|
+
notes: "02 原 ❌ · 已刷新"
|
|
233
|
+
mechanism_quality: null
|
|
234
|
+
- id: D3
|
|
235
|
+
source: 10-requirements.md
|
|
236
|
+
summary: 预置 R0–R5 + 思考轮控制表
|
|
237
|
+
status: mechanical
|
|
238
|
+
mechanism: "task lint E8–E10(G4 · 条件触发)"
|
|
239
|
+
gap: G4
|
|
240
|
+
notes: "02 原 ❌ · 已刷新;内容质量仍 20 帽"
|
|
241
|
+
mechanism_quality: null
|
|
242
|
+
- id: D4
|
|
243
|
+
source: 10-requirements.md
|
|
244
|
+
summary: 缺验收/failure_paths → 仅输出阻塞清单
|
|
245
|
+
status: mechanical
|
|
246
|
+
mechanism: "task lint(G1)"
|
|
247
|
+
gap: G1
|
|
248
|
+
notes: null
|
|
249
|
+
mechanism_quality: null
|
|
250
|
+
- id: E1
|
|
251
|
+
source: FRAGMENT_30
|
|
252
|
+
summary: invoke 中禁止预写 HG-AUDIT-R1 approved
|
|
253
|
+
status: prompt-only
|
|
254
|
+
mechanism: null
|
|
255
|
+
gap: G3
|
|
256
|
+
notes: "SPEC R2 缓做 · 误报风险"
|
|
257
|
+
mechanism_quality: null
|
|
258
|
+
- id: E2
|
|
259
|
+
source: FRAGMENT_30
|
|
260
|
+
summary: 用户确认 approved = 须核验非事实
|
|
261
|
+
status: partial
|
|
262
|
+
mechanism: "M2 读表"
|
|
263
|
+
gap: null
|
|
264
|
+
notes: null
|
|
265
|
+
mechanism_quality: null
|
|
266
|
+
- id: F1
|
|
267
|
+
source: TEMPLATE_invoke.md
|
|
268
|
+
summary: 落盘路径 by-task/<task_slug>/
|
|
269
|
+
status: mechanical
|
|
270
|
+
mechanism: "M5/M6"
|
|
271
|
+
gap: null
|
|
272
|
+
notes: null
|
|
273
|
+
mechanism_quality: null
|
|
274
|
+
- id: F2
|
|
275
|
+
source: TEMPLATE_invoke.md
|
|
276
|
+
summary: 同帽追问不新增 invoke;打回用 _r2
|
|
277
|
+
status: prompt-only
|
|
278
|
+
mechanism: null
|
|
279
|
+
gap: null
|
|
280
|
+
notes: "命名纪律 · 可留 prompt"
|
|
281
|
+
mechanism_quality: null
|
|
282
|
+
- id: F3
|
|
283
|
+
source: TEMPLATE_invoke.md
|
|
284
|
+
summary: 落盘 + task 回填后再 commit
|
|
285
|
+
status: prompt-only
|
|
286
|
+
mechanism: null
|
|
287
|
+
gap: G6
|
|
288
|
+
notes: null
|
|
289
|
+
mechanism_quality: null
|
package/harness/lifecycle.yaml
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
# Task lifecycle ·
|
|
1
|
+
# Task lifecycle · 最小真值(方向二 · v1)
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# 登记真值:状态 / 转移 / 守卫。yaml 本身不是引擎。
|
|
4
|
+
# 由 `lifecycle dry-run` 引擎消费(v2.10+ · 资格判定旁路报告 · 不写盘)。
|
|
5
|
+
# 只读展示:npx @cyning/harness lifecycle show [--json]
|
|
5
6
|
# Schema:schema/lifecycle.v1.schema.json
|
|
6
7
|
|
|
7
8
|
version: "1"
|
|
@@ -34,7 +35,7 @@ transitions:
|
|
|
34
35
|
from: ["draft", "in_progress"]
|
|
35
36
|
to: in_progress
|
|
36
37
|
hat: "30"
|
|
37
|
-
description: "执行帽开工(verify --task
|
|
38
|
+
description: "执行帽开工(verify --task 与全量模式聚合 · v2.9+)"
|
|
38
39
|
guards:
|
|
39
40
|
- id: HG-AUDIT-R1
|
|
40
41
|
command_or_check: "gate-check · human_gate HG-AUDIT-R1=approved"
|
|
@@ -46,9 +47,10 @@ transitions:
|
|
|
46
47
|
command_or_check: "verify/audit · test_strategy vs 测试文件存在"
|
|
47
48
|
severity: block
|
|
48
49
|
- id: reviews_retention
|
|
49
|
-
command_or_check: "verify · findReview R<n> 存在性(v2.5+)"
|
|
50
|
+
command_or_check: "verify --task 与全量 · findReview R<n> 存在性(v2.5+ · 全量 v2.9+)"
|
|
50
51
|
severity: block
|
|
51
52
|
allow_flag: "--allow-no-review"
|
|
53
|
+
note: "全量模式扫描双路径 active;不跑 lint / D5"
|
|
52
54
|
- id: task_lint
|
|
53
55
|
command_or_check: "verify --task · lintTaskFile(v2.7+)"
|
|
54
56
|
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/
|
|
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 的工作区根>
|
|
@@ -504,34 +505,98 @@ async function cmdVerify(args) {
|
|
|
504
505
|
}
|
|
505
506
|
|
|
506
507
|
async function cmdLifecycle(args) {
|
|
507
|
-
const {
|
|
508
|
+
const {
|
|
509
|
+
loadLifecycle,
|
|
510
|
+
formatLifecycleShow,
|
|
511
|
+
dryRunTransition,
|
|
512
|
+
formatLifecycleDryRun,
|
|
513
|
+
} = await import('./lifecycle.js');
|
|
508
514
|
const [sub, ...rest] = args;
|
|
509
515
|
if (!sub || sub === '--help' || sub === '-h' || args.includes('--help') || args.includes('-h')) {
|
|
510
|
-
console.log(`用法:
|
|
516
|
+
console.log(`用法:
|
|
517
|
+
npx @cyning/harness lifecycle show [--json]
|
|
518
|
+
npx @cyning/harness lifecycle dry-run --transition ID --from STATE [--task PATH] [--json]
|
|
519
|
+
[--allow-no-review] [--allow-lint-fail] [--allow-no-spec-review]
|
|
511
520
|
|
|
512
|
-
|
|
513
|
-
|
|
521
|
+
show · 只读展示 harness/lifecycle.yaml(登记真值 · v2.7+)
|
|
522
|
+
dry-run · 转移资格判定(引擎消费 yaml · v2.10+ · 旁路报告 · 非 runner / 非 G7)
|
|
514
523
|
`);
|
|
515
524
|
return;
|
|
516
525
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
526
|
+
|
|
527
|
+
if (sub === 'show') {
|
|
528
|
+
const json = rest.includes('--json');
|
|
529
|
+
const unknown = rest.filter((a) => a !== '--json');
|
|
530
|
+
if (unknown.length > 0) {
|
|
531
|
+
const err = new Error(`lifecycle show 未知参数: ${unknown.join(' ')}`);
|
|
532
|
+
err.exitCode = 1;
|
|
533
|
+
throw err;
|
|
534
|
+
}
|
|
535
|
+
const { data } = loadLifecycle();
|
|
536
|
+
if (json) {
|
|
537
|
+
console.log(JSON.stringify(data, null, 2));
|
|
538
|
+
} else {
|
|
539
|
+
console.log(formatLifecycleShow(data));
|
|
540
|
+
}
|
|
541
|
+
return;
|
|
528
542
|
}
|
|
529
|
-
|
|
530
|
-
if (
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
543
|
+
|
|
544
|
+
if (sub === 'dry-run') {
|
|
545
|
+
let remaining = rest;
|
|
546
|
+
const { value: transitionId, rest: r1 } = takeOption(remaining, '--transition');
|
|
547
|
+
remaining = r1;
|
|
548
|
+
const { value: fromState, rest: r2 } = takeOption(remaining, '--from');
|
|
549
|
+
remaining = r2;
|
|
550
|
+
const { value: taskPath, rest: r3 } = takeOption(remaining, '--task');
|
|
551
|
+
remaining = r3;
|
|
552
|
+
const json = remaining.includes('--json');
|
|
553
|
+
const allowNoReview = remaining.includes('--allow-no-review');
|
|
554
|
+
const allowLintFail = remaining.includes('--allow-lint-fail');
|
|
555
|
+
const allowNoSpecReview = remaining.includes('--allow-no-spec-review');
|
|
556
|
+
remaining = remaining.filter(
|
|
557
|
+
(a) =>
|
|
558
|
+
a !== '--json' &&
|
|
559
|
+
a !== '--allow-no-review' &&
|
|
560
|
+
a !== '--allow-lint-fail' &&
|
|
561
|
+
a !== '--allow-no-spec-review',
|
|
562
|
+
);
|
|
563
|
+
if (remaining.length > 0) {
|
|
564
|
+
const err = new Error(`lifecycle dry-run 未知参数: ${remaining.join(' ')}`);
|
|
565
|
+
err.exitCode = 1;
|
|
566
|
+
throw err;
|
|
567
|
+
}
|
|
568
|
+
if (!transitionId || !fromState) {
|
|
569
|
+
const err = new Error(
|
|
570
|
+
'lifecycle dry-run 须 --transition <id> 与 --from <state>\n提示: lifecycle show',
|
|
571
|
+
);
|
|
572
|
+
err.exitCode = 1;
|
|
573
|
+
throw err;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const report = dryRunTransition({
|
|
577
|
+
transitionId,
|
|
578
|
+
fromState,
|
|
579
|
+
taskPath,
|
|
580
|
+
flags: { allowNoReview, allowLintFail, allowNoSpecReview },
|
|
581
|
+
cwd: process.cwd(),
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
if (json) {
|
|
585
|
+
console.log(JSON.stringify(report, null, 2));
|
|
586
|
+
} else {
|
|
587
|
+
console.log(formatLifecycleDryRun(report));
|
|
588
|
+
}
|
|
589
|
+
if (report.exitCode && report.exitCode !== 0) {
|
|
590
|
+
process.exitCode = report.exitCode;
|
|
591
|
+
}
|
|
592
|
+
return;
|
|
534
593
|
}
|
|
594
|
+
|
|
595
|
+
const err = new Error(
|
|
596
|
+
`lifecycle 子命令未知: ${sub}\n用法: lifecycle show [--json] · lifecycle dry-run --transition ID --from STATE`,
|
|
597
|
+
);
|
|
598
|
+
err.exitCode = 1;
|
|
599
|
+
throw err;
|
|
535
600
|
}
|
|
536
601
|
|
|
537
602
|
async function cmdSync(args) {
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
import { resolveHarnessRoot } from './paths.js';
|
|
5
|
+
|
|
6
|
+
const STATUSES = new Set(['mechanical', 'partial', 'prompt-only']);
|
|
7
|
+
const GAP_STATUSES = new Set(['open', 'closed', 'deferred']);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 轻量结构校验(对齐 schema/discipline-coverage.v1.schema.json 必填约束)。
|
|
11
|
+
* @returns {{ ok: boolean, errors: string[] }}
|
|
12
|
+
*/
|
|
13
|
+
export function validateDisciplineCoverage(data) {
|
|
14
|
+
const errors = [];
|
|
15
|
+
if (!data || typeof data !== 'object') {
|
|
16
|
+
return { ok: false, errors: ['根须为 object'] };
|
|
17
|
+
}
|
|
18
|
+
if (typeof data.version !== 'string' || !data.version.trim()) {
|
|
19
|
+
errors.push('缺 version');
|
|
20
|
+
}
|
|
21
|
+
if (typeof data.as_of_package_version !== 'string' || !data.as_of_package_version.trim()) {
|
|
22
|
+
errors.push('缺 as_of_package_version');
|
|
23
|
+
}
|
|
24
|
+
if (typeof data.scope !== 'string' || !data.scope.trim()) {
|
|
25
|
+
errors.push('缺 scope');
|
|
26
|
+
}
|
|
27
|
+
if (!Array.isArray(data.statements) || data.statements.length === 0) {
|
|
28
|
+
errors.push('缺 statements[] 或为空');
|
|
29
|
+
} else {
|
|
30
|
+
data.statements.forEach((s, i) => {
|
|
31
|
+
if (!s || typeof s.id !== 'string' || !s.id.trim()) {
|
|
32
|
+
errors.push(`statements[${i}] 缺 id`);
|
|
33
|
+
}
|
|
34
|
+
if (!s || typeof s.source !== 'string') {
|
|
35
|
+
errors.push(`statements[${i}] 缺 source`);
|
|
36
|
+
}
|
|
37
|
+
if (!s || typeof s.summary !== 'string') {
|
|
38
|
+
errors.push(`statements[${i}] 缺 summary`);
|
|
39
|
+
}
|
|
40
|
+
if (!s || !STATUSES.has(s.status)) {
|
|
41
|
+
errors.push(`statements[${i}] status 非法: ${s?.status}`);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (data.gaps != null) {
|
|
46
|
+
if (!Array.isArray(data.gaps)) {
|
|
47
|
+
errors.push('gaps 须为 array');
|
|
48
|
+
} else {
|
|
49
|
+
data.gaps.forEach((g, i) => {
|
|
50
|
+
if (!g || typeof g.id !== 'string') errors.push(`gaps[${i}] 缺 id`);
|
|
51
|
+
if (!g || typeof g.title !== 'string') errors.push(`gaps[${i}] 缺 title`);
|
|
52
|
+
if (!g || !GAP_STATUSES.has(g.status)) {
|
|
53
|
+
errors.push(`gaps[${i}] status 非法: ${g?.status}`);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { ok: errors.length === 0, errors };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 加载并校验 harness/discipline-coverage.yaml。
|
|
63
|
+
*/
|
|
64
|
+
export function loadDisciplineCoverage(options = {}) {
|
|
65
|
+
const harnessRoot = options.harnessRoot ?? resolveHarnessRoot();
|
|
66
|
+
const filePath =
|
|
67
|
+
options.filePath ??
|
|
68
|
+
path.join(harnessRoot, 'harness', 'discipline-coverage.yaml');
|
|
69
|
+
|
|
70
|
+
if (!fs.existsSync(filePath)) {
|
|
71
|
+
const e = new Error(`discipline-coverage.yaml 不存在: ${filePath}`);
|
|
72
|
+
e.exitCode = 1;
|
|
73
|
+
throw e;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let data;
|
|
77
|
+
try {
|
|
78
|
+
data = yaml.load(fs.readFileSync(filePath, 'utf8'));
|
|
79
|
+
} catch (err) {
|
|
80
|
+
const e = new Error(`discipline-coverage.yaml 解析失败: ${err.message}`);
|
|
81
|
+
e.exitCode = 1;
|
|
82
|
+
throw e;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const v = validateDisciplineCoverage(data);
|
|
86
|
+
if (!v.ok) {
|
|
87
|
+
const e = new Error(
|
|
88
|
+
`discipline-coverage.yaml 校验失败:\n - ${v.errors.join('\n - ')}`,
|
|
89
|
+
);
|
|
90
|
+
e.exitCode = 1;
|
|
91
|
+
e.validationErrors = v.errors;
|
|
92
|
+
throw e;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { data, filePath, harnessRoot };
|
|
96
|
+
}
|