@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.
Files changed (191) hide show
  1. package/.claude-plugin/plugin.json +22 -0
  2. package/AGENTS.md +101 -0
  3. package/LICENSE-APACHE +202 -0
  4. package/LICENSE-MIT +21 -0
  5. package/README.md +143 -0
  6. package/SECURITY.md +22 -0
  7. package/bin/cckit +215 -0
  8. package/cckit.config.json +34 -0
  9. package/commands/kit-add.md +42 -0
  10. package/commands/kit-docs.md +45 -0
  11. package/commands/kit-doctor.md +52 -0
  12. package/commands/kit-export-project.md +58 -0
  13. package/commands/kit-export-training.md +49 -0
  14. package/commands/kit-init.md +126 -0
  15. package/commands/kit-routines.md +59 -0
  16. package/commands/kit-update.md +132 -0
  17. package/docs/kit-annotate/01-explainer.html +225 -0
  18. package/docs/kit-annotate/02-implementation-plan.html +196 -0
  19. package/docs/media/.onboarding-capture.cast +5 -0
  20. package/docs/media/README.md +43 -0
  21. package/docs/media/build-demo.sh +63 -0
  22. package/docs/media/build-kit-init.sh +51 -0
  23. package/docs/media/build-onboarding.sh +51 -0
  24. package/docs/media/kit-dry-run.cast +107 -0
  25. package/docs/media/kit-dry-run.gif +0 -0
  26. package/docs/media/kit-init.cast +56 -0
  27. package/docs/media/kit-init.gif +0 -0
  28. package/docs/media/kit-onboarding.cast +148 -0
  29. package/docs/media/kit-onboarding.gif +0 -0
  30. package/githooks/pre-commit +18 -0
  31. package/kit.config.schema.json +105 -0
  32. package/package.json +54 -0
  33. package/privacy-denylist.example +8 -0
  34. package/profiles/automation.json +36 -0
  35. package/profiles/content.json +41 -0
  36. package/profiles/minimal.json +31 -0
  37. package/profiles/research.json +37 -0
  38. package/profiles/software.json +32 -0
  39. package/scripts/annotate-setup.sh +149 -0
  40. package/scripts/autopilot.sh +50 -0
  41. package/scripts/capture-project-ids.sh +53 -0
  42. package/scripts/check.sh +66 -0
  43. package/scripts/contribute.sh +48 -0
  44. package/scripts/debug.sh +54 -0
  45. package/scripts/init-upgrade-test.sh +99 -0
  46. package/scripts/init.sh +827 -0
  47. package/scripts/install.sh +24 -0
  48. package/scripts/kit-add-test.sh +62 -0
  49. package/scripts/kit-add.sh +115 -0
  50. package/scripts/kit-adopt-test.sh +61 -0
  51. package/scripts/kit-adopt.sh +122 -0
  52. package/scripts/kit-bump-version.sh +79 -0
  53. package/scripts/kit-digest.sh +126 -0
  54. package/scripts/kit-doctor.sh +663 -0
  55. package/scripts/kit-export-project-test.sh +82 -0
  56. package/scripts/kit-export-project.sh +245 -0
  57. package/scripts/kit-export-training-test.sh +51 -0
  58. package/scripts/kit-export-training.sh +175 -0
  59. package/scripts/kit-migrate-test.sh +80 -0
  60. package/scripts/kit-migrate.sh +190 -0
  61. package/scripts/kit-onboard-test.sh +63 -0
  62. package/scripts/kit-onboard.sh +69 -0
  63. package/scripts/kit-promote-test.sh +54 -0
  64. package/scripts/kit-promote.sh +102 -0
  65. package/scripts/kit-remove-test.sh +61 -0
  66. package/scripts/kit-remove.sh +84 -0
  67. package/scripts/kit-routines.sh +322 -0
  68. package/scripts/kit-version-check.sh +91 -0
  69. package/scripts/kit-wire-test.sh +54 -0
  70. package/scripts/kit-wire.sh +132 -0
  71. package/scripts/knowledge-lint.sh +96 -0
  72. package/scripts/lib/cckit-output.sh +36 -0
  73. package/scripts/lib/effort-metrics.sh +452 -0
  74. package/scripts/lib/effort-ops-test.sh +83 -0
  75. package/scripts/lib/effort-ops.sh +132 -0
  76. package/scripts/lib/effort-plan.sh +104 -0
  77. package/scripts/lib/effort.sh +191 -0
  78. package/scripts/lib/engine-adapter.sh +92 -0
  79. package/scripts/lib/gh-log.sh +58 -0
  80. package/scripts/lib/gh-project.sh +212 -0
  81. package/scripts/lib/handoff.sh +35 -0
  82. package/scripts/lib/kit-cli-test.sh +42 -0
  83. package/scripts/lib/kit-cli.sh +32 -0
  84. package/scripts/lib/kit-config-resolve.sh +145 -0
  85. package/scripts/lib/kit-config.sh +88 -0
  86. package/scripts/lib/kit-engine-test.sh +107 -0
  87. package/scripts/lib/kit-events.sh +62 -0
  88. package/scripts/lib/kit-gc.sh +117 -0
  89. package/scripts/lib/kit-interview-test.sh +77 -0
  90. package/scripts/lib/kit-interview.sh +203 -0
  91. package/scripts/lib/kit-local.sh +79 -0
  92. package/scripts/lib/kit-manifest.sh +127 -0
  93. package/scripts/lib/kit-mode-test.sh +49 -0
  94. package/scripts/lib/kit-mode.sh +67 -0
  95. package/scripts/lib/kit-operate.sh +105 -0
  96. package/scripts/lib/kit-profile-test.sh +62 -0
  97. package/scripts/lib/kit-profile.sh +115 -0
  98. package/scripts/lib/kit-task-ops-test.sh +63 -0
  99. package/scripts/lib/kit-task-ops.sh +341 -0
  100. package/scripts/lib/pr-evidence.sh +173 -0
  101. package/scripts/lib/project-scan.sh +16 -0
  102. package/scripts/lib/react-detect.sh +78 -0
  103. package/scripts/lib/role-identity.sh +47 -0
  104. package/scripts/lib/secret-guard.sh +96 -0
  105. package/scripts/lib/toon.sh +35 -0
  106. package/scripts/lib/ui.sh +42 -0
  107. package/scripts/lib/version-bump.sh +59 -0
  108. package/scripts/lib/worktree-issue-test.sh +45 -0
  109. package/scripts/lib/worktree-issue.sh +73 -0
  110. package/scripts/lib/worktree-start.sh +280 -0
  111. package/scripts/orchestrate.sh +160 -0
  112. package/scripts/portable-test.sh +53 -0
  113. package/scripts/publish.sh +94 -0
  114. package/scripts/setup-labels.sh +25 -0
  115. package/scripts/setup-milestones.sh +17 -0
  116. package/scripts/showcase.sh +64 -0
  117. package/scripts/status.sh +44 -0
  118. package/scripts/task-sync.sh +59 -0
  119. package/scripts/test.sh +48 -0
  120. package/scripts/web-install.sh +22 -0
  121. package/skills/kit-annotate/SKILL.md +107 -0
  122. package/skills/kit-autopilot/SKILL.md +108 -0
  123. package/skills/kit-contribute/SKILL.md +134 -0
  124. package/skills/kit-customize/SKILL.md +134 -0
  125. package/skills/kit-dev/SKILL.md +67 -0
  126. package/skills/kit-digest/SKILL.md +41 -0
  127. package/skills/kit-effort-close/SKILL.md +156 -0
  128. package/skills/kit-effort-new/SKILL.md +173 -0
  129. package/skills/kit-effort-pr/SKILL.md +139 -0
  130. package/skills/kit-effort-start/SKILL.md +85 -0
  131. package/skills/kit-gc/SKILL.md +80 -0
  132. package/skills/kit-onboard/SKILL.md +50 -0
  133. package/skills/kit-security-sweep/SKILL.md +57 -0
  134. package/skills/kit-ship/SKILL.md +43 -0
  135. package/skills/kit-task-close/SKILL.md +66 -0
  136. package/skills/kit-task-new/SKILL.md +51 -0
  137. package/skills/kit-task-pr/SKILL.md +43 -0
  138. package/skills/kit-task-pr-auto/SKILL.md +27 -0
  139. package/skills/kit-task-pr-merge/SKILL.md +53 -0
  140. package/skills/kit-task-start/SKILL.md +76 -0
  141. package/skills/kit-task-sync/SKILL.md +37 -0
  142. package/templates/CLAUDE.md.tmpl +106 -0
  143. package/templates/agents/analyst.md +55 -0
  144. package/templates/agents/auto-dev.md +93 -0
  145. package/templates/agents/backend.md +59 -0
  146. package/templates/agents/designer.md +73 -0
  147. package/templates/agents/devops.md +57 -0
  148. package/templates/agents/editor.md +48 -0
  149. package/templates/agents/frontend.md +81 -0
  150. package/templates/agents/generalist.md +46 -0
  151. package/templates/agents/local-delegate.md +70 -0
  152. package/templates/agents/n8n.md +65 -0
  153. package/templates/agents/pm.md +69 -0
  154. package/templates/agents/qa.md +66 -0
  155. package/templates/agents/researcher.md +57 -0
  156. package/templates/agents/security.md +65 -0
  157. package/templates/agents/tech-lead.md +75 -0
  158. package/templates/hooks/guard-base-branch-commit.sh.tmpl +45 -0
  159. package/templates/hooks/kit-local-status.sh.tmpl +34 -0
  160. package/templates/hooks/kit_version_check.sh.tmpl +6 -0
  161. package/templates/hooks/mempal_followup.sh.tmpl +97 -0
  162. package/templates/hooks/mempal_precompact.sh.tmpl +4 -0
  163. package/templates/hooks/mempal_save.sh.tmpl +4 -0
  164. package/templates/hooks/mempal_session_start.sh.tmpl +8 -0
  165. package/templates/hooks/prepush_gate.sh.tmpl +36 -0
  166. package/templates/hooks/repo-hygiene.sh.tmpl +72 -0
  167. package/templates/kit.config.json.tmpl +32 -0
  168. package/templates/knowledge-INDEX.md.tmpl +12 -0
  169. package/templates/lib/kit-sigil.sh.tmpl +124 -0
  170. package/templates/rules/branch-naming.md +104 -0
  171. package/templates/rules/communication-style.md +22 -0
  172. package/templates/rules/delegation-brief.md +40 -0
  173. package/templates/rules/design-routing.md +35 -0
  174. package/templates/rules/effort-model.md +122 -0
  175. package/templates/rules/knowledge-base.md +41 -0
  176. package/templates/rules/mempalace.md +110 -0
  177. package/templates/rules/plan-output-format.md +58 -0
  178. package/templates/rules/react-annotate.md +69 -0
  179. package/templates/rules/risk-tiered-review.md +62 -0
  180. package/templates/rules/skill-gaps.md +48 -0
  181. package/templates/rules/task-management.md +42 -0
  182. package/templates/settings/settings.local.json.tmpl +27 -0
  183. package/templates/skills/NAMESPACED +13 -0
  184. package/templates/skills/copywriting/SKILL.md +252 -0
  185. package/templates/skills/copywriting/references/copy-frameworks.md +344 -0
  186. package/templates/skills/copywriting/references/natural-transitions.md +272 -0
  187. package/templates/skills/feature-build-refine/SKILL.md +367 -0
  188. package/templates/skills/karpathy-guidelines/SKILL.md +69 -0
  189. package/templates/skills/morning-briefing/SKILL.md +46 -0
  190. package/templates/skills/speckit/SKILL.md +239 -0
  191. package/templates/skills/supabase-patterns/SKILL.md +88 -0
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env bash
2
+ # kit-export-project-test.sh — self-test for kit-export-project (#376). Runs under bash AND zsh.
3
+ # Run: bash scripts/kit-export-project-test.sh
4
+ PLUGIN_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
5
+
6
+ if [ -n "${KIT_EXPORT_TEST_INNER:-}" ]; then
7
+ set -u
8
+ fail=0
9
+ t() { if [ "$2" != "$3" ]; then echo "FAIL($KIT_EXPORT_TEST_INNER): $1 -> got '[$2]' want '[$3]'"; fail=1; else echo "ok($KIT_EXPORT_TEST_INNER): $1"; fi; }
10
+
11
+ proj="$(mktemp -d)"; trap 'rm -rf "$proj"' EXIT
12
+ cd "$proj"
13
+ . "$PLUGIN_DIR/scripts/kit-export-project.sh"
14
+ . "$PLUGIN_DIR/scripts/lib/kit-manifest.sh"
15
+
16
+ # A kit project: CLAUDE.md importing a tier-A rule + a tier-B hook; a knowledge corpus.
17
+ mkdir -p .claude/rules .claude/hooks .claude/agents knowledge
18
+ printf '# My Project\n\nRules:\n@.claude/rules/branch-naming.md\n\nHooks:\n@.claude/hooks/guard.sh\n' > CLAUDE.md
19
+ printf 'BRANCH RULE BODY\n' > .claude/rules/branch-naming.md
20
+ printf '#!/bin/sh\nguard\n' > .claude/hooks/guard.sh
21
+ printf 'AGENT BODY\n' > .claude/agents/pm.md
22
+ printf 'KNOWLEDGE DOC\n' > knowledge/doc.md
23
+
24
+ # Tier classification: rule = A, hook = B (heuristic, no manifest yet).
25
+ t "rule is tier A" "$(kit_export_tier .claude/rules/branch-naming.md)" "A"
26
+ t "hook is tier B" "$(kit_export_tier .claude/hooks/guard.sh)" "B"
27
+
28
+ # --verify: a tier-B import that CLAUDE.md requires is a portability defect -> rc 1.
29
+ kit_export_verify CLAUDE.md >/dev/null 2>&1
30
+ t "verify flags tier-B require (rc 1)" "$?" "1"
31
+
32
+ # Flattened instructions: inline the tier-A rule body, SKIP the tier-B hook (note it).
33
+ flat="$(kit_export_flatten_instructions CLAUDE.md)"
34
+ t "inlines tier-A rule body" "$(printf '%s' "$flat" | grep -c 'BRANCH RULE BODY')" "1"
35
+ t "marks tier-A inline" "$(printf '%s' "$flat" | grep -c 'tier A')" "1"
36
+ t "skips tier-B hook body" "$(printf '%s' "$flat" | grep -c 'guard$')" "0"
37
+ t "notes tier-B skip" "$(printf '%s' "$flat" | grep -c 'tier B')" "1"
38
+ t "keeps non-import prose" "$(printf '%s' "$flat" | grep -c 'My Project')" "1"
39
+
40
+ # dry-run writes nothing.
41
+ KIT_DRY_RUN=1 kit_export_all .kit-export knowledge >/dev/null 2>&1
42
+ t "dry-run writes no out dir" "$([ -d .kit-export ] && echo yes || echo no)" "no"
43
+
44
+ # real export: instructions + knowledge copy + matrix all present; knowledge content carried.
45
+ kit_export_all .kit-export knowledge >/dev/null 2>&1
46
+ t "instructions written" "$([ -f .kit-export/claude-instructions.md ] && echo yes || echo no)" "yes"
47
+ t "matrix written" "$([ -f .kit-export/SUPPORT-MATRIX.md ] && echo yes || echo no)" "yes"
48
+ t "knowledge copied" "$([ -f .kit-export/project-knowledge/knowledge/doc.md ] && echo yes || echo no)" "yes"
49
+ t "tier-A agents copied" "$([ -f .kit-export/project-knowledge/agents/pm.md ] && echo yes || echo no)" "yes"
50
+ t "tier-B hook NOT in instructions" "$(grep -c 'guard$' .kit-export/claude-instructions.md)" "0"
51
+
52
+ # matrix has the three surfaces.
53
+ m="$(kit_export_matrix)"
54
+ t "matrix names claude.ai" "$(printf '%s' "$m" | grep -qi 'claude.ai' && echo yes || echo no)" "yes"
55
+ t "matrix names Cowork" "$(printf '%s' "$m" | grep -qi 'Cowork' && echo yes || echo no)" "yes"
56
+ t "matrix names Terminal" "$(printf '%s' "$m" | grep -qi 'Terminal' && echo yes || echo no)" "yes"
57
+
58
+ # Now make the hook tier-A via the manifest: --verify should pass (portable).
59
+ kit_manifest_record .claude/rules/branch-naming.md A wire >/dev/null 2>&1
60
+ kit_manifest_record .claude/hooks/guard.sh A wire >/dev/null 2>&1 # force A to simulate a portable file
61
+ t "manifest tier wins over heuristic" "$(kit_export_tier .claude/hooks/guard.sh)" "A"
62
+ kit_export_verify CLAUDE.md >/dev/null 2>&1
63
+ t "verify passes when all imports tier-A (rc 0)" "$?" "0"
64
+
65
+ # A missing import is a defect on every surface.
66
+ printf '\n@.claude/rules/gone.md\n' >> CLAUDE.md
67
+ kit_export_verify CLAUDE.md >/dev/null 2>&1
68
+ t "verify flags a missing import (rc 1)" "$?" "1"
69
+
70
+ [ "$fail" -eq 0 ] && echo "ALL OK($KIT_EXPORT_TEST_INNER)"
71
+ exit "$fail"
72
+ fi
73
+
74
+ rc=0; ran=0
75
+ for sh in bash zsh; do
76
+ command -v "$sh" >/dev/null 2>&1 || continue
77
+ ran=$((ran+1)); echo "--- $sh ---"
78
+ rcflag=""; [ "$sh" = "zsh" ] && rcflag="--no-rcs"
79
+ KIT_EXPORT_TEST_INNER="$sh" PATH="$PATH" PLUGIN_DIR="$PLUGIN_DIR" "$sh" $rcflag "$0" || rc=1
80
+ done
81
+ [ "$ran" -eq 0 ] && { echo "no shell"; exit 1; }
82
+ exit "$rc"
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env bash
2
+ # kit-export-project.sh — export a kit project to the two non-terminal Claude surfaces (#376).
3
+ #
4
+ # THE GOAL (the acceptance gate): the SAME project runs in three places with NO hand edits —
5
+ # 1. terminal — Claude Code reads CLAUDE.md + .claude/ natively (the home surface).
6
+ # 2. Cowork — Claude Code in the cloud; reads the same CLAUDE.md + .claude/ tree natively.
7
+ # 3. claude.ai — a Project has TWO slots (no .claude/ tree): "custom instructions" (one text box)
8
+ # + "project knowledge" (uploaded files). It cannot run scripts.
9
+ #
10
+ # Terminal + Cowork need NOTHING from this tool — they read the repo as-is. The only surface that
11
+ # needs a transform is claude.ai, because it has no filesystem: we FLATTEN CLAUDE.md (resolving its
12
+ # @-imports of .claude/rules/*) into ONE instructions file, and COPY the knowledge corpus into an
13
+ # upload-ready folder. We also VERIFY Cowork compat (every file the project leans on is tier-A
14
+ # portable, not a tier-B CLI-only shim) and print a support matrix.
15
+ #
16
+ # TIER MODEL (#373, recorded in .claude/kit.manifest.json):
17
+ # tier A = portable — skills/rules/agents/commands; meaningful in Cowork AND claude.ai.
18
+ # tier B = CLI-only — statusline.sh / settings.json / hooks/ / lib/ ; need a terminal+filesystem.
19
+ # The export carries ONLY tier-A semantics to claude.ai; a tier-B file the project REQUIRES to
20
+ # function is a portability defect we flag (it would silently no-op off-terminal).
21
+ #
22
+ # OUTPUT (default .kit-export/, override with --out DIR):
23
+ # claude-instructions.md the flattened CLAUDE.md + inlined @.claude/rules/* — paste into the
24
+ # claude.ai Project's "custom instructions" box.
25
+ # project-knowledge/ knowledge/** (+ agents/ + rules/ as reference) — upload as Project knowledge.
26
+ # SUPPORT-MATRIX.md terminal / Cowork / claude.ai feature support, generated from the manifest.
27
+ #
28
+ # Run:
29
+ # scripts/kit-export-project.sh # full export to .kit-export/
30
+ # scripts/kit-export-project.sh --out build/x # custom output dir
31
+ # scripts/kit-export-project.sh --verify # Cowork+claude.ai compat check only, write nothing (rc 1 on defect)
32
+ # scripts/kit-export-project.sh --matrix # print the support matrix to stdout, write nothing
33
+ # scripts/kit-export-project.sh --dry-run # report what it would write, write nothing
34
+ # Requires: jq + a sha256 tool (for tier lookups via the manifest).
35
+
36
+ set -uo pipefail
37
+ _export_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd)"
38
+ # shellcheck source=/dev/null
39
+ . "$_export_dir/lib/kit-cli.sh" # kit_is_main
40
+ # shellcheck source=/dev/null
41
+ . "$_export_dir/lib/kit-manifest.sh" # tier lookups
42
+
43
+ _kex_say() { printf '%s\n' "$*" >&2; }
44
+
45
+ # Tier-classify a kit-shaped path the same way kit-wire/kit-adopt do — manifest first (authoritative,
46
+ # carries the tier the file was wired with), heuristic fallback when the file isn't manifest-tracked
47
+ # (e.g. a hand-imported project that never ran kit-adopt).
48
+ kit_export_tier() {
49
+ local p="$1" t
50
+ t="$(kit_manifest_get "$p" 2>/dev/null | jq -r '.tier // empty' 2>/dev/null || true)"
51
+ if [ -n "$t" ]; then printf '%s\n' "$t"; return 0; fi
52
+ case "$p" in
53
+ .claude/statusline.sh|.claude/settings.json|.claude/settings.local.json|.claude/hooks/*|.claude/lib/*) printf 'B\n' ;;
54
+ *) printf 'A\n' ;;
55
+ esac
56
+ }
57
+
58
+ # Resolve a Claude `@path` import to a relpath. Claude Code imports are written `@.claude/rules/x.md`
59
+ # or `@./KIT.md`; strip the leading @ and any ./ — return empty for a non-import line.
60
+ _kex_import_path() {
61
+ case "$1" in
62
+ @*) printf '%s\n' "${1#@}" | sed 's#^\./##' ;;
63
+ *) printf '\n' ;;
64
+ esac
65
+ }
66
+
67
+ # Flatten CLAUDE.md into a single instructions document for claude.ai. We INLINE every @-import
68
+ # (the .claude/rules/* and any @./KIT.md) so the one text box carries the whole instruction set —
69
+ # claude.ai can't follow an @ to a file it doesn't have. Imports are inlined ONCE (dedup) and only
70
+ # tier-A files are inlined (a tier-B shim has no meaning without a terminal). Non-import lines pass
71
+ # through verbatim. Recurses one level into imported rule files (they rarely chain, but we guard a
72
+ # seen-set so a cycle can't loop).
73
+ kit_export_flatten_instructions() {
74
+ local claude_md="${1:-CLAUDE.md}"
75
+ [ -f "$claude_md" ] || { _kex_say "kit-export: $claude_md not found"; return 1; }
76
+
77
+ # seen-set for dedup/cycle-guard (newline-delimited paths already inlined)
78
+ local seen=""
79
+ _kex_emit_file() { # <relpath> <depth>
80
+ local f="$1" depth="$2" line ip tier
81
+ case "$seen" in *"|$f|"*) return 0 ;; esac
82
+ seen="$seen|$f|"
83
+ while IFS= read -r line || [ -n "$line" ]; do
84
+ ip="$(_kex_import_path "$line")"
85
+ if [ -n "$ip" ] && [ -f "$ip" ] && [ "$depth" -lt 4 ]; then
86
+ tier="$(kit_export_tier "$ip")"
87
+ if [ "$tier" = "A" ]; then
88
+ printf '\n<!-- inlined from %s (tier A) -->\n' "$ip"
89
+ _kex_emit_file "$ip" $((depth + 1))
90
+ else
91
+ printf '\n<!-- skipped %s (tier B — CLI-only, no meaning on claude.ai) -->\n' "$ip"
92
+ fi
93
+ else
94
+ printf '%s\n' "$line"
95
+ fi
96
+ done < "$f"
97
+ }
98
+
99
+ printf '<!-- Generated by kit-export-project (#376) — paste into the claude.ai Project custom-instructions box. -->\n'
100
+ printf '<!-- Source: %s + inlined @.claude/rules/* (tier-A). Terminal/Cowork read the repo directly. -->\n\n' "$claude_md"
101
+ _kex_emit_file "$claude_md" 0
102
+ }
103
+
104
+ # Cowork + claude.ai compat verdict. Cowork runs Claude Code natively, so the question is: does the
105
+ # project FUNCTION when only the portable (tier-A) surface is honored? Any file CLAUDE.md imports
106
+ # that is tier-B is a portability defect (it silently no-ops off-terminal). rc 0 = portable, 1 = defect.
107
+ kit_export_verify() {
108
+ local claude_md="${1:-CLAUDE.md}" defects=0 line ip tier
109
+ if [ ! -f "$claude_md" ]; then _kex_say "kit-export --verify: $claude_md not found"; return 1; fi
110
+ _kex_say "kit-export --verify: checking that the portable surface is self-sufficient..."
111
+ while IFS= read -r line || [ -n "$line" ]; do
112
+ ip="$(_kex_import_path "$line")"
113
+ [ -n "$ip" ] || continue
114
+ if [ ! -f "$ip" ]; then
115
+ _kex_say " ! CLAUDE.md imports $ip but it is MISSING — broken on every surface"
116
+ defects=$((defects + 1)); continue
117
+ fi
118
+ tier="$(kit_export_tier "$ip")"
119
+ if [ "$tier" = "B" ]; then
120
+ _kex_say " ! CLAUDE.md requires $ip (tier B / CLI-only) — silently no-ops in Cowork/claude.ai"
121
+ defects=$((defects + 1))
122
+ else
123
+ _kex_say " ok $ip (tier A — portable)"
124
+ fi
125
+ done < "$claude_md"
126
+ if [ "$defects" -eq 0 ]; then
127
+ _kex_say "kit-export --verify: PORTABLE — the same project runs in terminal / Cowork / claude.ai with no hand edits."
128
+ return 0
129
+ fi
130
+ _kex_say "kit-export --verify: $defects portability defect(s) — fix before relying on the non-terminal surfaces."
131
+ return 1
132
+ }
133
+
134
+ # The support matrix — what each surface can do, generated from the manifest tiers present.
135
+ kit_export_matrix() {
136
+ local a_count b_count
137
+ a_count="$(kit_manifest_list A 2>/dev/null | grep -c . || true)"
138
+ b_count="$(kit_manifest_list B 2>/dev/null | grep -c . || true)"
139
+ cat <<MATRIX
140
+ # claude-kit surface support matrix
141
+
142
+ Generated by kit-export-project (#376). The kit project runs on three Claude surfaces; this table
143
+ records what each supports and how the project gets there.
144
+
145
+ | Capability | Terminal (Claude Code) | Cowork (cloud Claude Code) | claude.ai Project |
146
+ | ----------------------------------- | :--------------------: | :------------------------: | :---------------: |
147
+ | Reads CLAUDE.md + .claude/ natively | yes | yes | no (no FS) |
148
+ | Tier-A skills / rules / agents | yes | yes | via export* |
149
+ | Tier-B statusline / hooks / settings| yes | partial** | no |
150
+ | Runs \`kit\` / \`scripts/*\` verbs | yes | yes | no (can't shell) |
151
+ | GitHub lifecycle (gh, worktrees) | yes | yes | no |
152
+ | Knowledge corpus available | in repo | in repo | via export* |
153
+
154
+ \* claude.ai has no filesystem: run \`kit-export-project\` → paste \`claude-instructions.md\` into the
155
+ Project custom-instructions box and upload \`project-knowledge/\` as Project knowledge.
156
+ \*\* Cowork runs Claude Code but a shim like the statusline / a guard hook may be inert in that
157
+ environment; the PROJECT still functions because tier-B files are conveniences, not requirements
158
+ (verified by \`kit-export-project --verify\`).
159
+
160
+ ## This project's manifest tiering
161
+ - Tier A (portable): ${a_count} file(s)
162
+ - Tier B (CLI-only): ${b_count} file(s)
163
+
164
+ Tier A = portable to Cowork AND claude.ai. Tier B = needs a terminal + filesystem. The export
165
+ carries only tier-A semantics off-terminal; a tier-B file the project *requires* is a defect
166
+ \`--verify\` flags.
167
+ MATRIX
168
+ }
169
+
170
+ # Copy the knowledge corpus + portable reference files into an upload-ready folder for claude.ai.
171
+ # Honors KIT_DRY_RUN. Knowledge dir is config-resolved (defaults to knowledge/).
172
+ kit_export_knowledge() {
173
+ local out="$1" kdir="${2:-knowledge}" copied=0
174
+ local dest="$out/project-knowledge"
175
+ if [ -n "${KIT_DRY_RUN:-}" ]; then
176
+ [ -d "$kdir" ] && _kex_say " [dry-run] would copy $kdir/** -> $dest/"
177
+ _kex_say " [dry-run] would copy .claude/rules/ + .claude/agents/ (tier-A reference) -> $dest/"
178
+ return 0
179
+ fi
180
+ mkdir -p "$dest"
181
+ if [ -d "$kdir" ]; then
182
+ # -R preserves the knowledge/ tree under the dest; portable on bash + zsh, macOS + linux cp.
183
+ cp -R "$kdir" "$dest/" && copied=1
184
+ fi
185
+ # Carry tier-A reference material (rules + agents) so a claude.ai Project has the same context.
186
+ [ -d .claude/rules ] && cp -R .claude/rules "$dest/" 2>/dev/null && copied=1
187
+ [ -d .claude/agents ] && cp -R .claude/agents "$dest/" 2>/dev/null && copied=1
188
+ if [ "$copied" -eq 1 ]; then
189
+ _kex_say " knowledge + tier-A reference copied -> $dest/"
190
+ else
191
+ _kex_say " (no knowledge/ or tier-A reference found to copy)"
192
+ fi
193
+ }
194
+
195
+ # Full export: instructions + knowledge + matrix into OUT.
196
+ kit_export_all() {
197
+ local out="$1" kdir="${2:-knowledge}"
198
+ command -v jq >/dev/null 2>&1 || { _kex_say "kit-export: jq required"; return 1; }
199
+ [ -f CLAUDE.md ] || { _kex_say "kit-export: no CLAUDE.md here — run from the project root (or /kit-init first)."; return 1; }
200
+
201
+ _kex_say "kit-export: exporting to $out/ (terminal/Cowork read the repo directly; this is for claude.ai)"
202
+
203
+ if [ -n "${KIT_DRY_RUN:-}" ]; then
204
+ _kex_say " [dry-run] would write $out/claude-instructions.md (flattened CLAUDE.md + tier-A rules)"
205
+ kit_export_knowledge "$out" "$kdir"
206
+ _kex_say " [dry-run] would write $out/SUPPORT-MATRIX.md"
207
+ return 0
208
+ fi
209
+
210
+ mkdir -p "$out"
211
+ kit_export_flatten_instructions CLAUDE.md > "$out/claude-instructions.md" \
212
+ && _kex_say " wrote $out/claude-instructions.md"
213
+ kit_export_knowledge "$out" "$kdir"
214
+ kit_export_matrix > "$out/SUPPORT-MATRIX.md" && _kex_say " wrote $out/SUPPORT-MATRIX.md"
215
+
216
+ _kex_say "kit-export: done."
217
+ _kex_say " -> paste $out/claude-instructions.md into the claude.ai Project custom-instructions box"
218
+ _kex_say " -> upload $out/project-knowledge/ as the Project's knowledge"
219
+ # A portability nudge: report (don't fail) if the verify check finds defects.
220
+ kit_export_verify CLAUDE.md >/dev/null 2>&1 || _kex_say " note: run --verify — the portable surface has defect(s)."
221
+ }
222
+
223
+ # CLI (direct execution only; sourcing exposes the functions for tests)
224
+ if kit_is_main; then
225
+ _mode=export _out=".kit-export" _kdir="knowledge"
226
+ # config-resolve the knowledge dir if available (kit-config.sh ships alongside in scripts/lib)
227
+ if [ -f "$_export_dir/lib/kit-config.sh" ]; then
228
+ # shellcheck source=/dev/null
229
+ . "$_export_dir/lib/kit-config.sh"
230
+ load_kit_config >/dev/null 2>&1 && _kdir="${KIT_KNOWLEDGE_DIR:-knowledge}" || true
231
+ fi
232
+ while [ $# -gt 0 ]; do case "$1" in
233
+ --out) _out="${2:?--out needs a dir}"; shift 2;;
234
+ --verify) _mode=verify; shift;;
235
+ --matrix) _mode=matrix; shift;;
236
+ --dry-run) export KIT_DRY_RUN=1; shift;;
237
+ -h|--help) sed -n '/^# Run:/,/^# Requires:/p' "$0" | sed 's/^# \{0,1\}//'; exit 0;;
238
+ *) echo "unknown arg: $1" >&2; exit 2;;
239
+ esac; done
240
+ case "$_mode" in
241
+ verify) kit_export_verify CLAUDE.md;;
242
+ matrix) kit_export_matrix;;
243
+ *) kit_export_all "$_out" "$_kdir";;
244
+ esac
245
+ fi
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bash
2
+ # kit-export-training-test.sh — self-test for kit-export-training (#881). Runs under bash AND zsh.
3
+ # Proves the dataset-builder path is config-driven (NOT hardcoded): --builder > env > sibling discovery.
4
+ # Run: bash scripts/kit-export-training-test.sh
5
+ PLUGIN_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
6
+
7
+ if [ -n "${KIT_XT_TEST_INNER:-}" ]; then
8
+ set -u
9
+ fail=0
10
+ t() { if [ "$2" != "$3" ]; then echo "FAIL($KIT_XT_TEST_INNER): $1 -> got '[$2]' want '[$3]'"; fail=1; else echo "ok($KIT_XT_TEST_INNER): $1"; fi; }
11
+
12
+ # Source the script: exposes functions, never runs main (verified by the source guard).
13
+ . "$PLUGIN_DIR/scripts/kit-export-training.sh"
14
+ t "resolver is defined" "$(command -v _kxt_resolve_builder >/dev/null && echo yes || echo no)" "yes"
15
+
16
+ # 1) explicit --builder value wins over everything.
17
+ KIT_DATASET_BUILDER="/env/build_dataset.py" \
18
+ t "explicit --builder wins" "$(_kxt_resolve_builder /explicit/build_dataset.py)" "/explicit/build_dataset.py"
19
+
20
+ # 2) KIT_DATASET_BUILDER env is used when no explicit value.
21
+ t "env var used when no flag" "$(KIT_DATASET_BUILDER=/env/build_dataset.py _kxt_resolve_builder '')" "/env/build_dataset.py"
22
+
23
+ # 3) sibling-repo discovery: a chat-datasets/build_dataset.py beside the git repo root is found,
24
+ # with NO hardcoded user path. Build a fake parent dir holding a repo + a sibling builder.
25
+ parent="$(mktemp -d)"; trap 'rm -rf "$parent"' EXIT
26
+ parent="$(cd "$parent" && pwd -P)" # canonicalize (macOS /var -> /private/var) to match git rev-parse
27
+ mkdir -p "$parent/myrepo" "$parent/chat-datasets"
28
+ ( cd "$parent/myrepo" && git init -q )
29
+ printf '#!/usr/bin/env python3\n' > "$parent/chat-datasets/build_dataset.py"
30
+ got="$(cd "$parent/myrepo" && unset KIT_DATASET_BUILDER 2>/dev/null; _kxt_resolve_builder '')"
31
+ t "sibling discovery off repo root" "$got" "$parent/chat-datasets/build_dataset.py"
32
+
33
+ # 4) returns non-zero when nothing resolves (no flag, no env, no sibling).
34
+ empty="$(mktemp -d)"; mkdir -p "$empty/solo"; ( cd "$empty/solo" && git init -q )
35
+ ( cd "$empty/solo" && unset KIT_DATASET_BUILDER 2>/dev/null; _kxt_resolve_builder '' >/dev/null 2>&1 )
36
+ t "fails closed when unresolved (rc 1)" "$?" "1"
37
+ rm -rf "$empty"
38
+
39
+ [ "$fail" -eq 0 ] && echo "ALL OK($KIT_XT_TEST_INNER)"
40
+ exit "$fail"
41
+ fi
42
+
43
+ rc=0; ran=0
44
+ for sh in bash zsh; do
45
+ command -v "$sh" >/dev/null 2>&1 || continue
46
+ ran=$((ran+1)); echo "--- $sh ---"
47
+ rcflag=""; [ "$sh" = "zsh" ] && rcflag="--no-rcs"
48
+ KIT_XT_TEST_INNER="$sh" PATH="$PATH" PLUGIN_DIR="$PLUGIN_DIR" "$sh" $rcflag "$0" || rc=1
49
+ done
50
+ [ "$ran" -eq 0 ] && { echo "no shell"; exit 1; }
51
+ exit "$rc"
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env bash
2
+ # kit-export-training.sh — OPT-IN export of your Claude Code sessions to redacted training JSONL.
3
+ #
4
+ # Fine-tuning a local model on your own work only helps if the data leaves the machine clean. This
5
+ # handler is the consent gate in front of a dataset builder (chat-datasets/build_dataset.py): it
6
+ # lets a builder pick which sessions to export, ALWAYS runs the builder with redaction ON
7
+ # (secret/key/email masking is the builder's default — there is deliberately NO --no-redact
8
+ # passthrough here), and prints exactly where it wrote plus a reminder that redaction must be
9
+ # verified before sharing.
10
+ #
11
+ # Nothing here is automatic — it runs only when explicitly invoked, and it never uploads anything.
12
+ # The output is plain JSONL on disk for the training pipeline; what you do with it is your call.
13
+ #
14
+ # Run:
15
+ # scripts/kit-export-training.sh [--builder PATH] [--match KEY ...] [--dirs DIR ...]
16
+ # [--sessions FILE ...] [--out PATH] [--split FRAC]
17
+ # [--final-only] [--drop-narration]
18
+ # Selection (pick one; default = the current git project, matched by its slug):
19
+ # --match KEY ... project-dir name substrings under ~/.claude/projects (e.g. my-app acme)
20
+ # --dirs DIR ... explicit ~/.claude/projects/<dir> session directories
21
+ # --sessions FILE ... individual *.jsonl transcript files to export (symlinked into a temp dir)
22
+ # Options:
23
+ # --builder PATH path to build_dataset.py (overrides KIT_DATASET_BUILDER + auto-discovery)
24
+ # --out PATH output JSONL (default: ~/.claude/exports/<slug>-training.jsonl)
25
+ # --split FRAC also emit train.jsonl/valid.jsonl with FRAC held out (e.g. 0.1)
26
+ # --final-only keep only the last assistant message per turn
27
+ # --drop-narration drop short process-narration openers
28
+ # Env:
29
+ # KIT_DATASET_BUILDER path to build_dataset.py (used when --builder is not given)
30
+ # KIT_PROJECTS_ROOT sessions root (default: ~/.claude/projects)
31
+ # KIT_ASSUME_YES skip the final confirm (for batch/CI).
32
+ #
33
+ # Builder discovery (config-driven, NO hardcoded user path): --builder > KIT_DATASET_BUILDER >
34
+ # a sibling `chat-datasets/build_dataset.py` next to the git repo root (then $PWD). If none resolve,
35
+ # the script stops and tells you to pass --builder or set KIT_DATASET_BUILDER.
36
+ #
37
+ # Redaction is ALWAYS on here — the builder masks by default and this script never passes --no-redact.
38
+ set -uo pipefail
39
+
40
+ # ---- locate self + optional shared cli helpers ----------------------------
41
+ _kxt_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd)"
42
+ # shellcheck source=/dev/null
43
+ [ -f "$_kxt_dir/lib/kit-cli.sh" ] && . "$_kxt_dir/lib/kit-cli.sh" # provides kit_is_main (optional)
44
+
45
+ PROJECTS_ROOT="${KIT_PROJECTS_ROOT:-$HOME/.claude/projects}"
46
+
47
+ # Staged-sessions temp dir, cleaned on exit. Script-global so the EXIT trap can read it
48
+ # after _kxt_main returns (a function-local would be out of scope under `set -u`).
49
+ _KXT_TMP_SEL=""
50
+ _kxt_cleanup() { [ -n "${_KXT_TMP_SEL:-}" ] && rm -rf "$_KXT_TMP_SEL"; return 0; }
51
+
52
+ # ---- minimal logging ------------------------------------------------------
53
+ if [ -t 2 ]; then _AZ=$'\033[38;2;201;122;44m'; _RD=$'\033[38;2;194;87;56m'; _GR=$'\033[38;2;120;160;90m'; _DM=$'\033[2m'; _RS=$'\033[0m'
54
+ else _AZ=''; _RD=''; _GR=''; _DM=''; _RS=''; fi
55
+ _kxt_say() { printf '%s->%s %s\n' "$_AZ" "$_RS" "$*" >&2; }
56
+ _kxt_warn() { printf '%s!%s %s\n' "$_RD" "$_RS" "$*" >&2; }
57
+ _kxt_err() { printf '%sx%s %s\n' "$_RD" "$_RS" "$*" >&2; }
58
+ _kxt_die() { _kxt_err "$*"; exit 1; }
59
+
60
+ # ---- builder resolution (config-driven, generalized off any one machine) ---
61
+ # $1 = explicit --builder value (may be empty). Priority: explicit > env > sibling discovery.
62
+ _kxt_resolve_builder() {
63
+ local explicit="${1:-}"
64
+ [ -n "$explicit" ] && { printf '%s\n' "$explicit"; return 0; }
65
+ [ -n "${KIT_DATASET_BUILDER:-}" ] && { printf '%s\n' "$KIT_DATASET_BUILDER"; return 0; }
66
+ local rel="chat-datasets/build_dataset.py" root="" rootp="" base="" cand=""
67
+ root="$(git -C "$PWD" rev-parse --show-toplevel 2>/dev/null || true)"
68
+ [ -n "$root" ] && rootp="$(dirname "$root")"
69
+ for base in "$rootp" "$PWD" "$(dirname "$PWD")"; do
70
+ [ -n "$base" ] || continue
71
+ cand="$base/$rel"
72
+ [ -f "$cand" ] && { printf '%s\n' "$cand"; return 0; }
73
+ done
74
+ return 1
75
+ }
76
+
77
+ # ---- main (direct execution only; sourcing exposes the functions for tests) ---
78
+ _kxt_main() {
79
+ local BUILDER_FLAG="" OUT="" SPLIT="" SLUG="export" BUILDER="" _root=""
80
+ local -a MATCH=() DIRS=() SESSIONS=() EXTRA=() SEL=() SPLIT_ARGS=()
81
+ while [ $# -gt 0 ]; do case "$1" in
82
+ --builder) BUILDER_FLAG="${2:-}"; shift 2 ;;
83
+ --match) shift; while [ $# -gt 0 ] && [ "${1#--}" = "$1" ]; do MATCH+=("$1"); shift; done ;;
84
+ --dirs) shift; while [ $# -gt 0 ] && [ "${1#--}" = "$1" ]; do DIRS+=("$1"); shift; done ;;
85
+ --sessions) shift; while [ $# -gt 0 ] && [ "${1#--}" = "$1" ]; do SESSIONS+=("$1"); shift; done ;;
86
+ --out) OUT="${2:-}"; shift 2 ;;
87
+ --split) SPLIT="${2:-}"; shift 2 ;;
88
+ --final-only) EXTRA+=(--final-only); shift ;;
89
+ --drop-narration) EXTRA+=(--drop-narration); shift ;;
90
+ -h|--help) sed -n '13,33p' "$0" | sed 's/^# \{0,1\}//'; return 0 ;;
91
+ *) _kxt_die "unknown flag: $1" ;;
92
+ esac; done
93
+
94
+ # ---- preflight ----------------------------------------------------------
95
+ command -v python3 >/dev/null 2>&1 || _kxt_die "python3 is required."
96
+ BUILDER="$(_kxt_resolve_builder "$BUILDER_FLAG")" || _kxt_die \
97
+ "dataset builder not found — pass --builder PATH or set KIT_DATASET_BUILDER (looked for chat-datasets/build_dataset.py beside the repo root)."
98
+ [ -f "$BUILDER" ] || _kxt_die "dataset builder not found: $BUILDER"
99
+
100
+ # ---- resolve a default selection from the current git project -----------
101
+ if [ ${#MATCH[@]} -eq 0 ] && [ ${#DIRS[@]} -eq 0 ] && [ ${#SESSIONS[@]} -eq 0 ]; then
102
+ _root="$(git -C "$PWD" rev-parse --show-toplevel 2>/dev/null || true)"
103
+ [ -n "$_root" ] || _kxt_die "no selection given and not inside a git repo — pass --match/--dirs/--sessions."
104
+ SLUG="$(basename "$_root" | tr '[:upper:]' '[:lower:]')"
105
+ MATCH=("$SLUG")
106
+ _kxt_say "no selection given — defaulting to the current project (match: $SLUG)"
107
+ elif [ ${#MATCH[@]} -gt 0 ]; then
108
+ SLUG="$(printf '%s' "${MATCH[0]}" | tr '[:upper:]' '[:lower:]')"
109
+ fi
110
+
111
+ # ---- default output path ------------------------------------------------
112
+ if [ -z "$OUT" ]; then
113
+ mkdir -p "$HOME/.claude/exports"
114
+ OUT="$HOME/.claude/exports/${SLUG}-training.jsonl"
115
+ fi
116
+ mkdir -p "$(dirname "$OUT")"
117
+
118
+ # ---- build the selection args for the builder ---------------------------
119
+ # --sessions: stage the chosen transcript files into a temp dir and pass via --dirs,
120
+ # since build_dataset.py selects at directory granularity.
121
+ local f=""
122
+ trap _kxt_cleanup EXIT
123
+
124
+ if [ ${#SESSIONS[@]} -gt 0 ]; then
125
+ _KXT_TMP_SEL="$(mktemp -d)"
126
+ for f in "${SESSIONS[@]}"; do
127
+ [ -f "$f" ] || _kxt_die "session file not found: $f"
128
+ ln -s "$(cd "$(dirname "$f")" && pwd)/$(basename "$f")" "$_KXT_TMP_SEL/$(basename "$f")"
129
+ done
130
+ SEL=(--dirs "$_KXT_TMP_SEL")
131
+ _kxt_say "exporting ${#SESSIONS[@]} selected session file(s)"
132
+ elif [ ${#DIRS[@]} -gt 0 ]; then
133
+ SEL=(--dirs "${DIRS[@]}")
134
+ _kxt_say "exporting from ${#DIRS[@]} session director(ies)"
135
+ else
136
+ SEL=(--match "${MATCH[@]}")
137
+ _kxt_say "exporting sessions matching: ${MATCH[*]}"
138
+ fi
139
+
140
+ [ -d "$PROJECTS_ROOT" ] || _kxt_warn "sessions root $PROJECTS_ROOT not found — the export may be empty."
141
+
142
+ # ---- confirm (opt-in) ---------------------------------------------------
143
+ if [ -z "${KIT_ASSUME_YES:-}" ] && [ -t 0 ]; then
144
+ printf '%sExport redacted training JSONL to %s ? [y/N] %s' "$_DM" "$OUT" "$_RS" >&2
145
+ local _c=""; read -r _c < /dev/tty || true
146
+ case "$_c" in y|Y|yes|YES) ;; *) _kxt_die "aborted — nothing was written." ;; esac
147
+ fi
148
+
149
+ # ---- run the builder with redaction ON (always) -------------------------
150
+ [ -n "$SPLIT" ] && SPLIT_ARGS=(--split "$SPLIT")
151
+ _kxt_say "running build_dataset.py (redaction ON): $BUILDER"
152
+ python3 "$BUILDER" "${SEL[@]}" --out "$OUT" ${EXTRA[@]+"${EXTRA[@]}"} ${SPLIT_ARGS[@]+"${SPLIT_ARGS[@]}"} \
153
+ || _kxt_die "build_dataset.py failed — nothing trustworthy was written."
154
+
155
+ # ---- report -------------------------------------------------------------
156
+ local STATS="${OUT%.jsonl}.stats.json"
157
+ printf '\n' >&2
158
+ _kxt_say "Wrote training data:"
159
+ printf ' %sJSONL:%s %s\n' "$_GR" "$_RS" "$OUT" >&2
160
+ [ -f "$STATS" ] && printf ' %sstats:%s %s\n' "$_GR" "$_RS" "$STATS" >&2
161
+ if [ -n "$SPLIT" ]; then
162
+ printf ' %ssplit:%s %s/train.jsonl + valid.jsonl\n' "$_GR" "$_RS" "$(dirname "$OUT")" >&2
163
+ fi
164
+ printf '\n' >&2
165
+ _kxt_warn "Redaction was applied automatically, but it is NOT a guarantee."
166
+ _kxt_warn "VERIFY the output for any leftover secrets, tokens, private names, or customer data"
167
+ _kxt_warn "BEFORE sharing it or feeding it into the training pipeline."
168
+ }
169
+
170
+ # Run only on direct execution; `source`-ing the file just defines the functions (for tests).
171
+ if command -v kit_is_main >/dev/null 2>&1; then
172
+ kit_is_main && _kxt_main "$@"
173
+ else
174
+ [ "${BASH_SOURCE[0]:-$0}" = "${0}" ] && _kxt_main "$@"
175
+ fi
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env bash
2
+ # kit-migrate-test.sh — self-test for kit-migrate (#373). Runs under bash AND zsh.
3
+ # Asserts the acceptance gate: migrate twice = no-op; old paths registered (survives /kit-update);
4
+ # manifest re-keyed; docs rewritten; .claude/ backed up.
5
+ # Run: bash scripts/kit-migrate-test.sh
6
+ PLUGIN_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
7
+
8
+ if [ -n "${KIT_MIGRATE_TEST_INNER:-}" ]; then
9
+ set -u
10
+ fail=0
11
+ t() { if [ "$2" != "$3" ]; then echo "FAIL($KIT_MIGRATE_TEST_INNER): $1 -> got '[$2]' want '[$3]'"; fail=1; else echo "ok($KIT_MIGRATE_TEST_INNER): $1"; fi; }
12
+
13
+ proj="$(mktemp -d)"; trap 'rm -rf "$proj"' EXIT
14
+ cd "$proj"
15
+ export KIT_ASSUME_YES=1
16
+ . "$PLUGIN_DIR/scripts/kit-migrate.sh"
17
+ . "$PLUGIN_DIR/scripts/lib/kit-manifest.sh"
18
+
19
+ # Old-layout project: task-* skills, a CLAUDE.md referencing /task-pr, a manifest tracking the old
20
+ # skill path, a kit.config.json with no upgrade map yet.
21
+ mkdir -p .claude/skills/task-pr .claude/skills/task-gc .claude/rules
22
+ printf 'pr skill body\n' > .claude/skills/task-pr/SKILL.md
23
+ printf 'gc skill body\n' > .claude/skills/task-gc/SKILL.md
24
+ printf 'Run /task-pr then /task-pr-merge; sweep with /task-gc.\n' > CLAUDE.md
25
+ printf '{ "schema":1 }\n' > .claude/kit.config.json
26
+ kit_manifest_record .claude/skills/task-pr/SKILL.md A wire 1 2026-01-01T00:00:00Z
27
+
28
+ # --- run 1: the real migration ---
29
+ kit_migrate_all >/dev/null 2>&1
30
+
31
+ t "old task-pr gone" "$([ -e .claude/skills/task-pr ] && echo yes || echo no)" "no"
32
+ t "new kit-task-pr present" "$([ -f .claude/skills/kit-task-pr/SKILL.md ] && echo yes || echo no)" "yes"
33
+ t "content preserved" "$(cat .claude/skills/kit-task-pr/SKILL.md)" "pr skill body"
34
+ t "task-gc -> kit-gc" "$([ -f .claude/skills/kit-gc/SKILL.md ] && echo yes || echo no)" "yes"
35
+ t "backup taken" "$(find . -maxdepth 1 -name ".claude.bak.*" 2>/dev/null | grep -c .)" "1"
36
+
37
+ # resurrection guard: old path registered in upgrade.renamed{} -> /kit-update skips it.
38
+ t "rename registered" "$(jq -r '.upgrade.renamed[".claude/skills/task-pr"] // "MISS"' .claude/kit.config.json)" ".claude/skills/kit-task-pr"
39
+ t "gc rename registered" "$(jq -r '.upgrade.renamed[".claude/skills/task-gc"] // "MISS"' .claude/kit.config.json)" ".claude/skills/kit-gc"
40
+
41
+ # manifest re-keyed to the v2 path.
42
+ t "manifest re-keyed" "$(kit_manifest_verify .claude/skills/kit-task-pr/SKILL.md)" "intact"
43
+ t "old manifest entry dropped" "$(kit_manifest_verify .claude/skills/task-pr/SKILL.md)" "untracked"
44
+
45
+ # CLAUDE.md surgery: /task-pr -> /kit-task-pr (no bare /task- left).
46
+ t "doc rewritten kit-task-pr" "$(grep -c '/kit-task-pr' CLAUDE.md)" "1"
47
+ t "doc rewritten kit-gc" "$(grep -c '/kit-gc' CLAUDE.md)" "1"
48
+ t "no bare /task- left" "$(grep -Ec '/task-(pr|gc|new)' CLAUDE.md)" "0"
49
+
50
+ # --- run 2: idempotency — migrate twice = no-op (nothing moves, no second backup) ---
51
+ out2="$(kit_migrate_all 2>&1)"
52
+ t "2nd run is no-op" "$(printf '%s' "$out2" | grep -c 'already on v2')" "1"
53
+ t "still ONE backup" "$(find . -maxdepth 1 -name ".claude.bak.*" 2>/dev/null | grep -c .)" "1"
54
+ t "kit-task-pr still there" "$([ -f .claude/skills/kit-task-pr/SKILL.md ] && echo yes || echo no)" "yes"
55
+ t "old still absent" "$([ -e .claude/skills/task-pr ] && echo yes || echo no)" "no"
56
+
57
+ # --- dry-run on a fresh old project moves nothing ---
58
+ proj2="$(mktemp -d)"; cd "$proj2"
59
+ mkdir -p .claude/skills/task-pr
60
+ printf 'x\n' > .claude/skills/task-pr/SKILL.md
61
+ printf '{ "schema":1 }\n' > .claude/kit.config.json
62
+ KIT_DRY_RUN=1 kit_migrate_all >/dev/null 2>&1
63
+ t "dry-run does not move" "$([ -e .claude/skills/task-pr ] && echo yes || echo no)" "yes"
64
+ t "dry-run no new dir" "$([ -e .claude/skills/kit-task-pr ] && echo yes || echo no)" "no"
65
+ t "dry-run no backup" "$(find . -maxdepth 1 -name ".claude.bak.*" 2>/dev/null | grep -c .)" "0"
66
+ t "dry-run no registry write" "$(jq -r '.upgrade // "NONE"' .claude/kit.config.json)" "NONE"
67
+
68
+ [ "$fail" -eq 0 ] && echo "ALL OK($KIT_MIGRATE_TEST_INNER)"
69
+ exit "$fail"
70
+ fi
71
+
72
+ rc=0; ran=0
73
+ for sh in bash zsh; do
74
+ command -v "$sh" >/dev/null 2>&1 || continue
75
+ ran=$((ran+1)); echo "--- $sh ---"
76
+ rcflag=""; [ "$sh" = "zsh" ] && rcflag="--no-rcs"
77
+ KIT_MIGRATE_TEST_INNER="$sh" PATH="$PATH" PLUGIN_DIR="$PLUGIN_DIR" "$sh" $rcflag "$0" || rc=1
78
+ done
79
+ [ "$ran" -eq 0 ] && { echo "no shell"; exit 1; }
80
+ exit "$rc"