@cyning/harness 2.0.1 → 2.0.4

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.
Files changed (31) hide show
  1. package/AGENTS.md +4 -3
  2. package/CHANGELOG.md +41 -0
  3. package/README.md +7 -5
  4. package/docs/ARCHITECTURE.md +2 -1
  5. package/docs/USER_GUIDE_v1.0_zh.md +25 -1
  6. package/docs/methodology/execution/PROMPT_review_v203_v2_hat_flow_bump_v1_zh.md +136 -0
  7. package/docs/methodology/execution/README.md +1 -0
  8. package/docs/methodology/execution/reviews/review_v2_hat_flow_20260621.md +71 -0
  9. package/docs/methodology/pointers/SDD_HAT_FLOW_v1_zh.md +5 -4
  10. package/docs/methodology/product/DESIGN_ONTOLOGY_v1_zh.md +100 -47
  11. package/docs/methodology/product/README.md +2 -1
  12. package/docs/methodology/product/SDD_HAT_FLOW_v2_zh.md +78 -0
  13. package/examples/compliance_bench/S6_agent_handoff/.cyning-harness/manifest.json +1 -0
  14. package/examples/compliance_bench/S6_agent_handoff/docs/harness/invokes/by-task/s6/PROMPT_START_30_v1.md +3 -0
  15. package/examples/compliance_bench/S6_agent_handoff/docs/harness/reviews/task_s6_agent_handoff_audit_R1_20260618.md +3 -0
  16. package/examples/compliance_bench/S6_agent_handoff/task_s6_agent_handoff.md +31 -0
  17. package/harness/prompts/10-requirements.md +3 -1
  18. package/harness/prompts/22-task-audit.md +3 -1
  19. package/harness/prompts/30-execute-code.md +2 -1
  20. package/harness/prompts/40-self-check.md +7 -4
  21. package/harness/prompts/FRAGMENT_30_gate_verify_v1_zh.md +1 -1
  22. package/harness/prompts/README.md +31 -22
  23. package/harness/templates/QUICKREF_v1_zh.md +1 -1
  24. package/lib/cli.js +39 -10
  25. package/lib/task-meta.js +223 -0
  26. package/lib/verify.js +69 -0
  27. package/ontology.yaml +27 -7
  28. package/package.json +1 -1
  29. package/schema/verify_result.v1.schema.json +65 -0
  30. package/wizard/compliance-bench.sh +34 -7
  31. package/wizard/lib/generate-invoke-index.js +63 -3
@@ -2,7 +2,8 @@
2
2
 
3
3
  | 文档 | 说明 |
4
4
  | --- | --- |
5
- | **[`DESIGN_ONTOLOGY_v1_zh.md`](./DESIGN_ONTOLOGY_v1_zh.md)** | **真值** v1.2 · Track / Hat / Sync / P·S·D 公理 / ICVO |
5
+ | **[`DESIGN_ONTOLOGY_v1_zh.md`](./DESIGN_ONTOLOGY_v1_zh.md)** | **真值** v1.3 · Track / Hat / Sync / P·S·D 公理 / ICVO |
6
+ | **[`SDD_HAT_FLOW_v2_zh.md`](./SDD_HAT_FLOW_v2_zh.md)** | **帽链流程** · 人读版 · 30→40 · 打回规则 |
6
7
 
7
8
  **分层**:回答「产品 **是什么** · 由哪些实体组成 · 允许哪些状态转移」。
8
9
 
