@cyning/harness 0.3.2 → 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.
- package/CHANGELOG.md +41 -0
- package/LICENSE +21 -0
- package/README.md +61 -13
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/BENCHMARK_REPORT_TEMPLATE.md +135 -0
- package/docs/ETCLOVG_MAPPING_v1_zh.md +65 -0
- package/docs/ONBOARDING.md +29 -0
- package/docs/PUSH_AUDIT_a3_v1.md +52 -0
- package/docs/RELEASE_v0.4.0.md +89 -0
- package/docs/RELEASE_v1.0.0.md +90 -0
- package/docs/ROADMAP_TO_AGENT_GOVERNANCE.md +209 -0
- package/docs/methodology/AUDIT_doc_consistency_2026-06-15_zh.md +5 -3
- package/docs/methodology/README.md +7 -6
- package/docs/methodology/ROADMAP_v1_zh.md +23 -11
- package/docs/methodology/execution/PILOT_EVIDENCE_B2_v1_zh.md +45 -37
- package/docs/methodology/execution/TEMPLATE_HG_RELEASE_v1_zh.md +57 -0
- package/docs/methodology/graph/HARNESS_GRAPH_MODEL_design_v0_zh.md +24 -23
- package/docs/methodology/graph/HARNESS_GRAPH_MODEL_dialogue_archive_v1_zh.md +2 -2
- package/docs/methodology/graph/README.md +2 -2
- package/docs/methodology/product/DESIGN_ONTOLOGY_v1_zh.md +2 -2
- package/docs/methodology/prompts/PROMPT_article_theory_roundtable_v1_zh.md +3 -3
- package/docs/methodology/prompts/PROMPT_doc_consistency_audit_v1_zh.md +4 -4
- package/examples/compliance_bench/README.md +28 -0
- package/examples/compliance_bench/S1_r1_pending/task.md +30 -0
- package/examples/compliance_bench/S2_r1_no_review/task.md +31 -0
- package/examples/compliance_bench/S3_r1_with_review/reviews/s3_r1_with_review_audit_R1_20260616.md +18 -0
- package/examples/compliance_bench/S3_r1_with_review/task.md +31 -0
- package/examples/compliance_bench/S4_sync_domain/profile.json +16 -0
- package/examples/demo_checkout/ACCEPTANCE.md +16 -3
- package/examples/oss-fork/README.md +3 -4
- package/golden/POINTER_gold_epic_serial.md +11 -50
- package/golden/README.md +6 -6
- package/harness/prompts/README.md +4 -4
- package/lib/audit.js +168 -0
- package/lib/cli.js +50 -0
- package/ontology.yaml +93 -0
- package/package.json +3 -1
- package/schema/invoke_index.v1.schema.json +53 -0
- package/standards/POINTER_workspace_truth_v1_zh.md +11 -26
- package/wizard/compliance-bench.sh +156 -0
- package/wizard/gate-check.sh +139 -19
- package/wizard/harness-sync.sh +16 -3
- package/wizard/lib/generate-invoke-index.js +60 -0
package/wizard/gate-check.sh
CHANGED
|
@@ -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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
101
|
-
echo "
|
|
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"
|
package/wizard/harness-sync.sh
CHANGED
|
@@ -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
|
|
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}`);
|