@jeiemgi/cckit 0.1.6
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/.claude-plugin/plugin.json +22 -0
- package/AGENTS.md +101 -0
- package/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +21 -0
- package/README.md +143 -0
- package/SECURITY.md +22 -0
- package/bin/cckit +215 -0
- package/cckit.config.json +34 -0
- package/commands/kit-add.md +42 -0
- package/commands/kit-docs.md +45 -0
- package/commands/kit-doctor.md +52 -0
- package/commands/kit-export-project.md +58 -0
- package/commands/kit-export-training.md +49 -0
- package/commands/kit-init.md +126 -0
- package/commands/kit-routines.md +59 -0
- package/commands/kit-update.md +132 -0
- package/docs/kit-annotate/01-explainer.html +225 -0
- package/docs/kit-annotate/02-implementation-plan.html +196 -0
- package/docs/media/.onboarding-capture.cast +5 -0
- package/docs/media/README.md +43 -0
- package/docs/media/build-demo.sh +63 -0
- package/docs/media/build-kit-init.sh +51 -0
- package/docs/media/build-onboarding.sh +51 -0
- package/docs/media/kit-dry-run.cast +107 -0
- package/docs/media/kit-dry-run.gif +0 -0
- package/docs/media/kit-init.cast +56 -0
- package/docs/media/kit-init.gif +0 -0
- package/docs/media/kit-onboarding.cast +148 -0
- package/docs/media/kit-onboarding.gif +0 -0
- package/githooks/pre-commit +18 -0
- package/kit.config.schema.json +105 -0
- package/package.json +54 -0
- package/privacy-denylist.example +8 -0
- package/profiles/automation.json +36 -0
- package/profiles/content.json +41 -0
- package/profiles/minimal.json +31 -0
- package/profiles/research.json +37 -0
- package/profiles/software.json +32 -0
- package/scripts/annotate-setup.sh +149 -0
- package/scripts/autopilot.sh +50 -0
- package/scripts/capture-project-ids.sh +53 -0
- package/scripts/check.sh +66 -0
- package/scripts/contribute.sh +48 -0
- package/scripts/debug.sh +54 -0
- package/scripts/init-upgrade-test.sh +99 -0
- package/scripts/init.sh +827 -0
- package/scripts/install.sh +24 -0
- package/scripts/kit-add-test.sh +62 -0
- package/scripts/kit-add.sh +115 -0
- package/scripts/kit-adopt-test.sh +61 -0
- package/scripts/kit-adopt.sh +122 -0
- package/scripts/kit-bump-version.sh +79 -0
- package/scripts/kit-digest.sh +126 -0
- package/scripts/kit-doctor.sh +663 -0
- package/scripts/kit-export-project-test.sh +82 -0
- package/scripts/kit-export-project.sh +245 -0
- package/scripts/kit-export-training-test.sh +51 -0
- package/scripts/kit-export-training.sh +175 -0
- package/scripts/kit-migrate-test.sh +80 -0
- package/scripts/kit-migrate.sh +190 -0
- package/scripts/kit-onboard-test.sh +63 -0
- package/scripts/kit-onboard.sh +69 -0
- package/scripts/kit-promote-test.sh +54 -0
- package/scripts/kit-promote.sh +102 -0
- package/scripts/kit-remove-test.sh +61 -0
- package/scripts/kit-remove.sh +84 -0
- package/scripts/kit-routines.sh +322 -0
- package/scripts/kit-version-check.sh +91 -0
- package/scripts/kit-wire-test.sh +54 -0
- package/scripts/kit-wire.sh +132 -0
- package/scripts/knowledge-lint.sh +96 -0
- package/scripts/lib/cckit-output.sh +36 -0
- package/scripts/lib/effort-metrics.sh +452 -0
- package/scripts/lib/effort-ops-test.sh +83 -0
- package/scripts/lib/effort-ops.sh +132 -0
- package/scripts/lib/effort-plan.sh +104 -0
- package/scripts/lib/effort.sh +191 -0
- package/scripts/lib/engine-adapter.sh +92 -0
- package/scripts/lib/gh-log.sh +58 -0
- package/scripts/lib/gh-project.sh +212 -0
- package/scripts/lib/handoff.sh +35 -0
- package/scripts/lib/kit-cli-test.sh +42 -0
- package/scripts/lib/kit-cli.sh +32 -0
- package/scripts/lib/kit-config-resolve.sh +145 -0
- package/scripts/lib/kit-config.sh +88 -0
- package/scripts/lib/kit-engine-test.sh +107 -0
- package/scripts/lib/kit-events.sh +62 -0
- package/scripts/lib/kit-gc.sh +117 -0
- package/scripts/lib/kit-interview-test.sh +77 -0
- package/scripts/lib/kit-interview.sh +203 -0
- package/scripts/lib/kit-local.sh +79 -0
- package/scripts/lib/kit-manifest.sh +127 -0
- package/scripts/lib/kit-mode-test.sh +49 -0
- package/scripts/lib/kit-mode.sh +67 -0
- package/scripts/lib/kit-operate.sh +105 -0
- package/scripts/lib/kit-profile-test.sh +62 -0
- package/scripts/lib/kit-profile.sh +115 -0
- package/scripts/lib/kit-task-ops-test.sh +63 -0
- package/scripts/lib/kit-task-ops.sh +341 -0
- package/scripts/lib/pr-evidence.sh +173 -0
- package/scripts/lib/project-scan.sh +16 -0
- package/scripts/lib/react-detect.sh +78 -0
- package/scripts/lib/role-identity.sh +47 -0
- package/scripts/lib/secret-guard.sh +96 -0
- package/scripts/lib/toon.sh +35 -0
- package/scripts/lib/ui.sh +42 -0
- package/scripts/lib/version-bump.sh +59 -0
- package/scripts/lib/worktree-issue-test.sh +45 -0
- package/scripts/lib/worktree-issue.sh +73 -0
- package/scripts/lib/worktree-start.sh +280 -0
- package/scripts/orchestrate.sh +160 -0
- package/scripts/portable-test.sh +53 -0
- package/scripts/publish.sh +94 -0
- package/scripts/setup-labels.sh +25 -0
- package/scripts/setup-milestones.sh +17 -0
- package/scripts/showcase.sh +64 -0
- package/scripts/status.sh +44 -0
- package/scripts/task-sync.sh +59 -0
- package/scripts/test.sh +48 -0
- package/scripts/web-install.sh +22 -0
- package/skills/kit-annotate/SKILL.md +107 -0
- package/skills/kit-autopilot/SKILL.md +108 -0
- package/skills/kit-contribute/SKILL.md +134 -0
- package/skills/kit-customize/SKILL.md +134 -0
- package/skills/kit-dev/SKILL.md +67 -0
- package/skills/kit-digest/SKILL.md +41 -0
- package/skills/kit-effort-close/SKILL.md +156 -0
- package/skills/kit-effort-new/SKILL.md +173 -0
- package/skills/kit-effort-pr/SKILL.md +139 -0
- package/skills/kit-effort-start/SKILL.md +85 -0
- package/skills/kit-gc/SKILL.md +80 -0
- package/skills/kit-onboard/SKILL.md +50 -0
- package/skills/kit-security-sweep/SKILL.md +57 -0
- package/skills/kit-ship/SKILL.md +43 -0
- package/skills/kit-task-close/SKILL.md +66 -0
- package/skills/kit-task-new/SKILL.md +51 -0
- package/skills/kit-task-pr/SKILL.md +43 -0
- package/skills/kit-task-pr-auto/SKILL.md +27 -0
- package/skills/kit-task-pr-merge/SKILL.md +53 -0
- package/skills/kit-task-start/SKILL.md +76 -0
- package/skills/kit-task-sync/SKILL.md +37 -0
- package/templates/CLAUDE.md.tmpl +106 -0
- package/templates/agents/analyst.md +55 -0
- package/templates/agents/auto-dev.md +93 -0
- package/templates/agents/backend.md +59 -0
- package/templates/agents/designer.md +73 -0
- package/templates/agents/devops.md +57 -0
- package/templates/agents/editor.md +48 -0
- package/templates/agents/frontend.md +81 -0
- package/templates/agents/generalist.md +46 -0
- package/templates/agents/local-delegate.md +70 -0
- package/templates/agents/n8n.md +65 -0
- package/templates/agents/pm.md +69 -0
- package/templates/agents/qa.md +66 -0
- package/templates/agents/researcher.md +57 -0
- package/templates/agents/security.md +65 -0
- package/templates/agents/tech-lead.md +75 -0
- package/templates/hooks/guard-base-branch-commit.sh.tmpl +45 -0
- package/templates/hooks/kit-local-status.sh.tmpl +34 -0
- package/templates/hooks/kit_version_check.sh.tmpl +6 -0
- package/templates/hooks/mempal_followup.sh.tmpl +97 -0
- package/templates/hooks/mempal_precompact.sh.tmpl +4 -0
- package/templates/hooks/mempal_save.sh.tmpl +4 -0
- package/templates/hooks/mempal_session_start.sh.tmpl +8 -0
- package/templates/hooks/prepush_gate.sh.tmpl +36 -0
- package/templates/hooks/repo-hygiene.sh.tmpl +72 -0
- package/templates/kit.config.json.tmpl +32 -0
- package/templates/knowledge-INDEX.md.tmpl +12 -0
- package/templates/lib/kit-sigil.sh.tmpl +124 -0
- package/templates/rules/branch-naming.md +104 -0
- package/templates/rules/communication-style.md +22 -0
- package/templates/rules/delegation-brief.md +40 -0
- package/templates/rules/design-routing.md +35 -0
- package/templates/rules/effort-model.md +122 -0
- package/templates/rules/knowledge-base.md +41 -0
- package/templates/rules/mempalace.md +110 -0
- package/templates/rules/plan-output-format.md +58 -0
- package/templates/rules/react-annotate.md +69 -0
- package/templates/rules/risk-tiered-review.md +62 -0
- package/templates/rules/skill-gaps.md +48 -0
- package/templates/rules/task-management.md +42 -0
- package/templates/settings/settings.local.json.tmpl +27 -0
- package/templates/skills/NAMESPACED +13 -0
- package/templates/skills/copywriting/SKILL.md +252 -0
- package/templates/skills/copywriting/references/copy-frameworks.md +344 -0
- package/templates/skills/copywriting/references/natural-transitions.md +272 -0
- package/templates/skills/feature-build-refine/SKILL.md +367 -0
- package/templates/skills/karpathy-guidelines/SKILL.md +69 -0
- package/templates/skills/morning-briefing/SKILL.md +46 -0
- package/templates/skills/speckit/SKILL.md +239 -0
- package/templates/skills/supabase-patterns/SKILL.md +88 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# kit-migrate.sh — reshuffle an OLD kit layout to the v2 one, idempotently, surviving /kit-update (#373).
|
|
3
|
+
#
|
|
4
|
+
# THE PROBLEM: early kits scaffolded skills as `task-*` (task-new/task-pr/...). v2 renamed them to
|
|
5
|
+
# `kit-task-*` (skillPrefix='kit-'). A project that initialized on the old layout still has the old
|
|
6
|
+
# dirs; a naive rename leaves three latent bugs:
|
|
7
|
+
# 1. /kit-update RESURRECTS the old `task-*` dirs ("add missing files") — the known incident.
|
|
8
|
+
# 2. The ownership manifest still points at the dead old paths -> remove can't leave clean.
|
|
9
|
+
# 3. CLAUDE.md / rules still say `/task-pr` -> the docs reference skills that no longer exist.
|
|
10
|
+
# kit-migrate fixes all three in one audited pass, and is a no-op on a project already on v2.
|
|
11
|
+
#
|
|
12
|
+
# WHAT IT DOES (per old->new pair in the migration map):
|
|
13
|
+
# a. BACKUP the project's .claude/ once (timestamped) before the first change.
|
|
14
|
+
# b. MOVE the file/dir content old->new (only if old exists and new doesn't — never clobber).
|
|
15
|
+
# c. RE-KEY any manifest entry old->new (so update/remove track the v2 path).
|
|
16
|
+
# d. REGISTER the rename in kit.config.json `upgrade.renamed{}` so /kit-update NEVER re-adds the
|
|
17
|
+
# old path (this is the resurrection guard, #334).
|
|
18
|
+
# e. CLAUDE.md / rules SURGERY: rewrite `/task-<verb>` references to `/kit-task-<verb>`.
|
|
19
|
+
# Idempotent: a second run finds the old paths already gone + already registered -> no-op.
|
|
20
|
+
# Honors KIT_DRY_RUN (preview, no writes) and KIT_ASSUME_YES (no confirm).
|
|
21
|
+
#
|
|
22
|
+
# Run:
|
|
23
|
+
# scripts/kit-migrate.sh # interactive
|
|
24
|
+
# scripts/kit-migrate.sh --dry-run # preview the reshuffle, write nothing
|
|
25
|
+
# KIT_ASSUME_YES=1 scripts/kit-migrate.sh # non-interactive (CI / batch)
|
|
26
|
+
# Requires: jq.
|
|
27
|
+
|
|
28
|
+
set -uo pipefail
|
|
29
|
+
_mig_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd)"
|
|
30
|
+
# shellcheck source=/dev/null
|
|
31
|
+
. "$_mig_dir/lib/kit-operate.sh" # four-beat machine + kit-manifest.sh
|
|
32
|
+
# shellcheck source=/dev/null
|
|
33
|
+
. "$_mig_dir/lib/kit-cli.sh" # kit_is_main
|
|
34
|
+
|
|
35
|
+
# The old->v2 migration map: one "OLD_RELPATH|NEW_RELPATH" per line. Data-driven so the set of
|
|
36
|
+
# renames is auditable + extensible without touching the engine. Today: the task-* -> kit-task-*
|
|
37
|
+
# skill rename (skillPrefix='kit-'), plus task-gc -> kit-gc.
|
|
38
|
+
_kit_migrate_map() {
|
|
39
|
+
cat <<'MAP'
|
|
40
|
+
.claude/skills/task-new|.claude/skills/kit-task-new
|
|
41
|
+
.claude/skills/task-start|.claude/skills/kit-task-start
|
|
42
|
+
.claude/skills/task-pr|.claude/skills/kit-task-pr
|
|
43
|
+
.claude/skills/task-pr-merge|.claude/skills/kit-task-pr-merge
|
|
44
|
+
.claude/skills/task-pr-auto|.claude/skills/kit-task-pr-auto
|
|
45
|
+
.claude/skills/task-sync|.claude/skills/kit-task-sync
|
|
46
|
+
.claude/skills/task-close|.claude/skills/kit-task-close
|
|
47
|
+
.claude/skills/task-gc|.claude/skills/kit-gc
|
|
48
|
+
MAP
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_kit_migrate_cfg() { printf '%s\n' ".claude/kit.config.json"; }
|
|
52
|
+
|
|
53
|
+
# Register OLD path in kit.config.json upgrade.renamed{} (-> NEW) so /kit-update skips re-adding it.
|
|
54
|
+
# Idempotent: writes only if the key is absent or points elsewhere. (#334 resurrection guard.)
|
|
55
|
+
_kit_migrate_register_rename() {
|
|
56
|
+
local old="$1" new="$2" cfg cur tmp
|
|
57
|
+
cfg="$(_kit_migrate_cfg)"
|
|
58
|
+
[ -f "$cfg" ] || { _kit_op_say " (no $cfg — skip rename registry for $old)"; return 0; }
|
|
59
|
+
cur="$(jq -r --arg k "$old" '.upgrade.renamed[$k] // empty' "$cfg" 2>/dev/null || true)"
|
|
60
|
+
[ "$cur" = "$new" ] && return 0 # already registered
|
|
61
|
+
if _kit_op_dry; then _kit_op_say " [dry-run] would register upgrade.renamed[$old]=$new in $cfg"; return 0; fi
|
|
62
|
+
tmp="$(mktemp)" || return 1
|
|
63
|
+
jq --arg k "$old" --arg v "$new" \
|
|
64
|
+
'.upgrade = (.upgrade // {}) | .upgrade.renamed = (.upgrade.renamed // {}) | .upgrade.renamed[$k] = $v' \
|
|
65
|
+
"$cfg" > "$tmp" && mv "$tmp" "$cfg" || { rm -f "$tmp"; return 1; }
|
|
66
|
+
_kit_op_say " registered upgrade.renamed[$old] -> $new"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Re-key a manifest entry old->new, preserving tier/op (so update/remove track the v2 path).
|
|
70
|
+
_kit_migrate_rekey_manifest() {
|
|
71
|
+
local old="$1" new="$2" mf entry tier op tmp
|
|
72
|
+
mf="$(kit_manifest_path)"; [ -f "$mf" ] || return 0
|
|
73
|
+
entry="$(jq -ec --arg p "$old" '.entries[$p] // empty' "$mf" 2>/dev/null || true)"
|
|
74
|
+
[ -n "$entry" ] || return 0
|
|
75
|
+
tier="$(printf '%s' "$entry" | jq -r '.tier // "A"')"
|
|
76
|
+
op="$(printf '%s' "$entry" | jq -r '.op // "wire"')"
|
|
77
|
+
if _kit_op_dry; then _kit_op_say " [dry-run] would re-key manifest $old -> $new"; return 0; fi
|
|
78
|
+
# record the new path at its (now moved) content, then drop the old entry
|
|
79
|
+
kit_manifest_record "$new" "$tier" "$op" >/dev/null 2>&1 || true
|
|
80
|
+
kit_manifest_remove_entry "$old" || true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_kit_migrate_backup_done=""
|
|
84
|
+
# Backup .claude/ once per run, before the first mutation. Skipped under dry-run.
|
|
85
|
+
_kit_migrate_backup() {
|
|
86
|
+
[ -n "$_kit_migrate_backup_done" ] && return 0
|
|
87
|
+
_kit_migrate_backup_done=1
|
|
88
|
+
_kit_op_dry && { _kit_op_say " [dry-run] would back up .claude/ before migrating"; return 0; }
|
|
89
|
+
[ -d .claude ] || return 0
|
|
90
|
+
local bk=".claude.bak.$(date -u +%Y%m%d%H%M%S 2>/dev/null || echo migrate)"
|
|
91
|
+
cp -R .claude "$bk" 2>/dev/null && _kit_op_say " backed up .claude/ -> $bk" || _kit_op_say " (backup skipped — cp failed, continuing)"
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# CLAUDE.md + rules surgery: rewrite /task-<verb> -> /kit-task-<verb>, task-gc -> kit-gc.
|
|
95
|
+
# Operates on CLAUDE.md and .claude/rules/*.md. Idempotent (already-migrated text has no /task-).
|
|
96
|
+
_kit_migrate_doc_surgery() {
|
|
97
|
+
local f changed=0 tmp files=()
|
|
98
|
+
[ -f CLAUDE.md ] && files+=(CLAUDE.md)
|
|
99
|
+
if [ -d .claude/rules ]; then
|
|
100
|
+
while IFS= read -r f; do [ -n "$f" ] && files+=("$f"); done < <(find .claude/rules -maxdepth 1 -type f -name '*.md' 2>/dev/null)
|
|
101
|
+
fi
|
|
102
|
+
for f in ${files[@]+"${files[@]}"}; do
|
|
103
|
+
[ -f "$f" ] || continue
|
|
104
|
+
# match /task-<verb> not already preceded by 'kit-' ; and the bare task-gc skill name.
|
|
105
|
+
if grep -Eq '/(task-(new|start|pr|pr-merge|pr-auto|sync|close)|task-gc)\b' "$f" 2>/dev/null; then
|
|
106
|
+
if _kit_op_dry; then _kit_op_say " [dry-run] would rewrite /task-* -> /kit-task-* in $f"; changed=1; continue; fi
|
|
107
|
+
tmp="$(mktemp)" || return 1
|
|
108
|
+
sed -E 's#/task-(new|start|pr-merge|pr-auto|pr|sync|close)#/kit-task-\1#g; s#/task-gc#/kit-gc#g' "$f" > "$tmp" \
|
|
109
|
+
&& mv "$tmp" "$f" && { _kit_op_say " rewrote skill refs in $f"; changed=1; } || rm -f "$tmp"
|
|
110
|
+
fi
|
|
111
|
+
done
|
|
112
|
+
[ "$changed" -eq 0 ] && _kit_op_say " (docs reference current skill names — no rewrite needed)"
|
|
113
|
+
return 0
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# kit_migrate_all — run the full reshuffle. Returns 0 (a project already on v2 is success / no-op).
|
|
117
|
+
kit_migrate_all() {
|
|
118
|
+
command -v jq >/dev/null 2>&1 || { echo "kit-migrate: jq required" >&2; return 1; }
|
|
119
|
+
|
|
120
|
+
# First pass: what WOULD move? (so we can show a plan + confirm before any write)
|
|
121
|
+
local arr=() line old new pending=0
|
|
122
|
+
while IFS= read -r line; do [ -n "$line" ] && arr+=("$line"); done < <(_kit_migrate_map)
|
|
123
|
+
for line in ${arr[@]+"${arr[@]}"}; do
|
|
124
|
+
old="${line%%|*}"; new="${line##*|}"
|
|
125
|
+
[ -e "$old" ] && [ ! -e "$new" ] && pending=$((pending+1))
|
|
126
|
+
done
|
|
127
|
+
|
|
128
|
+
if [ "$pending" -eq 0 ]; then
|
|
129
|
+
_kit_op_say "kit-migrate: layout already on v2 (no old task-* paths to move)."
|
|
130
|
+
# Still ensure the renames are registered + docs are clean (idempotent backfill for partial runs).
|
|
131
|
+
for line in ${arr[@]+"${arr[@]}"}; do
|
|
132
|
+
old="${line%%|*}"; new="${line##*|}"
|
|
133
|
+
[ ! -e "$old" ] && _kit_migrate_register_rename "$old" "$new"
|
|
134
|
+
done
|
|
135
|
+
_kit_migrate_doc_surgery
|
|
136
|
+
return 0
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
_kit_op_say "kit-migrate: $pending old-layout path(s) to reshuffle to v2:"
|
|
140
|
+
for line in ${arr[@]+"${arr[@]}"}; do
|
|
141
|
+
old="${line%%|*}"; new="${line##*|}"
|
|
142
|
+
[ -e "$old" ] && [ ! -e "$new" ] && _kit_op_say " ~ $old -> $new"
|
|
143
|
+
done
|
|
144
|
+
|
|
145
|
+
if [ -z "${KIT_ASSUME_YES:-}" ] && [ -z "${KIT_DRY_RUN:-}" ]; then
|
|
146
|
+
printf 'kit-migrate: reshuffle %s path(s) (a .claude/ backup is taken first)? [y/N] ' "$pending" >&2
|
|
147
|
+
local _c=""; IFS= read -r _c </dev/tty 2>/dev/null || IFS= read -r _c 2>/dev/null || _c=""
|
|
148
|
+
case "$_c" in y|Y|yes|YES) ;; *) _kit_op_say "kit-migrate: aborted (nothing moved)."; return 0;; esac
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
_kit_migrate_backup
|
|
152
|
+
|
|
153
|
+
local moved=0
|
|
154
|
+
for line in ${arr[@]+"${arr[@]}"}; do
|
|
155
|
+
old="${line%%|*}"; new="${line##*|}"
|
|
156
|
+
if [ -e "$old" ] && [ ! -e "$new" ]; then
|
|
157
|
+
if _kit_op_dry; then
|
|
158
|
+
_kit_op_say " [dry-run] would move $old -> $new"
|
|
159
|
+
else
|
|
160
|
+
mkdir -p "$(dirname "$new")" && mv "$old" "$new" && { _kit_op_say " moved $old -> $new"; moved=$((moved+1)); }
|
|
161
|
+
# re-key the manifest for the moved file(s). For a dir, re-key each tracked child.
|
|
162
|
+
if [ -d "$new" ]; then
|
|
163
|
+
local child rel
|
|
164
|
+
while IFS= read -r child; do
|
|
165
|
+
rel="${child#./}"
|
|
166
|
+
_kit_migrate_rekey_manifest "${old}/${rel#"$new/"}" "$rel" 2>/dev/null || true
|
|
167
|
+
done < <(find "$new" -type f 2>/dev/null)
|
|
168
|
+
else
|
|
169
|
+
_kit_migrate_rekey_manifest "$old" "$new"
|
|
170
|
+
fi
|
|
171
|
+
fi
|
|
172
|
+
fi
|
|
173
|
+
# Register the rename either way (so update never resurrects $old) — idempotent.
|
|
174
|
+
_kit_migrate_register_rename "$old" "$new"
|
|
175
|
+
done
|
|
176
|
+
|
|
177
|
+
_kit_migrate_doc_surgery
|
|
178
|
+
_kit_op_say "kit-migrate: done ($moved moved). Old paths registered as renamed — /kit-update will not resurrect them."
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
# CLI (direct execution only)
|
|
182
|
+
if kit_is_main; then
|
|
183
|
+
while [ $# -gt 0 ]; do case "$1" in
|
|
184
|
+
--dry-run) export KIT_DRY_RUN=1; shift;;
|
|
185
|
+
--yes|-y) export KIT_ASSUME_YES=1; shift;;
|
|
186
|
+
-h|--help) echo "usage: kit-migrate.sh [--dry-run] [--yes] (env: KIT_ASSUME_YES, KIT_DRY_RUN)"; exit 0;;
|
|
187
|
+
*) echo "unknown arg: $1" >&2; exit 2;;
|
|
188
|
+
esac; done
|
|
189
|
+
kit_migrate_all
|
|
190
|
+
fi
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# kit-onboard-test.sh — self-test for kit-onboard persistence (#372). Runs under bash AND zsh.
|
|
3
|
+
# Run: bash scripts/kit-onboard-test.sh
|
|
4
|
+
dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
|
|
5
|
+
|
|
6
|
+
if [ -n "${KIT_OB_TEST_INNER:-}" ]; then
|
|
7
|
+
set -u
|
|
8
|
+
fail=0
|
|
9
|
+
t() { if [ "$2" != "$3" ]; then echo "FAIL($KIT_OB_TEST_INNER): $1 -> got '[$2]' want '[$3]'"; fail=1; else echo "ok($KIT_OB_TEST_INNER): $1"; fi; }
|
|
10
|
+
OB="$dir/kit-onboard.sh"
|
|
11
|
+
|
|
12
|
+
export KIT_PROFILE_HOME="$(mktemp -d)"
|
|
13
|
+
export KIT_ASSUME_YES=1 KIT_VERSION=9.9.9 KIT_PROFILE_USER=tester
|
|
14
|
+
work="$(mktemp -d)"; trap 'rm -rf "$KIT_PROFILE_HOME" "$work"' EXIT
|
|
15
|
+
|
|
16
|
+
# status: none before global interview
|
|
17
|
+
"$OB" status >/dev/null 2>&1; t "status before rc" "$?" "1"
|
|
18
|
+
|
|
19
|
+
# global: writes profile, preserves schema/user, applies answers
|
|
20
|
+
printf '%s\n' '{"name":"Ada","language":"es","mode":"enforced","plans_format":"markdown","memory":"on"}' > "$work/g.json"
|
|
21
|
+
"$OB" global --answers "$work/g.json" >/dev/null 2>&1; t "global rc" "$?" "0"
|
|
22
|
+
prof="$KIT_PROFILE_HOME/kit.profile.tester.json"
|
|
23
|
+
t "profile created" "$([ -f "$prof" ] && echo yes)" "yes"
|
|
24
|
+
t "schema kept" "$(jq -r .schema "$prof")" "1"
|
|
25
|
+
t "user kept" "$(jq -r .user "$prof")" "tester"
|
|
26
|
+
t "name applied" "$(jq -r .name "$prof")" "Ada"
|
|
27
|
+
t "nested default" "$(jq -r .defaults.mode "$prof")" "enforced"
|
|
28
|
+
t "memory applied" "$(jq -r .defaults.memory "$prof")" "on"
|
|
29
|
+
"$OB" status >/dev/null 2>&1; t "status after rc" "$?" "0"
|
|
30
|
+
|
|
31
|
+
# global is mergeable (a 2nd partial run keeps prior fields)
|
|
32
|
+
printf '%s\n' '{"language":"en"}' > "$work/g2.json"
|
|
33
|
+
"$OB" global --answers "$work/g2.json" >/dev/null 2>&1
|
|
34
|
+
t "merge keeps name" "$(jq -r .name "$prof")" "Ada"
|
|
35
|
+
t "merge updates lang" "$(jq -r .language "$prof")" "en"
|
|
36
|
+
|
|
37
|
+
# project: writes ./.claude/kit.config.json via operate (manifest-tracked)
|
|
38
|
+
mkdir -p "$work/proj"
|
|
39
|
+
printf '%s\n' '{"name":"My App","owner":"Ada","language":"es","mode":"guided","software":"no"}' > "$work/p.json"
|
|
40
|
+
"$OB" project --answers "$work/p.json" --dir "$work/proj" >/dev/null 2>&1; t "project rc" "$?" "0"
|
|
41
|
+
cfg="$work/proj/.claude/kit.config.json"
|
|
42
|
+
t "config created" "$([ -f "$cfg" ] && echo yes)" "yes"
|
|
43
|
+
t "project.name" "$(jq -r .project.name "$cfg")" "My App"
|
|
44
|
+
t "project.mode" "$(jq -r .mode "$cfg")" "guided"
|
|
45
|
+
t "manifest tracks" "$(jq -r '.entries["'.claude/kit.config.json'"]|has("hash")' "$work/proj/.claude/kit.manifest.json")" "true"
|
|
46
|
+
|
|
47
|
+
# project idempotent (byte-identical)
|
|
48
|
+
before="$(cat "$cfg")"; "$OB" project --answers "$work/p.json" --dir "$work/proj" >/dev/null 2>&1
|
|
49
|
+
t "project idempotent" "$(cat "$cfg")" "$before"
|
|
50
|
+
|
|
51
|
+
[ "$fail" -eq 0 ] && echo "ALL OK($KIT_OB_TEST_INNER)"
|
|
52
|
+
exit "$fail"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
rc=0; ran=0
|
|
56
|
+
for sh in bash zsh; do
|
|
57
|
+
command -v "$sh" >/dev/null 2>&1 || continue
|
|
58
|
+
ran=$((ran+1)); echo "--- $sh ---"
|
|
59
|
+
rcflag=""; [ "$sh" = "zsh" ] && rcflag="--no-rcs"
|
|
60
|
+
KIT_OB_TEST_INNER="$sh" PATH="$PATH" "$sh" $rcflag "$0" || rc=1
|
|
61
|
+
done
|
|
62
|
+
[ "$ran" -eq 0 ] && { echo "no shell"; exit 1; }
|
|
63
|
+
exit "$rc"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# kit-onboard.sh — persist two-tier interview answers (D11, #372). The ASKING is the kit-onboard
|
|
3
|
+
# skill (AskUserQuestion, Tier A). This script is the deterministic persistence half:
|
|
4
|
+
#
|
|
5
|
+
# global answers -> ~/.claude/kit.profile.<user>.json (the once-in-a-lifetime profile, D7)
|
|
6
|
+
# project answers -> ./.claude/kit.config.json (manifest-tracked via kit-operate)
|
|
7
|
+
#
|
|
8
|
+
# Split this way the persistence is unit-testable under bash AND zsh; the skill only renders.
|
|
9
|
+
#
|
|
10
|
+
# Run:
|
|
11
|
+
# scripts/kit-onboard.sh global --answers FILE [--user U]
|
|
12
|
+
# scripts/kit-onboard.sh project --answers FILE [--dir DIR]
|
|
13
|
+
# scripts/kit-onboard.sh status # has the global profile been created?
|
|
14
|
+
# Env: KIT_ASSUME_YES / KIT_DRY_RUN (project tier, via kit-operate). Requires: jq.
|
|
15
|
+
|
|
16
|
+
set -uo pipefail
|
|
17
|
+
_ob_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd)"
|
|
18
|
+
# shellcheck source=/dev/null
|
|
19
|
+
. "$_ob_dir/lib/kit-interview.sh" # catalog/apply (pulls kit-cli + kit-profile)
|
|
20
|
+
# shellcheck source=/dev/null
|
|
21
|
+
. "$_ob_dir/lib/kit-operate.sh" # four-beat machine + manifest (project tier)
|
|
22
|
+
|
|
23
|
+
# kit_onboard_global <answersfile> [user]
|
|
24
|
+
# Ensure the profile exists (schema + user + slug), then merge the global-tier answers onto it.
|
|
25
|
+
kit_onboard_global() {
|
|
26
|
+
command -v jq >/dev/null 2>&1 || kit_die "jq required"
|
|
27
|
+
local answers="$1" user="${2:-}"
|
|
28
|
+
[ -f "$answers" ] || kit_die "answers file missing: $answers"
|
|
29
|
+
kit_profile_init "$user" || kit_die "could not init profile"
|
|
30
|
+
local p merged tmp; p="$(kit_profile_path "$user")"
|
|
31
|
+
merged="$(kit_interview_apply global "$answers" "$p")" || kit_die "deriving global profile failed"
|
|
32
|
+
tmp="$(mktemp)"; printf '%s\n' "$merged" > "$tmp" && mv "$tmp" "$p" || { rm -f "$tmp"; kit_die "write failed"; }
|
|
33
|
+
kit_say "global profile updated: $p"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# kit_onboard_project <answersfile> [targetdir]
|
|
37
|
+
# Merge the project-tier answers onto ./.claude/kit.config.json through kit-operate (manifest).
|
|
38
|
+
kit_onboard_project() {
|
|
39
|
+
command -v jq >/dev/null 2>&1 || kit_die "jq required"
|
|
40
|
+
local answers="$1" target="${2:-$PWD}"
|
|
41
|
+
[ -f "$answers" ] || kit_die "answers file missing: $answers"
|
|
42
|
+
target="$(cd "$target" 2>/dev/null && pwd)" || kit_die "no such dir: $target"
|
|
43
|
+
cd "$target" || kit_die "cannot enter $target"
|
|
44
|
+
local cfg=".claude/kit.config.json" base merged
|
|
45
|
+
base=""; [ -f "$cfg" ] && base="$cfg"
|
|
46
|
+
merged="$(kit_interview_apply project "$answers" "$base")" || kit_die "deriving project config failed"
|
|
47
|
+
kit_say "kit onboard project -> $target/$cfg"
|
|
48
|
+
printf '%s\n' "$merged" | kit_op_write_content "$cfg" A onboard
|
|
49
|
+
case $? in 0|10) return 0;; *) kit_die "failed to write $cfg";; esac
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# CLI
|
|
53
|
+
if kit_is_main; then
|
|
54
|
+
_sub="${1:-}"; shift 2>/dev/null || true
|
|
55
|
+
_answers=""; _user=""; _dir="$PWD"
|
|
56
|
+
while [ $# -gt 0 ]; do case "$1" in
|
|
57
|
+
--answers) _answers="$2"; shift 2;;
|
|
58
|
+
--user) _user="$2"; shift 2;;
|
|
59
|
+
--dir) _dir="$2"; shift 2;;
|
|
60
|
+
-h|--help) echo "usage: kit-onboard.sh global|project|status [--answers F] [--user U] [--dir D]"; exit 0;;
|
|
61
|
+
*) kit_die "unknown arg: $1";;
|
|
62
|
+
esac; done
|
|
63
|
+
case "$_sub" in
|
|
64
|
+
global) kit_onboard_global "$_answers" "$_user";;
|
|
65
|
+
project) kit_onboard_project "$_answers" "$_dir";;
|
|
66
|
+
status) if kit_profile_exists "$_user"; then echo "profile: $(kit_profile_path "$_user") (exists)"; else echo "profile: none yet — run the global interview"; exit 1; fi;;
|
|
67
|
+
*) kit_die "usage: kit-onboard.sh global|project|status ...";;
|
|
68
|
+
esac
|
|
69
|
+
fi
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# kit-promote-test.sh — self-test for kit-promote (#374). Runs under bash AND zsh.
|
|
3
|
+
# Run: bash scripts/kit-promote-test.sh
|
|
4
|
+
PLUGIN_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
|
|
5
|
+
|
|
6
|
+
if [ -n "${KIT_PROMOTE_TEST_INNER:-}" ]; then
|
|
7
|
+
set -u
|
|
8
|
+
fail=0
|
|
9
|
+
t() { if [ "$2" != "$3" ]; then echo "FAIL($KIT_PROMOTE_TEST_INNER): $1 -> got '[$2]' want '[$3]'"; fail=1; else echo "ok($KIT_PROMOTE_TEST_INNER): $1"; fi; }
|
|
10
|
+
|
|
11
|
+
ws="$(mktemp -d)"; trap 'rm -rf "$ws"' EXIT
|
|
12
|
+
printf '{"workspace":{"name":"w"}}\n' > "$ws/kit.workspace.json"
|
|
13
|
+
mkdir -p "$ws/proj-a/.claude/skills/brand" "$ws/proj-b/.claude/skills/brand" "$ws/proj-a/.claude/skills/solo"
|
|
14
|
+
printf 'SHARED VOICE\n' > "$ws/proj-a/.claude/skills/brand/SKILL.md"
|
|
15
|
+
printf 'SHARED VOICE\n' > "$ws/proj-b/.claude/skills/brand/SKILL.md" # identical -> dup candidate
|
|
16
|
+
printf 'only here\n' > "$ws/proj-a/.claude/skills/solo/SKILL.md"
|
|
17
|
+
|
|
18
|
+
. "$PLUGIN_DIR/scripts/kit-promote.sh"
|
|
19
|
+
. "$PLUGIN_DIR/scripts/lib/kit-manifest.sh"
|
|
20
|
+
|
|
21
|
+
# workspace root detection from inside a project
|
|
22
|
+
t "ws root from project" "$(cd "$ws/proj-a" && kit_workspace_root | sed "s#$ws#WS#")" "WS"
|
|
23
|
+
t "ws root from nested" "$(cd "$ws/proj-a/.claude/skills" && kit_workspace_root | sed "s#$ws#WS#")" "WS"
|
|
24
|
+
|
|
25
|
+
# promote brand/SKILL.md from proj-a up to the commons (assume-yes skips the above-project prompt)
|
|
26
|
+
( cd "$ws/proj-a" && export KIT_ASSUME_YES=1 && kit_promote "skills/brand/SKILL.md" >/dev/null 2>&1 )
|
|
27
|
+
t "copied to commons" "$([ -f "$ws/.claude/skills/brand/SKILL.md" ] && echo yes || echo no)" "yes"
|
|
28
|
+
t "commons content" "$(cat "$ws/.claude/skills/brand/SKILL.md")" "SHARED VOICE"
|
|
29
|
+
t "commons manifest" "$(cd "$ws" && KIT_MANIFEST=.claude/kit.manifest.json kit_manifest_verify .claude/skills/brand/SKILL.md)" "intact"
|
|
30
|
+
t "project mirror tracked" "$(cd "$ws/proj-a" && kit_manifest_verify .claude/skills/brand/SKILL.md)" "intact"
|
|
31
|
+
|
|
32
|
+
# dup detection across the two projects: brand/SKILL.md is identical -> candidate; solo is not
|
|
33
|
+
cands="$(kit_promote_candidates "$ws/proj-a" "$ws/proj-b" 2>/dev/null)"
|
|
34
|
+
t "candidate found" "$(printf '%s' "$cands" | grep -c 'skills/brand/SKILL.md')" "1"
|
|
35
|
+
t "no false candidate" "$(printf '%s' "$cands" | grep -c 'skills/solo/SKILL.md')" "0"
|
|
36
|
+
|
|
37
|
+
# dry-run promote writes nothing new
|
|
38
|
+
mkdir -p "$ws/proj-b/.claude/skills/new"; printf 'x\n' > "$ws/proj-b/.claude/skills/new/SKILL.md"
|
|
39
|
+
( cd "$ws/proj-b" && KIT_DRY_RUN=1 kit_promote "skills/new/SKILL.md" >/dev/null 2>&1 )
|
|
40
|
+
t "dry-run no commons write" "$([ -f "$ws/.claude/skills/new/SKILL.md" ] && echo yes || echo no)" "no"
|
|
41
|
+
|
|
42
|
+
[ "$fail" -eq 0 ] && echo "ALL OK($KIT_PROMOTE_TEST_INNER)"
|
|
43
|
+
exit "$fail"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
rc=0; ran=0
|
|
47
|
+
for sh in bash zsh; do
|
|
48
|
+
command -v "$sh" >/dev/null 2>&1 || continue
|
|
49
|
+
ran=$((ran+1)); echo "--- $sh ---"
|
|
50
|
+
rcflag=""; [ "$sh" = "zsh" ] && rcflag="--no-rcs"
|
|
51
|
+
KIT_PROMOTE_TEST_INNER="$sh" PATH="$PATH" PLUGIN_DIR="$PLUGIN_DIR" "$sh" $rcflag "$0" || rc=1
|
|
52
|
+
done
|
|
53
|
+
[ "$ran" -eq 0 ] && { echo "no shell"; exit 1; }
|
|
54
|
+
exit "$rc"
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# kit-promote.sh — promote a project-local skill/agent to the workspace COMMONS so sibling
|
|
3
|
+
# projects can share it (#374). Born-local, shareable by ascent.
|
|
4
|
+
#
|
|
5
|
+
# MECHANICS (decided in the v2 plan, F6):
|
|
6
|
+
# - The commons is `<workspace>/.claude/` (workspace root = nearest ancestor with kit.workspace.json).
|
|
7
|
+
# - Promotion COPIES the file up to the commons (not symlink): Cowork ignores symlinks pointing
|
|
8
|
+
# outside a plugin dir, and a copy works on every surface. Hash in the manifest detects drift.
|
|
9
|
+
# - Writing ABOVE the current project ALWAYS asks permission (D10) — the commons is shared ground.
|
|
10
|
+
# - The promoted copy is recorded in BOTH manifests (workspace + project) so kit-wire/remove and
|
|
11
|
+
# duplicate-detection can reason about it. The project copy becomes a tracked mirror: editing it
|
|
12
|
+
# locally is drift the kit will flag (edit at the workspace level instead).
|
|
13
|
+
#
|
|
14
|
+
# Run: scripts/kit-promote.sh <relpath-under-.claude> # e.g. skills/brand-voice/SKILL.md
|
|
15
|
+
# scripts/kit-promote.sh --candidates <dir1> <dir2> ... # list exact-dup files across projects
|
|
16
|
+
# Env: KIT_ASSUME_YES (skip the above-project permission prompt), KIT_DRY_RUN.
|
|
17
|
+
# Requires: jq.
|
|
18
|
+
|
|
19
|
+
set -uo pipefail
|
|
20
|
+
_pr_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd)"
|
|
21
|
+
# shellcheck source=/dev/null
|
|
22
|
+
. "$_pr_dir/lib/kit-operate.sh" # manifest + operate
|
|
23
|
+
# shellcheck source=/dev/null
|
|
24
|
+
. "$_pr_dir/lib/kit-cli.sh" # kit_is_main, kit_say/die
|
|
25
|
+
|
|
26
|
+
# kit_workspace_root [startdir] -> path of nearest ancestor holding kit.workspace.json (empty if none)
|
|
27
|
+
kit_workspace_root() {
|
|
28
|
+
local d; d="$(cd "${1:-$PWD}" 2>/dev/null && pwd)" || return 1
|
|
29
|
+
while [ -n "$d" ]; do
|
|
30
|
+
[ -f "$d/kit.workspace.json" ] && { printf '%s\n' "$d"; return 0; }
|
|
31
|
+
[ "$d" = "/" ] && break
|
|
32
|
+
d="$(dirname "$d")"
|
|
33
|
+
done
|
|
34
|
+
return 1
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# kit_promote <relpath> — relpath is under the project's .claude/ (e.g. skills/foo/SKILL.md).
|
|
38
|
+
# Copies <proj>/.claude/<relpath> -> <ws>/.claude/<relpath>, records both sides.
|
|
39
|
+
kit_promote() {
|
|
40
|
+
command -v jq >/dev/null 2>&1 || { kit_die "jq required"; }
|
|
41
|
+
local rel="$1"
|
|
42
|
+
[ -n "$rel" ] || { kit_die "usage: kit-promote.sh <relpath-under-.claude>"; }
|
|
43
|
+
local proj_file=".claude/$rel"
|
|
44
|
+
[ -f "$proj_file" ] || { kit_die "not found: $proj_file"; }
|
|
45
|
+
|
|
46
|
+
local ws; ws="$(kit_workspace_root)" || { kit_die "no workspace root (kit.workspace.json) found above $PWD — promotion needs a workspace."; }
|
|
47
|
+
local proj; proj="$(pwd)"
|
|
48
|
+
if [ "$ws" = "$proj" ]; then kit_die "already at the workspace root — nothing to promote up."; fi
|
|
49
|
+
|
|
50
|
+
local dest="$ws/.claude/$rel"
|
|
51
|
+
kit_say "promote: $proj_file"
|
|
52
|
+
kit_say " -> $dest (workspace commons)"
|
|
53
|
+
|
|
54
|
+
# Writing ABOVE the project always asks (D10), unless KIT_ASSUME_YES.
|
|
55
|
+
if [ -z "${KIT_ASSUME_YES:-}" ] && [ -z "${KIT_DRY_RUN:-}" ]; then
|
|
56
|
+
printf 'Write to the workspace commons (above this project)? [y/N] ' >&2
|
|
57
|
+
local c=""; IFS= read -r c </dev/tty 2>/dev/null || IFS= read -r c 2>/dev/null || c=""
|
|
58
|
+
case "$c" in y|Y|yes|YES) ;; *) kit_say "aborted."; return 0;; esac
|
|
59
|
+
fi
|
|
60
|
+
if [ -n "${KIT_DRY_RUN:-}" ]; then kit_say " [dry-run] would copy to commons + record both manifests"; return 10; fi
|
|
61
|
+
|
|
62
|
+
mkdir -p "$(dirname "$dest")"
|
|
63
|
+
cp -- "$proj_file" "$dest"
|
|
64
|
+
# record in the WORKSPACE manifest (commons owner)
|
|
65
|
+
( cd "$ws" && KIT_MANIFEST=".claude/kit.manifest.json" kit_manifest_record ".claude/$rel" A promote >/dev/null 2>&1 ) || true
|
|
66
|
+
# record the project copy as a promoted mirror (so drift can be detected; edit upstream instead)
|
|
67
|
+
kit_manifest_record "$proj_file" A promote-mirror >/dev/null 2>&1 || true
|
|
68
|
+
kit_say " promoted. Edit it at the workspace level; the project copy is a tracked mirror."
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# kit_promote_candidates <dir>... — find files that are byte-identical across 2+ given project
|
|
72
|
+
# .claude/ trees (exact-dup = a promotion candidate). Prints "<count>\t<relpath>" for dups.
|
|
73
|
+
kit_promote_candidates() {
|
|
74
|
+
command -v jq >/dev/null 2>&1 || { kit_die "jq required"; }
|
|
75
|
+
[ "$#" -ge 2 ] || { kit_die "usage: --candidates <dir1> <dir2> [..]"; }
|
|
76
|
+
local d f rel h
|
|
77
|
+
# emit "hash<TAB>relpath" for every skill/agent file under each dir's .claude/{skills,agents}
|
|
78
|
+
{ for d in "$@"; do
|
|
79
|
+
[ -d "$d/.claude" ] || continue
|
|
80
|
+
while IFS= read -r f; do
|
|
81
|
+
[ -f "$f" ] || continue
|
|
82
|
+
rel="${f#"$d"/.claude/}"
|
|
83
|
+
h="$(kit_manifest_hash "$f")"
|
|
84
|
+
printf '%s\t%s\n' "$h" "$rel"
|
|
85
|
+
done <<EOF
|
|
86
|
+
$(find "$d/.claude/skills" "$d/.claude/agents" -type f 2>/dev/null)
|
|
87
|
+
EOF
|
|
88
|
+
done
|
|
89
|
+
} | LC_ALL=C sort | awk -F'\t' '
|
|
90
|
+
{ if ($1==ph) { cnt++ } else { if (cnt>1) print cnt"\t"prel; ph=$1; prel=$2; cnt=1 } }
|
|
91
|
+
END { if (cnt>1) print cnt"\t"prel }'
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# CLI
|
|
95
|
+
if kit_is_main; then
|
|
96
|
+
case "${1:-}" in
|
|
97
|
+
--candidates) shift; kit_promote_candidates "$@";;
|
|
98
|
+
-h|--help) echo "usage: kit-promote.sh <relpath-under-.claude> | --candidates <dir>...";;
|
|
99
|
+
"") kit_die "usage: kit-promote.sh <relpath-under-.claude> | --candidates <dir>...";;
|
|
100
|
+
*) kit_promote "$1";;
|
|
101
|
+
esac
|
|
102
|
+
fi
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# kit-remove-test.sh — self-test for kit-remove (#373). Runs under bash AND zsh.
|
|
3
|
+
# Run: bash scripts/kit-remove-test.sh
|
|
4
|
+
PLUGIN_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
|
|
5
|
+
|
|
6
|
+
if [ -n "${KIT_REMOVE_TEST_INNER:-}" ]; then
|
|
7
|
+
set -u
|
|
8
|
+
fail=0
|
|
9
|
+
t() { if [ "$2" != "$3" ]; then echo "FAIL($KIT_REMOVE_TEST_INNER): $1 -> got '[$2]' want '[$3]'"; fail=1; else echo "ok($KIT_REMOVE_TEST_INNER): $1"; fi; }
|
|
10
|
+
|
|
11
|
+
proj="$(mktemp -d)"; trap 'rm -rf "$proj"' EXIT
|
|
12
|
+
cd "$proj"
|
|
13
|
+
export KIT_ASSUME_YES=1
|
|
14
|
+
. "$PLUGIN_DIR/scripts/kit-remove.sh"
|
|
15
|
+
. "$PLUGIN_DIR/scripts/lib/kit-manifest.sh"
|
|
16
|
+
|
|
17
|
+
# set up a project the kit "installed": two tracked files + one user-modified + one untracked
|
|
18
|
+
mkdir -p .claude/hooks knowledge
|
|
19
|
+
printf 'a\n' > .claude/statusline.sh; kit_manifest_record .claude/statusline.sh B wire 1 2026-01-01T00:00:00Z
|
|
20
|
+
printf 'b\n' > .claude/hooks/guard.sh; kit_manifest_record .claude/hooks/guard.sh B wire 1 2026-01-01T00:00:00Z
|
|
21
|
+
printf 'c\n' > .claude/settings.json; kit_manifest_record .claude/settings.json B wire 1 2026-01-01T00:00:00Z
|
|
22
|
+
printf 'USER EDITED\n' > .claude/settings.json # now modified vs manifest
|
|
23
|
+
printf 'my notes\n' > knowledge/doc.md # NOT tracked — must survive
|
|
24
|
+
|
|
25
|
+
t "before: statusline tracked" "$(kit_manifest_verify .claude/statusline.sh)" "intact"
|
|
26
|
+
t "before: settings modified" "$(kit_manifest_verify .claude/settings.json)" "modified"
|
|
27
|
+
|
|
28
|
+
# remove with --yes: intact files deleted, modified kept (assume-yes still declines modified),
|
|
29
|
+
# untracked knowledge/ untouched.
|
|
30
|
+
printf 'n\n' | kit_remove_all >/dev/null 2>&1 # answer 'n' to the modified-file prompt
|
|
31
|
+
|
|
32
|
+
t "intact deleted (statusline)" "$([ -f .claude/statusline.sh ] && echo yes || echo no)" "no"
|
|
33
|
+
t "intact deleted (guard)" "$([ -f .claude/hooks/guard.sh ] && echo yes || echo no)" "no"
|
|
34
|
+
t "modified KEPT (settings)" "$([ -f .claude/settings.json ] && echo yes || echo no)" "yes"
|
|
35
|
+
t "modified content intact" "$(cat .claude/settings.json)" "USER EDITED"
|
|
36
|
+
t "untracked knowledge SAFE" "$([ -f knowledge/doc.md ] && echo yes || echo no)" "yes"
|
|
37
|
+
# manifest kept because one modified entry remained
|
|
38
|
+
t "manifest remains" "$([ -f .claude/kit.manifest.json ] && echo yes || echo no)" "yes"
|
|
39
|
+
t "only modified entry left" "$(kit_manifest_list | tr '\n' ',')" ".claude/settings.json,"
|
|
40
|
+
|
|
41
|
+
# dry-run never deletes
|
|
42
|
+
proj2="$(mktemp -d)"; cd "$proj2"
|
|
43
|
+
printf 'x\n' > .claude/f.sh 2>/dev/null || { mkdir -p .claude; printf 'x\n' > .claude/f.sh; }
|
|
44
|
+
kit_manifest_record .claude/f.sh B wire 1 2026-01-01T00:00:00Z
|
|
45
|
+
KIT_DRY_RUN=1 kit_remove_all >/dev/null 2>&1
|
|
46
|
+
t "dry-run keeps file" "$([ -f .claude/f.sh ] && echo yes || echo no)" "yes"
|
|
47
|
+
t "dry-run keeps manifest" "$([ -f .claude/kit.manifest.json ] && echo yes || echo no)" "yes"
|
|
48
|
+
|
|
49
|
+
[ "$fail" -eq 0 ] && echo "ALL OK($KIT_REMOVE_TEST_INNER)"
|
|
50
|
+
exit "$fail"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
rc=0; ran=0
|
|
54
|
+
for sh in bash zsh; do
|
|
55
|
+
command -v "$sh" >/dev/null 2>&1 || continue
|
|
56
|
+
ran=$((ran+1)); echo "--- $sh ---"
|
|
57
|
+
rcflag=""; [ "$sh" = "zsh" ] && rcflag="--no-rcs"
|
|
58
|
+
KIT_REMOVE_TEST_INNER="$sh" PATH="$PATH" PLUGIN_DIR="$PLUGIN_DIR" "$sh" $rcflag "$0" || rc=1
|
|
59
|
+
done
|
|
60
|
+
[ "$ran" -eq 0 ] && { echo "no shell"; exit 1; }
|
|
61
|
+
exit "$rc"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# kit-remove.sh — uninstall the kit from a project, by MANIFEST, never by guesswork (#373).
|
|
3
|
+
#
|
|
4
|
+
# "You can leave clean" is the trust feature: removal walks the F0 ownership manifest in reverse
|
|
5
|
+
# and deletes only what the kit installed, applying the conffiles rule (D12):
|
|
6
|
+
# intact -> delete (kit wrote it, user never touched it)
|
|
7
|
+
# modified -> ASK (user edited it; default keep)
|
|
8
|
+
# missing -> just drop the entry
|
|
9
|
+
# untracked -> never touched (can't happen via the manifest, but kit_op_remove refuses anyway)
|
|
10
|
+
# It NEVER deletes knowledge/, plans/, drafts/, the user's CLAUDE.md, or anything not in the
|
|
11
|
+
# manifest. Honors KIT_DRY_RUN; KIT_ASSUME_YES applies non-interactively (still asks on modified).
|
|
12
|
+
#
|
|
13
|
+
# Run: scripts/kit-remove.sh # interactive
|
|
14
|
+
# scripts/kit-remove.sh --dry-run # preview only
|
|
15
|
+
# scripts/kit-remove.sh --yes # non-interactive (modified files still prompt)
|
|
16
|
+
# Requires: jq.
|
|
17
|
+
|
|
18
|
+
set -uo pipefail
|
|
19
|
+
_rm_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd)"
|
|
20
|
+
# shellcheck source=/dev/null
|
|
21
|
+
. "$_rm_dir/lib/kit-operate.sh" # brings kit-manifest.sh too
|
|
22
|
+
# shellcheck source=/dev/null
|
|
23
|
+
. "$_rm_dir/lib/kit-cli.sh" # kit_is_main
|
|
24
|
+
|
|
25
|
+
# kit_remove_all — remove every manifest entry (reverse order so nested files go before dirs),
|
|
26
|
+
# then drop the manifest + prune now-empty .claude/kit dirs. Returns count kept (modified/declined).
|
|
27
|
+
kit_remove_all() {
|
|
28
|
+
command -v jq >/dev/null 2>&1 || { echo "kit-remove: jq required" >&2; return 1; }
|
|
29
|
+
local mf; mf="$(kit_manifest_path)"
|
|
30
|
+
if [ ! -f "$mf" ]; then echo "kit-remove: no manifest ($mf) — nothing kit-managed here." >&2; return 0; fi
|
|
31
|
+
|
|
32
|
+
# snapshot the tracked paths, reverse-sorted (longest/deepest first)
|
|
33
|
+
local paths; paths="$(kit_manifest_list | LC_ALL=C sort -r)"
|
|
34
|
+
if [ -z "$paths" ]; then echo "kit-remove: manifest is empty." >&2; rm -f "$mf"; return 0; fi
|
|
35
|
+
|
|
36
|
+
# One top-level confirm for the whole uninstall (skipped under --yes / --dry-run). Per-file the
|
|
37
|
+
# machine still protects user-modified files individually.
|
|
38
|
+
local n; n="$(printf '%s\n' "$paths" | grep -c .)"
|
|
39
|
+
if [ -z "${KIT_ASSUME_YES:-}" ] && [ -z "${KIT_DRY_RUN:-}" ]; then
|
|
40
|
+
printf 'kit-remove: about to remove %s kit-managed file(s). Continue? [y/N] ' "$n" >&2
|
|
41
|
+
local _c=""; IFS= read -r _c </dev/tty 2>/dev/null || IFS= read -r _c 2>/dev/null || _c=""
|
|
42
|
+
case "$_c" in y|Y|yes|YES) ;; *) echo "kit-remove: aborted." >&2; return 0;; esac
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# Collect into an array FIRST, then iterate — so a per-file prompt's `read` inside the loop
|
|
46
|
+
# can't steal the next path off the loop's stdin (classic while-read pitfall).
|
|
47
|
+
local arr=() line
|
|
48
|
+
while IFS= read -r line; do [ -n "$line" ] && arr+=("$line"); done <<EOF
|
|
49
|
+
$paths
|
|
50
|
+
EOF
|
|
51
|
+
local total=0 removed=0 kept=0 p rc
|
|
52
|
+
for p in ${arr[@]+"${arr[@]}"}; do
|
|
53
|
+
total=$((total+1))
|
|
54
|
+
kit_op_remove "$p"; rc=$?
|
|
55
|
+
if [ "$rc" -eq 0 ]; then removed=$((removed+1)); else kept=$((kept+1)); fi
|
|
56
|
+
done
|
|
57
|
+
|
|
58
|
+
_kit_op_say "kit-remove: $removed removed, $kept kept (modified/declined), of $total tracked."
|
|
59
|
+
# If every entry is gone, remove the (now-empty) manifest too.
|
|
60
|
+
if [ -z "$(kit_manifest_list)" ]; then
|
|
61
|
+
if _kit_op_dry; then _kit_op_say " [dry-run] would delete empty manifest $mf"
|
|
62
|
+
else rm -f "$mf"; _kit_op_say " deleted empty manifest $mf"; fi
|
|
63
|
+
else
|
|
64
|
+
_kit_op_say " manifest kept ($(kit_manifest_list | grep -c . ) entr(y/ies) remain — modified files you chose to keep)."
|
|
65
|
+
fi
|
|
66
|
+
# Remove any kit-managed cron entries for this project (fail-soft — missing crontab is fine).
|
|
67
|
+
local rt_script; rt_script="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd)/kit-routines.sh"
|
|
68
|
+
if [ -f "$rt_script" ] && command -v crontab >/dev/null 2>&1; then
|
|
69
|
+
bash "$rt_script" remove-all || true
|
|
70
|
+
fi
|
|
71
|
+
return 0
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# CLI (direct execution only; zsh-safe guard)
|
|
75
|
+
# CLI (direct execution only)
|
|
76
|
+
if kit_is_main; then
|
|
77
|
+
while [ $# -gt 0 ]; do case "$1" in
|
|
78
|
+
--dry-run) export KIT_DRY_RUN=1; shift;;
|
|
79
|
+
--yes|-y) export KIT_ASSUME_YES=1; shift;;
|
|
80
|
+
-h|--help) echo "usage: kit-remove.sh [--dry-run] [--yes]"; exit 0;;
|
|
81
|
+
*) echo "unknown arg: $1" >&2; exit 2;;
|
|
82
|
+
esac; done
|
|
83
|
+
kit_remove_all
|
|
84
|
+
fi
|