@@ -0,0 +1,78 @@
1
+ # SDD 帽链流程(V2 · 产品仓真值)
2
+
3
+ | 项 | 内容 |
4
+ |----|------|
5
+ | **版本** | v2.0 |
6
+ | **日期** | 2026-06-21 |
7
+ | **关联** | [`DESIGN_ONTOLOGY_v1_zh.md`](./DESIGN_ONTOLOGY_v1_zh.md) §3.2 · §4.1 · §8 · [`../../../harness/prompts/README.md`](../../../harness/prompts/README.md) |
8
+
9
+ ---
10
+
11
+ ## 1. 标准流程(功能 / Epic)
12
+
13
+ ```text
14
+ 人 + 00 chat 生成大纲
15
+ → 10-spec R0–R9(回填 SPEC · 可多轮)
16
+ → 20-spec-audit(规格验收 · reviews/)
17
+ ↔ HG-SPEC-SIGNOFF(人签 · 未过则回到 10-spec 或 20 再审)
18
+ → 00 起草 P0 task(自 SPEC §13 投影)
19
+ → 10-task(回填 task §5)
20
+ → 20-task-audit R1(reviews/ · 可打回 10-task)
21
+ ↔ HG-AUDIT-R1(人签)
22
+ → 30 实现
23
+ → 40 自检(通常由 30 同一 Agent 连续执行 · 失败自修并重跑 40 直至通过 · 无需单独开 40 对话)
24
+ → 50 独立复检(可选 · 未过打回 30)
25
+ → CLOSE(关账 · KPI)
26
+ ```
27
+
28
+ **10 / 20 配对**
29
+
30
+ | 10 | 20 | 人闸 |
31
+ |----|-----|------|
32
+ | 10-spec | 20-spec-audit | HG-SPEC-SIGNOFF |
33
+ | 10-task | 20-task-audit | HG-AUDIT-R1 |
34
+
35
+ 历史别名:`10-requirements`→10-task · `22-task-audit`→20-task-audit。
36
+
37
+ ---
38
+
39
+ ## 2. 打回与关账
40
+
41
+ | 节点 | 常见打回 | 说明 |
42
+ |------|----------|------|
43
+ | 20-spec-audit | → **10-spec** | 规格缺口 · 人闸 pending / rejected |
44
+ | 20-task-audit | → **10-task** | task 验收 / failure_paths 不可执行 |
45
+ | 50 | → **30** | 独立视角发现实现问题 |
46
+ | CLOSE | → **下一 task / SPEC 增量** | 小问题留后续迭代 |
47
+ | CLOSE | → **10-spec 或 10-task** | 结果与预期相差太远 · 维护者决策重开 |
48
+
49
+ **30→40**:不强制新对话;30 Agent 跑验证命令、回填 `### 自检结论(执行者)`,不通过则改码再跑,直到 task 所列命令绿。
50
+
51
+ **00**:编排层 · 不写产品代码 · 起草 SPEC/task · 派下一棒 Prompt。
52
+
53
+ ---
54
+
55
+ ## 3. bugfix 捷径
56
+
57
+ Issue ≈ mini-SPEC 且已签收时:**跳过 10-spec / 20-spec-audit** · 自 **00 草稿 task → 10-task → 20-task-audit → …**。
58
+
59
+ ---
60
+
61
+ ## 4. Starter 包与用户仓
62
+
63
+ | 帽 | 产品仓 Starter | 完整 Prompt |
64
+ |----|----------------|-------------|
65
+ | 10-task | `harness/prompts/10-requirements.md` | 工作区 Extended |
66
+ | 20-task-audit | `harness/prompts/22-task-audit.md` | 同上 |
67
+ | 30 / 40 | ✅ Starter | 同上 |
68
+ | 10-spec / 20-spec-audit / 00 / 50 | POINTER | 工作区 `docs/harness/prompts/` |
69
+
70
+ 工作区 Ink 全量库:[`docs/harness/guides/GUIDANCE_harness_hat_v2_chain_v1_zh.md`](../../../../docs/harness/guides/GUIDANCE_harness_hat_v2_chain_v1_zh.md)(POINTER · 私有 monorepo)。
71
+
72
+ ---
73
+
74
+ ## 修订记录
75
+
76
+ | 版本 | 日期 | 说明 |
77
+ |------|------|------|
78
+ | v2.0 | 2026-06-21 | 与 Ink 工作区 V2 对齐 · 30→40 同 Agent · 50/CLOSE 打回规则 |
@@ -0,0 +1 @@
1
+ {"version":"2.0.2","preset":"harness-only"}
@@ -0,0 +1,3 @@
1
+ # PROMPT_START_30 · S6 fixture
2
+
3
+ Static invoke path for compliance bench S6.
@@ -0,0 +1,3 @@
1
+ # 任务审核 · cyning-harness A6 · R1 fixture
2
+
3
+ S6 compliance bench · static review path.
@@ -0,0 +1,31 @@
1
+ # Task fixture · S6 · Agent handoff · verify --json
2
+
3
+ > 用途:SDD-Compliance micro-bench S6 · approved + entry_invoke_30 → may_start_30 true
4
+
5
+ ---
6
+
7
+ ## Harness 元信息
8
+
9
+ | 字段 | 值 |
10
+ | --- | --- |
11
+ | **task_slug** | `s6_agent_handoff` |
12
+ | **test_strategy** | `not_applicable` |
13
+ | **test_strategy_note** | compliance bench 静态夹具 |
14
+ | **entry_invoke_30** | `docs/harness/invokes/by-task/s6/PROMPT_START_30_v1.md` |
15
+
16
+ ### 人工闸
17
+
18
+ | human_gate_id | status | blocks_hats | 说明 |
19
+ | --- | --- | --- | --- |
20
+ | HG-TASK-DRAFT | approved | 20, 30 | ok |
21
+ | HG-AUDIT-R1 | approved | 30 | R1 已签 |
22
+
23
+ ---
24
+
25
+ ## 背景与目标
26
+
27
+ 验证 A6 Agent handoff:`verify --json` 输出 `may_start_30: true` 且 `entry_invoke_30` 非空。
28
+
29
+ ## 验收标准
30
+
31
+ - `verify --json --task task_s6_agent_handoff.md` · `may_start_30` = true · `entry_invoke_30` 非空
@@ -1,6 +1,8 @@
1
1
  # 帽子:需求 / 任务分析(Harness · Starter 子集)
