@cyning/harness 0.4.0 → 1.0.0

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 (32) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +50 -0
  3. package/docs/ARCHITECTURE.md +1 -1
  4. package/docs/BENCHMARK_REPORT_TEMPLATE.md +135 -0
  5. package/docs/ONBOARDING.md +29 -0
  6. package/docs/RELEASE_v1.0.0.md +90 -0
  7. package/docs/ROADMAP_TO_AGENT_GOVERNANCE.md +209 -0
  8. package/docs/methodology/AUDIT_doc_consistency_2026-06-15_zh.md +5 -3
  9. package/docs/methodology/README.md +7 -6
  10. package/docs/methodology/ROADMAP_v1_zh.md +23 -11
  11. package/docs/methodology/execution/PILOT_EVIDENCE_B2_v1_zh.md +45 -37
  12. package/docs/methodology/graph/HARNESS_GRAPH_MODEL_design_v0_zh.md +24 -23
  13. package/docs/methodology/graph/HARNESS_GRAPH_MODEL_dialogue_archive_v1_zh.md +2 -2
  14. package/docs/methodology/graph/README.md +2 -2
  15. package/docs/methodology/product/DESIGN_ONTOLOGY_v1_zh.md +2 -2
  16. package/docs/methodology/prompts/PROMPT_article_theory_roundtable_v1_zh.md +3 -3
  17. package/docs/methodology/prompts/PROMPT_doc_consistency_audit_v1_zh.md +4 -4
  18. package/examples/compliance_bench/README.md +28 -0
  19. package/examples/compliance_bench/S1_r1_pending/task.md +30 -0
  20. package/examples/compliance_bench/S2_r1_no_review/task.md +31 -0
  21. package/examples/compliance_bench/S3_r1_with_review/reviews/s3_r1_with_review_audit_R1_20260616.md +18 -0
  22. package/examples/compliance_bench/S3_r1_with_review/task.md +31 -0
  23. package/examples/compliance_bench/S4_sync_domain/profile.json +16 -0
  24. package/lib/audit.js +168 -0
  25. package/lib/cli.js +50 -0
  26. package/ontology.yaml +2 -2
  27. package/package.json +1 -1
  28. package/schema/invoke_index.v1.schema.json +53 -0
  29. package/wizard/compliance-bench.sh +156 -0
  30. package/wizard/gate-check.sh +139 -19
  31. package/wizard/harness-sync.sh +16 -3
  32. package/wizard/lib/generate-invoke-index.js +60 -0
@@ -4,6 +4,8 @@ set -euo pipefail
4
4
 
5
5
  TARGET="${TARGET:-$(pwd)}"
6
6
  TASK_FILE=""
7
+ GRAPH_MODE=0
8
+ JSON_MODE=0
7
9
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
10
 
9
11
  while [[ $# -gt 0 ]]; do
@@ -13,8 +15,10 @@ while [[ $# -gt 0 ]]; do
13
15
  TASK_FILE="$2"
14
16
  shift 2
15
17
  ;;
18
+ --graph) GRAPH_MODE=1; shift ;;
19
+ --json) JSON_MODE=1; shift ;;
16
20
  -h|--help)
17
- echo "用法: gate-check.sh [--target /path/to/repo] [--task docs/tasks/active/task_*.md]"
21
+ echo "用法: gate-check.sh [--target /path/to/repo] [--task docs/tasks/active/task_*.md] [--graph] [--json]"
18
22
  exit 0
19
23
  ;;
20
24
  *) echo "未知参数: $1" >&2; exit 1 ;;
