@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
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.
|
|
6
|
-
product_semver: "2.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 时
|
|
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-
|
|
74
|
-
role:
|
|
75
|
-
|
|
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: ["
|
|
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
|
@@ -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 S5] [--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 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–
|
|
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
|
-
|
|
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}`);
|