2
2
 
3
- > **完整版 POINTER**(Ink 工作区):`docs/harness/prompts/10-requirements.md`
3
+ > **hat_id(V2)**:**10-task** · 对应 **20-task-audit** · 历史文件名 **10-requirements** 保留。
4
+ > **姊妹帽**:SPEC 思考 **10-spec**(Extended · 工作区 `docs/harness/prompts/10-spec-requirements.md`)。
5
+ > **完整版 POINTER**(Ink 工作区):`docs/harness/prompts/10-task-requirements.md`
4
6
  > **本文件**:嵌入用户仓 `docs/harness/prompts/` 的 **精简真值**。
5
7
 
6
8
  ## 身份
@@ -1,6 +1,8 @@
1
1
  # 帽子:任务审核(Harness · Starter 子集)
2
2
 
3
- > **完整版 POINTER**(Ink 工作区):`docs/harness/prompts/22-task-audit.md`
3
+ > **hat_id(V2)**:**20-task-audit** · 对应 **10-task** · 历史文件名 **22-task-audit** 保留。
4
+ > **姊妹帽**:SPEC 书面审 **20-spec-audit**(Extended · 工作区 `docs/harness/prompts/20-spec-audit.md`)。
5
+ > **完整版 POINTER**(Ink 工作区):`docs/harness/prompts/20-task-audit.md`
4
6
  > **本文件**:嵌入用户仓 `docs/harness/prompts/` 的 **精简真值**。
5
7
 
6
8
  ## 身份
@@ -38,7 +38,8 @@
38
38
  ## 输出形状
39
39
 
40
40
  - (拒开工)仅闸扫描 STOP 模板
41
- - (通过)diff + PR 验证说明 + invoke + task 自检回填 + 下一棒 40 Prompt
41
+ - (通过)diff + 验证说明 + invoke + **40 自检闭环**(同上下文跑命令 · 不通过则改码重跑 · 回填 `### 自检结论`)
42
+ - **40 不强制新开对话**;须 task 验证命令全绿后再交 50 / CLOSE
42
43
 
43
44
  ## 交接物
44
45
 
@@ -5,7 +5,9 @@
5
5
 
6
6
  ## 身份
7
7
 
8
- **执行者自检**:与 30 执行帽 **可同上下文连续**;把「声称完成」变成 **可核对证据**。
8
+ **执行者自检**:默认由 **30 同一 Agent** 在本轮连续完成;把「声称完成」变成 **可核对证据**。
9
+
10
+ > **纪律**:不通过则 30 改码并重跑本帽步骤,直至 task 验证命令绿;**无需**维护者单独开 40 对话澄清。
9
11
 
10
12
  ## 只做什么
11
13
 
@@ -36,8 +38,9 @@
36
38
 
37
39
  ## 交接物
38
40
 
39
- - 给 50 复检 / 00 统筹:**diff + 日志摘要 + 自检验收表**
40
- - task 内 **`### 自检结论(执行者)`** 相对路径引用
41
+ - 给 50 复检 / 00 统筹 / **CLOSE**:diff + 日志 + 自检验收表
42
+ - task 内 **`### 自检结论(执行者)`** 须已回填
43
+ - 50 未过 → 打回 **30**;CLOSE 偏差过大 → 维护者决策 ↺ **10-task** 或 **10-spec**
41
44
 
42
45
  ---
43
46
 
@@ -45,4 +48,4 @@
45
48
 
46
49
  | 日期 | 摘要 |
47
50
  |------|------|
48
- | 2026-06-15 | A2 Starter 初版 · Ink 完整库 POINTER 对齐 |
51
+ | 2026-06-21 | 30 Agent 闭环 · 50/CLOSE 打回规则 |
@@ -20,7 +20,7 @@ reviews:task_*_audit_R1_*.md 存在且 R1 通过? {是/否}
20
20
 
21
21
  结论:{STOP · 签闸指引 | 可进入读码/改码}
22
22
 