@@ -24,7 +28,7 @@ done
24
28
  gate_status() {
25
29
  local file="$1" gate="$2"
26
30
  awk -F'|' -v g="$gate" '
27
- index($0, g) > 0 {
31
+ $0 ~ /^[[:space:]]*\|/ && index($0, g) > 0 {
28
32
  gsub(/^[[:space:]]+|[[:space:]]+$/, "", $3)
29
33
  gsub(/\*/, "", $3)
30
34
  print $3
@@ -36,7 +40,7 @@ gate_status() {
36
40
  gate_blocks() {
37
41
  local file="$1" gate="$2"
38
42
  awk -F'|' -v g="$gate" '
39
- index($0, g) > 0 {
43
+ $0 ~ /^[[:space:]]*\|/ && index($0, g) > 0 {
40
44
  gsub(/^[[:space:]]+|[[:space:]]+$/, "", $4)
41
45
  gsub(/\*/, "", $4)
42
46
  print $4
@@ -82,28 +86,144 @@ check_one_task() {
82
86
  return "$blocked"
83
87
  }
84
88
 
85
- echo "=== Harness gate-check ==="
86
- echo "目标: $TARGET"
87
-
88
- MANIFEST_FILE="$TARGET/.cyning-harness/manifest.json"
89
- if [[ -f "$MANIFEST_FILE" ]]; then
90
- mf_version="$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$MANIFEST_FILE" | head -1 | sed -E 's/.*"([^"]+)"$/\1/')"
91
- mf_preset="$(grep -o '"preset"[[:space:]]*:[[:space:]]*"[^"]*"' "$MANIFEST_FILE" | head -1 | sed -E 's/.*"([^"]+)"$/\1/')"
92
- echo "manifest.version: ${mf_version:-?}"
93
- echo "manifest.preset: ${mf_preset:-?}"
94
- if [[ -n "${CYNING_HARNESS:-}" && -f "$CYNING_HARNESS/package.json" ]]; then
95
- pkg_version="$(grep '"version"' "$CYNING_HARNESS/package.json" | head -1 | sed -E 's/.*:[[:space:]]*"([^"]+)".*/\1/')"
96
- if [[ -n "$pkg_version" && -n "$mf_version" && "$mf_version" != "$pkg_version" ]]; then
97
- echo "提示: manifest 版本 ($mf_version) 与产品包 ($pkg_version) 不一致 · 可运行 upgrade"
89
+ # D3 · Inform 图谱闸:输出模块/流程文件 + 审核状态摘要
90
+ graph_for_task() {
91
+ local tf="$1"
92
+ local graph_status
93
+ graph_status="$(gate_status "$tf" HG-GRAPH-MODULES)"
94
+ local tech_graph_dir="$TARGET/docs/_tech_graph"
95
+
96
+ local files=()
97
+ if [[ -d "$tech_graph_dir" ]]; then
98
+ while IFS= read -r -d '' f; do
99
+ files+=("$f")
100
+ done < <(find "$tech_graph_dir" -maxdepth 2 -type f -print0 2>/dev/null)
101
+ fi
102
+
103
+ if [[ "$JSON_MODE" == "1" ]]; then
104
+ printf '{"task":"%s","hg_graph_modules":"%s","tech_graph_dir":"%s","files":[' "$(basename "$tf")" "${graph_status:-unknown}" "$tech_graph_dir"
105
+ local first=1
106
+ if [[ ${#files[@]} -gt 0 ]]; then
107
+ for f in "${files[@]}"; do
108
+ [[ "$first" == "1" ]] || printf ','
109
+ first=0
110
+ local name status
111
+ name="$(basename "$f")"
112
+ status="$(file_audit_status "$f")"
113
+ printf '{"name":"%s","status":"%s"}' "$name" "$status"
114
+ done
98
115
  fi
116
+ printf ']}'
117
+ return 0
99
118
  fi
100
- else
101
- echo "manifest: (未接入 · $MANIFEST_FILE)"
119
+
120
+ echo "=== Harness gate-check --graph ==="
121
+ echo "task: $(basename "$tf")"
122
+ echo "HG-GRAPH-MODULES: ${graph_status:-未声明}"
123
+ echo ""
124
+
125
+ if [[ ${#files[@]} -eq 0 ]]; then
126
+ printf 'WARN: 未找到 %s(docs-only task 友好:warn 不 fail)\n' "$tech_graph_dir"
127
+ echo ""
128
+ return 0
129
+ fi
130
+
131
+ echo "| 模块/流程文件 | 审核状态 |"
132
+ echo "|-------------|----------|"
133
+ for f in "${files[@]}"; do
134
+ printf "| %s | %s |\n" "$(basename "$f")" "$(file_audit_status "$f")"
135
+ done
136
+ echo ""
137
+ }
138
+
139
+ file_audit_status() {
140
+ local f="$1"
141
+ local name
142
+ name="$(basename "$f")"
143
+ # 文件名或内容含 approved / rejected / pending 即视为状态摘要
144
+ if [[ "$name" == *approved* ]] || grep -iq 'status.*approved\|状态.*approved' "$f" 2>/dev/null; then
145
+ echo "approved"
146
+ elif [[ "$name" == *rejected* ]] || grep -iq 'status.*rejected\|状态.*rejected' "$f" 2>/dev/null; then
147
+ echo "rejected"
148
+ elif [[ "$name" == *pending* ]] || grep -iq 'status.*pending\|状态.*pending' "$f" 2>/dev/null; then
149
+ echo "pending"
150
+ else
151
+ echo "unknown"
152
+ fi
153
+ }
154
+
155
+ if [[ "$JSON_MODE" != "1" ]]; then
156
+ echo "=== Harness gate-check ==="
157
+ echo "目标: $TARGET"
158
+
159
+ MANIFEST_FILE="$TARGET/.cyning-harness/manifest.json"
160
+ if [[ -f "$MANIFEST_FILE" ]]; then
161
+ mf_version="$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$MANIFEST_FILE" | head -1 | sed -E 's/.*"([^"]+)"$/\1/')"
162
+ mf_preset="$(grep -o '"preset"[[:space:]]*:[[:space:]]*"[^"]*"' "$MANIFEST_FILE" | head -1 | sed -E 's/.*"([^"]+)"$/\1/')"
163
+ echo "manifest.version: ${mf_version:-?}"
164
+ echo "manifest.preset: ${mf_preset:-?}"
165
+ if [[ -n "${CYNING_HARNESS:-}" && -f "$CYNING_HARNESS/package.json" ]]; then
166
+ pkg_version="$(grep '"version"' "$CYNING_HARNESS/package.json" | head -1 | sed -E 's/.*:[[:space:]]*"([^"]+)".*/\1/')"
167
+ if [[ -n "$pkg_version" && -n "$mf_version" && "$mf_version" != "$pkg_version" ]]; then
168
+ echo "提示: manifest 版本 ($mf_version) 与产品包 ($pkg_version) 不一致 · 可运行 upgrade"
169
+ fi
170
+ fi
171
+ else
172
+ echo "manifest: (未接入 · 无 $MANIFEST_FILE)"
173
+ fi
174
+ echo ""
102
175
  fi
103
- echo ""
104
176
 
105
177
  BLOCKED=0
106
178
 
179
+ if [[ "$GRAPH_MODE" == "1" ]]; then
180
+ if [[ "$JSON_MODE" == "1" ]]; then
181
+ printf '['
182
+ fi
183
+ local_first=1
184
+ if [[ -n "$TASK_FILE" ]]; then
185
+ if [[ "$TASK_FILE" != /* ]]; then
186
+ TASK_FILE="$TARGET/$TASK_FILE"
187
+ fi
188
+ if [[ ! -f "$TASK_FILE" ]]; then
189
+ echo "错误: 未找到 --task 文件 $TASK_FILE" >&2
190
+ exit 1
191
+ fi
192
+ graph_for_task "$TASK_FILE"
193
+ else
194
+ ACTIVE_DIR="$TARGET/docs/tasks/active"
195
+ if [[ ! -d "$ACTIVE_DIR" ]]; then
196
+ if [[ "$JSON_MODE" == "1" ]]; then
197
+ printf ']\n'
198
+ else
199
+ echo "⚠️ 无 $ACTIVE_DIR" >&2
200
+ fi
201
+ exit 0
202
+ fi
203
+ TASK_FILES=("$ACTIVE_DIR"/task_*.md)
204
+ if [[ ! -e "${TASK_FILES[0]}" ]]; then
205
+ if [[ "$JSON_MODE" == "1" ]]; then
206
+ printf ']\n'
207
+ else
208
+ echo "无 active task_*.md"
209
+ fi
210
+ exit 0
211
+ fi
212
+ for tf in "$ACTIVE_DIR"/task_*.md; do
213
+ [[ -f "$tf" ]] || continue
214
+ if [[ "$JSON_MODE" == "1" ]]; then
215
+ [[ "$local_first" == "1" ]] || printf ','
216
+ local_first=0
217
+ fi
218
+ graph_for_task "$tf"
219
+ done
220
+ fi
221
+ if [[ "$JSON_MODE" == "1" ]]; then
222
+ printf ']\n'
223
+ fi
224
+ exit 0
225
+ fi
226
+
107
227
  if [[ -n "$TASK_FILE" ]]; then
108
228
  if [[ "$TASK_FILE" != /* ]]; then
109
229
  TASK_FILE="$TARGET/$TASK_FILE"
@@ -23,6 +23,8 @@ usage() {
23
23
 
24
24
  "$CYNING_HARNESS/wizard/harness-sync.sh" apply
25
25
 
26
+ "$CYNING_HARNESS/wizard/harness-sync.sh" --index --target /path/to/project
27
+
26
28
  或:
27
29
 
28
30
  "$CYNING_HARNESS/wizard/harness-sync.sh" plan --target /path/to/project
@@ -31,14 +33,16 @@ usage() {
31
33
 
32
34
  依赖:业务仓已有 .cyning-harness/profile.json(由 install.sh 生成)
33
35
 
34
- plan — 仅打印将复制的文件
35
- apply — 执行复制(S5:git 仓须工作区干净,或 --force)
36
+ plan — 仅打印将复制的文件
37
+ apply — 执行复制(S5:git 仓须工作区干净,或 --force)
38
+ --index — 生成 .cyning-harness/invoke_index.json(只读聚合 invokes/by-task,不覆盖 S2 域)
36
39
  EOF
37
40
  }
38
41
 
39
42
  while [[ $# -gt 0 ]]; do
40
43
  case "$1" in
41
44
  plan|apply) MODE="$1"; shift ;;
45
+ --index) MODE="index"; shift ;;
42
46
  --target) TARGET="$2"; shift 2 ;;
43
47
  --force) SYNC_FORCE=1; shift ;;
44
48
  -h|--help) usage; exit 0 ;;
@@ -46,12 +50,21 @@ while [[ $# -gt 0 ]]; do
46
50
  esac
47
51
  done
48
52
 
49
- [[ "$MODE" == "plan" || "$MODE" == "apply" ]] || { usage; exit 1; }
53
+ [[ "$MODE" == "plan" || "$MODE" == "apply" || "$MODE" == "index" ]] || { usage; exit 1; }
50
54
 
51
55
  CYNING_HARNESS="${CYNING_HARNESS:-$HARNESS_ROOT}"
52
56
  refuse_if_product_root "$TARGET" "$HARNESS_ROOT"
53
57
  PROFILE_FILE="$TARGET/.cyning-harness/profile.json"
54
58
 
59
+ if [[ "$MODE" == "index" ]]; then
60
+ if [[ ! -d "$TARGET" ]]; then
61
+ echo "错误: 目标目录不存在 $TARGET" >&2
62
+ exit 1
63
+ fi
64
+ node "$SCRIPT_DIR/lib/generate-invoke-index.js" "$TARGET"
65
+ exit 0
66
+ fi
67
+
55
68
  if [[ ! -f "$PROFILE_FILE" ]]; then
56
69
  echo "错误: 未找到 $PROFILE_FILE" >&2
57
70
  echo "请先运行: $CYNING_HARNESS/wizard/install.sh --target $TARGET --preset <name>" >&2
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ // 生成 .cyning-harness/invoke_index.json(harness-sync --index 的 JSON 引擎)
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+
6
+ const target = process.argv[2];
7
+ if (!target) {
8
+ console.error('用法: node generate-invoke-index.js /path/to/target-repo');
9
+ process.exit(1);
10
+ }
11
+
12
+ const byTaskDir = path.join(target, 'docs/harness/invokes/by-task');
13
+ const indexFile = path.join(target, '.cyning-harness/invoke_index.json');
14
+
15
+ function extractHatId(filename) {
16
+ // invoke_YYYYMMDD_<hat_id>_*.md 或 invoke_YYYYMMDD_<hat_id>.md
17
+ const base = path.basename(filename, '.md');
18
+ const parts = base.split('_');
19
+ if (parts.length >= 3 && parts[0] === 'invoke') {
20
+ return parts[2];
21
+ }
22
+ return 'unknown';
23
+ }
24
+
25
+ const index = {
26
+ schema_version: '1',
27
+ generated_at: new Date().toISOString(),
28
+ index: {},
29
+ };
30
+
31
+ if (fs.existsSync(byTaskDir)) {
32
+ for (const entry of fs.readdirSync(byTaskDir, { withFileTypes: true })) {
33
+ if (!entry.isDirectory()) continue;
34
+ const slug = entry.name;
35
+ const slugDir = path.join(byTaskDir, slug);
36
+ const invokes = [];
37
+
38
+ for (const file of fs.readdirSync(slugDir)) {
39
+ if (!file.startsWith('invoke_') || !file.endsWith('.md')) continue;
40
+ const full = path.join(slugDir, file);
41
+ const stat = fs.statSync(full);
42
+ invokes.push({
43
+ path: path.relative(target, full).replace(/\\/g, '/'),
44
+ mtime: Math.floor(stat.mtimeMs / 1000),
45
+ hat_id: extractHatId(file),
46
+ });
47
+ }
48
+
49
+ invokes.sort((a, b) => a.path.localeCompare(b.path));
50
+
51
+ index.index[slug] = {
52
+ task_slug: slug,
53
+ invokes,
54
+ };
55
+ }
56
+ }
57
+
58
+ fs.mkdirSync(path.dirname(indexFile), { recursive: true });
59
+ fs.writeFileSync(indexFile, JSON.stringify(index, null, 2) + '\n');
60
+ console.log(`invoke_index: ${indexFile}`);