@cyning/harness 0.4.0 → 1.0.1

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 (48) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +65 -11
  3. package/bin/harness.js +1 -1
  4. package/docs/ARCHITECTURE.md +1 -1
  5. package/docs/BENCHMARK_REPORT_TEMPLATE.md +135 -0
  6. package/docs/ONBOARDING.md +32 -0
  7. package/docs/README.md +2 -2
  8. package/docs/RELEASE_v1.0.0.md +91 -0
  9. package/docs/RELEASE_v1.0.1.md +87 -0
  10. package/docs/ROADMAP_TO_AGENT_GOVERNANCE.md +209 -0
  11. package/docs/USER_GUIDE_v1.0_zh.md +284 -0
  12. package/docs/methodology/AUDIT_doc_consistency_2026-06-15_zh.md +5 -3
  13. package/docs/methodology/README.md +7 -6
  14. package/docs/methodology/ROADMAP_v1_zh.md +45 -12
  15. package/docs/methodology/execution/PILOT_EVIDENCE_B2_v1_zh.md +45 -37
  16. package/docs/methodology/graph/HARNESS_GRAPH_MODEL_design_v0_zh.md +25 -23
  17. package/docs/methodology/graph/HARNESS_GRAPH_MODEL_dialogue_archive_v1_zh.md +2 -2
  18. package/docs/methodology/graph/HGM_UPGRADE_OUTLINE_v1_zh.md +248 -0
  19. package/docs/methodology/graph/README.md +2 -2
  20. package/docs/methodology/product/DESIGN_ONTOLOGY_v1_zh.md +2 -2
  21. package/docs/methodology/prompts/PROMPT_article_theory_roundtable_v1_zh.md +3 -3
  22. package/docs/methodology/prompts/PROMPT_doc_consistency_audit_v1_zh.md +4 -4
  23. package/examples/compliance_bench/README.md +81 -0
  24. package/examples/compliance_bench/S1_r1_pending/task.md +30 -0
  25. package/examples/compliance_bench/S2_r1_no_review/task.md +31 -0
  26. package/examples/compliance_bench/S3_r1_with_review/reviews/s3_r1_with_review_audit_R1_20260616.md +18 -0
  27. package/examples/compliance_bench/S3_r1_with_review/task.md +31 -0
  28. package/examples/compliance_bench/S4_sync_domain/profile.json +16 -0
  29. package/harness/prompts/FRAGMENT_30_gate_verify_v1_zh.md +1 -1
  30. package/harness/templates/QUICKREF_v1_zh.md +47 -0
  31. package/ide/adapters/AGENTS.md.fragment.example +1 -0
  32. package/ide/adapters/CLAUDE.md.fragment.example +1 -0
  33. package/ide/adapters/cursor-harness-starter.mdc.example +1 -0
  34. package/lib/audit.js +166 -0
  35. package/lib/cli.js +245 -5
  36. package/lib/package-scripts.js +65 -0
  37. package/lib/paths.js +27 -0
  38. package/lib/verify.js +103 -0
  39. package/ontology.yaml +2 -2
  40. package/package.json +1 -1
  41. package/schema/invoke_index.v1.schema.json +53 -0
  42. package/wizard/README.md +4 -0
  43. package/wizard/compliance-bench.sh +251 -0
  44. package/wizard/gate-check.sh +139 -19
  45. package/wizard/harness-sync.sh +16 -3
  46. package/wizard/install.sh +5 -3
  47. package/wizard/lib/generate-invoke-index.js +60 -0
  48. package/wizard/upgrade.sh +7 -2
package/ontology.yaml CHANGED
@@ -1,9 +1,9 @@
1
- # cyning-harness 产品设计本体 · 机器可读抽取(草案 v0.4.0)
1
+ # cyning-harness 产品设计本体 · 机器可读抽取(草案 v1.0.0)
2
2
  # 人类真值:docs/methodology/product/DESIGN_ONTOLOGY_v1_zh.md
3
3
  # 冲突时以 Markdown 为准 · 供未来 harness ontology-check 使用
4
4
 
5
5
  version: "1.2"
6
- product_semver: "0.4.0"
6
+ product_semver: "1.0.1"
7
7
  license: MIT
8
8
 
9
9
  classes:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyning/harness",
3
- "version": "0.4.0",
3
+ "version": "1.0.1",
4
4
  "description": "cyning-harness discipline package · init / upgrade / check CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,53 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://cyning.me/harness/schemas/invoke_index.v1.schema.json",