23
- 机械辅助(推荐):`npx @cyning/harness verify --target <meta> --task docs/tasks/active/task_*.md`
23
+ 机械辅助(30 改码前必须):`npx @cyning/harness verify --target <meta> --task docs/tasks/active/task_*.md [--json] [--agent-hint] [--workspace-root <Projects>]`
24
24
  ```
25
25
 
26
26
  ## Agent 纪律
@@ -1,31 +1,40 @@
1
1
  # harness/prompts
2
2
 
3
- 从本目录向用户仓 **`docs/harness/prompts/`** 复制 **Starter 子集**(非 Ink 全量帽子库)。
4
-
5
- ## v0.1 已交付(T3 · M2
6
-
7
- | 文件 | 状态 | 说明 |
8
- |------|------|------|
9
- | [`10-requirements.md`](./10-requirements.md) | ✅ | 需求 / 任务分析 · 精简 + POINTER |
10
- | [`22-task-audit.md`](./22-task-audit.md) | ✅ | 任务审核 · 落盘 reviews · HG-AUDIT-R1 |
11
- | [`30-execute-code.md`](./30-execute-code.md) | v0.1.1 | 执行编码 · 强制闸扫描 · AUDIT approved |
12
- | [`40-self-check.md`](./40-self-check.md) | ✅ v0.3.2 | 自检 · 命令证据 · 回填 task |
13
- | [`TEMPLATE_30_gate_stop.md`](./TEMPLATE_30_gate_stop.md) | v0.1.1 | 30 拒开工输出模板 |
3
+ 从本目录向用户仓 **`docs/harness/prompts/`** 复制 **Starter 子集**。
4
+
5
+ ## 标准流程(V2
6
+
7
+ ```text
8
+ 人 + 00 chat 大纲
9
+ 10-spec R0–R9
10
+ 20-spec-audit + HG-SPEC-SIGNOFF(人签 · 可多轮)
11
+ 00 起草 P0 task
12
+ 10-task → 20-task-audit R1(↺ 10-task)→ HG-AUDIT-R1
13
+ 30 40(同 Agent · 自修重跑直至通过)
14
+ → 50(↺ 30 · 可选)→ CLOSE
15
+ ```
14
16
 
15
- **Starter 闭包**:10 / 22 / 30 / **40**(A2 · v0.3.x)
17
+ 详述:[`../docs/methodology/product/SDD_HAT_FLOW_v2_zh.md`](../docs/methodology/product/SDD_HAT_FLOW_v2_zh.md)
16
18
 
17
- ## 完整库(POINTER · 不复制全文)
19
+ | 10 | 20 | 人闸 |
20
+ |----|-----|------|
21
+ | 10-spec | 20-spec-audit | HG-SPEC-SIGNOFF |
22
+ | 10-task | 20-task-audit | HG-AUDIT-R1 |
18
23
 
19
- Extended 帽(00/20/40/50、链式 PROMPT)由维护者在 **私有工作区或签约伙伴仓** 维护 · **不**默认复制进用户仓。
24
+ ## Starter(本目录)
20
25
 
21
- 嵌入用户仓后可在 README 追加(示例):
26
+ | 文件 | hat_id | 说明 |
27
+ |------|--------|------|
28
+ | [`10-requirements.md`](./10-requirements.md) | 10-task | task §5 思考 |
29
+ | [`22-task-audit.md`](./22-task-audit.md) | 20-task-audit | reviews/ · HG-AUDIT-R1 |
30
+ | [`30-execute-code.md`](./30-execute-code.md) | 30 | 实现 · **含 40 自检闭环** |
31
+ | [`40-self-check.md`](./40-self-check.md) | 40 | 与 30 同 Agent · 规则分文件 |
32
+ | [`TEMPLATE_30_gate_stop.md`](./TEMPLATE_30_gate_stop.md) | — | 30 拒开工 |
22
33
 
23
- ```markdown
24
- ## 完整 Harness 库
25
- - 上游:你的组织/monorepo `docs/harness/prompts/`(只读对照 · 非 Starter 默认)
26
- ```
34
+ Extended(10-spec / 20-spec-audit / 00 / 50):工作区 `docs/harness/prompts/` · 见 [`SDD_HAT_FLOW_v2_zh.md`](../docs/methodology/product/SDD_HAT_FLOW_v2_zh.md) §4。
27
35
 
28
- ## 链式执行
36
+ ## 修订记录
29
37
 
30
- - 串行 Task 链:维护者工作区链式 PROMPT(M3 `harness ctx` 前手工 `@` 引用)
31
- - 每帽 invoke:[`../invokes/TEMPLATE_invoke.md`](../invokes/TEMPLATE_invoke.md)
38
+ | 日期 | 摘要 |
39
+ |------|------|
40
+ | 2026-06-21 | V2 标准流程 · 30→40 同 Agent · 50/CLOSE 打回 |
@@ -8,7 +8,7 @@
8
8
 
9
9
  ```bash
