@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.
- package/AGENTS.md +4 -3
- package/CHANGELOG.md +41 -0
- package/README.md +7 -5
- package/docs/ARCHITECTURE.md +2 -1
- package/docs/USER_GUIDE_v1.0_zh.md +25 -1
- package/docs/methodology/execution/PROMPT_review_v203_v2_hat_flow_bump_v1_zh.md +136 -0
- package/docs/methodology/execution/README.md +1 -0
- package/docs/methodology/execution/reviews/review_v2_hat_flow_20260621.md +71 -0
- package/docs/methodology/pointers/SDD_HAT_FLOW_v1_zh.md +5 -4
- package/docs/methodology/product/DESIGN_ONTOLOGY_v1_zh.md +100 -47
- package/docs/methodology/product/README.md +2 -1
- package/docs/methodology/product/SDD_HAT_FLOW_v2_zh.md +78 -0
- package/examples/compliance_bench/S6_agent_handoff/.cyning-harness/manifest.json +1 -0
- package/examples/compliance_bench/S6_agent_handoff/docs/harness/invokes/by-task/s6/PROMPT_START_30_v1.md +3 -0
- package/examples/compliance_bench/S6_agent_handoff/docs/harness/reviews/task_s6_agent_handoff_audit_R1_20260618.md +3 -0
- package/examples/compliance_bench/S6_agent_handoff/task_s6_agent_handoff.md +31 -0
- package/harness/prompts/10-requirements.md +3 -1
- package/harness/prompts/22-task-audit.md +3 -1
- package/harness/prompts/30-execute-code.md +2 -1
- package/harness/prompts/40-self-check.md +7 -4
- package/harness/prompts/FRAGMENT_30_gate_verify_v1_zh.md +1 -1
- package/harness/prompts/README.md +31 -22
- package/harness/templates/QUICKREF_v1_zh.md +1 -1
- package/lib/cli.js +39 -10
- package/lib/task-meta.js +223 -0
- package/lib/verify.js +69 -0
- package/ontology.yaml +27 -7
- package/package.json +1 -1
- package/schema/verify_result.v1.schema.json +65 -0
- package/wizard/compliance-bench.sh +34 -7
- 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.
|
|
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,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
|
-
>
|
|
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
|
-
>
|
|
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
|
## 身份
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
## 身份
|
|
7
7
|
|
|
8
|
-
|
|
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
|
|
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-
|
|
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
|
-
|
|
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
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
17
|
+
详述:[`../docs/methodology/product/SDD_HAT_FLOW_v2_zh.md`](../docs/methodology/product/SDD_HAT_FLOW_v2_zh.md)
|
|
16
18
|
|
|
17
|
-
|
|
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
|
-
|
|
24
|
+
## Starter(本目录)
|
|
20
25
|
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
|
411
|
-
|
|
412
|
-
:
|
|
413
|
-
|
|
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;
|
package/lib/task-meta.js
ADDED
|
@@ -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
|
+
}
|