@cyning/harness 0.3.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/AGENTS.md +20 -0
- package/CHANGELOG.md +127 -0
- package/README.md +133 -0
- package/bin/harness.js +7 -0
- package/ci/samples/README.md +42 -0
- package/ci/samples/pytest.yml.example +49 -0
- package/ci/samples/quality.yml.example +56 -0
- package/coding_wiki/templates/README.md +11 -0
- package/coding_wiki/templates/context.md +11 -0
- package/coding_wiki/templates/stable.md +19 -0
- package/coding_wiki/templates/volatile.md +15 -0
- package/docs/ARCHITECTURE.md +71 -0
- package/docs/DESIGN_ONTOLOGY_v1_zh.md +9 -0
- package/docs/HARNESS_GRAPH_MODEL_design_v0_zh.md +6 -0
- package/docs/HARNESS_GRAPH_MODEL_dialogue_archive_v1_zh.md +8 -0
- package/docs/ONBOARDING.md +126 -0
- package/docs/P0_V0.2_GAP.md +6 -0
- package/docs/README.md +38 -0
- package/docs/methodology/AUDIT_doc_consistency_2026-06-15_zh.md +69 -0
- package/docs/methodology/DOCUMENT_MAP_v1_zh.md +122 -0
- package/docs/methodology/README.md +147 -0
- package/docs/methodology/ROADMAP_v1_zh.md +136 -0
- package/docs/methodology/execution/P0_V0.2_GAP.md +93 -0
- package/docs/methodology/execution/PILOT_EVIDENCE_B2_v1_zh.md +132 -0
- package/docs/methodology/execution/README.md +12 -0
- package/docs/methodology/graph/HARNESS_GRAPH_MODEL_design_v0_zh.md +258 -0
- package/docs/methodology/graph/HARNESS_GRAPH_MODEL_dialogue_archive_v1_zh.md +132 -0
- package/docs/methodology/graph/README.md +22 -0
- package/docs/methodology/pointers/ADOPTION_METHODOLOGY_v1_zh.md +8 -0
- package/docs/methodology/pointers/ADOPTION_TARGET_STATE_v1_zh.md +6 -0
- package/docs/methodology/pointers/ASSESSMENT_etclovg_v1_zh.md +5 -0
- package/docs/methodology/pointers/ASSESSMENT_ink_v1_zh.md +5 -0
- package/docs/methodology/pointers/GUIDANCE_distribution_v1_zh.md +6 -0
- package/docs/methodology/pointers/HARNESS_V2_PLAN_v1_zh.md +8 -0
- package/docs/methodology/pointers/PILOT_kimi_code_fork_v1_zh.md +6 -0
- package/docs/methodology/pointers/POINTER_cyning_harness_repo_v1_zh.md +6 -0
- package/docs/methodology/pointers/README.md +40 -0
- package/docs/methodology/pointers/SDD_HAT_FLOW_v1_zh.md +8 -0
- package/docs/methodology/pointers/STRATEGY_MASTER_v1_zh.md +8 -0
- package/docs/methodology/pointers/STRATEGY_ONTOLOGY_v1_zh.md +9 -0
- package/docs/methodology/product/DESIGN_ONTOLOGY_v1_zh.md +719 -0
- package/docs/methodology/product/README.md +25 -0
- package/docs/methodology/prompts/PROMPT_article_theory_roundtable_v1_zh.md +127 -0
- package/docs/methodology/prompts/PROMPT_doc_consistency_audit_v1_zh.md +131 -0
- package/docs/methodology/prompts/PROMPT_feasibility_review_b_evidence_v1_zh.md +86 -0
- package/docs/methodology/prompts/README.md +10 -0
- package/docs/methodology/reviews/PROCESS_track_b_evidence_audit_2026-06-15_v1_zh.md +101 -0
- package/docs/methodology/reviews/README.md +9 -0
- package/docs/methodology/reviews/REVIEW_B3_pilot_evidence_and_sdd_compliance_bench_v1_zh.md +154 -0
- package/docs/methodology/reviews/REVIEW_B9_agent_shell_parallel_v1_zh.md +158 -0
- package/examples/demo_checkout/ACCEPTANCE.md +80 -0
- package/examples/demo_checkout/README.md +41 -0
- package/examples/demo_checkout/task_demo_p0_golden_v1.md +101 -0
- package/examples/oss-fork/README.md +139 -0
- package/examples/oss-fork/kimi-code/00_main.ai.md +23 -0
- package/examples/oss-fork/kimi-code/00_main.md +38 -0
- package/examples/oss-fork/kimi-code/01_struct.md +39 -0
- package/examples/oss-fork/kimi-code/02_version.md +6 -0
- package/examples/oss-fork/kimi-code/10_flow_cli_session.md +22 -0
- package/examples/oss-fork/kimi-code/README.md +15 -0
- package/golden/POINTER_gold_epic_serial.md +93 -0
- package/golden/README.md +30 -0
- package/graph/stubs/README.md +6 -0
- package/graph/templates/00_main.ai.md +28 -0
- package/graph/templates/00_main.md +52 -0
- package/graph/templates/01_struct.md +38 -0
- package/graph/templates/10_flow_MAIN.ai.md +43 -0
- package/graph/templates/10_flow_MAIN.md +47 -0
- package/graph/templates/99_mermaid_protocol.md +100 -0
- package/graph/templates/README.md +34 -0
- package/harness/README.md +18 -0
- package/harness/invokes/README.md +22 -0
- package/harness/invokes/TEMPLATE_invoke.md +59 -0
- package/harness/prompts/10-requirements.md +42 -0
- package/harness/prompts/22-task-audit.md +67 -0
- package/harness/prompts/30-execute-code.md +50 -0
- package/harness/prompts/FRAGMENT_30_gate_verify_v1_zh.md +30 -0
- package/harness/prompts/FRAGMENT_30_invoke_block_v1_zh.md +27 -0
- package/harness/prompts/README.md +28 -0
- package/harness/prompts/TEMPLATE_30_gate_stop.md +34 -0
- package/harness/templates/FRAGMENT_task_domain_infer_v1_zh.md +16 -0
- package/harness/templates/README.md +48 -0
- package/harness/templates/TASK_TEMPLATE.md +111 -0
- package/harness/templates/TASK_done_README.md +75 -0
- package/harness/templates/TASK_epic.md +101 -0
- package/harness/templates/TASK_graph_bootstrap.md +113 -0
- package/harness/templates/VIEW_done_by_domain.md +66 -0
- package/harness/templates/VIEW_done_thin_pointer.md +16 -0
- package/ide/adapters/AGENTS.md.fragment.example +31 -0
- package/ide/adapters/CLAUDE.md.fragment.example +30 -0
- package/ide/adapters/README.md +46 -0
- package/ide/adapters/cursor-harness-starter.mdc.example +37 -0
- package/lib/cli.js +180 -0
- package/lib/manifest.js +38 -0
- package/lib/paths.js +36 -0
- package/lib/run.js +33 -0
- package/package.json +37 -0
- package/schema/manifest.v1.schema.json +37 -0
- package/standards/POINTER_workspace_truth_v1_zh.md +57 -0
- package/standards/README.md +35 -0
- package/standards/SOURCES_v1_zh.md +49 -0
- package/standards/TEMPLATE_CODING_BASELINE_L1_v1_zh.md +88 -0
- package/standards/TEMPLATE_CODING_BASELINE_L2_backend_v1_zh.md +75 -0
- package/standards/TEMPLATE_CODING_BASELINE_L2_frontend_v1_zh.md +76 -0
- package/wizard/ONBOARDING_wizard_v1_zh.md +112 -0
- package/wizard/README.md +213 -0
- package/wizard/adopt-existing.sh +37 -0
- package/wizard/bootstrap-oss-fork-meta.sh +177 -0
- package/wizard/gate-check.sh +118 -0
- package/wizard/harness-sync.sh +237 -0
- package/wizard/install.sh +206 -0
- package/wizard/lib/common.sh +247 -0
- package/wizard/lib/manifest.sh +110 -0
- package/wizard/profiles/fullstack-node-py.json +21 -0
- package/wizard/profiles/harness-only.json +19 -0
- package/wizard/profiles/ios-cursor.json +19 -0
- package/wizard/profiles/issue-scan-presets.json +51 -0
- package/wizard/profiles/oss-fork-meta.json +17 -0
- package/wizard/scan-upstream-issues.sh +287 -0
- package/wizard/templates/FRAGMENT_rethink_backfill_task_v1_zh.md +44 -0
- package/wizard/templates/POINTER_PILOT_adoption_workspace.md +23 -0
- package/wizard/templates/TASK_TEMPLATE_upstream_pr_v1.md +164 -0
- package/wizard/templates/task_graph_bootstrap_v1.md +53 -0
- package/wizard/uninstall.sh +218 -0
- package/wizard/upgrade.sh +164 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# cyning-harness · 将产品包选定轨道同步到已接入业务仓(不覆盖业务 task / reviews / 自定义图谱正文)
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
MODE="${1:-}"
|
|
6
|
+
TARGET="${TARGET:-$(pwd)}"
|
|
7
|
+
SYNC_FORCE=0
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
# shellcheck source=lib/common.sh
|
|
10
|
+
source "$SCRIPT_DIR/lib/common.sh"
|
|
11
|
+
HARNESS_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
|
+
|
|
13
|
+
MARKER_BEGIN='<!-- cyning-harness:begin -->'
|
|
14
|
+
MARKER_END='<!-- cyning-harness:end -->'
|
|
15
|
+
|
|
16
|
+
usage() {
|
|
17
|
+
cat <<'EOF'
|
|
18
|
+
用法(从业务仓根,或指定 TARGET):
|
|
19
|
+
|
|
20
|
+
CYNING_HARNESS=/path/to/cyning-harness \
|
|
21
|
+
TARGET=/path/to/ios_buy \
|
|
22
|
+
"$CYNING_HARNESS/wizard/harness-sync.sh" plan
|
|
23
|
+
|
|
24
|
+
"$CYNING_HARNESS/wizard/harness-sync.sh" apply
|
|
25
|
+
|
|
26
|
+
或:
|
|
27
|
+
|
|
28
|
+
"$CYNING_HARNESS/wizard/harness-sync.sh" plan --target /path/to/project
|
|
29
|
+
"$CYNING_HARNESS/wizard/harness-sync.sh" apply --target /path/to/project
|
|
30
|
+
"$CYNING_HARNESS/wizard/harness-sync.sh" apply --target /path/to/project --force # 跳过 S5 git-clean
|
|
31
|
+
|
|
32
|
+
依赖:业务仓已有 .cyning-harness/profile.json(由 install.sh 生成)
|
|
33
|
+
|
|
34
|
+
plan — 仅打印将复制的文件
|
|
35
|
+
apply — 执行复制(S5:git 仓须工作区干净,或 --force)
|
|
36
|
+
EOF
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
while [[ $# -gt 0 ]]; do
|
|
40
|
+
case "$1" in
|
|
41
|
+
plan|apply) MODE="$1"; shift ;;
|
|
42
|
+
--target) TARGET="$2"; shift 2 ;;
|
|
43
|
+
--force) SYNC_FORCE=1; shift ;;
|
|
44
|
+
-h|--help) usage; exit 0 ;;
|
|
45
|
+
*) echo "未知参数: $1" >&2; usage; exit 1 ;;
|
|
46
|
+
esac
|
|
47
|
+
done
|
|
48
|
+
|
|
49
|
+
[[ "$MODE" == "plan" || "$MODE" == "apply" ]] || { usage; exit 1; }
|
|
50
|
+
|
|
51
|
+
CYNING_HARNESS="${CYNING_HARNESS:-$HARNESS_ROOT}"
|
|
52
|
+
refuse_if_product_root "$TARGET" "$HARNESS_ROOT"
|
|
53
|
+
PROFILE_FILE="$TARGET/.cyning-harness/profile.json"
|
|
54
|
+
|
|
55
|
+
if [[ ! -f "$PROFILE_FILE" ]]; then
|
|
56
|
+
echo "错误: 未找到 $PROFILE_FILE" >&2
|
|
57
|
+
echo "请先运行: $CYNING_HARNESS/wizard/install.sh --target $TARGET --preset <name>" >&2
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# 简易 JSON 布尔/字符串读取(无 jq 依赖)
|
|
62
|
+
json_bool() { grep -q "\"$1\"[[:space:]]*:[[:space:]]*true" "$PROFILE_FILE"; }
|
|
63
|
+
|
|
64
|
+
track_enabled() {
|
|
65
|
+
local k="$1" default="${2:-false}"
|
|
66
|
+
if ! grep -q "\"$k\"" "$PROFILE_FILE"; then
|
|
67
|
+
[[ "$default" == "true" ]]
|
|
68
|
+
return
|
|
69
|
+
fi
|
|
70
|
+
json_bool "$k"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
json_str() {
|
|
74
|
+
local k="$1" def="$2"
|
|
75
|
+
local line
|
|
76
|
+
line="$(grep "\"$k\"" "$PROFILE_FILE" | head -1)"
|
|
77
|
+
if [[ -z "$line" ]]; then echo "$def"; return; fi
|
|
78
|
+
sed -E 's/.*:[[:space:]]*"([^"]*)".*/\1/' <<<"$line" | head -1
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
IDE_REL=""
|
|
82
|
+
if grep -q '"paths"' "$PROFILE_FILE"; then
|
|
83
|
+
IDE_REL="$(sed -n '/"paths"/,/^ \}/p' "$PROFILE_FILE" | grep '"ide_cursor"' | head -1 | sed -E 's/.*:[[:space:]]*"([^"]+)".*/\1/')"
|
|
84
|
+
fi
|
|
85
|
+
[[ -z "$IDE_REL" ]] && IDE_REL=".cursor/rules/06-harness-pointer.mdc"
|
|
86
|
+
CI_TRACK="$(json_str ci none)"
|
|
87
|
+
|
|
88
|
+
OPS=()
|
|
89
|
+
|
|
90
|
+
add_cp() {
|
|
91
|
+
local src="$1" dst="$2" note="${3:-}"
|
|
92
|
+
OPS+=("cp|$src|$dst|$note")
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
merge_action_label() {
|
|
96
|
+
local dst="$1"
|
|
97
|
+
if [[ ! -f "$dst" ]]; then
|
|
98
|
+
echo "merge(create)"
|
|
99
|
+
elif grep -qF "$MARKER_BEGIN" "$dst"; then
|
|
100
|
+
echo "merge(replace marker)"
|
|
101
|
+
else
|
|
102
|
+
echo "merge(append marker)"
|
|
103
|
+
fi
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
add_merge() {
|
|
107
|
+
local src="$1" dst="$2" note="${3:-}"
|
|
108
|
+
local action
|
|
109
|
+
action="$(merge_action_label "$dst")"
|
|
110
|
+
OPS+=("merge|$src|$dst|$note|$action")
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
merge_fragment_apply() {
|
|
114
|
+
local src="$1" dst="$2"
|
|
115
|
+
local tmp_block tmp_out
|
|
116
|
+
|
|
117
|
+
tmp_block="$(mktemp)"
|
|
118
|
+
{
|
|
119
|
+
printf '%s\n' "$MARKER_BEGIN"
|
|
120
|
+
cat "$src"
|
|
121
|
+
printf '%s\n' "$MARKER_END"
|
|
122
|
+
} > "$tmp_block"
|
|
123
|
+
|
|
124
|
+
if [[ ! -f "$dst" ]]; then
|
|
125
|
+
cp "$tmp_block" "$dst"
|
|
126
|
+
rm -f "$tmp_block"
|
|
127
|
+
return
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
if grep -qF "$MARKER_BEGIN" "$dst"; then
|
|
131
|
+
tmp_out="$(mktemp)"
|
|
132
|
+
awk -v begin="$MARKER_BEGIN" -v end="$MARKER_END" -v blockfile="$tmp_block" '
|
|
133
|
+
BEGIN {
|
|
134
|
+
while ((getline line < blockfile) > 0) block = block line "\n"
|
|
135
|
+
close(blockfile)
|
|
136
|
+
inblock = 0
|
|
137
|
+
replaced = 0
|
|
138
|
+
}
|
|
139
|
+
index($0, begin) && !replaced {
|
|
140
|
+
printf "%s", block
|
|
141
|
+
inblock = 1
|
|
142
|
+
replaced = 1
|
|
143
|
+
next
|
|
144
|
+
}
|
|
145
|
+
inblock {
|
|
146
|
+
if (index($0, end)) inblock = 0
|
|
147
|
+
next
|
|
148
|
+
}
|
|
149
|
+
{ print }
|
|
150
|
+
' "$dst" > "$tmp_out"
|
|
151
|
+
mv "$tmp_out" "$dst"
|
|
152
|
+
else
|
|
153
|
+
printf '\n' >> "$dst"
|
|
154
|
+
cat "$tmp_block" >> "$dst"
|
|
155
|
+
fi
|
|
156
|
+
rm -f "$tmp_block"
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if track_enabled harness_prompts false; then
|
|
160
|
+
for f in "$CYNING_HARNESS/harness/prompts/"*.md; do
|
|
161
|
+
[[ -f "$f" ]] || continue
|
|
162
|
+
add_cp "$f" "$TARGET/docs/harness/prompts/$(basename "$f")" "harness prompts"
|
|
163
|
+
done
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
if track_enabled harness_invoke_template false; then
|
|
167
|
+
add_cp "$CYNING_HARNESS/harness/invokes/TEMPLATE_invoke.md" \
|
|
168
|
+
"$TARGET/docs/harness/invokes/TEMPLATE_invoke.md" "invoke 模板"
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
if track_enabled ide_cursor true; then
|
|
172
|
+
add_cp "$CYNING_HARNESS/ide/adapters/cursor-harness-starter.mdc.example" \
|
|
173
|
+
"$TARGET/$IDE_REL" "Cursor 规则"
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
if track_enabled ide_claude false; then
|
|
177
|
+
add_merge "$CYNING_HARNESS/ide/adapters/CLAUDE.md.fragment.example" \
|
|
178
|
+
"$TARGET/CLAUDE.md" "Claude IDE"
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
if track_enabled ide_agents false; then
|
|
182
|
+
add_merge "$CYNING_HARNESS/ide/adapters/AGENTS.md.fragment.example" \
|
|
183
|
+
"$TARGET/AGENTS.md" "Agents IDE"
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
# 图谱/wiki/standards:默认 sync 不覆盖(避免洗掉 01_struct);install 时写入
|
|
187
|
+
FORCE_TRACKS="${FORCE_TRACKS:-0}"
|
|
188
|
+
if [[ "$FORCE_TRACKS" == "1" ]]; then
|
|
189
|
+
if json_bool graph || grep -q '"graph"[[:space:]]*:[[:space:]]*true' "$PROFILE_FILE"; then
|
|
190
|
+
for f in "$CYNING_HARNESS/graph/templates/"*; do
|
|
191
|
+
[[ -f "$f" ]] || continue
|
|
192
|
+
add_cp "$f" "$TARGET/docs/_tech_graph/$(basename "$f")" "graph(force)"
|
|
193
|
+
done
|
|
194
|
+
fi
|
|
195
|
+
if json_bool wiki || grep -q '"wiki"[[:space:]]*:[[:space:]]*true' "$PROFILE_FILE"; then
|
|
196
|
+
for f in "$CYNING_HARNESS/coding_wiki/templates/"*; do
|
|
197
|
+
[[ -f "$f" ]] || continue
|
|
198
|
+
add_cp "$f" "$TARGET/docs/coding_wiki/$(basename "$f")" "wiki(force)"
|
|
199
|
+
done
|
|
200
|
+
fi
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
if [[ ${#OPS[@]} -eq 0 ]]; then
|
|
204
|
+
echo "无同步项(检查 profile tracks)" >&2
|
|
205
|
+
exit 1
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
if [[ "$MODE" == "apply" ]]; then
|
|
209
|
+
check_git_clean "$TARGET" "$SYNC_FORCE"
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
echo "=== cyning-harness sync ($MODE) ==="
|
|
213
|
+
echo "产品包: $CYNING_HARNESS"
|
|
214
|
+
echo "目标仓: $TARGET"
|
|
215
|
+
echo "profile: $PROFILE_FILE"
|
|
216
|
+
echo ""
|
|
217
|
+
|
|
218
|
+
for op in "${OPS[@]}"; do
|
|
219
|
+
IFS='|' read -r kind src dst note extra <<<"$op"
|
|
220
|
+
if [[ "$kind" == "merge" ]]; then
|
|
221
|
+
echo "[$note · $extra] $src -> $dst"
|
|
222
|
+
if [[ "$MODE" == "apply" ]]; then
|
|
223
|
+
merge_fragment_apply "$src" "$dst"
|
|
224
|
+
fi
|
|
225
|
+
else
|
|
226
|
+
echo "[$note] $src -> $dst"
|
|
227
|
+
if [[ "$MODE" == "apply" ]]; then
|
|
228
|
+
mkdir -p "$(dirname "$dst")"
|
|
229
|
+
cp "$src" "$dst"
|
|
230
|
+
fi
|
|
231
|
+
fi
|
|
232
|
+
done
|
|
233
|
+
|
|
234
|
+
if [[ "$MODE" == "apply" ]]; then
|
|
235
|
+
echo ""
|
|
236
|
+
echo "完成。建议: $CYNING_HARNESS/wizard/gate-check.sh --target $TARGET"
|
|
237
|
+
fi
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# cyning-harness · 首次接入业务仓(生成 profile + 按 preset 复制)
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
# shellcheck source=lib/common.sh
|
|
7
|
+
source "$SCRIPT_DIR/lib/common.sh"
|
|
8
|
+
# shellcheck source=lib/manifest.sh
|
|
9
|
+
source "$SCRIPT_DIR/lib/manifest.sh"
|
|
10
|
+
HARNESS_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
11
|
+
TARGET=""
|
|
12
|
+
PRESET="harness-only"
|
|
13
|
+
DRY_RUN=0
|
|
14
|
+
IDE_LIST=""
|
|
15
|
+
TARGET_PROVIDED=0
|
|
16
|
+
PRESET_PROVIDED=0
|
|
17
|
+
IDE_PROVIDED=0
|
|
18
|
+
|
|
19
|
+
usage() {
|
|
20
|
+
cat <<'EOF'
|
|
21
|
+
用法:
|
|
22
|
+
|
|
23
|
+
# 在产品包目录 · 交互引导(推荐)
|
|
24
|
+
cd /path/to/cyning-harness
|
|
25
|
+
./wizard/install.sh
|
|
26
|
+
|
|
27
|
+
# 在业务仓目录 · 默认 target 为当前目录
|
|
28
|
+
cd /path/to/your-project
|
|
29
|
+
/path/to/cyning-harness/wizard/install.sh --preset harness-only
|
|
30
|
+
|
|
31
|
+
# 全参数非交互
|
|
32
|
+
/path/to/cyning-harness/wizard/install.sh \
|
|
33
|
+
--target /path/to/project --preset harness-only --ide cursor,claude,agents
|
|
34
|
+
|
|
35
|
+
preset(交互向导:1 或 2):
|
|
36
|
+
harness-only — 仅过程轨 + IDE(常用 · kimi-code-meta 等)
|
|
37
|
+
fullstack-node-py — 全栈五轨 + quality + pytest(Ink 类)
|
|
38
|
+
其他 preset 仍可通过 --preset <name> 使用(如 ios-cursor、oss-fork-meta)
|
|
39
|
+
|
|
40
|
+
选项:
|
|
41
|
+
--target PATH 业务仓根(未指定时进入引导;在业务仓内可默认当前目录)
|
|
42
|
+
--preset NAME 见 wizard/profiles/
|
|
43
|
+
--ide LIST 逗号分隔:cursor,claude,agents(写入 profile tracks)
|
|
44
|
+
--dry-run 只打印,不写入(profile 仍会写入供 sync plan)
|
|
45
|
+
EOF
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
while [[ $# -gt 0 ]]; do
|
|
49
|
+
case "$1" in
|
|
50
|
+
--target) TARGET="$2"; TARGET_PROVIDED=1; shift 2 ;;
|
|
51
|
+
--preset) PRESET="$2"; PRESET_PROVIDED=1; shift 2 ;;
|
|
52
|
+
--ide) IDE_LIST="$2"; IDE_PROVIDED=1; shift 2 ;;
|
|
53
|
+
--dry-run) DRY_RUN=1; shift ;;
|
|
54
|
+
-h|--help) usage; exit 0 ;;
|
|
55
|
+
*) echo "未知参数: $1" >&2; usage; exit 1 ;;
|
|
56
|
+
esac
|
|
57
|
+
done
|
|
58
|
+
|
|
59
|
+
CYNING_HARNESS="${CYNING_HARNESS:-$HARNESS_ROOT}"
|
|
60
|
+
VERSION="${HARNESS_VERSION:-$(git -C "$HARNESS_ROOT" describe --tags --always 2>/dev/null || echo unknown)}"
|
|
61
|
+
|
|
62
|
+
# --- 交互:仅补全命令行未提供的项 ---
|
|
63
|
+
if [[ "$TARGET_PROVIDED" -eq 0 ]]; then
|
|
64
|
+
echo "=== cyning-harness 安装向导 ==="
|
|
65
|
+
echo "产品包: $HARNESS_ROOT"
|
|
66
|
+
echo "版本: $VERSION"
|
|
67
|
+
echo ""
|
|
68
|
+
cwd="$(pwd)"
|
|
69
|
+
default_target=""
|
|
70
|
+
if ! is_product_root_path "$cwd" "$HARNESS_ROOT"; then
|
|
71
|
+
default_target="$cwd"
|
|
72
|
+
fi
|
|
73
|
+
if [[ -n "$default_target" ]]; then
|
|
74
|
+
read -r -p "业务仓路径 [$default_target]: " TARGET_INPUT
|
|
75
|
+
TARGET="${TARGET_INPUT:-$default_target}"
|
|
76
|
+
else
|
|
77
|
+
echo "请输入要接入 Harness 的业务仓路径"
|
|
78
|
+
read -r -p "业务仓路径: " TARGET_INPUT
|
|
79
|
+
[[ -n "$TARGET_INPUT" ]] || { echo "未输入路径,已取消。" >&2; exit 1; }
|
|
80
|
+
TARGET="$TARGET_INPUT"
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
if [[ "$PRESET_PROVIDED" -eq 0 ]]; then
|
|
85
|
+
if [[ "$TARGET_PROVIDED" -eq 1 ]]; then
|
|
86
|
+
echo "=== cyning-harness install(补全参数)==="
|
|
87
|
+
fi
|
|
88
|
+
print_preset_menu
|
|
89
|
+
read -r -p "preset [1]: " PRESET_INPUT
|
|
90
|
+
PRESET="$(resolve_preset_choice "$PRESET_INPUT" "$SCRIPT_DIR/profiles")"
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
if [[ "$IDE_PROVIDED" -eq 0 ]]; then
|
|
94
|
+
if prompt_yes_no "是否配置 IDE 勾选?" "y"; then
|
|
95
|
+
print_ide_menu
|
|
96
|
+
read -r -p "IDE [1]: " IDE_INPUT
|
|
97
|
+
IDE_LIST="$(resolve_ide_choice "${IDE_INPUT:-1}")"
|
|
98
|
+
fi
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
[[ -d "$TARGET" ]] || { echo "错误: 目录不存在: $TARGET" >&2; exit 1; }
|
|
102
|
+
TARGET="$(abs_path "$TARGET")"
|
|
103
|
+
refuse_if_product_root "$TARGET" "$HARNESS_ROOT"
|
|
104
|
+
|
|
105
|
+
PRESET_FILE="$SCRIPT_DIR/profiles/${PRESET}.json"
|
|
106
|
+
[[ -f "$PRESET_FILE" ]] || { echo "未知 preset: $PRESET" >&2; exit 1; }
|
|
107
|
+
|
|
108
|
+
run() {
|
|
109
|
+
if [[ "$DRY_RUN" == "1" ]]; then echo "[dry-run] $*"; else eval "$@"; fi
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
echo ""
|
|
113
|
+
echo "=== cyning-harness install ==="
|
|
114
|
+
echo "目标: $TARGET"
|
|
115
|
+
echo "preset: $PRESET"
|
|
116
|
+
[[ -n "$IDE_LIST" ]] && echo "ide: $IDE_LIST"
|
|
117
|
+
echo ""
|
|
118
|
+
|
|
119
|
+
run mkdir -p "$TARGET/.cyning-harness"
|
|
120
|
+
run mkdir -p "$TARGET/docs/tasks/active" "$TARGET/docs/tasks/done" \
|
|
121
|
+
"$TARGET/docs/tasks/_views" \
|
|
122
|
+
"$TARGET/docs/tasks/done/harness" "$TARGET/docs/tasks/done/cyning-harness" \
|
|
123
|
+
"$TARGET/docs/tasks/done/engineering" "$TARGET/docs/tasks/done/governance" \
|
|
124
|
+
"$TARGET/docs/tasks/done/chatbi" "$TARGET/docs/tasks/done/standards" \
|
|
125
|
+
"$TARGET/docs/tasks/done/epics"
|
|
126
|
+
run mkdir -p "$TARGET/docs/harness/prompts" "$TARGET/docs/harness/reviews" "$TARGET/docs/harness/invokes/by-task"
|
|
127
|
+
run mkdir -p "$TARGET/docs/_tech_graph" "$TARGET/docs/coding_wiki" "$TARGET/docs/standards"
|
|
128
|
+
|
|
129
|
+
PROFILE_DST="$TARGET/.cyning-harness/profile.json"
|
|
130
|
+
|
|
131
|
+
# profile 须真实写入,供 harness-sync plan/apply 读取(含 dry-run)
|
|
132
|
+
mkdir -p "$TARGET/.cyning-harness"
|
|
133
|
+
cp "$PRESET_FILE" "$PROFILE_DST"
|
|
134
|
+
if [[ -n "$IDE_LIST" ]]; then
|
|
135
|
+
patch_profile_ide "$PROFILE_DST" "$IDE_LIST"
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
if [[ "$DRY_RUN" == "1" ]]; then
|
|
139
|
+
echo "[dry-run] profile: $PROFILE_DST"
|
|
140
|
+
else
|
|
141
|
+
printf '%s\n' "{\"cyning_harness_root\":\"$CYNING_HARNESS\"}" > "$TARGET/.cyning-harness/local.json"
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# 委托 sync(install 时 force 图谱/wiki)
|
|
145
|
+
export CYNING_HARNESS TARGET FORCE_TRACKS=1
|
|
146
|
+
if [[ "$DRY_RUN" == "1" ]]; then
|
|
147
|
+
"$SCRIPT_DIR/harness-sync.sh" plan --target "$TARGET"
|
|
148
|
+
else
|
|
149
|
+
"$SCRIPT_DIR/harness-sync.sh" apply --target "$TARGET"
|
|
150
|
+
write_manifest_init "$TARGET" "$VERSION" "$PRESET" "$IDE_LIST" "$PROFILE_DST"
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
# CI
|
|
154
|
+
CI_MODE="$(grep '"ci"' "$PRESET_FILE" | sed -E 's/.*:[[:space:]]*"([^"]*)".*/\1/' | head -1)"
|
|
155
|
+
if [[ "$CI_MODE" == "both" ]]; then
|
|
156
|
+
run mkdir -p "$TARGET/.github/workflows"
|
|
157
|
+
run cp "$CYNING_HARNESS/ci/samples/quality.yml.example" "$TARGET/.github/workflows/quality.yml"
|
|
158
|
+
run cp "$CYNING_HARNESS/ci/samples/pytest.yml.example" "$TARGET/.github/workflows/pytest.yml"
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
# standards L1/L2
|
|
162
|
+
if grep -q '"standards_l1"[[:space:]]*:[[:space:]]*true' "$PRESET_FILE"; then
|
|
163
|
+
for f in "$CYNING_HARNESS/standards/"TEMPLATE_CODING_BASELINE_L1*.md; do
|
|
164
|
+
[[ -f "$f" ]] || continue
|
|
165
|
+
run cp "$f" "$TARGET/docs/standards/"
|
|
166
|
+
done
|
|
167
|
+
fi
|
|
168
|
+
if grep -q '"standards_l2"[[:space:]]*:[[:space:]]*true' "$PRESET_FILE"; then
|
|
169
|
+
for f in "$CYNING_HARNESS/standards/"TEMPLATE_CODING_BASELINE_L2*.md; do
|
|
170
|
+
[[ -f "$f" ]] || continue
|
|
171
|
+
run cp "$f" "$TARGET/docs/standards/"
|
|
172
|
+
done
|
|
173
|
+
for f in "$CYNING_HARNESS/standards/"TEMPLATE_SOURCES*.md; do
|
|
174
|
+
[[ -f "$f" ]] || continue
|
|
175
|
+
run cp "$f" "$TARGET/docs/standards/"
|
|
176
|
+
done
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# done Hub + _views(仅不存在时)
|
|
180
|
+
if [[ ! -f "$TARGET/docs/tasks/done/README.md" ]]; then
|
|
181
|
+
run cp "$CYNING_HARNESS/harness/templates/TASK_done_README.md" \
|
|
182
|
+
"$TARGET/docs/tasks/done/README.md"
|
|
183
|
+
fi
|
|
184
|
+
if [[ ! -f "$TARGET/docs/tasks/_views/done.md" ]]; then
|
|
185
|
+
run cp "$CYNING_HARNESS/harness/templates/VIEW_done_thin_pointer.md" \
|
|
186
|
+
"$TARGET/docs/tasks/_views/done.md"
|
|
187
|
+
fi
|
|
188
|
+
if [[ ! -f "$TARGET/docs/tasks/_views/done_by_domain.md" ]]; then
|
|
189
|
+
run cp "$CYNING_HARNESS/harness/templates/VIEW_done_by_domain.md" \
|
|
190
|
+
"$TARGET/docs/tasks/_views/done_by_domain.md"
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
# bootstrap task(仅不存在时)
|
|
194
|
+
if grep -q '"harness_task_bootstrap"[[:space:]]*:[[:space:]]*true' "$PRESET_FILE"; then
|
|
195
|
+
if [[ ! -f "$TARGET/docs/tasks/active/task_graph_bootstrap.md" ]] && [[ ! -f "$TARGET/docs/tasks/done/task_graph_bootstrap"*.md ]]; then
|
|
196
|
+
run cp "$CYNING_HARNESS/harness/templates/TASK_graph_bootstrap.md" \
|
|
197
|
+
"$TARGET/docs/tasks/active/task_graph_bootstrap.md"
|
|
198
|
+
fi
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
echo ""
|
|
202
|
+
echo "安装完成。profile: $PROFILE_DST"
|
|
203
|
+
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"
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# cyning-harness · wizard 脚本共享函数(source 用,勿直接执行)
|
|
2
|
+
# shellcheck shell=bash
|
|
3
|
+
|
|
4
|
+
MARKER_BEGIN='<!-- cyning-harness:begin -->'
|
|
5
|
+
MARKER_END='<!-- cyning-harness:end -->'
|
|
6
|
+
|
|
7
|
+
# 规范化绝对路径
|
|
8
|
+
abs_path() {
|
|
9
|
+
cd "$1" && pwd
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
# 判断目标路径是否为产品包根目录
|
|
13
|
+
is_product_root_path() {
|
|
14
|
+
local target="$1" harness_root="$2"
|
|
15
|
+
[[ "$(abs_path "$target")" == "$(abs_path "$harness_root")" ]]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# 禁止对产品包自身执行 install/sync(误在 cyning-harness 根目录 apply 时拦截)
|
|
19
|
+
refuse_if_product_root() {
|
|
20
|
+
local target="$1" harness_root="$2"
|
|
21
|
+
local target_abs harness_abs
|
|
22
|
+
target_abs="$(abs_path "$target")"
|
|
23
|
+
harness_abs="$(abs_path "$harness_root")"
|
|
24
|
+
if is_product_root_path "$target" "$harness_root"; then
|
|
25
|
+
echo "错误: 目标路径不能是 cyning-harness 产品包自身: ${harness_abs}" >&2
|
|
26
|
+
echo "提示: 在业务仓升级;或在产品包目录运行 wizard/upgrade.sh 按提示输入业务仓路径。" >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# 更新 profile.json tracks 布尔(无 jq)
|
|
32
|
+
set_json_track_bool() {
|
|
33
|
+
local file="$1" key="$2" val="$3"
|
|
34
|
+
local tmp
|
|
35
|
+
tmp="$(mktemp)"
|
|
36
|
+
if grep -q "\"$key\"" "$file"; then
|
|
37
|
+
sed -E "s/\"$key\"[[:space:]]*:[[:space:]]*(true|false)/\"$key\": $val/" "$file" > "$tmp"
|
|
38
|
+
else
|
|
39
|
+
sed "/\"ci\"/i\\
|
|
40
|
+
\"$key\": $val,
|
|
41
|
+
" "$file" > "$tmp"
|
|
42
|
+
fi
|
|
43
|
+
mv "$tmp" "$file"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
patch_profile_ide() {
|
|
47
|
+
local profile="$1" ide_list="$2"
|
|
48
|
+
local want_cursor=false want_claude=false want_agents=false
|
|
49
|
+
|
|
50
|
+
IFS=',' read -ra parts <<< "$ide_list"
|
|
51
|
+
for raw in "${parts[@]}"; do
|
|
52
|
+
local p="${raw// /}"
|
|
53
|
+
case "$p" in
|
|
54
|
+
cursor) want_cursor=true ;;
|
|
55
|
+
claude) want_claude=true ;;
|
|
56
|
+
agents) want_agents=true ;;
|
|
57
|
+
*) echo "未知 IDE: $p(允许: cursor, claude, agents)" >&2; exit 1 ;;
|
|
58
|
+
esac
|
|
59
|
+
done
|
|
60
|
+
|
|
61
|
+
set_json_track_bool "$profile" ide_cursor "$want_cursor"
|
|
62
|
+
set_json_track_bool "$profile" ide_claude "$want_claude"
|
|
63
|
+
set_json_track_bool "$profile" ide_agents "$want_agents"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# 从 profile 读取 preset 名(无 jq)
|
|
67
|
+
profile_preset_name() {
|
|
68
|
+
local file="$1"
|
|
69
|
+
grep '"preset"' "$file" | head -1 | sed -E 's/.*:[[:space:]]*"([^"]+)".*/\1/'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# 从 profile 拼 IDE 勾选展示
|
|
73
|
+
profile_ide_summary() {
|
|
74
|
+
local file="$1"
|
|
75
|
+
local parts=()
|
|
76
|
+
if grep -q '"ide_cursor"[[:space:]]*:[[:space:]]*true' "$file"; then parts+=("cursor"); fi
|
|
77
|
+
if grep -q '"ide_claude"[[:space:]]*:[[:space:]]*true' "$file"; then parts+=("claude"); fi
|
|
78
|
+
if grep -q '"ide_agents"[[:space:]]*:[[:space:]]*true' "$file"; then parts+=("agents"); fi
|
|
79
|
+
if [[ ${#parts[@]} -eq 0 ]]; then
|
|
80
|
+
echo "(无 IDE 轨启用)"
|
|
81
|
+
else
|
|
82
|
+
local IFS=,
|
|
83
|
+
echo "${parts[*]}"
|
|
84
|
+
fi
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
prompt_yes_no() {
|
|
88
|
+
local prompt="$1" default="${2:-n}"
|
|
89
|
+
local hint="y/N"
|
|
90
|
+
if [[ "$default" == "y" ]]; then hint="Y/n"; fi
|
|
91
|
+
local ans=""
|
|
92
|
+
read -r -p "$prompt [$hint]: " ans
|
|
93
|
+
ans="${ans:-$default}"
|
|
94
|
+
[[ "$ans" =~ ^[yY] ]]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# 安装向导 · preset 代号菜单(交互仅 1/2;CLI --preset 仍可用 profiles 下任意 json)
|
|
98
|
+
print_preset_menu() {
|
|
99
|
+
cat <<'EOF'
|
|
100
|
+
preset 代号(回车=1):
|
|
101
|
+
|
|
102
|
+
1 harness-only
|
|
103
|
+
· 仅 Harness 过程轨:prompts、invoke 模板、IDE 入口(Cursor / Claude / Agents)
|
|
104
|
+
· 不写入图谱 / wiki / standards / CI;不覆盖已有业务 task / reviews / by-task invoke
|
|
105
|
+
· 适合:kimi-code-meta、任意仓只要 SDD 帽子链与 invoke 纪律
|
|
106
|
+
|
|
107
|
+
2 fullstack-node-py
|
|
108
|
+
· Ink 类全栈五轨:图谱模板 + coding_wiki + L1/L2 规范 + Harness 过程轨 + Cursor
|
|
109
|
+
· 附带 .github/workflows/quality.yml + pytest.yml(Next 前端 + Python API 后端 CI 样例)
|
|
110
|
+
· 适合:ai-ink-brain + api-python 同类前后端分仓或 monorepo
|
|
111
|
+
|
|
112
|
+
亦可输入全称:harness-only / fullstack-node-py
|
|
113
|
+
EOF
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# 将代号或全称解析为 preset 文件名(不含 .json)
|
|
117
|
+
resolve_preset_choice() {
|
|
118
|
+
local input="$1"
|
|
119
|
+
local profiles_dir="${2:-}"
|
|
120
|
+
case "${input:-1}" in
|
|
121
|
+
1|h|harness|harness-only) echo "harness-only"; return ;;
|
|
122
|
+
2|fs|full|fullstack|fullstack-node-py) echo "fullstack-node-py"; return ;;
|
|
123
|
+
esac
|
|
124
|
+
if [[ -n "$profiles_dir" && -f "$profiles_dir/${input}.json" ]]; then
|
|
125
|
+
echo "$input"
|
|
126
|
+
return
|
|
127
|
+
fi
|
|
128
|
+
echo "未知 preset 代号: $input(交互可用 1 或 2,或输入全称)" >&2
|
|
129
|
+
exit 1
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# 安装向导 · IDE 代号菜单
|
|
133
|
+
print_ide_menu() {
|
|
134
|
+
cat <<'EOF'
|
|
135
|
+
IDE 代号(回车=1 全部):
|
|
136
|
+
1 全部(cursor + claude + agents)
|
|
137
|
+
2 仅 cursor
|
|
138
|
+
3 仅 claude
|
|
139
|
+
4 仅 agents
|
|
140
|
+
可组合: 2,3 或 c,cl,a
|
|
141
|
+
EOF
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# 将代号解析为 install --ide 逗号列表
|
|
145
|
+
resolve_ide_choice() {
|
|
146
|
+
local input="${1:-1}"
|
|
147
|
+
local want_cursor=false want_claude=false want_agents=false
|
|
148
|
+
|
|
149
|
+
if [[ -z "$input" || "$input" == "1" || "$input" == "all" ]]; then
|
|
150
|
+
echo "cursor,claude,agents"
|
|
151
|
+
return
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
IFS=',' read -ra parts <<< "$input"
|
|
155
|
+
for raw in "${parts[@]}"; do
|
|
156
|
+
local p="${raw// /}"
|
|
157
|
+
case "$p" in
|
|
158
|
+
1|all) want_cursor=true; want_claude=true; want_agents=true ;;
|
|
159
|
+
2|c|cursor) want_cursor=true ;;
|
|
160
|
+
3|cl|claude) want_claude=true ;;
|
|
161
|
+
4|a|agents) want_agents=true ;;
|
|
162
|
+
*) echo "未知 IDE 代号: $p(可用 1–4 或 c/cl/a)" >&2; exit 1 ;;
|
|
163
|
+
esac
|
|
164
|
+
done
|
|
165
|
+
|
|
166
|
+
local out=()
|
|
167
|
+
[[ "$want_cursor" == true ]] && out+=("cursor")
|
|
168
|
+
[[ "$want_claude" == true ]] && out+=("claude")
|
|
169
|
+
[[ "$want_agents" == true ]] && out+=("agents")
|
|
170
|
+
if [[ ${#out[@]} -eq 0 ]]; then
|
|
171
|
+
echo "未选择任何 IDE" >&2
|
|
172
|
+
exit 1
|
|
173
|
+
fi
|
|
174
|
+
local IFS=,
|
|
175
|
+
echo "${out[*]}"
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
profile_json_bool() {
|
|
179
|
+
local file="$1" key="$2"
|
|
180
|
+
grep -q "\"$key\"[[:space:]]*:[[:space:]]*true" "$file"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
profile_track_enabled() {
|
|
184
|
+
local file="$1" key="$2" default="${3:-false}"
|
|
185
|
+
if ! grep -q "\"$key\"" "$file"; then
|
|
186
|
+
[[ "$default" == "true" ]]
|
|
187
|
+
return
|
|
188
|
+
fi
|
|
189
|
+
profile_json_bool "$file" "$key"
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
profile_ide_cursor_rel() {
|
|
193
|
+
local file="$1" rel=""
|
|
194
|
+
if grep -q '"paths"' "$file"; then
|
|
195
|
+
rel="$(sed -n '/"paths"/,/^ \}/p' "$file" | grep '"ide_cursor"' | head -1 | sed -E 's/.*:[[:space:]]*"([^"]+)".*/\1/')"
|
|
196
|
+
fi
|
|
197
|
+
[[ -z "$rel" ]] && rel=".cursor/rules/06-harness-pointer.mdc"
|
|
198
|
+
echo "$rel"
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
# 从 CLAUDE.md / AGENTS.md 剥离 Harness marker 块;若仅剩空文件则删除
|
|
202
|
+
# S5 · sync/apply 前检测 Git 工作区干净(v0.3+)
|
|
203
|
+
# 非 git 仓跳过;--force 或 HARNESS_SYNC_FORCE=1 跳过
|
|
204
|
+
check_git_clean() {
|
|
205
|
+
local target="$1" force="${2:-0}"
|
|
206
|
+
if [[ "$force" == "1" || "${HARNESS_SYNC_FORCE:-0}" == "1" ]]; then
|
|
207
|
+
return 0
|
|
208
|
+
fi
|
|
209
|
+
if ! git -C "$target" rev-parse --git-dir >/dev/null 2>&1; then
|
|
210
|
+
return 0
|
|
211
|
+
fi
|
|
212
|
+
if git -C "$target" diff --quiet 2>/dev/null && git -C "$target" diff --cached --quiet 2>/dev/null; then
|
|
213
|
+
return 0
|
|
214
|
+
fi
|
|
215
|
+
echo "错误(S5): Git 工作区不干净,sync apply 已中止。" >&2
|
|
216
|
+
echo "请先 commit/stash,或使用 --force / HARNESS_SYNC_FORCE=1" >&2
|
|
217
|
+
git -C "$target" status --short >&2 || true
|
|
218
|
+
exit 1
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
remove_harness_marker_file() {
|
|
222
|
+
local dst="$1"
|
|
223
|
+
if [[ ! -f "$dst" ]]; then
|
|
224
|
+
return 0
|
|
225
|
+
fi
|
|
226
|
+
if ! grep -qF "$MARKER_BEGIN" "$dst"; then
|
|
227
|
+
return 0
|
|
228
|
+
fi
|
|
229
|
+
local tmp_out
|
|
230
|
+
tmp_out="$(mktemp)"
|
|
231
|
+
awk -v begin="$MARKER_BEGIN" -v end="$MARKER_END" '
|
|
232
|
+
BEGIN { inblock = 0 }
|
|
233
|
+
index($0, begin) { inblock = 1; next }
|
|
234
|
+
inblock {
|
|
235
|
+
if (index($0, end)) inblock = 0
|
|
236
|
+
next
|
|
237
|
+
}
|
|
238
|
+
{ print }
|
|
239
|
+
' "$dst" > "$tmp_out"
|
|
240
|
+
if [[ ! -s "$tmp_out" ]]; then
|
|
241
|
+
rm -f "$dst" "$tmp_out"
|
|
242
|
+
echo " 已删除(仅剩 Harness 内容): $dst"
|
|
243
|
+
else
|
|
244
|
+
mv "$tmp_out" "$dst"
|
|
245
|
+
echo " 已剥离 marker 块: $dst"
|
|
246
|
+
fi
|
|
247
|
+
}
|