10
10
  # 30 前聚合验证(gate-check + audit D5 + S5 warn + 可选 --graph)
11
- npx @cyning/harness verify [--target .] [--task docs/tasks/active/task_xxx.md] [--graph]
11
+ npx @cyning/harness verify [--target .] [--task docs/tasks/active/task_xxx.md] [--graph] [--json] [--agent-hint] [--workspace-root PATH]
12
12
 
13
13
  # 仅人工闸 npx @cyning/harness gate-check [--target .] [--task ...] [--graph] [--json]
14
14
 
package/lib/cli.js CHANGED
@@ -38,7 +38,7 @@ function usage(version = 'unknown') {
38
38
  npx @cyning/harness check [--target PATH]
39
39
  npx @cyning/harness audit [--target PATH] [--task FILE]
40
40
  npx @cyning/harness gate-check [--target PATH] [--task FILE] [--graph] [--json]
41
- npx @cyning/harness verify [--target PATH] [--task FILE] [--graph]
41
+ npx @cyning/harness verify [--target PATH] [--task FILE] [--graph] [--json] [--agent-hint] [--workspace-root PATH]
42
42
  npx @cyning/harness sync index [--target PATH]
43
43
  npx @cyning/harness task check --file PATH [--no-circular] [--registry DIR]...
44
44
  npx @cyning/harness graph yaml compile --graph-id ID [--input DIR] [--output FILE]
@@ -375,12 +375,15 @@ async function cmdGateCheck(args) {
375
375
 
376
376
  async function cmdVerify(args) {
377
377
  // D3 · implemented in lib/verify.js
378
- const { verifyTarget } = await import('./verify.js');
378
+ const { verifyTarget, buildVerifyHandoff, formatAgentHint } = await import('./verify.js');
379
379
  if (args.includes('--help') || args.includes('-h')) {
380
- console.log(`用法: npx @cyning/harness verify [--target PATH] [--task FILE] [--graph]
380
+ console.log(`用法: npx @cyning/harness verify [--target PATH] [--task FILE] [--graph] [--json] [--agent-hint] [--workspace-root PATH]
381
381
 
382
382
  无 --task 时:扫描 docs/tasks/active/task_*.md 并逐项列出闸表(同 gate-check)。
383
383
  任一 task 阻塞则 VERIFY: BLOCKED · exit 2;全部可 30 则 VERIFY: PASS。
384
+ --json:输出 Agent handoff JSON(may_start_30 · entry_invoke_30 · review_path 等)。
385
+ --agent-hint:人类可读 handoff 摘要(可与 --json 同用)。
386
+ --workspace-root:解析 task 中 Projects/ 前缀的 entry_invoke 路径。
384
387
  `);
385
388
  return;
386
389
  }
@@ -390,8 +393,12 @@ async function cmdVerify(args) {
390
393
  rest = r1;
391
394
  const { value: taskFile, rest: r2 } = takeOption(rest, '--task');
392
395
  rest = r2;
396
+ const { value: workspaceRoot, rest: r3 } = takeOption(rest, '--workspace-root');
397
+ rest = r3;
393
398
  const graph = rest.includes('--graph');
394
- rest = rest.filter((a) => a !== '--graph');
399
+ const json = rest.includes('--json');
400
+ const agentHint = rest.includes('--agent-hint');
401
+ rest = rest.filter((a) => a !== '--graph' && a !== '--json' && a !== '--agent-hint');
395
402
 
396
403
  if (rest.length > 0) {
397
404
  const err = new Error(`verify 未知参数: ${rest.join(' ')}`);
@@ -402,15 +409,37 @@ async function cmdVerify(args) {
402
409
  const target = resolveTarget(process.cwd(), targetArg);
403
410
  const result = verifyTarget(target, { taskFile, graph });
404
411
 
405
- // forward gate-check/audit/graph stdout if any
406
- if (result.stdout) {
412
+ // forward gate-check/audit/graph stdout if any(--json 时仍透传机械闸表)
413
+ if (result.stdout && !json) {
407
414
  process.stdout.write(result.stdout);
415
+ } else if (result.stdout && json) {
416
+ // JSON 模式:闸表走 stderr 避免污染 stdout JSON
417
+ process.stderr.write(result.stdout);
408
418
  }
409
419
 
410
- const summary = taskFile
411
- ? `VERIFY: ${result.ok ? 'PASS' : 'BLOCKED'}${result.reason ? ` · ${result.reason}` : ''} · ${path.basename(taskFile)}`
412
- : `VERIFY: ${result.ok ? 'PASS' : 'BLOCKED'}${result.reason ? ` · ${result.reason}` : ''}`;
413
- console.log(summary);
420
+ const handoff = buildVerifyHandoff(target, {
421
+ taskFile,
422
+ workspaceRoot: workspaceRoot ? path.resolve(workspaceRoot) : undefined,
423
+ });
424
+
425
+ if (json) {
426
+ console.log(JSON.stringify(handoff, null, 2));
427
+ }
428
+ if (agentHint) {
429
+ console.log(formatAgentHint(handoff));
430
+ }
431
+
432
+ if (!json && !agentHint) {
433
+ const summary = taskFile
434
+ ? `VERIFY: ${result.ok ? 'PASS' : 'BLOCKED'}${result.reason ? ` · ${result.reason}` : ''} · ${path.basename(taskFile)}`
435
+ : `VERIFY: ${result.ok ? 'PASS' : 'BLOCKED'}${result.reason ? ` · ${result.reason}` : ''}`;
436
+ console.log(summary);
437
+ } else if (json || agentHint) {
438
+ const summary = taskFile
439
+ ? `VERIFY: ${result.ok ? 'PASS' : 'BLOCKED'}${result.reason ? ` · ${result.reason}` : ''} · ${path.basename(taskFile)}`
440
+ : `VERIFY: ${result.ok ? 'PASS' : 'BLOCKED'}${result.reason ? ` · ${result.reason}` : ''}`;
441
+ console.error(summary);
442
+ }
414
443
 
415
444
  if (!result.ok) {
416
445
  process.exitCode = result.exitCode ?? 2;
@@ -0,0 +1,223 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ const META_FIELD_RE = /\|\s*\*\*([^*]+)\*\*\s*\|\s*`([^`]+)`/;
5
+ const GATE_ROW_RE = /^\|\s*(?:\*\*)?([^*|]+?)(?:\*\*)?\s*\|\s*([^|]+)\|\s*([^|]+)\|\s*([^|]*)\|/;
6
+
7
+ /**
8
+ * 从 task Markdown 解析 Harness 元信息表(反引号包裹的值)。
9
+ */
10
+ export function parseHarnessMeta(content) {
11
+ const meta = {};
12
+ const section = extractSection(content, '## Harness 元信息', '###');
13
+ if (!section) return meta;
14
+
15
+ for (const line of section.split('\n')) {
16
+ const match = line.match(META_FIELD_RE);
17
+ if (!match) continue;
18
+ const key = match[1].trim();
19
+ meta[key] = match[2].trim();
20
+ }
21
+ return meta;
22
+ }
23
+
24
+ /**
25
+ * 解析 ### 人工闸 表。
26
+ */
27
+ export function parseHumanGates(content) {
28
+ const section = extractSection(content, '### 人工闸', '##');
29
+ if (!section) return [];
30
+
31
+ const gates = [];
32
+ for (const line of section.split('\n')) {
33
+ const match = line.match(GATE_ROW_RE);
34
+ if (!match) continue;
35
+ const id = match[1].trim();
36
+ if (!id.startsWith('HG-') || id.includes('human_gate')) continue;
37
+ gates.push({
38
+ id,
39
+ status: normalizeCell(match[2]),
40
+ blocks_hats: normalizeCell(match[3]),
41
+ note: normalizeCell(match[4]),
42
+ });
43
+ }
44
+ return gates;
45
+ }
46
+
47
+ /**
48
+ * 机械判定 may_start_30(与 gate-check.sh 一致)。
49
+ */
50
+ export function evaluateMayStart30(gates) {
51
+ const byId = Object.fromEntries(gates.map((g) => [g.id, g]));
52
+ const draft = byId['HG-TASK-DRAFT'];
53
+ const audit = byId['HG-AUDIT-R1'];
54
+ const graph = byId['HG-GRAPH-MODULES'];
55
+
56
+ if (audit?.status !== 'approved') {
57
+ return {
58
+ may_start_30: false,
59
+ blocked_reason: 'HG-AUDIT-R1 非 approved(须维护者签 task 表)',
60
+ };
61
+ }
62
+
63
+ if (
64
+ draft?.status !== 'approved' &&
65
+ draft?.blocks_hats?.includes('30')
66
+ ) {
67
+ return {
68
+ may_start_30: false,
69
+ blocked_reason: 'HG-TASK-DRAFT pending 且 blocks 30',
70
+ };
71
+ }
72
+
73
+ if (graph?.status === 'pending') {
74
+ return {
75
+ may_start_30: false,
76
+ blocked_reason: 'HG-GRAPH-MODULES pending',
77
+ };
78
+ }
79
+
80
+ return { may_start_30: true, blocked_reason: null };
81
+ }
82
+
83
+ /**
84
+ * 解析 Projects/ 前缀路径;需 workspaceRoot 才 resolve。
85
+ */
86
+ export function resolveInvokePath(rawPath, { target, workspaceRoot } = {}) {
87
+ if (!rawPath) {
88
+ return { resolved: null, warnings: [] };
89
+ }
90
+
91
+ const warnings = [];
92
+
93
+ if (rawPath.startsWith('Projects/')) {
94
+ if (!workspaceRoot) {
95
+ warnings.push(
96
+ `entry_invoke 路径以 Projects/ 开头但未提供 --workspace-root:${rawPath}`,
97
+ );
98
+ return { resolved: null, warnings };
99
+ }
100
+ const abs = path.join(workspaceRoot, rawPath.replace(/^Projects\//, ''));
101
+ if (!fs.existsSync(abs)) {
102
+ warnings.push(`entry_invoke 路径不存在:${rawPath}`);
103
+ }
104
+ return { resolved: abs, warnings };
105
+ }
106
+
107
+ if (path.isAbsolute(rawPath)) {
108
+ if (!fs.existsSync(rawPath)) {
109
+ warnings.push(`entry_invoke 路径不存在:${rawPath}`);
110
+ }
111
+ return { resolved: rawPath, warnings };
112
+ }
113
+
114
+ const abs = path.resolve(target ?? process.cwd(), rawPath);
115
+ if (!fs.existsSync(abs)) {
116
+ warnings.push(`entry_invoke 路径不存在(相对 target):${rawPath}`);
117
+ }
118
+ return { resolved: abs, warnings };
119
+ }
120
+
121
+ /**
122
+ * 查找最新 R1 审查文(相对 target)。
123
+ */
124
+ export function findReviewPath(target, taskFile) {
125
+ const reviewsDir = path.join(target, 'docs/harness/reviews');
126
+ if (!fs.existsSync(reviewsDir)) return null;
127
+
128
+ const taskBase = path.basename(taskFile, '.md');
129
+ const prefix = `${taskBase}_audit_R1_`;
130
+
131
+ let best = null;
132
+ let bestMtime = 0;
133
+
134
+ for (const name of fs.readdirSync(reviewsDir)) {
135
+ if (!name.startsWith(prefix) || !name.endsWith('.md')) continue;
136
+ const full = path.join(reviewsDir, name);
137
+ const mtime = fs.statSync(full).mtimeMs;
138
+ if (mtime > bestMtime) {
139
+ bestMtime = mtime;
140
+ best = path.relative(target, full).replace(/\\/g, '/');
141
+ }
142
+ }
143
+
144
+ return best;
145
+ }
146
+
147
+ /**
148
+ * 聚合单 task 的 Agent handoff 结果。
149
+ */
150
+ export function buildTaskHandoff(target, taskFile, options = {}) {
151
+ const { workspaceRoot } = options;
152
+ const absTask = path.isAbsolute(taskFile)
153
+ ? taskFile
154
+ : path.join(target, taskFile);
155
+
156
+ const content = fs.readFileSync(absTask, 'utf8');
157
+ const meta = parseHarnessMeta(content);
158
+ const gates = parseHumanGates(content);
159
+ const gateEval = evaluateMayStart30(gates);
160
+
161
+ const warnings = [];
162
+ const entry30Raw = meta.entry_invoke_30 ?? null;
163
+ const entry20Raw = meta.entry_invoke_20 ?? null;
164
+
165
+ const entry30 = resolveInvokePath(entry30Raw, { target, workspaceRoot });
166
+ const entry20 = resolveInvokePath(entry20Raw, { target, workspaceRoot });
167
+ warnings.push(...entry30.warnings, ...entry20.warnings);
168
+
169
+ const reviewPath = findReviewPath(target, taskFile);
170
+
171
+ return {
172
+ task: path.basename(taskFile),
173
+ task_path: taskFile.replace(/\\/g, '/'),
174
+ task_slug: meta.task_slug ?? null,
175
+ may_start_30: gateEval.may_start_30,
176
+ blocked_reason: gateEval.blocked_reason,
177
+ review_path: reviewPath,
178
+ entry_invoke_20: entry20Raw,
179
+ entry_invoke_20_resolved: entry20.resolved,
180
+ entry_invoke_30: entry30Raw,
181
+ entry_invoke_30_resolved: entry30.resolved,
182
+ next_hat: gateEval.may_start_30 ? '30' : null,
183
+ agent_preamble: gateEval.may_start_30
184
+ ? '首输出 GATE_VERIFY 闸扫描表,再读 entry_invoke_30 开 30 帽。'
185
+ : null,
186
+ warnings,
187
+ gates: gates.map((g) => ({
188
+ id: g.id,
189
+ status: g.status,
190
+ blocks_hats: g.blocks_hats,
191
+ })),
192
+ };
193
+ }
194
+
195
+ /**
196
+ * 扫描 target 下 active task 列表。
197
+ */
198
+ export function listActiveTasks(target) {
199
+ const activeDir = path.join(target, 'docs/tasks/active');
200
+ if (!fs.existsSync(activeDir)) return [];
201
+
202
+ return fs
203
+ .readdirSync(activeDir)
204
+ .filter((f) => f.startsWith('task_') && f.endsWith('.md'))
205
+ .map((f) => path.join('docs/tasks/active', f).replace(/\\/g, '/'))
206
+ .sort();
207
+ }
208
+
209
+ function extractSection(content, startMarker, endMarker) {
210
+ const start = content.indexOf(startMarker);
211
+ if (start === -1) return null;
212
+
213
+ let end = content.length;
214
+ if (endMarker) {
215
+ const next = content.indexOf(endMarker, start + startMarker.length);
216
+ if (next !== -1) end = next;
217
+ }
218
+ return content.slice(start, end);
219
+ }
220
+
221
+ function normalizeCell(cell) {
222
+ return cell.replace(/\*/g, '').trim();
223
+ }
package/lib/verify.js CHANGED
@@ -3,6 +3,7 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { resolveHarnessRootForTarget, wizardScript } from './paths.js';
5
5
  import { auditTarget } from './audit.js';
6
+ import { buildTaskHandoff, listActiveTasks } from './task-meta.js';
6
7
 
7
8
  /**
8
9
  * 30 前聚合验证:gate-check + audit D5(仅 --task)+ S5 git-clean warn + 可选 --graph
@@ -150,3 +151,71 @@ function runGitCleanCheck(target) {
150
151
  const dirty = result.status === 0 && result.stdout.trim().length > 0;
151
152
  return { dirty };
152
153
  }
154
+
155
+ /**
156
+ * 构建 verify --json / --agent-hint 的 Agent handoff 载荷。
157
+ */
158
+ export function buildVerifyHandoff(target, options = {}) {
159
+ const { taskFile, workspaceRoot } = options;
160
+ const handoffOpts = { workspaceRoot };
161
+
162
+ if (taskFile) {
163
+ const handoff = buildTaskHandoff(target, taskFile, handoffOpts);
164
+ return {
165
+ schema_version: '1',
166
+ verify_ok: handoff.may_start_30,
167
+ ...handoff,
168
+ };
169
+ }
170
+
171
+ const taskFiles = listActiveTasks(target);
172
+ const tasks = taskFiles.map((tf) => {
173
+ const handoff = buildTaskHandoff(target, tf, handoffOpts);
174
+ return {
175
+ schema_version: '1',
176
+ verify_ok: handoff.may_start_30,
177
+ ...handoff,
178
+ };
179
+ });
180
+
181
+ const verifyOk = tasks.length > 0 && tasks.every((t) => t.may_start_30);
182
+ return {
183
+ schema_version: '1',
184
+ verify_ok: verifyOk,
185
+ tasks,
186
+ };
187
+ }
188
+
189
+ /**
190
+ * 人类可读的 --agent-hint(5–10 行)。
191
+ */
192
+ export function formatAgentHint(payload) {
193
+ const lines = ['=== Harness verify · agent-hint ==='];
194
+
195
+ const items = payload.tasks ?? [payload];
196
+ for (const item of items) {
197
+ lines.push(`task: ${item.task}`);
198
+ lines.push(` may_start_30: ${item.may_start_30}`);
199
+ if (item.blocked_reason) {
200
+ lines.push(` blocked: ${item.blocked_reason}`);
201
+ }
202
+ if (item.review_path) {
203
+ lines.push(` review: ${item.review_path}`);
204
+ }
205
+ if (item.entry_invoke_30) {
206
+ lines.push(` entry_invoke_30: ${item.entry_invoke_30}`);
207
+ if (item.entry_invoke_30_resolved) {
208
+ lines.push(` resolved: ${item.entry_invoke_30_resolved}`);
209
+ }
210
+ }
211
+ if (item.next_hat) {
212
+ lines.push(` next_hat: ${item.next_hat}`);
213
+ }
214
+ for (const w of item.warnings ?? []) {
215
+ lines.push(` warn: ${w}`);
216
+ }
217
+ lines.push('');
218
+ }
219
+
220
+ return lines.join('\n').trimEnd();
221
+ }