4
+ "title": "Harness InvokeSnapshot Index v1",
5
+ "description": "由 harness-sync --index 生成的 invokes/by-task 机器索引 · 只读聚合 · 不覆盖 S2 域",
6
+ "type": "object",
7
+ "required": ["schema_version", "generated_at", "index"],
8
+ "properties": {
9
+ "schema_version": {
10
+ "type": "string",
11
+ "const": "1"
12
+ },
13
+ "generated_at": {
14
+ "type": "string",
15
+ "format": "date-time"
16
+ },
17
+ "index": {
18
+ "type": "object",
19
+ "description": "task_slug → 该 task 的 invoke 列表",
20
+ "additionalProperties": {
21
+ "type": "object",
22
+ "required": ["task_slug", "invokes"],
23
+ "properties": {
24
+ "task_slug": {
25
+ "type": "string",
26
+ "pattern": "^[a-z0-9][a-z0-9_-]*$"
27
+ },
28
+ "invokes": {
29
+ "type": "array",
30
+ "items": {
31
+ "type": "object",
32
+ "required": ["path", "mtime", "hat_id"],
33
+ "properties": {
34
+ "path": {
35
+ "type": "string",
36
+ "description": "相对业务仓根的 invoke 文件路径"
37
+ },
38
+ "mtime": {
39
+ "type": "integer",
40
+ "description": "文件修改时间戳(秒)"
41
+ },
42
+ "hat_id": {
43
+ "type": "string",
44
+ "description": "从文件名解析的帽 ID,如 30 / 40 / CLOSE / HG-RELEASE"
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }
package/wizard/README.md CHANGED
@@ -15,6 +15,9 @@ npx @cyning/harness@latest init --preset harness-only --ide cursor,agents
15
15
  npx @cyning/harness upgrade
16
16
  npx @cyning/harness upgrade --target /path/to/project --yes --force
17
17
 
18
+ # 30 前验证
19
+ npx @cyning/harness verify --target /path/to/project
20
+
18
21
  # 仅检查版本
19
22
  npx @cyning/harness check
20
23
  ```
@@ -22,6 +25,7 @@ npx @cyning/harness check
22
25
  - 内部单源:`wizard/install.sh` → `harness-sync.sh apply`(S2 写路径不变)
23
26
  - manifest schema:[`schema/manifest.v1.schema.json`](../schema/manifest.v1.schema.json)
24
27
  - 本地开发:`node bin/harness.js init --target /path/to/project --preset harness-only --ide cursor,agents --yes`
28
+ - Node 仓可选:`npx @cyning/harness init --with-scripts --pm pnpm`
25
29
 
26
30
  ---
27
31
 
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env bash
2
+ # SDD-Compliance micro-bench · v1 · S1–S4
3
+ # 输出合规率 %;不测 LLM 解题,只测 Orchestrate/Verify 可机械部分。
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ HARNESS_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
8
+ BENCH_DIR="$HARNESS_ROOT/examples/compliance_bench"
9
+
10
+ SCENARIOS=()
11
+ QUIET=0
12
+
13
+ usage() {
14
+ cat <<'EOF'
15
+ 用法:compliance-bench.sh [--all | S1 S2 S3 S4] [--quiet]
16
+
17
+ --all 运行 S1–S4(默认输出含公理解释与摘要表)
18
+ --quiet CI/脚本模式:stdout 仅打印合规率数字;摘要说明走 stderr
19
+
20
+ 说明:
21
+ 合规率 = PASS 场景数 ÷ 总场景数 × 100。
22
+ 100 表示 S1–S4 全部通过,即 gate-check / sync 行为符合 SDD 公理;
23
+ 不是 LLM 解题正确率,也不是业务项目胜率。
24
+
25
+ 示例:
26
+ ./wizard/compliance-bench.sh --all # 维护者人工验收(推荐)
27
+ ./wizard/compliance-bench.sh --quiet --all # 关账勾选 / CI(解析 stdout 数字)
28
+ EOF
29
+ }
30
+
31
+ while [[ $# -gt 0 ]]; do
32
+ case "$1" in
33
+ --all) SCENARIOS=(S1 S2 S3 S4); shift ;;
34
+ --quiet) QUIET=1; shift ;;
35
+ S1|S2|S3|S4) SCENARIOS+=("$1"); shift ;;
36
+ -h|--help) usage; exit 0 ;;
37
+ *) echo "未知参数: $1" >&2; usage; exit 1 ;;
38
+ esac
39
+ done
40
+
41
+ [[ ${#SCENARIOS[@]} -gt 0 ]] || { usage; exit 1; }
42
+
43
+ # 非 quiet → stdout 全量;quiet → 场景静默,摘要走 stderr
44
+ log() {
45
+ if [[ "$QUIET" == "1" ]]; then
46
+ echo "$@" >&2
47
+ else
48
+ echo "$@"
49
+ fi
50
+ }
51
+
52
+ log_scenario() {
53
+ [[ "$QUIET" == "1" ]] || echo "$@"
54
+ }
55
+
56
+ print_banner() {
57
+ [[ "$QUIET" == "1" ]] && return 0
58
+ log_scenario "╔══════════════════════════════════════════════════════════════╗"
59
+ log_scenario "║ SDD-Compliance micro-bench · v1 · S1–S4 ║"
60
+ log_scenario "╚══════════════════════════════════════════════════════════════╝"
61
+ log_scenario ""
62
+ log_scenario "性质 机械合规测试 · 合成夹具 · 不调用 LLM"
63
+ log_scenario "公理 D2 HumanGate · D3 30 前置闸 · S2 sync 域边界"
64
+ log_scenario "夹具 $BENCH_DIR/"
65
+ log_scenario "场景 ${SCENARIOS[*]}"
66
+ log_scenario ""
67
+ }
68
+
69
+ PASS=0
70
+ FAIL=0
71
+ # 每行: id|status|detail
72
+ RESULTS=()
73
+
74
+ scenario_header() {
75
+ local id="$1" title="$2" axiom="$3" fixture="$4" expect="$5"
76
+ log_scenario "──────────────────────────────────────────────────────────────"
77
+ log_scenario "[$id] $title"
78
+ log_scenario " 公理 $axiom"
79
+ log_scenario " 夹具 $fixture"
80
+ log_scenario " 期望 $expect"
81
+ }
82
+
83
+ scenario_footer() {
84
+ local id="$1" status="$2" detail="$3" explain="$4"
85
+ if [[ "$status" == "PASS" ]]; then
86
+ log_scenario " 结果 ✅ PASS · $detail"
87
+ else
88
+ log_scenario " 结果 ❌ FAIL · $detail"
89
+ fi
90
+ log_scenario " 说明 $explain"
91
+ log_scenario ""
92
+ }
93
+
94
+ scenario_S1() {
95
+ local dir="$BENCH_DIR/S1_r1_pending"
96
+ scenario_header "S1" "R1 未签 · 30 应被拒" \
97
+ "D2 HumanGate · D3 30 前置闸" \
98
+ "examples/compliance_bench/S1_r1_pending/" \
99
+ "HG-AUDIT-R1=pending 时 gate-check 必须 exit≠0(拒 30)"
100
+
101
+ local gate_exit=0
102
+ "$HARNESS_ROOT/wizard/gate-check.sh" --target "$dir" --task task.md >/dev/null 2>&1 || gate_exit=$?
103
+
104
+ if [[ "$gate_exit" -ne 0 ]]; then
105
+ scenario_footer "S1" "PASS" "gate-check exit=$gate_exit" \
106
+ "未审核任务不得进入 30 执行编码 · 人闸有效"
107
+ PASS=$((PASS+1))
108
+ RESULTS+=("S1|PASS|gate-check exit=$gate_exit · 30 被拒")
109
+ else
110
+ scenario_footer "S1" "FAIL" "gate-check exit=0" \
111
+ "pending R1 仍放行 30 · 违反 D3 · 须修 gate-check"
112
+ FAIL=$((FAIL+1))
113
+ RESULTS+=("S1|FAIL|gate-check exit=0 · 未拦截 pending R1")
114
+ fi
115
+ }
116
+
117
+ scenario_S2() {
118
+ local dir="$BENCH_DIR/S2_r1_no_review"
119
+ scenario_header "S2" "R1 已签但无 review 落盘" \
120
+ "D2/D3 · 审核制品完整性" \
121
+ "examples/compliance_bench/S2_r1_no_review/" \
122
+ "task 表 HG-AUDIT-R1=approved 但 reviews/ 无 *_audit_R1_* → bench 判非合规"
123
+
124
+ local gate_exit=0
125
+ "$HARNESS_ROOT/wizard/gate-check.sh" --target "$dir" --task task.md >/dev/null 2>&1 && gate_exit=0 || gate_exit=$?
126
+ local review_exists=0
127
+ [[ -n "$(find "$dir/reviews" -type f -name '*_audit_R1_*' 2>/dev/null)" ]] && review_exists=1 || true
128
+
129
+ if [[ "$review_exists" == "0" ]]; then
130
+ scenario_footer "S2" "PASS" "review 缺失 · gate-check exit=$gate_exit" \
131
+ "仅改 task 表不算真审核 · 22 须落盘 review 文件(本夹具测 bench 判定逻辑)"
132
+ PASS=$((PASS+1))
133
+ RESULTS+=("S2|PASS|无 review 文件 · bench 判非合规")
134
+ else
135
+ scenario_footer "S2" "FAIL" "夹具不应含 review 文件" \
136
+ "夹具数据错误 · 检查 S2_r1_no_review/reviews/"
137
+ FAIL=$((FAIL+1))
138
+ RESULTS+=("S2|FAIL|夹具含不应存在的 review")
139
+ fi
140
+ }
141
+
142
+ scenario_S3() {
143
+ local dir="$BENCH_DIR/S3_r1_with_review"
144
+ scenario_header "S3" "R1 已签且 review 已落盘" \
145
+ "D2/D3 · 审核闭环" \
146
+ "examples/compliance_bench/S3_r1_with_review/" \
147
+ "HG-AUDIT-R1=approved 且 reviews/*_audit_R1_* 存在 → gate-check exit=0"
148
+
149
+ local gate_exit=0
150
+ "$HARNESS_ROOT/wizard/gate-check.sh" --target "$dir" --task task.md >/dev/null 2>&1 && gate_exit=0 || gate_exit=$?
151
+ local review_exists=0
152
+ [[ -n "$(find "$dir/reviews" -type f -name '*_audit_R1_*' 2>/dev/null)" ]] && review_exists=1 || true
153
+
154
+ if [[ "$gate_exit" == "0" && "$review_exists" == "1" ]]; then
155
+ scenario_footer "S3" "PASS" "gate-check exit=0 · review 存在" \
156
+ "审核链完整 · 允许进入 30(Happy Path 闸口)"
157
+ PASS=$((PASS+1))
158
+ RESULTS+=("S3|PASS|gate-check 放行 · review 落盘")
159
+ else
160
+ scenario_footer "S3" "FAIL" "gate_ok=$([[ $gate_exit -eq 0 ]] && echo 1 || echo 0) review=$review_exists" \
161
+ "完整审核场景未通过 · 检查 gate-check 或夹具 review 路径"
162
+ FAIL=$((FAIL+1))
163
+ RESULTS+=("S3|FAIL|gate exit=$gate_exit review=$review_exists")
164
+ fi
165
+ }
166
+
167
+ scenario_S4() {
168
+ local dir="$BENCH_DIR/S4_sync_domain"
169
+ scenario_header "S4" "sync plan 不得覆盖业务域" \
170
+ "S2 · 纪律层与 task/reviews 分离" \
171
+ "examples/compliance_bench/S4_sync_domain/ + 临时目标仓" \
172
+ "harness-sync.sh plan 输出不得含 docs/tasks/ 或 docs/harness/reviews/"
173
+
174
+ local tmp
175
+ tmp="$(mktemp -d)"
176
+ mkdir -p "$tmp/.cyning-harness"
177
+ cp "$dir/profile.json" "$tmp/.cyning-harness/profile.json"
178
+ mkdir -p "$tmp/docs/harness/prompts" "$tmp/docs/harness/invokes" "$tmp/.cursor/rules"
179
+
180
+ local plan="$tmp/plan.txt"
181
+ "$HARNESS_ROOT/wizard/harness-sync.sh" plan --target "$tmp" >"$plan" 2>&1 || true
182
+
183
+ local bad=0 bad_lines=""
184
+ if bad_lines="$(grep -E 'docs/tasks/|docs/harness/reviews/' "$plan" 2>/dev/null || true)" && [[ -n "$bad_lines" ]]; then
185
+ bad=1
186
+ fi
187
+ rm -rf "$tmp"
188
+
189
+ if [[ "$bad" == "0" ]]; then
190
+ scenario_footer "S4" "PASS" "sync plan 无 task/reviews 路径" \
191
+ "upgrade 不会覆盖用户 task 与审核记录 · S2 边界成立"
192
+ PASS=$((PASS+1))
193
+ RESULTS+=("S4|PASS|sync plan 未越界")
194
+ else
195
+ scenario_footer "S4" "FAIL" "plan 含 task/reviews 路径" \
196
+ "sync 会污染业务数据 · 须修 harness-sync 写路径"
197
+ FAIL=$((FAIL+1))
198
+ RESULTS+=("S4|FAIL|plan 含 docs/tasks 或 reviews")
199
+ fi
200
+ }
201
+
202
+ print_summary() {
203
+ local total=$((PASS+FAIL))
204
+ local rate=0
205
+ [[ "$total" -gt 0 ]] && rate=$((PASS*100/total))
206
+
207
+ log "══════════════════════════════════════════════════════════════"
208
+ log "摘要 · SDD-Compliance bench"
209
+ log "══════════════════════════════════════════════════════════════"
210
+ log "$(printf '%-6s %-10s %s' '场景' '结果' '详情')"
211
+ for r in "${RESULTS[@]}"; do
212
+ IFS='|' read -r id status detail <<<"$r"
213
+ local mark="✅"
214
+ [[ "$status" == "FAIL" ]] && mark="❌"
215
+ log "$(printf '%-6s %-8s %s' "$id" "$mark $status" "$detail")"
216
+ done
217
+ log ""
218
+ log "合规率 $PASS/$total = ${rate}%"
219
+ log ""
220
+ if [[ "$FAIL" -eq 0 ]]; then
221
+ log "解读 ${rate} = 全部 ${total} 个场景行为符合 SDD 公理(gate-check / sync 回归通过)"
222
+ log " 不是 LLM 解题分数 · 不是业务 CI 绿率 · 不可外推为「AI 胜率」"
223
+ else
224
+ log "解读 ${rate} = ${FAIL} 个场景未通过 · 须修 gate-check / sync 或夹具后再跑"
225
+ log " 失败场景见上表 ❌ 行"
226
+ fi
227
+ log ""
228
+ log "用途 发布关账证据 · 改 wizard 脚本后回归 · 与 B2 试点 retro 互补"
229
+ log "文档 examples/compliance_bench/README.md"
230
+ log "══════════════════════════════════════════════════════════════"
231
+
232
+ # quiet 模式:stdout 仅数字(供 CI / 勾选表解析)
233
+ if [[ "$QUIET" == "1" ]]; then
234
+ echo "$rate"
235
+ fi
236
+ }
237
+
238
+ print_banner
239
+
240
+ for s in "${SCENARIOS[@]}"; do
241
+ case "$s" in
242
+ S1) scenario_S1 ;;
243
+ S2) scenario_S2 ;;
244
+ S3) scenario_S3 ;;
245
+ S4) scenario_S4 ;;
246
+ esac
247
+ done
248
+
249
+ print_summary
250
+
251
+ [[ "$FAIL" -eq 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
package/wizard/install.sh CHANGED
@@ -148,6 +148,7 @@ if [[ "$DRY_RUN" == "1" ]]; then
148
148
  else
149
149
  "$SCRIPT_DIR/harness-sync.sh" apply --target "$TARGET"
150
150
  write_manifest_init "$TARGET" "$VERSION" "$PRESET" "$IDE_LIST" "$PROFILE_DST"
151
+ run cp "$CYNING_HARNESS/harness/templates/QUICKREF_v1_zh.md" "$TARGET/.cyning-harness/QUICKREF.md"
151
152
  fi
152
153
 
153
154
  # CI
@@ -201,6 +202,7 @@ fi
201
202
  echo ""
202
203
  echo "安装完成。profile: $PROFILE_DST"
203
204
  echo "manifest: $(manifest_file_path "$TARGET")"
204
- echo "日后升级: npx @cyning/harness upgrade --target $TARGET"
205
- echo "或维护者: cd $CYNING_HARNESS && ./wizard/upgrade.sh --target $TARGET"
206
- echo "清空重装: $CYNING_HARNESS/wizard/uninstall.sh --target $TARGET"
205
+ echo "常用命令:"
206
+ echo " npx @cyning/harness verify --target $TARGET"
207
+ echo " npx @cyning/harness gate-check --target $TARGET"
208
+ echo " npx @cyning/harness upgrade --target $TARGET --yes"
@@ -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}`);