@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
package/ontology.yaml CHANGED
@@ -2,8 +2,8 @@
2
2
  # 人类真值:docs/methodology/product/DESIGN_ONTOLOGY_v1_zh.md
3
3
  # 冲突时以 Markdown 为准 · 供未来 harness ontology-check 使用
4
4
 
5
- version: "1.2"
6
- product_semver: "2.0.0"
5
+ version: "1.3"
6
+ product_semver: "2.0.4"
7
7
  license: MIT
8
8
 
9
9
  classes:
@@ -64,24 +64,44 @@ axioms:
64
64
  text: "禁止 sync 覆盖 docs/tasks、reviews、invokes/by-task"
65
65
  - id: S5
66
66
  text: "harness-sync apply 前须 git-clean(可 --force 跳过)"
67
+ - id: D1
68
+ text: "每次 20-task-audit 必须产出 AuditReview(零阻塞须写明)"
67
69
  - id: D2
68
- text: "HG-AUDIT-R1 pending 时 22 不得附 30 Prompt"
70
+ text: "HG-AUDIT-R1 pending 时 20-task-audit 不得附 30 Prompt(别名 22-task-audit)"
69
71
  - id: D7
70
72
  text: "public push 须 HG-RELEASE 人闸 checklist 全勾"
71
73
 
74
+ # V2 hat_id · Starter 闭包 + Extended POINTER(见 DESIGN_ONTOLOGY §3.2)
72
75
  starter_hats:
73
- - hat_id: "10-requirements"
74
- role: RequirementsHat
75
- - hat_id: "22-task-audit"
76
+ - hat_id: "10-task"
77
+ role: TaskRequirementsHat
78
+ alias: "10-requirements"
79
+ - hat_id: "20-task-audit"
76
80
  role: TaskAuditHat
81
+ alias: "22-task-audit"
77
82
  - hat_id: "30-execute-code"
78
83
  role: ExecuteHat
79
84
  - hat_id: "40-self-check"
80
85
  role: SelfCheckHat
81
86
 
87
+ extended_hats:
88
+ - hat_id: "10-spec"
89
+ role: SpecRequirementsHat
90
+ pointer: "工作区 docs/harness/prompts/10-spec-requirements.md"
91
+ - hat_id: "20-spec-audit"
92
+ role: SpecAuditHat
93
+ pointer: "工作区 docs/harness/prompts/20-spec-audit.md"
94
+ - hat_id: "00-orchestrator"
95
+ role: OrchestratorHat
96
+ - hat_id: "50-independent-reinspect"
97
+ role: ReinspectHat
98
+
82
99
  human_gates:
83
100
  - id: HG-TASK-DRAFT
84
- blocks_hats: ["22-task-audit", "30-execute-code"]
101
+ blocks_hats: ["20-task-audit", "30-execute-code"]
102
+ - id: HG-SPEC-SIGNOFF
103
+ blocks_hats: ["30-execute-code"]
104
+ note: "Epic 级 · SPEC approved 后 00 方可派实现链至 30"
85
105
  - id: HG-AUDIT-R1
86
106
  blocks_hats: ["30-execute-code"]
87
107
  - id: HG-RELEASE
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyning/harness",
3
- "version": "2.0.1",
3
+ "version": "2.0.4",
4
4
  "description": "cyning-harness discipline package · init / upgrade / check CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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–S5
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 S5] [--quiet]
15
+ 用法:compliance-bench.sh [--all | S1 S2 S3 S4 S5 S6] [--quiet]
16
16
 
17
- --all 运行 S1–S5(默认输出含公理解释与摘要表)
17
+ --all 运行 S1–S6(默认输出含公理解释与摘要表)
18
18
  --quiet CI/脚本模式:stdout 仅打印合规率数字;摘要说明走 stderr
19
19
 
20
20
  说明:
21
21
  合规率 = PASS 场景数 ÷ 总场景数 × 100。
22
- 100 表示 S1–S5 全部通过,即 gate-check / sync 行为符合 SDD 公理;
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 S5); shift ;;
33
+ --all) SCENARIOS=(S1 S2 S3 S4 S5 S6); shift ;;
34
34
  --quiet) QUIET=1; shift ;;
35
- S1|S2|S3|S4|S5) 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
@@ -68,7 +68,7 @@ gate_status() {
68
68
  print_banner() {
69
69
  [[ "$QUIET" == "1" ]] && return 0
70
70
  log_scenario "╔══════════════════════════════════════════════════════════════╗"
71
- log_scenario "║ SDD-Compliance micro-bench · v1 · S1–S5 ║"
71
+ log_scenario "║ SDD-Compliance micro-bench · v1 · S1–S6 ║"
72
72
  log_scenario "╚══════════════════════════════════════════════════════════════╝"
73
73
  log_scenario ""
74
74
  log_scenario "性质 机械合规测试 · 合成夹具 · 不调用 LLM"
@@ -241,6 +241,32 @@ scenario_S5() {
241
241
  fi
242
242
  }
243
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
+
244
270
  print_summary() {
245
271
  local total=$((PASS+FAIL))
246
272
  local rate=0
@@ -286,6 +312,7 @@ for s in "${SCENARIOS[@]}"; do
286
312
  S3) scenario_S3 ;;
287
313
  S4) scenario_S4 ;;
288
314
  S5) scenario_S5 ;;
315
+ S6) scenario_S6 ;;
289
316
  esac
290
317
  done
291
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
- index.index[slug] = {
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}`);