@dannylee1020/kkt 0.5.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/LICENSE +201 -0
- package/README.md +275 -0
- package/assets/kkt-readme-modern.png +0 -0
- package/bin/kkt-install.mjs +25 -0
- package/cmd/kkt/main.go +15 -0
- package/go.mod +3 -0
- package/internal/workflow/cli.go +349 -0
- package/internal/workflow/cli_test.go +1266 -0
- package/internal/workflow/guardrails.go +900 -0
- package/internal/workflow/init.go +164 -0
- package/internal/workflow/init_test.go +113 -0
- package/internal/workflow/operations.go +1855 -0
- package/internal/workflow/validation_commands.go +291 -0
- package/internal/workflow/workspace.go +802 -0
- package/internal/workflow/workspace_test.go +285 -0
- package/package.json +45 -0
- package/scripts/install-cli.sh +210 -0
- package/scripts/install.sh +644 -0
- package/skills/kkt/SKILL.md +74 -0
- package/skills/kkt/references/discovery-tooling.md +53 -0
- package/skills/kkt/references/feature-optimization-model.md +120 -0
- package/skills/kkt/references/kkt-kernel.md +58 -0
- package/skills/kkt/references/layered-modeling-methods.md +101 -0
- package/skills/kkt/references/plan-assimilation.md +46 -0
- package/skills/kkt/references/schemas.md +231 -0
- package/skills/kkt/references/state-contract.md +76 -0
- package/skills/kkt-loop/SKILL.md +99 -0
- package/skills/kkt-loop/references/discovery-tooling.md +53 -0
- package/skills/kkt-loop/references/feature-optimization-model.md +120 -0
- package/skills/kkt-loop/references/kkt-kernel.md +58 -0
- package/skills/kkt-loop/references/layered-modeling-methods.md +101 -0
- package/skills/kkt-loop/references/plan-assimilation.md +46 -0
- package/skills/kkt-loop/references/schemas.md +231 -0
- package/skills/kkt-loop/references/state-contract.md +76 -0
- package/skills/kkt-model/SKILL.md +76 -0
- package/skills/kkt-model/references/discovery-tooling.md +53 -0
- package/skills/kkt-model/references/feature-optimization-model.md +120 -0
- package/skills/kkt-model/references/kkt-kernel.md +58 -0
- package/skills/kkt-model/references/layered-modeling-methods.md +101 -0
- package/skills/kkt-model/references/plan-assimilation.md +46 -0
- package/skills/kkt-model/references/schemas.md +231 -0
- package/skills/kkt-model/references/state-contract.md +76 -0
- package/skills/kkt-run/SKILL.md +62 -0
- package/skills/kkt-run/references/discovery-tooling.md +53 -0
- package/skills/kkt-run/references/feature-optimization-model.md +120 -0
- package/skills/kkt-run/references/kkt-kernel.md +58 -0
- package/skills/kkt-run/references/layered-modeling-methods.md +101 -0
- package/skills/kkt-run/references/plan-assimilation.md +46 -0
- package/skills/kkt-run/references/schemas.md +231 -0
- package/skills/kkt-run/references/state-contract.md +76 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
skill_names=("kkt" "kkt-loop" "kkt-model" "kkt-run")
|
|
5
|
+
legacy_skill_names=("kkt-intent" "kkt-discovery" "kkt-modeling" "kkt-execution" "kkt-validation")
|
|
6
|
+
native_targets=("codex" "claude" "pi" "opencode")
|
|
7
|
+
|
|
8
|
+
usage() {
|
|
9
|
+
cat <<'EOF'
|
|
10
|
+
kkt installer
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
scripts/install.sh [installer options]
|
|
14
|
+
scripts/install.sh upgrade [installer options]
|
|
15
|
+
scripts/install.sh uninstall [installer options]
|
|
16
|
+
scripts/install.sh doctor
|
|
17
|
+
curl -fsSL https://raw.githubusercontent.com/dannylee1020/kkt/main/scripts/install.sh | bash
|
|
18
|
+
curl -fsSL https://raw.githubusercontent.com/dannylee1020/kkt/main/scripts/install.sh | bash -s -- [installer options]
|
|
19
|
+
curl -fsSL https://raw.githubusercontent.com/dannylee1020/kkt/main/scripts/install.sh | bash -s -- upgrade [installer options]
|
|
20
|
+
curl -fsSL <install.sh-url> | KKT_INSTALL_URL=<archive-url> bash -s -- [installer options]
|
|
21
|
+
|
|
22
|
+
Installs KKT skills, ast-grep, and the companion kkt CLI used for durable state.
|
|
23
|
+
|
|
24
|
+
Common options:
|
|
25
|
+
--target <name> auto | codex | claude | pi | opencode | all
|
|
26
|
+
--local [path] Install to project-local skill directories. Defaults to cwd.
|
|
27
|
+
--dir <path> Install to an explicit skill root directory.
|
|
28
|
+
--bin-dir <path> Install the kkt CLI here. Defaults to ~/.local/bin.
|
|
29
|
+
--force Overwrite existing KKT skill directories during install.
|
|
30
|
+
--dry-run Print operations without writing files.
|
|
31
|
+
--help, -h Show this help.
|
|
32
|
+
|
|
33
|
+
Commands:
|
|
34
|
+
install Install missing skills and CLI; keep existing skills unchanged.
|
|
35
|
+
upgrade Remove known old KKT skill directories, then install the latest skills and CLI.
|
|
36
|
+
uninstall Remove KKT skill directories and CLI.
|
|
37
|
+
doctor Check that source skills are present.
|
|
38
|
+
|
|
39
|
+
Environment:
|
|
40
|
+
KKT_BUILD_FROM_SOURCE Build the CLI from source without downloading a release binary.
|
|
41
|
+
|
|
42
|
+
Default install auto-detects supported coding agents and writes to:
|
|
43
|
+
~/.agents/skills (Codex, Pi, OpenCode)
|
|
44
|
+
~/.claude/skills (Claude Code)
|
|
45
|
+
EOF
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
log() {
|
|
49
|
+
printf '%s\n' "$*" >&2
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fail() {
|
|
53
|
+
printf 'Error: %s\n' "$*" >&2
|
|
54
|
+
exit 1
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
need_command() {
|
|
58
|
+
command -v "$1" >/dev/null 2>&1 || fail "Required command not found: $1"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
script_dir() {
|
|
62
|
+
cd -- "$(dirname -- "$0")" && pwd -P
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
download_archive() {
|
|
66
|
+
local url="$1"
|
|
67
|
+
local dest="$2"
|
|
68
|
+
|
|
69
|
+
if command -v curl >/dev/null 2>&1; then
|
|
70
|
+
curl -fsSL "$url" -o "$dest" || fail "Failed to download kkt archive from $url."
|
|
71
|
+
elif command -v wget >/dev/null 2>&1; then
|
|
72
|
+
wget -qO "$dest" "$url" || fail "Failed to download kkt archive from $url."
|
|
73
|
+
else
|
|
74
|
+
fail "curl or wget is required to download KKT from KKT_INSTALL_URL."
|
|
75
|
+
fi
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
default_archive_url() {
|
|
79
|
+
if [ -n "${KKT_VERSION:-}" ]; then
|
|
80
|
+
printf '%s\n' "https://github.com/dannylee1020/kkt/archive/refs/tags/${KKT_VERSION}.tar.gz"
|
|
81
|
+
return
|
|
82
|
+
fi
|
|
83
|
+
printf '%s\n' "https://github.com/dannylee1020/kkt/archive/refs/heads/main.tar.gz"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
extract_archive() {
|
|
87
|
+
local archive="$1"
|
|
88
|
+
local dest="$2"
|
|
89
|
+
|
|
90
|
+
need_command tar
|
|
91
|
+
mkdir -p "$dest"
|
|
92
|
+
tar -xzf "$archive" -C "$dest" --strip-components 1
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
resolve_root() {
|
|
96
|
+
if [ -f "$0" ] && [ "$0" != "bash" ] && [ "$0" != "sh" ]; then
|
|
97
|
+
local dir
|
|
98
|
+
dir="$(script_dir)"
|
|
99
|
+
for root in "$dir" "$dir/.."; do
|
|
100
|
+
if [ -d "$root/skills" ] && [ -f "$root/skills/kkt/SKILL.md" ]; then
|
|
101
|
+
cd -- "$root"
|
|
102
|
+
pwd -P
|
|
103
|
+
return
|
|
104
|
+
fi
|
|
105
|
+
done
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
local install_url tmp_dir archive
|
|
109
|
+
install_url="${KKT_INSTALL_URL:-$(default_archive_url)}"
|
|
110
|
+
tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/kkt-install.XXXXXX")"
|
|
111
|
+
archive="$tmp_dir/kkt.tar.gz"
|
|
112
|
+
download_archive "$install_url" "$archive"
|
|
113
|
+
log "source: downloaded $install_url"
|
|
114
|
+
extract_archive "$archive" "$tmp_dir/src"
|
|
115
|
+
printf '%s\n' "$tmp_dir/src"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
expand_home() {
|
|
119
|
+
case "$1" in
|
|
120
|
+
\~) printf '%s\n' "$HOME" ;;
|
|
121
|
+
\~/*) printf '%s/%s\n' "$HOME" "${1#~/}" ;;
|
|
122
|
+
*) printf '%s\n' "$1" ;;
|
|
123
|
+
esac
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
absolute_path() {
|
|
127
|
+
local value
|
|
128
|
+
value="$(expand_home "$1")"
|
|
129
|
+
if [ -d "$value" ]; then
|
|
130
|
+
cd -- "$value"
|
|
131
|
+
pwd -P
|
|
132
|
+
return
|
|
133
|
+
fi
|
|
134
|
+
case "$value" in
|
|
135
|
+
/*) printf '%s\n' "$value" ;;
|
|
136
|
+
*) printf '%s/%s\n' "$(pwd -P)" "$value" ;;
|
|
137
|
+
esac
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
command_exists() {
|
|
141
|
+
command -v "$1" >/dev/null 2>&1
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
agent_detected() {
|
|
145
|
+
case "$1" in
|
|
146
|
+
codex) command_exists codex || [ -d "$HOME/.codex" ] ;;
|
|
147
|
+
claude) command_exists claude || [ -d "$HOME/.claude" ] ;;
|
|
148
|
+
pi) command_exists pi || [ -d "$HOME/.pi" ] ;;
|
|
149
|
+
opencode) command_exists opencode || [ -d "$HOME/.config/opencode" ] ;;
|
|
150
|
+
*) return 1 ;;
|
|
151
|
+
esac
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
contains_value() {
|
|
155
|
+
local needle="$1"
|
|
156
|
+
shift
|
|
157
|
+
local value
|
|
158
|
+
for value in "$@"; do
|
|
159
|
+
[ "$value" = "$needle" ] && return 0
|
|
160
|
+
done
|
|
161
|
+
return 1
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
target_root() {
|
|
165
|
+
local target="$1"
|
|
166
|
+
local local_mode="$2"
|
|
167
|
+
local local_path="$3"
|
|
168
|
+
local project_root
|
|
169
|
+
project_root="$(absolute_path "$local_path")"
|
|
170
|
+
|
|
171
|
+
case "$target" in
|
|
172
|
+
codex|pi|opencode)
|
|
173
|
+
if [ "$local_mode" = "true" ]; then
|
|
174
|
+
printf '%s/.agents/skills\n' "$project_root"
|
|
175
|
+
else
|
|
176
|
+
printf '%s/.agents/skills\n' "$HOME"
|
|
177
|
+
fi
|
|
178
|
+
;;
|
|
179
|
+
claude)
|
|
180
|
+
if [ "$local_mode" = "true" ]; then
|
|
181
|
+
printf '%s/.claude/skills\n' "$project_root"
|
|
182
|
+
else
|
|
183
|
+
printf '%s/.claude/skills\n' "$HOME"
|
|
184
|
+
fi
|
|
185
|
+
;;
|
|
186
|
+
*)
|
|
187
|
+
fail "Unsupported target: $target"
|
|
188
|
+
;;
|
|
189
|
+
esac
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
add_root() {
|
|
193
|
+
local label="$1"
|
|
194
|
+
local root="$2"
|
|
195
|
+
local existing
|
|
196
|
+
if [ "${#root_paths[@]}" -gt 0 ]; then
|
|
197
|
+
for existing in "${root_paths[@]}"; do
|
|
198
|
+
[ "$existing" = "$root" ] && return
|
|
199
|
+
done
|
|
200
|
+
fi
|
|
201
|
+
root_labels+=("$label")
|
|
202
|
+
root_paths+=("$root")
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
resolve_targets() {
|
|
206
|
+
resolved_targets=()
|
|
207
|
+
case "$target" in
|
|
208
|
+
default|auto)
|
|
209
|
+
local native
|
|
210
|
+
for native in "${native_targets[@]}"; do
|
|
211
|
+
if agent_detected "$native"; then
|
|
212
|
+
resolved_targets+=("$native")
|
|
213
|
+
fi
|
|
214
|
+
done
|
|
215
|
+
if [ "${#resolved_targets[@]}" -eq 0 ]; then
|
|
216
|
+
fail "No supported coding agent was detected. Rerun with --target codex, --target claude, --target pi, --target opencode, or --target all."
|
|
217
|
+
fi
|
|
218
|
+
;;
|
|
219
|
+
all)
|
|
220
|
+
resolved_targets=("${native_targets[@]}")
|
|
221
|
+
;;
|
|
222
|
+
codex|claude|pi|opencode)
|
|
223
|
+
resolved_targets=("$target")
|
|
224
|
+
;;
|
|
225
|
+
*)
|
|
226
|
+
fail "Unsupported target: $target"
|
|
227
|
+
;;
|
|
228
|
+
esac
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
resolve_roots() {
|
|
232
|
+
root_labels=()
|
|
233
|
+
root_paths=()
|
|
234
|
+
if [ -n "$explicit_dir" ]; then
|
|
235
|
+
local label
|
|
236
|
+
label="$target"
|
|
237
|
+
if [ "$label" = "auto" ] || [ "$label" = "default" ]; then
|
|
238
|
+
label="explicit"
|
|
239
|
+
fi
|
|
240
|
+
add_root "$label" "$(absolute_path "$explicit_dir")"
|
|
241
|
+
return
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
resolve_targets
|
|
245
|
+
local native root
|
|
246
|
+
for native in "${resolved_targets[@]}"; do
|
|
247
|
+
root="$(target_root "$native" "$local_install" "$local_path")"
|
|
248
|
+
add_root "$native" "$root"
|
|
249
|
+
done
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
parse_args() {
|
|
253
|
+
command_name="${1:-install}"
|
|
254
|
+
case "$command_name" in
|
|
255
|
+
install|upgrade|uninstall|doctor)
|
|
256
|
+
shift || true
|
|
257
|
+
;;
|
|
258
|
+
--help|-h)
|
|
259
|
+
usage
|
|
260
|
+
exit 0
|
|
261
|
+
;;
|
|
262
|
+
*)
|
|
263
|
+
command_name="install"
|
|
264
|
+
;;
|
|
265
|
+
esac
|
|
266
|
+
|
|
267
|
+
target="auto"
|
|
268
|
+
local_install="false"
|
|
269
|
+
local_path="$(pwd -P)"
|
|
270
|
+
explicit_dir=""
|
|
271
|
+
bin_dir="${KKT_BIN_DIR:-$HOME/.local/bin}"
|
|
272
|
+
force="false"
|
|
273
|
+
dry_run="false"
|
|
274
|
+
|
|
275
|
+
while [ "$#" -gt 0 ]; do
|
|
276
|
+
case "$1" in
|
|
277
|
+
--target)
|
|
278
|
+
[ "$#" -ge 2 ] || fail "--target requires a value."
|
|
279
|
+
target="$2"
|
|
280
|
+
shift 2
|
|
281
|
+
;;
|
|
282
|
+
--local)
|
|
283
|
+
local_install="true"
|
|
284
|
+
if [ "$#" -ge 2 ] && [[ "$2" != -* ]]; then
|
|
285
|
+
local_path="$2"
|
|
286
|
+
shift 2
|
|
287
|
+
else
|
|
288
|
+
shift
|
|
289
|
+
fi
|
|
290
|
+
;;
|
|
291
|
+
--dir)
|
|
292
|
+
[ "$#" -ge 2 ] || fail "--dir requires a value."
|
|
293
|
+
explicit_dir="$2"
|
|
294
|
+
shift 2
|
|
295
|
+
;;
|
|
296
|
+
--bin-dir)
|
|
297
|
+
[ "$#" -ge 2 ] || fail "--bin-dir requires a value."
|
|
298
|
+
bin_dir="$(expand_home "$2")"
|
|
299
|
+
shift 2
|
|
300
|
+
;;
|
|
301
|
+
--force)
|
|
302
|
+
force="true"
|
|
303
|
+
shift
|
|
304
|
+
;;
|
|
305
|
+
--dry-run)
|
|
306
|
+
dry_run="true"
|
|
307
|
+
shift
|
|
308
|
+
;;
|
|
309
|
+
--help|-h)
|
|
310
|
+
usage
|
|
311
|
+
exit 0
|
|
312
|
+
;;
|
|
313
|
+
*)
|
|
314
|
+
fail "Unknown argument: $1"
|
|
315
|
+
;;
|
|
316
|
+
esac
|
|
317
|
+
done
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
ensure_source_skills() {
|
|
321
|
+
source_skills_root="$root/skills"
|
|
322
|
+
[ -d "$source_skills_root" ] || fail "Source skills directory not found: $source_skills_root"
|
|
323
|
+
local name
|
|
324
|
+
for name in "${skill_names[@]}"; do
|
|
325
|
+
[ -f "$source_skills_root/$name/SKILL.md" ] || fail "Missing source skill: $source_skills_root/$name/SKILL.md"
|
|
326
|
+
done
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
directories_equal() {
|
|
330
|
+
local left="$1"
|
|
331
|
+
local right="$2"
|
|
332
|
+
[ -d "$left" ] && [ -d "$right" ] || return 1
|
|
333
|
+
diff -qr "$left" "$right" >/dev/null 2>&1
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
record_operation() {
|
|
337
|
+
operation_actions+=("$1")
|
|
338
|
+
operation_names+=("$2")
|
|
339
|
+
operation_targets+=("$3")
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
reset_operations() {
|
|
343
|
+
operation_actions=()
|
|
344
|
+
operation_names=()
|
|
345
|
+
operation_targets=()
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
copy_skill() {
|
|
349
|
+
local name="$1"
|
|
350
|
+
local root_dir="$2"
|
|
351
|
+
local source="$source_skills_root/$name"
|
|
352
|
+
local target_dir="$root_dir/$name"
|
|
353
|
+
|
|
354
|
+
if [ -e "$target_dir" ]; then
|
|
355
|
+
if directories_equal "$source" "$target_dir"; then
|
|
356
|
+
record_operation "skip" "$name" "$target_dir"
|
|
357
|
+
return
|
|
358
|
+
fi
|
|
359
|
+
if [ "$force" != "true" ]; then
|
|
360
|
+
record_operation "keep" "$name" "$target_dir"
|
|
361
|
+
return
|
|
362
|
+
fi
|
|
363
|
+
record_operation "overwrite" "$name" "$target_dir"
|
|
364
|
+
return
|
|
365
|
+
fi
|
|
366
|
+
|
|
367
|
+
record_operation "copy" "$name" "$target_dir"
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
install_root() {
|
|
371
|
+
local root_dir="$1"
|
|
372
|
+
reset_operations
|
|
373
|
+
|
|
374
|
+
local name
|
|
375
|
+
for name in "${skill_names[@]}"; do
|
|
376
|
+
copy_skill "$name" "$root_dir"
|
|
377
|
+
done
|
|
378
|
+
|
|
379
|
+
if [ "$dry_run" != "true" ]; then
|
|
380
|
+
local i source target_dir
|
|
381
|
+
for i in "${!operation_actions[@]}"; do
|
|
382
|
+
case "${operation_actions[$i]}" in
|
|
383
|
+
copy)
|
|
384
|
+
source="$source_skills_root/${operation_names[$i]}"
|
|
385
|
+
target_dir="${operation_targets[$i]}"
|
|
386
|
+
cp -R -- "$source" "$target_dir"
|
|
387
|
+
;;
|
|
388
|
+
overwrite)
|
|
389
|
+
source="$source_skills_root/${operation_names[$i]}"
|
|
390
|
+
target_dir="${operation_targets[$i]}"
|
|
391
|
+
rm -rf -- "$target_dir"
|
|
392
|
+
cp -R -- "$source" "$target_dir"
|
|
393
|
+
;;
|
|
394
|
+
esac
|
|
395
|
+
done
|
|
396
|
+
fi
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
upgrade_root() {
|
|
400
|
+
local root_dir="$1"
|
|
401
|
+
reset_operations
|
|
402
|
+
|
|
403
|
+
local name target_dir
|
|
404
|
+
for name in "${skill_names[@]}" "${legacy_skill_names[@]}"; do
|
|
405
|
+
target_dir="$root_dir/$name"
|
|
406
|
+
if [ -e "$target_dir" ]; then
|
|
407
|
+
record_operation "remove" "$name" "$target_dir"
|
|
408
|
+
if [ "$dry_run" != "true" ]; then
|
|
409
|
+
rm -rf -- "$target_dir"
|
|
410
|
+
fi
|
|
411
|
+
else
|
|
412
|
+
record_operation "skip" "$name" "$target_dir"
|
|
413
|
+
fi
|
|
414
|
+
done
|
|
415
|
+
|
|
416
|
+
for name in "${skill_names[@]}"; do
|
|
417
|
+
target_dir="$root_dir/$name"
|
|
418
|
+
record_operation "copy" "$name" "$target_dir"
|
|
419
|
+
if [ "$dry_run" != "true" ]; then
|
|
420
|
+
cp -R -- "$source_skills_root/$name" "$target_dir"
|
|
421
|
+
fi
|
|
422
|
+
done
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
uninstall_root() {
|
|
426
|
+
local root_dir="$1"
|
|
427
|
+
reset_operations
|
|
428
|
+
|
|
429
|
+
local name target_dir
|
|
430
|
+
for name in "${skill_names[@]}" "${legacy_skill_names[@]}"; do
|
|
431
|
+
target_dir="$root_dir/$name"
|
|
432
|
+
if [ -e "$target_dir" ]; then
|
|
433
|
+
record_operation "remove" "$name" "$target_dir"
|
|
434
|
+
if [ "$dry_run" != "true" ]; then
|
|
435
|
+
rm -rf -- "$target_dir"
|
|
436
|
+
fi
|
|
437
|
+
else
|
|
438
|
+
record_operation "skip" "$name" "$target_dir"
|
|
439
|
+
fi
|
|
440
|
+
done
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
names_for_action() {
|
|
444
|
+
local action="$1"
|
|
445
|
+
local names=()
|
|
446
|
+
local i
|
|
447
|
+
for i in "${!operation_actions[@]}"; do
|
|
448
|
+
if [ "${operation_actions[$i]}" = "$action" ]; then
|
|
449
|
+
names+=("${operation_names[$i]}")
|
|
450
|
+
fi
|
|
451
|
+
done
|
|
452
|
+
if [ "${#names[@]}" -gt 0 ]; then
|
|
453
|
+
local joined=""
|
|
454
|
+
local name
|
|
455
|
+
for name in "${names[@]}"; do
|
|
456
|
+
if [ -n "$joined" ]; then
|
|
457
|
+
joined="$joined, $name"
|
|
458
|
+
else
|
|
459
|
+
joined="$name"
|
|
460
|
+
fi
|
|
461
|
+
done
|
|
462
|
+
printf '%s\n' "$joined"
|
|
463
|
+
fi
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
print_summary_line() {
|
|
467
|
+
local label="$1"
|
|
468
|
+
local summary="$2"
|
|
469
|
+
[ -n "$summary" ] || return 0
|
|
470
|
+
printf ' - %s: %s\n' "$label" "$summary"
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
print_operations() {
|
|
474
|
+
local command="$1"
|
|
475
|
+
local label="$2"
|
|
476
|
+
local root_dir="$3"
|
|
477
|
+
local suffix=""
|
|
478
|
+
if [ "$dry_run" = "true" ]; then
|
|
479
|
+
suffix=" (dry-run)"
|
|
480
|
+
fi
|
|
481
|
+
|
|
482
|
+
printf 'skills %s%s: %s\n' "$label" "$suffix" "$root_dir"
|
|
483
|
+
|
|
484
|
+
if [ "$command" = "upgrade" ]; then
|
|
485
|
+
print_summary_line "installed" "$(names_for_action copy || true)"
|
|
486
|
+
local removed_legacy=()
|
|
487
|
+
local i
|
|
488
|
+
for i in "${!operation_actions[@]}"; do
|
|
489
|
+
if [ "${operation_actions[$i]}" = "remove" ] && contains_value "${operation_names[$i]}" "${legacy_skill_names[@]}"; then
|
|
490
|
+
removed_legacy+=("${operation_names[$i]}")
|
|
491
|
+
fi
|
|
492
|
+
done
|
|
493
|
+
if [ "${#removed_legacy[@]}" -gt 0 ]; then
|
|
494
|
+
local joined=""
|
|
495
|
+
local name
|
|
496
|
+
for name in "${removed_legacy[@]}"; do
|
|
497
|
+
if [ -n "$joined" ]; then
|
|
498
|
+
joined="$joined, $name"
|
|
499
|
+
else
|
|
500
|
+
joined="$name"
|
|
501
|
+
fi
|
|
502
|
+
done
|
|
503
|
+
print_summary_line "removed legacy" "$joined"
|
|
504
|
+
fi
|
|
505
|
+
return
|
|
506
|
+
fi
|
|
507
|
+
|
|
508
|
+
if [ "$command" = "uninstall" ]; then
|
|
509
|
+
local removed current
|
|
510
|
+
removed="$(names_for_action remove || true)"
|
|
511
|
+
current="$(names_for_action skip || true)"
|
|
512
|
+
print_summary_line "removed" "$removed"
|
|
513
|
+
if [ -z "$removed" ]; then
|
|
514
|
+
print_summary_line "not installed" "$current"
|
|
515
|
+
fi
|
|
516
|
+
return
|
|
517
|
+
fi
|
|
518
|
+
|
|
519
|
+
local installed updated current kept
|
|
520
|
+
installed="$(names_for_action copy || true)"
|
|
521
|
+
updated="$(names_for_action overwrite || true)"
|
|
522
|
+
kept="$(names_for_action keep || true)"
|
|
523
|
+
current="$(names_for_action skip || true)"
|
|
524
|
+
print_summary_line "installed" "$installed"
|
|
525
|
+
print_summary_line "updated" "$updated"
|
|
526
|
+
print_summary_line "current" "$current"
|
|
527
|
+
print_summary_line "kept existing" "$kept"
|
|
528
|
+
if [ -n "$kept" ]; then
|
|
529
|
+
print_summary_line "next" "run scripts/install.sh upgrade to replace existing skills"
|
|
530
|
+
fi
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
doctor() {
|
|
534
|
+
ensure_source_skills
|
|
535
|
+
printf 'ok: %s\n' "$source_skills_root"
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
install_ast_grep_with() {
|
|
539
|
+
local installer="$1"
|
|
540
|
+
shift
|
|
541
|
+
log "dependency: installing ast-grep with $installer"
|
|
542
|
+
"$@"
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
ensure_ast_grep() {
|
|
546
|
+
local path
|
|
547
|
+
if path="$(command -v ast-grep 2>/dev/null)"; then
|
|
548
|
+
log "dependency: ast-grep already installed at $path"
|
|
549
|
+
return
|
|
550
|
+
fi
|
|
551
|
+
|
|
552
|
+
if [ "$dry_run" = "true" ]; then
|
|
553
|
+
printf 'dependency: would install ast-grep if missing\n'
|
|
554
|
+
return
|
|
555
|
+
fi
|
|
556
|
+
|
|
557
|
+
if command_exists brew; then
|
|
558
|
+
install_ast_grep_with "brew" brew install ast-grep
|
|
559
|
+
return
|
|
560
|
+
fi
|
|
561
|
+
|
|
562
|
+
if command_exists cargo; then
|
|
563
|
+
install_ast_grep_with "cargo" cargo install ast-grep --locked
|
|
564
|
+
return
|
|
565
|
+
fi
|
|
566
|
+
|
|
567
|
+
if command_exists npm; then
|
|
568
|
+
install_ast_grep_with "npm" npm i @ast-grep/cli -g
|
|
569
|
+
return
|
|
570
|
+
fi
|
|
571
|
+
|
|
572
|
+
fail "ast-grep is required for KKT structural discovery. Install it with: brew install ast-grep, cargo install ast-grep --locked, or npm i @ast-grep/cli -g."
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
install_cli() {
|
|
576
|
+
local kkt_command cli_installer
|
|
577
|
+
kkt_command="$(expand_home "$bin_dir")/kkt"
|
|
578
|
+
cli_installer="$root/scripts/install-cli.sh"
|
|
579
|
+
|
|
580
|
+
if [ "$dry_run" = "true" ]; then
|
|
581
|
+
printf 'cli: would install %s\n' "$kkt_command"
|
|
582
|
+
return
|
|
583
|
+
fi
|
|
584
|
+
|
|
585
|
+
[ -f "$cli_installer" ] || fail "CLI installer not found: $cli_installer"
|
|
586
|
+
bash "$cli_installer" --bin-dir "$bin_dir"
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
uninstall_cli() {
|
|
590
|
+
local kkt_command
|
|
591
|
+
kkt_command="$(expand_home "$bin_dir")/kkt"
|
|
592
|
+
|
|
593
|
+
if [ "$dry_run" = "true" ]; then
|
|
594
|
+
printf 'cli: would remove %s\n' "$kkt_command"
|
|
595
|
+
return
|
|
596
|
+
fi
|
|
597
|
+
|
|
598
|
+
if [ -e "$kkt_command" ]; then
|
|
599
|
+
rm -f -- "$kkt_command"
|
|
600
|
+
printf 'cli: removed %s\n' "$kkt_command"
|
|
601
|
+
else
|
|
602
|
+
printf 'cli: not installed %s\n' "$kkt_command"
|
|
603
|
+
fi
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
main() {
|
|
607
|
+
parse_args "$@"
|
|
608
|
+
root="$(resolve_root)"
|
|
609
|
+
ensure_source_skills
|
|
610
|
+
|
|
611
|
+
if [ "$command_name" = "doctor" ]; then
|
|
612
|
+
doctor
|
|
613
|
+
return
|
|
614
|
+
fi
|
|
615
|
+
|
|
616
|
+
case "$command_name" in
|
|
617
|
+
install|upgrade) ensure_ast_grep ;;
|
|
618
|
+
esac
|
|
619
|
+
|
|
620
|
+
need_command diff
|
|
621
|
+
resolve_roots
|
|
622
|
+
local i root_dir label
|
|
623
|
+
for i in "${!root_paths[@]}"; do
|
|
624
|
+
root_dir="${root_paths[$i]}"
|
|
625
|
+
label="${root_labels[$i]}"
|
|
626
|
+
if [ "$dry_run" != "true" ]; then
|
|
627
|
+
mkdir -p "$root_dir"
|
|
628
|
+
fi
|
|
629
|
+
case "$command_name" in
|
|
630
|
+
install) install_root "$root_dir" ;;
|
|
631
|
+
upgrade) upgrade_root "$root_dir" ;;
|
|
632
|
+
uninstall) uninstall_root "$root_dir" ;;
|
|
633
|
+
*) fail "Unsupported command: $command_name" ;;
|
|
634
|
+
esac
|
|
635
|
+
print_operations "$command_name" "$label" "$root_dir"
|
|
636
|
+
done
|
|
637
|
+
|
|
638
|
+
case "$command_name" in
|
|
639
|
+
install|upgrade) install_cli ;;
|
|
640
|
+
uninstall) uninstall_cli ;;
|
|
641
|
+
esac
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
main "$@"
|