@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.
Files changed (50) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +275 -0
  3. package/assets/kkt-readme-modern.png +0 -0
  4. package/bin/kkt-install.mjs +25 -0
  5. package/cmd/kkt/main.go +15 -0
  6. package/go.mod +3 -0
  7. package/internal/workflow/cli.go +349 -0
  8. package/internal/workflow/cli_test.go +1266 -0
  9. package/internal/workflow/guardrails.go +900 -0
  10. package/internal/workflow/init.go +164 -0
  11. package/internal/workflow/init_test.go +113 -0
  12. package/internal/workflow/operations.go +1855 -0
  13. package/internal/workflow/validation_commands.go +291 -0
  14. package/internal/workflow/workspace.go +802 -0
  15. package/internal/workflow/workspace_test.go +285 -0
  16. package/package.json +45 -0
  17. package/scripts/install-cli.sh +210 -0
  18. package/scripts/install.sh +644 -0
  19. package/skills/kkt/SKILL.md +74 -0
  20. package/skills/kkt/references/discovery-tooling.md +53 -0
  21. package/skills/kkt/references/feature-optimization-model.md +120 -0
  22. package/skills/kkt/references/kkt-kernel.md +58 -0
  23. package/skills/kkt/references/layered-modeling-methods.md +101 -0
  24. package/skills/kkt/references/plan-assimilation.md +46 -0
  25. package/skills/kkt/references/schemas.md +231 -0
  26. package/skills/kkt/references/state-contract.md +76 -0
  27. package/skills/kkt-loop/SKILL.md +99 -0
  28. package/skills/kkt-loop/references/discovery-tooling.md +53 -0
  29. package/skills/kkt-loop/references/feature-optimization-model.md +120 -0
  30. package/skills/kkt-loop/references/kkt-kernel.md +58 -0
  31. package/skills/kkt-loop/references/layered-modeling-methods.md +101 -0
  32. package/skills/kkt-loop/references/plan-assimilation.md +46 -0
  33. package/skills/kkt-loop/references/schemas.md +231 -0
  34. package/skills/kkt-loop/references/state-contract.md +76 -0
  35. package/skills/kkt-model/SKILL.md +76 -0
  36. package/skills/kkt-model/references/discovery-tooling.md +53 -0
  37. package/skills/kkt-model/references/feature-optimization-model.md +120 -0
  38. package/skills/kkt-model/references/kkt-kernel.md +58 -0
  39. package/skills/kkt-model/references/layered-modeling-methods.md +101 -0
  40. package/skills/kkt-model/references/plan-assimilation.md +46 -0
  41. package/skills/kkt-model/references/schemas.md +231 -0
  42. package/skills/kkt-model/references/state-contract.md +76 -0
  43. package/skills/kkt-run/SKILL.md +62 -0
  44. package/skills/kkt-run/references/discovery-tooling.md +53 -0
  45. package/skills/kkt-run/references/feature-optimization-model.md +120 -0
  46. package/skills/kkt-run/references/kkt-kernel.md +58 -0
  47. package/skills/kkt-run/references/layered-modeling-methods.md +101 -0
  48. package/skills/kkt-run/references/plan-assimilation.md +46 -0
  49. package/skills/kkt-run/references/schemas.md +231 -0
  50. 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 "$@"