@cyning/harness 2.0.0 → 2.0.3
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 +41 -1
- package/docs/USER_GUIDE_v1.0_zh.md +44 -6
- package/docs/methodology/ROADMAP_v1_zh.md +3 -3
- package/docs/methodology/graph/HGM_UPGRADE_OUTLINE_v1_zh.md +8 -6
- package/examples/compliance_bench/README.md +7 -7
- package/examples/compliance_bench/S5_rejected_draft/task.md +33 -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/FRAGMENT_30_gate_verify_v1_zh.md +1 -1
- package/harness/templates/QUICKREF_v1_zh.md +1 -1
- package/lib/cli.js +40 -11
- package/lib/graph-hgm.js +100 -15
- package/lib/task-meta.js +223 -0
- package/lib/verify.js +69 -0
- package/package.json +1 -1
- package/schema/verify_result.v1.schema.json +65 -0
- package/wizard/compliance-bench.sh +78 -8
- package/wizard/lib/generate-invoke-index.js +63 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,46 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.0.2] - 2026-06-18
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **A6 Agent handoff**:`verify --json` · `--agent-hint` · `--workspace-root`
|
|
12
|
+
- **lib/task-meta.js**:解析 task Harness 表 `entry_invoke_*` · 人工闸 · `may_start_30`
|
|
13
|
+
- **schema/verify_result.v1.schema.json**:JSON 输出文档化
|
|
14
|
+
- **sync index**:索引 task `entry_points.{10,20,30}`
|
|
15
|
+
- **compliance-bench S6**:`S6_agent_handoff/` · `--all` 含 S6(6/6 = 100%)
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **FRAGMENT_30**:verify 从「推荐」→ **「30 改码前必须」**
|
|
20
|
+
- **USER_GUIDE** §5.1 Agent handoff · **QUICKREF** 新 flags
|
|
21
|
+
- **版本**:`package.json` → **2.0.2**
|
|
22
|
+
|
|
23
|
+
### Notes
|
|
24
|
+
|
|
25
|
+
- **HG-RELEASE**:待维护者 publish `@cyning/harness@2.0.2`
|
|
26
|
+
- dogfood:`kimi-code-meta` · `task_meta_graph_issue_sync_gate_v1.md`
|
|
27
|
+
|
|
28
|
+
## [2.0.1] - 2026-06-17
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **D4-a 公理**:`checkAxioms()` 检测 in_progress + `HG-GRAPH-MODULES` pending/rejected · MUST_READ 缺 Inform 可选 warn
|
|
33
|
+
- **compliance-bench S5**:`S5_rejected_draft/` · rejected→draft 静态夹具 · `--all` 含 S5(5/5 = 100%)
|
|
34
|
+
- **optional pre-commit**:`.cyning-harness/hooks/pre-commit.sample` · USER_GUIDE §13.3 · 默认 init 不安装
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- **rejected→draft**:事件流精确匹配 `TaskStatusChanged(draft)` · 有回退则 pass · 缺则 error
|
|
39
|
+
- **版本**:`package.json` → **2.0.1**
|
|
40
|
+
- **USER_GUIDE**:§13 公理集 · optional Git hooks
|
|
41
|
+
|
|
42
|
+
### Notes
|
|
43
|
+
|
|
44
|
+
- **HG-RELEASE**:待维护者 publish `@cyning/harness@2.0.1`
|
|
45
|
+
- bench S5 语义向 HGM axioms 靠拢 · drift 时改夹具不改 axioms
|
|
46
|
+
|
|
7
47
|
## [2.0.0] - 2026-06-17
|
|
8
48
|
|
|
9
49
|
### Added
|
|
@@ -23,7 +63,7 @@
|
|
|
23
63
|
|
|
24
64
|
### Notes
|
|
25
65
|
|
|
26
|
-
- **HG-RELEASE
|
|
66
|
+
- **HG-RELEASE**:tag v2.0.0 · npm `@cyning/harness@2.0.0` 已发布
|
|
27
67
|
- `gate-check --graph` 语义不变;HGM 独立子命令
|
|
28
68
|
- HGM v2.0 默认本地 JSONL + snapshot,不含 Neo4j / SQLite / 远端同步
|
|
29
69
|
|
|
@@ -114,6 +114,12 @@ npx @cyning/harness@1.0.1 init --preset harness-only --ide cursor,agents --yes
|
|
|
114
114
|
# 30 前聚合验证(gate-check + audit D5 + S5 warn + 可选 --graph)
|
|
115
115
|
npx @cyning/harness verify --target . --task docs/tasks/active/task_xxx.md
|
|
116
116
|
|
|
117
|
+
# Agent handoff(v2.0.2+):JSON 路由 + 下一帽提示
|
|
118
|
+
npx @cyning/harness verify --target . \
|
|
119
|
+
--task docs/tasks/active/task_xxx.md \
|
|
120
|
+
[--json] [--agent-hint] \
|
|
121
|
+
[--workspace-root /path/to/Projects]
|
|
122
|
+
|
|
117
123
|
# 仅人工闸
|
|
118
124
|
npx @cyning/harness gate-check --target . --task docs/tasks/active/task_xxx.md
|
|
119
125
|
npx @cyning/harness gate-check --graph --target . # Inform 图谱闸
|
|
@@ -131,6 +137,24 @@ npx @cyning/harness gate-check --graph --target . # Inform 图谱闸
|
|
|
131
137
|
| `HG-GRAPH-MODULES` | 架构模块表人签 | pending → 拒改码 30 |
|
|
132
138
|
| `HG-RELEASE` | 发版闸(产品仓) | 一般业务仓不涉及 |
|
|
133
139
|
|
|
140
|
+
### 5.1 Agent handoff(v2.0.2+)
|
|
141
|
+
|
|
142
|
+
`verify --json` 从 task Harness 表解析 `entry_invoke_30` 等字段,输出 Agent 可消费的机械路由(不读取 Projects 文件正文):
|
|
143
|
+
|
|
144
|
+
| JSON 字段 | 含义 |
|
|
145
|
+
| --- | --- |
|
|
146
|
+
| `may_start_30` | 等价 gate-check「可 30」 |
|
|
147
|
+
| `blocked_reason` | 阻塞时 D2/D3 文案 |
|
|
148
|
+
| `review_path` | 最新 `*_audit_R1_*.md`(相对 target) |
|
|
149
|
+
| `entry_invoke_30` | task 表原始路径 |
|
|
150
|
+
| `entry_invoke_30_resolved` | 绝对路径(`Projects/` 前缀须 `--workspace-root`) |
|
|
151
|
+
| `next_hat` | `"30"` 或 `null` |
|
|
152
|
+
| `agent_preamble` | 短句提醒首输出 GATE_VERIFY |
|
|
153
|
+
|
|
154
|
+
Schema:[`schema/verify_result.v1.schema.json`](../schema/verify_result.v1.schema.json)
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
134
158
|
**Inform 图谱闸(v1.0)**:改码类 task 前,确保 `docs/_tech_graph/` 模块表已维护者签 `HG-GRAPH-MODULES = approved`。存量大仓可按 [`ONBOARDING.md`](./ONBOARDING.md) §3 选 S0–S3 档位,**不必一次画完所有 Mermaid**。
|
|
135
159
|
|
|
136
160
|
---
|
|
@@ -142,7 +166,7 @@ npx @cyning/harness gate-check --graph --target . # Inform 图谱闸
|
|
|
142
166
|
| `npx @cyning/harness init` | 首次安装模板与 manifest(可选 `--with-scripts`) |
|
|
143
167
|
| `npx @cyning/harness upgrade` | 同步产品包更新(可加 `--gate-check` 先 audit) |
|
|
144
168
|
| `npx @cyning/harness check` | 检查是否有新版本 |
|
|
145
|
-
| `npx @cyning/harness verify` | 30 前聚合:gate-check + audit D5 + S5 warn + 可选 `--graph` |
|
|
169
|
+
| `npx @cyning/harness verify` | 30 前聚合:gate-check + audit D5 + S5 warn + 可选 `--graph` · v2.0.2+ `--json` / `--agent-hint` / `--workspace-root` |
|
|
146
170
|
| `npx @cyning/harness gate-check` | 仅人工闸(`--graph` / `--json`) |
|
|
147
171
|
| `npx @cyning/harness audit` | ICVO 机械审计(D3/D5/S5) |
|
|
148
172
|
| `npx @cyning/harness sync index` | 生成 `.cyning-harness/invoke_index.json` |
|
|
@@ -276,12 +300,12 @@ npx @cyning/harness graph yaml check --all --input docs/_tech_graph
|
|
|
276
300
|
| 项 | 说明 |
|
|
277
301
|
| --- | --- |
|
|
278
302
|
| 不是胜率工具 | [`README` 试点证据 B2](../README.md) · 完整表 [`PILOT_EVIDENCE_B2_v1_zh.md`](./methodology/execution/PILOT_EVIDENCE_B2_v1_zh.md) · **小样本机制证据**,不可外推 |
|
|
279
|
-
| bench `100` | [SDD-Compliance](../examples/compliance_bench/README.md)
|
|
303
|
+
| bench `100` | [SDD-Compliance](../examples/compliance_bench/README.md) 五场景合规率 · 见上文 §6.1 |
|
|
280
304
|
| Extended 帽 | 00/50/链式 PROMPT 不在 Starter 默认包 · 见 [`harness/prompts/README.md`](../harness/prompts/README.md) |
|
|
281
305
|
| Inform-YAML | **v1.1+** · 可选编辑源 · 须 `graph yaml check` 与 `graph.json` 一致 |
|
|
282
306
|
| HGM / 图数据库 | **v2.0+ 已实现** `graph ingest|snapshot|axioms` · 本地 JSONL + snapshot;Neo4j / 远端同步 **仍提案** |
|
|
283
307
|
| Agent-shell | 研究轨 #9,非 npm 功能 |
|
|
284
|
-
| rejected→draft | bench S5
|
|
308
|
+
| rejected→draft | **v2.0.1+** bench S5 + HGM axioms 事件流精确匹配 |
|
|
285
309
|
|
|
286
310
|
---
|
|
287
311
|
|
|
@@ -327,18 +351,31 @@ npx @cyning/harness graph ingest --target /path/to/your-repo
|
|
|
327
351
|
# 事件重放 → .cyning-harness/graph/snapshot.json
|
|
328
352
|
npx @cyning/harness graph snapshot --target /path/to/your-repo
|
|
329
353
|
|
|
330
|
-
# 公理检查(D2 · D3 · S2 · rejected→draft)
|
|
354
|
+
# 公理检查(D2 · D3 · D4-a · S2 · rejected→draft)
|
|
331
355
|
npx @cyning/harness graph axioms check --target /path/to/your-repo
|
|
332
356
|
npx @cyning/harness graph axioms check --target /path/to/your-repo --json
|
|
333
357
|
```
|
|
334
358
|
|
|
335
|
-
|
|
359
|
+
**公理最小集(v2.0.1)**:D2 · D3 · **D4-a**(`HG-GRAPH-MODULES` pending/rejected + in_progress)· rejected→draft(事件流精确匹配)· S2。
|
|
360
|
+
|
|
361
|
+
### 13.3 可选 · Git hooks
|
|
362
|
+
|
|
363
|
+
`init` **默认不安装** pre-commit。维护者可手动复制:
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
cp .cyning-harness/hooks/pre-commit.sample .git/hooks/pre-commit
|
|
367
|
+
chmod +x .git/hooks/pre-commit
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
示例 hook 调用 `graph ingest --target`(幂等增量)· 失败默认 **warn** 不阻断 commit(可改 `exit 1`)。
|
|
371
|
+
|
|
372
|
+
### 13.4 与 Inform-YAML 的接口
|
|
336
373
|
|
|
337
374
|
- `InformArtifact` 节点 ID:`inform:{repo_rel_path}`
|
|
338
375
|
- Task → InformArtifact 边:`MUST_READ`
|
|
339
376
|
- HGM **不存储** Inform 正文,只存路径指针与 schema 版本
|
|
340
377
|
|
|
341
|
-
### 13.
|
|
378
|
+
### 13.5 局限
|
|
342
379
|
|
|
343
380
|
- v2.0 默认本地 JSONL + snapshot;**不含** Neo4j / SQLite / 远端同步
|
|
344
381
|
- 多仓聚合、时光机重建任意时点图:v2.1+ 提案
|
|
@@ -370,4 +407,5 @@ npx @cyning/harness graph axioms check --target /path/to/your-repo --json
|
|
|
370
407
|
| 2026-06-16 | 补全相对链接 · §6.1 compliance-bench · §12 阅读索引 |
|
|
371
408
|
| 2026-06-16 | v1.0.1:verify / gate-check / sync index CLI · `--with-scripts` · QUICKREF |
|
|
372
409
|
| 2026-06-17 | v1.1.0:新增 §10 Inform-YAML · `graph yaml compile|check` · 三轨边界说明 |
|
|
410
|
+
| 2026-06-17 | v2.0.1:D4-a axioms · rejected→draft 精确化 · S5 bench · optional pre-commit hook |
|
|
373
411
|
| 2026-06-17 | v2.0.0:新增 §13 HGM 过程轨 · `graph ingest|snapshot|axioms` · InformArtifact 与 MUST_READ 边 |
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
|
|
59
59
|
| 能力标签 | 代号 | 须交付(实现) | 文档 |
|
|
60
60
|
| --- | --- | --- | --- |
|
|
61
|
-
| **G1 · v2.0+** | HGM 起步 | `events/*.jsonl` · graph ingest · snapshot · axioms check
|
|
61
|
+
| **G1 · v2.0+** | HGM 起步 | `events/*.jsonl` · graph ingest · snapshot · axioms check 子集(**v2.0.0 已实现**) | HGM design v0.1→v0.2 · [`task_g1`](../../../../docs/harness/tasks/done/cyning-harness/task_cyning_harness_g1_hgm_v2_v1.md) |
|
|
62
62
|
| **G2 · v2.1+** | HGM 查询 | timeline · patterns · SQLite 投影 | 可选 Neo4j 导出 |
|
|
63
63
|
|
|
64
64
|
**刻意不做(至 v1.0)**:GrowingReasoningAgent · OWL 推理机 · 热插拔 Track(Q5)· Neo4j 默认后端 · **Track G ingest(v1.0 前)**。
|
|
@@ -118,8 +118,8 @@ Epic 与子 task 真值:`Projects/docs/harness/tasks/active/`
|
|
|
118
118
|
| #7 A4 v1.0 | v1.0.0 · 引用 #8 |
|
|
119
119
|
| **#8 B2 + bench** | Track B · **1+3 主证据** · P0 绿后优先 |
|
|
120
120
|
| **#9 Agent-shell** | Track B · **后置** · HGM+agent 后 | deferred · 见 §8 |
|
|
121
|
-
| **I-YAML** | Track A · v1.1+ Inform | [`task_y1`](../../../../docs/harness/tasks/
|
|
122
|
-
| **Track G G1** | HGM v2.0 | [`task_g1`](../../../../docs/harness/tasks/
|
|
121
|
+
| **I-YAML** | Track A · v1.1+ Inform | [`task_y1`](../../../../docs/harness/tasks/done/cyning-harness/task_cyning_harness_y1_yaml_inform_v19_v1.md) · YAML P0 后 |
|
|
122
|
+
| **Track G G1** | HGM v2.0 | [`task_g1`](../../../../docs/harness/tasks/done/cyning-harness/task_cyning_harness_g1_hgm_v2_v1.md) · I-YAML + G0 后 |
|
|
123
123
|
|
|
124
124
|
**证据策略(2026-06-15 锁定)**:
|
|
125
125
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
| **设计真值(已有)** | [`HARNESS_GRAPH_MODEL_design_v0_zh.md`](./HARNESS_GRAPH_MODEL_design_v0_zh.md) |
|
|
10
10
|
| **语义真值** | [`../product/DESIGN_ONTOLOGY_v1_zh.md`](../product/DESIGN_ONTOLOGY_v1_zh.md) v1.2 |
|
|
11
11
|
| **L2 路线** | [`../ROADMAP_v1_zh.md`](../ROADMAP_v1_zh.md) §2.2 · §8 |
|
|
12
|
-
| **编排 task** | 工作区 [`task_cyning_harness_g1_hgm_v2_v1.md`](../../../../docs/harness/tasks/
|
|
12
|
+
| **编排 task** | 工作区 [`task_cyning_harness_g1_hgm_v2_v1.md`](../../../../docs/harness/tasks/done/cyning-harness/task_cyning_harness_g1_hgm_v2_v1.md) |
|
|
13
13
|
| **G1 维护者决策** | [`DECISIONS_hgm_g1_maintainer_v1_zh.md`](./DECISIONS_hgm_g1_maintainer_v1_zh.md) |
|
|
14
14
|
| **Post-G1 序列表** | [`POST_G1_SEQUENCE_v1_zh.md`](../../../../docs/harness/guides/POST_G1_SEQUENCE_v1_zh.md) |
|
|
15
15
|
|
|
@@ -227,8 +227,8 @@ SEM-02 维持:**主轨 v1.x · HGM 从 v2.0 主版本起** · 不写「v0.5 =
|
|
|
227
227
|
| --- | --- |
|
|
228
228
|
| Post-v1.0 序列表 | [`POST_V1_0_SEQUENCE_v1_zh.md`](../../../../docs/harness/guides/POST_V1_0_SEQUENCE_v1_zh.md) |
|
|
229
229
|
| 后端 YAML P0 | [`task_engineering_graph_yaml_p0_00_main_v1.md`](../../../../ai-ink-brain-api-python/docs/tasks/active/task_engineering_graph_yaml_p0_00_main_v1.md) |
|
|
230
|
-
| 产品 I-YAML | [`task_cyning_harness_y1_yaml_inform_v19_v1.md`](../../../../docs/harness/tasks/
|
|
231
|
-
| HGM G1 task | [`task_cyning_harness_g1_hgm_v2_v1.md`](../../../../docs/harness/tasks/
|
|
230
|
+
| 产品 I-YAML | [`task_cyning_harness_y1_yaml_inform_v19_v1.md`](../../../../docs/harness/tasks/done/cyning-harness/task_cyning_harness_y1_yaml_inform_v19_v1.md) |
|
|
231
|
+
| HGM G1 task | [`task_cyning_harness_g1_hgm_v2_v1.md`](../../../../docs/harness/tasks/done/cyning-harness/task_cyning_harness_g1_hgm_v2_v1.md) |
|
|
232
232
|
| B9(后置) | [`task_cyning_harness_b9_agent_shell_v1.md`](../../../../docs/harness/tasks/active/task_cyning_harness_b9_agent_shell_v1.md) |
|
|
233
233
|
| A5 v1.0.1 | [`task_cyning_harness_a5_cli_verify_v101_v1.md`](../../../../docs/harness/tasks/active/task_cyning_harness_a5_cli_verify_v101_v1.md) |
|
|
234
234
|
|
|
@@ -239,9 +239,11 @@ SEM-02 维持:**主轨 v1.x · HGM 从 v2.0 主版本起** · 不写「v0.5 =
|
|
|
239
239
|
| 节 | 状态 | 签收人 | 日期 |
|
|
240
240
|
| --- | --- | --- | --- |
|
|
241
241
|
| §0 决策摘要 | `confirmed` | (维护者 2026-06-16 口头) | |
|
|
242
|
-
| §3 G1 范围 | `
|
|
243
|
-
| §5 事件 schema | `
|
|
244
|
-
| §6 I-YAML 接口 | `
|
|
242
|
+
| §3 G1 范围 | `confirmed` | 维护者 | 2026-06-17 |
|
|
243
|
+
| §5 事件 schema | `confirmed` | 维护者 | 2026-06-17 |
|
|
244
|
+
| §6 I-YAML 接口 | `confirmed` | 维护者 | 2026-06-17 |
|
|
245
|
+
| §8 agent/B9 | `confirmed` | 维护者 | 2026-06-17 |
|
|
246
|
+
| **G0 整体** | `signed` | 维护者 | 2026-06-17 |
|
|
245
247
|
| §8 agent/B9 | `pending` | | |
|
|
246
248
|
| **G0 整体** | `pending` | 全表勾选后开 G1 30 帽 | |
|
|
247
249
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# SDD-Compliance micro-bench · v1
|
|
2
2
|
|
|
3
3
|
> **标题**:SDD-Compliance · **非** Agent Score / pass@1 类模型 benchmark。
|
|
4
|
-
> **范围**:S1–
|
|
5
|
-
> **状态**:v1.
|
|
4
|
+
> **范围**:S1–S5 · 公理 D2/D3/S2/rejected→draft 可机械检查部分 · 不覆盖 D5 与 Terminal-Bench。
|
|
5
|
+
> **状态**:v1.1 · G1.1 · 5/5 期望合规率 **100%**。
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
| --- | --- |
|
|
13
13
|
| **测什么** | `gate-check.sh` 与 `harness-sync.sh` 是否符合 SDD 公理(合成夹具) |
|
|
14
14
|
| **不测什么** | LLM 会不会写代码 · 业务测试绿不绿 · 真实项目胜率 |
|
|
15
|
-
| **100 是什么** | 合规率 % = PASS 场景 ÷ 总场景 × 100;
|
|
15
|
+
| **100 是什么** | 合规率 % = PASS 场景 ÷ 总场景 × 100;5/5 全过即 **100** |
|
|
16
16
|
| **和 B2 关系** | B2 Part A 是真实试点 retro;bench 是可机械复现的 **机制回归** |
|
|
17
17
|
|
|
18
18
|
---
|
|
@@ -48,8 +48,7 @@ cd /path/to/cyning-harness
|
|
|
48
48
|
| **S2** | R1 approved 无 review | D2/D3 | `S2_r1_no_review/` | 无 `*_audit_R1_*` 文件 | 只改 task 表不算真审核 |
|
|
49
49
|
| **S3** | R1 approved + review | D2/D3 | `S3_r1_with_review/` | exit=0 且 review 存在 | 审核闭环 Happy Path |
|
|
50
50
|
| **S4** | sync 域边界 | S2 | `S4_sync_domain/` + 临时仓 | plan 无 `docs/tasks/`、`reviews/` | upgrade 不覆盖用户 task |
|
|
51
|
-
|
|
52
|
-
**v1 未纳入**:**S5** rejected→draft(待 gate-check 行为稳定后加夹具)。
|
|
51
|
+
| **S5** | rejected→draft | rejected→draft | `S5_rejected_draft/` | gate rejected + task status=draft | 否决后须回 draft(与 HGM axioms 同语义) |
|
|
53
52
|
|
|
54
53
|
---
|
|
55
54
|
|
|
@@ -66,9 +65,9 @@ cd /path/to/cyning-harness
|
|
|
66
65
|
场景 结果 详情
|
|
67
66
|
S1 ✅ PASS gate-check exit=1 · 30 被拒
|
|
68
67
|
...
|
|
69
|
-
合规率
|
|
68
|
+
合规率 5/5 = 100%
|
|
70
69
|
|
|
71
|
-
解读 100 = 全部
|
|
70
|
+
解读 100 = 全部 5 个场景行为符合 SDD 公理
|
|
72
71
|
不是 LLM 解题分数 · 不可外推为「AI 胜率」
|
|
73
72
|
```
|
|
74
73
|
|
|
@@ -78,4 +77,5 @@ S1 ✅ PASS gate-check exit=1 · 30 被拒
|
|
|
78
77
|
|
|
79
78
|
| 日期 | 说明 |
|
|
80
79
|
| --- | --- |
|
|
80
|
+
| 2026-06-17 | G1.1:新增 S5 rejected→draft · 5/5 合规率 |
|
|
81
81
|
| 2026-06-16 | 丰富脚本输出 · 公理/夹具/解读 · quiet 摘要走 stderr |
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Task fixture · S5 · rejected→draft 合规态
|
|
2
|
+
|
|
3
|
+
> 用途:SDD-Compliance micro-bench S5 夹具 · gate rejected 后 task 须回 draft(与 HGM axioms 同语义)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Harness 元信息
|
|
8
|
+
|
|
9
|
+
| 字段 | 值 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| **task_slug** | `s5_rejected_draft` |
|
|
12
|
+
| **test_strategy** | `not_applicable` |
|
|
13
|
+
| **audit_profile** | `full` |
|
|
14
|
+
|
|
15
|
+
> **状态**:`draft`
|
|
16
|
+
|
|
17
|
+
### 人工闸
|
|
18
|
+
|
|
19
|
+
| human_gate_id | status | blocks_hats | 说明 |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
21
|
+
| HG-TASK-DRAFT | approved | 22, 30 | 10 完成 |
|
|
22
|
+
| HG-AUDIT-R1 | rejected | 30 | 22 R1 否决 · 已回 draft |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 背景与目标
|
|
27
|
+
|
|
28
|
+
验证 **rejected→draft** 公理:维护者否决 R1 后,task 须回到 **draft** 状态(非 in_progress)。
|
|
29
|
+
|
|
30
|
+
## 验收标准
|
|
31
|
+
|
|
32
|
+
- task 表 **status = draft** · **HG-AUDIT-R1 = rejected**
|
|
33
|
+
- bench 判 **合规**(与 `graph axioms check` rejected→draft 语义一致)
|
|
@@ -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` 非空
|
|
@@ -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 纪律
|
|
@@ -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;
|
|
@@ -750,7 +779,7 @@ async function cmdGraphAxioms(args) {
|
|
|
750
779
|
const target = resolveTarget(process.cwd(), targetArg);
|
|
751
780
|
const events = loadEvents(target);
|
|
752
781
|
const snapshot = buildSnapshot(events);
|
|
753
|
-
const result = checkAxioms(snapshot);
|
|
782
|
+
const result = checkAxioms(snapshot, events);
|
|
754
783
|
|
|
755
784
|
if (json) {
|
|
756
785
|
console.log(JSON.stringify(result, null, 2));
|
package/lib/graph-hgm.js
CHANGED
|
@@ -226,6 +226,19 @@ export function ingestRepo(target, options = {}) {
|
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
const hasRejectedGate = parsed.gates.some((g) => g.status === 'rejected');
|
|
230
|
+
if (hasRejectedGate && parsed.status === 'draft') {
|
|
231
|
+
events.push({
|
|
232
|
+
event_id: eventId(new Date(occurredAt), seq++),
|
|
233
|
+
type: 'TaskStatusChanged',
|
|
234
|
+
occurred_at: occurredAt,
|
|
235
|
+
actor,
|
|
236
|
+
subject: taskSubject,
|
|
237
|
+
data: { task_slug: parsed.task_slug, new_status: 'draft', reason: 'rejected→draft from task table' },
|
|
238
|
+
source,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
229
242
|
for (const mr of parsed.must_read) {
|
|
230
243
|
events.push({
|
|
231
244
|
event_id: eventId(new Date(occurredAt), seq++),
|
|
@@ -454,9 +467,63 @@ export function buildSnapshot(events) {
|
|
|
454
467
|
}
|
|
455
468
|
|
|
456
469
|
/**
|
|
457
|
-
*
|
|
470
|
+
* 从事件流提取 rejected 事件(GateStatusChanged rejected 或 HumanGateRejected)。
|
|
471
|
+
*/
|
|
472
|
+
function collectRejectedEvents(events) {
|
|
473
|
+
if (!events || events.length === 0) return [];
|
|
474
|
+
return events.filter(
|
|
475
|
+
(e) =>
|
|
476
|
+
e.type === 'HumanGateRejected' ||
|
|
477
|
+
(e.type === 'GateStatusChanged' && e.data?.new_status === 'rejected'),
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* rejected→draft:重放事件流,rejected 后须同 task 的 TaskStatusChanged(draft)。
|
|
483
|
+
*/
|
|
484
|
+
function checkRejectedToDraft(events) {
|
|
485
|
+
const violations = [];
|
|
486
|
+
if (!events || events.length === 0) return violations;
|
|
487
|
+
|
|
488
|
+
const sorted = [...events].sort(
|
|
489
|
+
(a, b) => a.occurred_at.localeCompare(b.occurred_at) || a.event_id.localeCompare(b.event_id),
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
for (let i = 0; i < sorted.length; i += 1) {
|
|
493
|
+
const rej = sorted[i];
|
|
494
|
+
if (
|
|
495
|
+
rej.type !== 'HumanGateRejected' &&
|
|
496
|
+
!(rej.type === 'GateStatusChanged' && rej.data?.new_status === 'rejected')
|
|
497
|
+
) {
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
const taskSlug = rej.data?.task_slug;
|
|
501
|
+
if (!taskSlug) continue;
|
|
502
|
+
const taskSubject = `task:${taskSlug}`;
|
|
503
|
+
const hasDraftFollowUp = sorted.slice(i + 1).some(
|
|
504
|
+
(e) =>
|
|
505
|
+
e.type === 'TaskStatusChanged' &&
|
|
506
|
+
e.subject === taskSubject &&
|
|
507
|
+
e.data?.new_status === 'draft',
|
|
508
|
+
);
|
|
509
|
+
if (!hasDraftFollowUp) {
|
|
510
|
+
violations.push({
|
|
511
|
+
axiom: 'rejected→draft',
|
|
512
|
+
severity: 'error',
|
|
513
|
+
message: `gate rejected(${rej.event_id})后缺少 TaskStatusChanged(draft) 回退`,
|
|
514
|
+
node: rej.subject,
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return violations;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* 公理检查:D2 · D3 · D4-a · rejected→draft · S2 事件审计。
|
|
523
|
+
* @param {object} snapshot - buildSnapshot 产物
|
|
524
|
+
* @param {object[]|null} events - 原始事件流(rejected→draft 精确匹配须传入)
|
|
458
525
|
*/
|
|
459
|
-
export function checkAxioms(snapshot) {
|
|
526
|
+
export function checkAxioms(snapshot, events = null) {
|
|
460
527
|
const violations = [];
|
|
461
528
|
const { nodes, edges, projections } = snapshot;
|
|
462
529
|
const nodeMap = new Map(Object.entries(nodes));
|
|
@@ -465,10 +532,6 @@ export function checkAxioms(snapshot) {
|
|
|
465
532
|
return edges.filter((e) => e.from === id && e.type === type);
|
|
466
533
|
}
|
|
467
534
|
|
|
468
|
-
function hasEdge(from, to, type) {
|
|
469
|
-
return edges.some((e) => e.from === from && e.to === to && e.type === type);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
535
|
// D2: pending 的 HG-AUDIT-R1 BLOCKS 30 帽
|
|
473
536
|
for (const [id, node] of nodeMap) {
|
|
474
537
|
if (node.kind === 'HumanGate' && node.status === 'pending') {
|
|
@@ -501,17 +564,39 @@ export function checkAxioms(snapshot) {
|
|
|
501
564
|
}
|
|
502
565
|
}
|
|
503
566
|
|
|
504
|
-
//
|
|
505
|
-
for (const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
567
|
+
// D4-a: in_progress 改码 task 须 HG-GRAPH-MODULES approved
|
|
568
|
+
for (const [taskId, status] of Object.entries(projections.task_status || {})) {
|
|
569
|
+
if (status !== 'in_progress') continue;
|
|
570
|
+
const taskSlug = taskId.replace(/^task:/, '');
|
|
571
|
+
const graphGateId = `gate:${taskSlug}:HG-GRAPH-MODULES`;
|
|
572
|
+
const graphGate = nodeMap.get(graphGateId);
|
|
573
|
+
if (graphGate && (graphGate.status === 'pending' || graphGate.status === 'rejected')) {
|
|
574
|
+
violations.push({
|
|
575
|
+
axiom: 'D4-a',
|
|
576
|
+
severity: 'error',
|
|
577
|
+
message: `task ${taskId} in_progress 但 HG-GRAPH-MODULES=${graphGate.status}`,
|
|
578
|
+
node: graphGateId,
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
const mustReadTargets = edges
|
|
582
|
+
.filter((e) => e.from === taskId && e.type === 'MUST_READ')
|
|
583
|
+
.map((e) => e.to.replace(/^inform:/, ''));
|
|
584
|
+
const hasInformGraph = mustReadTargets.some(
|
|
585
|
+
(p) => p.includes('01_struct') || p.includes('_tech_graph'),
|
|
586
|
+
);
|
|
587
|
+
if (mustReadTargets.length > 0 && !hasInformGraph) {
|
|
588
|
+
violations.push({
|
|
589
|
+
axiom: 'D4-a',
|
|
590
|
+
severity: 'warn',
|
|
591
|
+
message: `task ${taskId} MUST_READ 缺 01_struct / _tech_graph InformArtifact`,
|
|
592
|
+
node: taskId,
|
|
593
|
+
});
|
|
594
|
+
}
|
|
513
595
|
}
|
|
514
596
|
|
|
597
|
+
// rejected→draft: 事件流精确匹配
|
|
598
|
+
violations.push(...checkRejectedToDraft(events));
|
|
599
|
+
|
|
515
600
|
// S2: SyncOperation 不得 touch S2 路径
|
|
516
601
|
const s2Prefixes = ['docs/tasks/', 'docs/harness/reviews/', 'docs/harness/invokes/by-task/'];
|
|
517
602
|
for (const edge of edges) {
|
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
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://cyning.me/harness/schemas/verify_result.v1.schema.json",
|
|
4
|
+
"title": "Harness Verify Agent Handoff Result v1",
|
|
5
|
+
"description": "npx @cyning/harness verify --json 输出 · A6 v2.0.2+",
|
|
6
|
+
"oneOf": [
|
|
7
|
+
{ "$ref": "#/definitions/single" },
|
|
8
|
+
{ "$ref": "#/definitions/multi" }
|
|
9
|
+
],
|
|
10
|
+
"definitions": {
|
|
11
|
+
"single": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"required": [
|
|
14
|
+
"schema_version",
|
|
15
|
+
"verify_ok",
|
|
16
|
+
"task",
|
|
17
|
+
"may_start_30"
|
|
18
|
+
],
|
|
19
|
+
"properties": {
|
|
20
|
+
"schema_version": { "type": "string", "const": "1" },
|
|
21
|
+
"verify_ok": { "type": "boolean" },
|
|
22
|
+
"task": { "type": "string" },
|
|
23
|
+
"task_path": { "type": "string" },
|
|
24
|
+
"task_slug": { "type": ["string", "null"] },
|
|
25
|
+
"may_start_30": { "type": "boolean" },
|
|
26
|
+
"blocked_reason": { "type": ["string", "null"] },
|
|
27
|
+
"review_path": { "type": ["string", "null"] },
|
|
28
|
+
"entry_invoke_20": { "type": ["string", "null"] },
|
|
29
|
+
"entry_invoke_20_resolved": { "type": ["string", "null"] },
|
|
30
|
+
"entry_invoke_30": { "type": ["string", "null"] },
|
|
31
|
+
"entry_invoke_30_resolved": { "type": ["string", "null"] },
|
|
32
|
+
"next_hat": { "type": ["string", "null"], "enum": ["30", null] },
|
|
33
|
+
"agent_preamble": { "type": ["string", "null"] },
|
|
34
|
+
"warnings": {
|
|
35
|
+
"type": "array",
|
|
36
|
+
"items": { "type": "string" }
|
|
37
|
+
},
|
|
38
|
+
"gates": {
|
|
39
|
+
"type": "array",
|
|
40
|
+
"items": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"required": ["id", "status"],
|
|
43
|
+
"properties": {
|
|
44
|
+
"id": { "type": "string" },
|
|
45
|
+
"status": { "type": "string" },
|
|
46
|
+
"blocks_hats": { "type": "string" }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"multi": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"required": ["schema_version", "verify_ok", "tasks"],
|
|
55
|
+
"properties": {
|
|
56
|
+
"schema_version": { "type": "string", "const": "1" },
|
|
57
|
+
"verify_ok": { "type": "boolean" },
|
|
58
|
+
"tasks": {
|
|
59
|
+
"type": "array",
|
|
60
|
+
"items": { "$ref": "#/definitions/single" }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# SDD-Compliance micro-bench · v1 · S1–
|
|
2
|
+
# SDD-Compliance micro-bench · v1 · S1–S6
|
|
3
3
|
# 输出合规率 %;不测 LLM 解题,只测 Orchestrate/Verify 可机械部分。
|
|
4
4
|
set -euo pipefail
|
|
5
5
|
|
|
@@ -12,14 +12,14 @@ QUIET=0
|
|
|
12
12
|
|
|
13
13
|
usage() {
|
|
14
14
|
cat <<'EOF'
|
|
15
|
-
用法:compliance-bench.sh [--all | S1 S2 S3 S4] [--quiet]
|
|
15
|
+
用法:compliance-bench.sh [--all | S1 S2 S3 S4 S5 S6] [--quiet]
|
|
16
16
|
|
|
17
|
-
--all 运行 S1–
|
|
17
|
+
--all 运行 S1–S6(默认输出含公理解释与摘要表)
|
|
18
18
|
--quiet CI/脚本模式:stdout 仅打印合规率数字;摘要说明走 stderr
|
|
19
19
|
|
|
20
20
|
说明:
|
|
21
21
|
合规率 = PASS 场景数 ÷ 总场景数 × 100。
|
|
22
|
-
100 表示 S1–
|
|
22
|
+
100 表示 S1–S6 全部通过,即 gate-check / sync / verify handoff 行为符合 SDD 公理;
|
|
23
23
|
不是 LLM 解题正确率,也不是业务项目胜率。
|
|
24
24
|
|
|
25
25
|
示例:
|
|
@@ -30,9 +30,9 @@ EOF
|
|
|
30
30
|
|
|
31
31
|
while [[ $# -gt 0 ]]; do
|
|
32
32
|
case "$1" in
|
|
33
|
-
--all) SCENARIOS=(S1 S2 S3 S4); shift ;;
|
|
33
|
+
--all) SCENARIOS=(S1 S2 S3 S4 S5 S6); shift ;;
|
|
34
34
|
--quiet) QUIET=1; shift ;;
|
|
35
|
-
S1|S2|S3|S4) SCENARIOS+=("$1"); shift ;;
|
|
35
|
+
S1|S2|S3|S4|S5|S6) SCENARIOS+=("$1"); shift ;;
|
|
36
36
|
-h|--help) usage; exit 0 ;;
|
|
37
37
|
*) echo "未知参数: $1" >&2; usage; exit 1 ;;
|
|
38
38
|
esac
|
|
@@ -53,14 +53,26 @@ log_scenario() {
|
|
|
53
53
|
[[ "$QUIET" == "1" ]] || echo "$@"
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
gate_status() {
|
|
57
|
+
local file="$1" gate="$2"
|
|
58
|
+
awk -F'|' -v g="$gate" '
|
|
59
|
+
$0 ~ /^[[:space:]]*\|/ && index($0, g) > 0 {
|
|
60
|
+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", $3)
|
|
61
|
+
gsub(/\*/, "", $3)
|
|
62
|
+
print $3
|
|
63
|
+
exit
|
|
64
|
+
}
|
|
65
|
+
' "$file"
|
|
66
|
+
}
|
|
67
|
+
|
|
56
68
|
print_banner() {
|
|
57
69
|
[[ "$QUIET" == "1" ]] && return 0
|
|
58
70
|
log_scenario "╔══════════════════════════════════════════════════════════════╗"
|
|
59
|
-
log_scenario "║ SDD-Compliance micro-bench · v1 · S1–
|
|
71
|
+
log_scenario "║ SDD-Compliance micro-bench · v1 · S1–S6 ║"
|
|
60
72
|
log_scenario "╚══════════════════════════════════════════════════════════════╝"
|
|
61
73
|
log_scenario ""
|
|
62
74
|
log_scenario "性质 机械合规测试 · 合成夹具 · 不调用 LLM"
|
|
63
|
-
log_scenario "公理 D2 HumanGate · D3 30 前置闸 · S2 sync
|
|
75
|
+
log_scenario "公理 D2 HumanGate · D3 30 前置闸 · S2 sync 域 · rejected→draft"
|
|
64
76
|
log_scenario "夹具 $BENCH_DIR/"
|
|
65
77
|
log_scenario "场景 ${SCENARIOS[*]}"
|
|
66
78
|
log_scenario ""
|
|
@@ -199,6 +211,62 @@ scenario_S4() {
|
|
|
199
211
|
fi
|
|
200
212
|
}
|
|
201
213
|
|
|
214
|
+
task_status() {
|
|
215
|
+
local file="$1"
|
|
216
|
+
sed -n 's/.*\*\*状态\*\*[^`]*`\([^`]*\)`.*/\1/p' "$file" | head -1
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
scenario_S5() {
|
|
220
|
+
local dir="$BENCH_DIR/S5_rejected_draft"
|
|
221
|
+
local tf="$dir/task.md"
|
|
222
|
+
scenario_header "S5" "rejected→draft · task 须回 draft" \
|
|
223
|
+
"rejected→draft(与 HGM axioms 同语义)" \
|
|
224
|
+
"examples/compliance_bench/S5_rejected_draft/" \
|
|
225
|
+
"HG-AUDIT-R1=rejected 且 task status=draft → 合规"
|
|
226
|
+
|
|
227
|
+
local audit status
|
|
228
|
+
audit="$(gate_status "$tf" HG-AUDIT-R1)"
|
|
229
|
+
status="$(task_status "$tf")"
|
|
230
|
+
|
|
231
|
+
if [[ "$audit" == "rejected" && "$status" == "draft" ]]; then
|
|
232
|
+
scenario_footer "S5" "PASS" "gate rejected · task draft" \
|
|
233
|
+
"否决后 task 已回 draft · 符合 rejected→draft 公理(bench 静态态 · axioms 为事件流真值)"
|
|
234
|
+
PASS=$((PASS+1))
|
|
235
|
+
RESULTS+=("S5|PASS|rejected gate + draft status")
|
|
236
|
+
else
|
|
237
|
+
scenario_footer "S5" "FAIL" "audit=$audit status=$status" \
|
|
238
|
+
"rejected 后 task 须 draft · 检查夹具或 gate-check 行为"
|
|
239
|
+
FAIL=$((FAIL+1))
|
|
240
|
+
RESULTS+=("S5|FAIL|audit=$audit status=$status")
|
|
241
|
+
fi
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
scenario_S6() {
|
|
245
|
+
local dir="$BENCH_DIR/S6_agent_handoff"
|
|
246
|
+
local tf="task_s6_agent_handoff.md"
|
|
247
|
+
scenario_header "S6" "Agent handoff · verify --json" \
|
|
248
|
+
"A6 · 30 前 Agent 可发现路由" \
|
|
249
|
+
"examples/compliance_bench/S6_agent_handoff/" \
|
|
250
|
+
"HG-AUDIT-R1=approved + entry_invoke_30 → may_start_30 true"
|
|
251
|
+
|
|
252
|
+
local json may entry
|
|
253
|
+
json="$(node "$HARNESS_ROOT/bin/harness.js" verify --target "$dir" --task "$tf" --json 2>/dev/null || true)"
|
|
254
|
+
may="$(printf '%s' "$json" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);console.log(j.may_start_30)}catch{console.log('')}})")"
|
|
255
|
+
entry="$(printf '%s' "$json" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);console.log(j.entry_invoke_30||'')}catch{console.log('')}})")"
|
|
256
|
+
|
|
257
|
+
if [[ "$may" == "true" && -n "$entry" ]]; then
|
|
258
|
+
scenario_footer "S6" "PASS" "may_start_30=true · entry_invoke_30 非空" \
|
|
259
|
+
"verify --json 输出 Agent handoff 字段 · A6 路由可机械发现"
|
|
260
|
+
PASS=$((PASS+1))
|
|
261
|
+
RESULTS+=("S6|PASS|may_start_30=true")
|
|
262
|
+
else
|
|
263
|
+
scenario_footer "S6" "FAIL" "may=$may entry=$entry" \
|
|
264
|
+
"verify --json 缺 handoff 字段 · 须修 lib/task-meta 或夹具"
|
|
265
|
+
FAIL=$((FAIL+1))
|
|
266
|
+
RESULTS+=("S6|FAIL|may=$may")
|
|
267
|
+
fi
|
|
268
|
+
}
|
|
269
|
+
|
|
202
270
|
print_summary() {
|
|
203
271
|
local total=$((PASS+FAIL))
|
|
204
272
|
local rate=0
|
|
@@ -243,6 +311,8 @@ for s in "${SCENARIOS[@]}"; do
|
|
|
243
311
|
S2) scenario_S2 ;;
|
|
244
312
|
S3) scenario_S3 ;;
|
|
245
313
|
S4) scenario_S4 ;;
|
|
314
|
+
S5) scenario_S5 ;;
|
|
315
|
+
S6) scenario_S6 ;;
|
|
246
316
|
esac
|
|
247
317
|
done
|
|
248
318
|
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
// 生成 .cyning-harness/invoke_index.json(harness-sync --index 的 JSON 引擎)
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import { parseHarnessMeta } from '../../lib/task-meta.js';
|
|
5
6
|
|
|
6
7
|
const target = process.argv[2];
|
|
8
|
+
const workspaceRoot = process.argv[3] || undefined;
|
|
9
|
+
|
|
7
10
|
if (!target) {
|
|
8
|
-
console.error('用法: node generate-invoke-index.js /path/to/target-repo');
|
|
11
|
+
console.error('用法: node generate-invoke-index.js /path/to/target-repo [/path/to/workspace-root]');
|
|
9
12
|
process.exit(1);
|
|
10
13
|
}
|
|
11
14
|
|
|
@@ -13,7 +16,6 @@ const byTaskDir = path.join(target, 'docs/harness/invokes/by-task');
|
|
|
13
16
|
const indexFile = path.join(target, '.cyning-harness/invoke_index.json');
|
|
14
17
|
|
|
15
18
|
function extractHatId(filename) {
|
|
16
|
-
// invoke_YYYYMMDD_<hat_id>_*.md 或 invoke_YYYYMMDD_<hat_id>.md
|
|
17
19
|
const base = path.basename(filename, '.md');
|
|
18
20
|
const parts = base.split('_');
|
|
19
21
|
if (parts.length >= 3 && parts[0] === 'invoke') {
|
|
@@ -22,12 +24,47 @@ function extractHatId(filename) {
|
|
|
22
24
|
return 'unknown';
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
function collectTaskEntryPoints() {
|
|
28
|
+
const entryBySlug = {};
|
|
29
|
+
const taskDirs = [
|
|
30
|
+
path.join(target, 'docs/tasks/active'),
|
|
31
|
+
path.join(target, 'docs/tasks/done'),
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
for (const dir of taskDirs) {
|
|
35
|
+
if (!fs.existsSync(dir)) continue;
|
|
36
|
+
for (const name of fs.readdirSync(dir)) {
|
|
37
|
+
if (!name.startsWith('task_') || !name.endsWith('.md')) continue;
|
|
38
|
+
const content = fs.readFileSync(path.join(dir, name), 'utf8');
|
|
39
|
+
const meta = parseHarnessMeta(content);
|
|
40
|
+
const slug = meta.task_slug;
|
|
41
|
+
if (!slug) continue;
|
|
42
|
+
|
|
43
|
+
const points = {};
|
|
44
|
+
if (meta.entry_invoke_10_task) points['10'] = meta.entry_invoke_10_task;
|
|
45
|
+
if (meta.entry_invoke_20) points['20'] = meta.entry_invoke_20;
|
|
46
|
+
if (meta.entry_invoke_30) points['30'] = meta.entry_invoke_30;
|
|
47
|
+
|
|
48
|
+
if (Object.keys(points).length === 0) continue;
|
|
49
|
+
|
|
50
|
+
entryBySlug[slug] = {
|
|
51
|
+
task_markdown: path.relative(target, path.join(dir, name)).replace(/\\/g, '/'),
|
|
52
|
+
entry_points: points,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return entryBySlug;
|
|
58
|
+
}
|
|
59
|
+
|
|
25
60
|
const index = {
|
|
26
61
|
schema_version: '1',
|
|
27
62
|
generated_at: new Date().toISOString(),
|
|
28
63
|
index: {},
|
|
29
64
|
};
|
|
30
65
|
|
|
66
|
+
const taskEntries = collectTaskEntryPoints();
|
|
67
|
+
|
|
31
68
|
if (fs.existsSync(byTaskDir)) {
|
|
32
69
|
for (const entry of fs.readdirSync(byTaskDir, { withFileTypes: true })) {
|
|
33
70
|
if (!entry.isDirectory()) continue;
|
|
@@ -48,13 +85,36 @@ if (fs.existsSync(byTaskDir)) {
|
|
|
48
85
|
|
|
49
86
|
invokes.sort((a, b) => a.path.localeCompare(b.path));
|
|
50
87
|
|
|
51
|
-
|
|
88
|
+
const record = {
|
|
52
89
|
task_slug: slug,
|
|
53
90
|
invokes,
|
|
54
91
|
};
|
|
92
|
+
|
|
93
|
+
if (taskEntries[slug]) {
|
|
94
|
+
record.task_markdown = taskEntries[slug].task_markdown;
|
|
95
|
+
record.entry_points = taskEntries[slug].entry_points;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
index.index[slug] = record;
|
|
55
99
|
}
|
|
56
100
|
}
|
|
57
101
|
|
|
102
|
+
// task 有 entry_invoke 但尚无 invokes/by-task 目录时也索引
|
|
103
|
+
for (const [slug, data] of Object.entries(taskEntries)) {
|
|
104
|
+
if (!index.index[slug]) {
|
|
105
|
+
index.index[slug] = {
|
|
106
|
+
task_slug: slug,
|
|
107
|
+
invokes: [],
|
|
108
|
+
task_markdown: data.task_markdown,
|
|
109
|
+
entry_points: data.entry_points,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (workspaceRoot) {
|
|
115
|
+
index.workspace_root = path.resolve(workspaceRoot);
|
|
116
|
+
}
|
|
117
|
+
|
|
58
118
|
fs.mkdirSync(path.dirname(indexFile), { recursive: true });
|
|
59
119
|
fs.writeFileSync(indexFile, JSON.stringify(index, null, 2) + '\n');
|
|
60
120
|
console.log(`invoke_index: ${indexFile}`);
|