@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,24 @@
1
+ #!/usr/bin/env bash
2
+ # install.sh — put cckit on your PATH. Symlinks bin/cckit into a bin dir (no copy, no build).
3
+ # Usage: ./scripts/install.sh [target-bin-dir] (default: ~/.local/bin, then /usr/local/bin)
4
+ set -euo pipefail
5
+
6
+ root="$(cd "$(dirname "$0")/.." && pwd)"
7
+ src="$root/bin/cckit"
8
+ [ -x "$src" ] || { echo "cckit: $src not found or not executable" >&2; exit 1; }
9
+
10
+ # Pick a writable bin dir on PATH.
11
+ target="${1:-}"
12
+ if [ -z "$target" ]; then
13
+ for d in "$HOME/.local/bin" "/usr/local/bin" "$HOME/bin"; do
14
+ if [ -d "$d" ] && [ -w "$d" ]; then target="$d"; break; fi
15
+ done
16
+ [ -z "$target" ] && { mkdir -p "$HOME/.local/bin"; target="$HOME/.local/bin"; }
17
+ fi
18
+
19
+ ln -sf "$src" "$target/cckit"
20
+ echo "cckit: linked $target/cckit -> $src"
21
+ case ":$PATH:" in
22
+ *":$target:"*) echo "cckit: '$target' is on your PATH. Run: cckit help" ;;
23
+ *) echo "cckit: add '$target' to your PATH, then run: cckit help" ;;
24
+ esac
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env bash
2
+ # kit-add-test.sh — self-test for kit-add module stacking (#372). Runs under bash AND zsh.
3
+ # Run: bash scripts/kit-add-test.sh
4
+ dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
5
+
6
+ if [ -n "${KIT_ADD_TEST_INNER:-}" ]; then
7
+ set -u
8
+ fail=0
9
+ t() { if [ "$2" != "$3" ]; then echo "FAIL($KIT_ADD_TEST_INNER): $1 -> got '[$2]' want '[$3]'"; fail=1; else echo "ok($KIT_ADD_TEST_INNER): $1"; fi; }
10
+ ADD="$dir/kit-add.sh"
11
+
12
+ export KIT_PROFILE_HOME="$(mktemp -d)"
13
+ export KIT_ASSUME_YES=1 KIT_VERSION=9.9.9
14
+ ws="$(mktemp -d)"; trap 'rm -rf "$KIT_PROFILE_HOME" "$ws"' EXIT
15
+ mkdir -p "$ws/myapp"
16
+ printf '%s\n' '{"workspace":{"name":"ws"},"mode":"guided"}' > "$ws/kit.workspace.json"
17
+ parent_before="$(cat "$ws/kit.workspace.json")"
18
+
19
+ # unknown module fails
20
+ "$ADD" doesnotexist --dir "$ws/myapp" >/dev/null 2>&1; t "unknown module rc" "$?" "1"
21
+
22
+ # add software (defaults) — island config created
23
+ "$ADD" software --dir "$ws/myapp" >/dev/null 2>&1; t "add rc" "$?" "0"
24
+ cfg="$ws/myapp/.claude/kit.config.json"
25
+ t "island created" "$([ -f "$cfg" ] && echo yes)" "yes"
26
+ t "modules" "$(jq -c .modules "$cfg")" '["software"]'
27
+ t "github wizard" "$(jq -r .github.projectsV2 "$cfg")" "true"
28
+ t "ci wizard" "$(jq -r .ci.provider "$cfg")" "github-actions"
29
+ t "manifest tracks" "$(jq -r '.entries["'.claude/kit.config.json'"] | has("hash")' "$ws/myapp/.claude/kit.manifest.json")" "true"
30
+
31
+ # parent workspace untouched
32
+ t "parent untouched" "$(cat "$ws/kit.workspace.json")" "$parent_before"
33
+
34
+ # idempotent: a 2nd identical run leaves the file byte-identical
35
+ before="$(cat "$cfg")"
36
+ "$ADD" software --dir "$ws/myapp" >/dev/null 2>&1
37
+ t "idempotent file" "$(cat "$cfg")" "$before"
38
+
39
+ # --set override changes only that field; modules stay deduped
40
+ "$ADD" software --dir "$ws/myapp" --set deploy=vercel >/dev/null 2>&1
41
+ t "override deploy" "$(jq -r .deploy.target "$cfg")" "vercel"
42
+ t "modules still one" "$(jq -c .modules "$cfg")" '["software"]'
43
+
44
+ # dry-run writes nothing new
45
+ fresh="$(mktemp -d)"; mkdir -p "$fresh/app"
46
+ KIT_DRY_RUN=1 "$ADD" software --dir "$fresh/app" >/dev/null 2>&1
47
+ t "dry-run no write" "$([ -f "$fresh/app/.claude/kit.config.json" ] && echo wrote || echo clean)" "clean"
48
+ rm -rf "$fresh"
49
+
50
+ [ "$fail" -eq 0 ] && echo "ALL OK($KIT_ADD_TEST_INNER)"
51
+ exit "$fail"
52
+ fi
53
+
54
+ rc=0; ran=0
55
+ for sh in bash zsh; do
56
+ command -v "$sh" >/dev/null 2>&1 || continue
57
+ ran=$((ran+1)); echo "--- $sh ---"
58
+ rcflag=""; [ "$sh" = "zsh" ] && rcflag="--no-rcs"
59
+ KIT_ADD_TEST_INNER="$sh" PATH="$PATH" "$sh" $rcflag "$0" || rc=1
60
+ done
61
+ [ "$ran" -eq 0 ] && { echo "no shell"; exit 1; }
62
+ exit "$rc"
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env bash
2
+ # kit-add.sh — stack a MODULE onto a project (D1 "modules apilables", #372). e.g. `kit add software`.
3
+ #
4
+ # THE MOMENT: a free workspace ("build me an app") becomes a GitHub-managed software island WITHOUT
5
+ # restructuring anything above it (D4/D17). A module is identity, not a file dump (D14): kit-add
6
+ # writes the island's .claude/kit.config.json (modules[] + wizard answers) through the F0 operation
7
+ # machine, so it is manifest-tracked, idempotent (2x = no-op), dry-runnable, and removable. The
8
+ # engine (agents/skills) is the installed plugin singleton — it is never copied per project.
9
+ #
10
+ # The ASKING (the wizard) is interactive and portable, so the kit-onboard skill / the /kit-add
11
+ # command render it via AskUserQuestion and pass answers here as a file. This script also takes a
12
+ # minimal terminal fallback so it stands alone.
13
+ #
14
+ # Run:
15
+ # scripts/kit-add.sh software [--dir DIR] --answers FILE # non-interactive (skill/CI/tests)
16
+ # scripts/kit-add.sh software --set versioning=github --set deploy=vercel --set ci=github-actions
17
+ # KIT_ASSUME_YES=1 scripts/kit-add.sh software # accept every recommended default
18
+ # KIT_DRY_RUN=1 scripts/kit-add.sh software # preview only
19
+ # Requires: jq.
20
+
21
+ set -uo pipefail
22
+ _add_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd)"
23
+ # shellcheck source=/dev/null
24
+ . "$_add_dir/lib/kit-interview.sh" # catalog/apply (pulls kit-cli + kit-profile)
25
+ # shellcheck source=/dev/null
26
+ . "$_add_dir/lib/kit-operate.sh" # four-beat machine + manifest
27
+
28
+ # kit_add_module_exists <module> -> rc 0 if a module spec is installed
29
+ kit_add_module_exists() { [ -f "$(kit_interview_catalog_file "$1")" ]; }
30
+
31
+ # kit_add <module> <answersfile> [targetdir]
32
+ # Merges the module's wizard answers onto the island config and persists via kit-operate.
33
+ kit_add() {
34
+ command -v jq >/dev/null 2>&1 || { kit_die "jq required"; }
35
+ local module="$1" answers="$2" target="${3:-$PWD}"
36
+ [ -n "$module" ] || kit_die "usage: kit-add.sh <module> [--dir DIR] [--answers FILE | --set k=v ...]"
37
+ kit_add_module_exists "$module" || kit_die "unknown module '$module' (no $(kit_interview_catalog_file "$module"))"
38
+ [ -f "$answers" ] || kit_die "internal: answers file missing ($answers)"
39
+
40
+ # Operate + manifest are relative to the island root — work from there.
41
+ target="$(cd "$target" 2>/dev/null && pwd)" || kit_die "no such dir: $target"
42
+ cd "$target" || kit_die "cannot enter $target"
43
+
44
+ local cfg=".claude/kit.config.json" base merged tmp
45
+ base=""; [ -f "$cfg" ] && base="$cfg"
46
+ merged="$(kit_interview_apply "$module" "$answers" "$base")" || kit_die "deriving module config failed"
47
+ # Belt-and-suspenders: guarantee the module name is in modules[] even if the spec omitted it.
48
+ merged="$(printf '%s' "$merged" | jq --arg m "$module" '.modules = ((.modules // []) + [$m] | unique)')"
49
+
50
+ kit_say "kit add $module -> $target/$cfg"
51
+ printf '%s\n' "$merged" | kit_op_write_content "$cfg" A "add:$module"
52
+ local rc=$?
53
+ case $rc in
54
+ 0) kit_say "module '$module' active here. modules: $(printf '%s' "$merged" | jq -c '.modules')";;
55
+ 10) kit_say "no change (already applied / declined / dry-run).";;
56
+ *) kit_die "failed to write $cfg";;
57
+ esac
58
+ return 0
59
+ }
60
+
61
+ # --- collect answers from flags / env / minimal terminal fallback ---------
62
+ # Builds a {key:value} JSON. Precedence: --answers FILE, then --set overrides, then for any unset
63
+ # question: KIT_ASSUME_YES/non-tty -> default; tty -> ask once with the recommended default.
64
+ _kit_add_collect() {
65
+ local module="$1" answers_file="$2"; shift 2
66
+ # All locals up front — a bare `local x` re-run in a loop prints the var under zsh (typeset-like).
67
+ local sets=("$@")
68
+ local catalog acc kv k v tmp nq i key def have pick qtext r
69
+ catalog="$(kit_interview_catalog "$module")" || return 1
70
+ acc="$(mktemp)"
71
+ if [ -n "$answers_file" ] && [ -f "$answers_file" ]; then cp "$answers_file" "$acc"; else printf '{}\n' > "$acc"; fi
72
+ # apply --set k=v
73
+ for kv in ${sets[@]+"${sets[@]}"}; do
74
+ k="${kv%%=*}"; v="${kv#*=}"
75
+ tmp="$(mktemp)"; jq --arg k "$k" --arg v "$v" '.[$k]=$v' "$acc" > "$tmp" && mv "$tmp" "$acc"
76
+ done
77
+ # fill the rest
78
+ nq="$(printf '%s' "$catalog" | jq '.questions | length')"; i=0
79
+ while [ "$i" -lt "$nq" ]; do
80
+ key="$(printf '%s' "$catalog" | jq -r ".questions[$i].key")"
81
+ def="$(printf '%s' "$catalog" | jq -r ".questions[$i].default // \"\"")"
82
+ have="$(jq -r --arg k "$key" '.[$k] // empty' "$acc")"
83
+ if [ -z "$have" ]; then
84
+ pick="$def"
85
+ if [ -z "${KIT_ASSUME_YES:-}" ] && [ -z "${KIT_DRY_RUN:-}" ] && [ -t 0 ]; then
86
+ qtext="$(printf '%s' "$catalog" | jq -r ".questions[$i].question")"
87
+ printf '%s [%s] ' "$qtext" "$def" >&2
88
+ r=""; IFS= read -r r </dev/tty 2>/dev/null || r=""
89
+ [ -n "$r" ] && pick="$r"
90
+ fi
91
+ tmp="$(mktemp)"; jq --arg k "$key" --arg v "$pick" '.[$k]=$v' "$acc" > "$tmp" && mv "$tmp" "$acc"
92
+ fi
93
+ i=$((i+1))
94
+ done
95
+ printf '%s\n' "$acc" # caller reads the path, then removes it
96
+ }
97
+
98
+ # CLI
99
+ if kit_is_main; then
100
+ _module=""; _target="$PWD"; _answers=""; _sets=()
101
+ while [ $# -gt 0 ]; do case "$1" in
102
+ --dir) _target="$2"; shift 2;;
103
+ --answers) _answers="$2"; shift 2;;
104
+ --set) _sets+=("$2"); shift 2;;
105
+ -h|--help) echo "usage: kit-add.sh <module> [--dir DIR] [--answers FILE] [--set k=v ...] (env: KIT_ASSUME_YES, KIT_DRY_RUN)"; exit 0;;
106
+ -*) kit_die "unknown flag: $1";;
107
+ *) [ -z "$_module" ] && _module="$1" || kit_die "unexpected arg: $1"; shift;;
108
+ esac; done
109
+ [ -n "$_module" ] || kit_die "usage: kit-add.sh <module> [--dir DIR] [--answers FILE] [--set k=v ...]"
110
+ kit_add_module_exists "$_module" || kit_die "unknown module '$_module'"
111
+ _af="$(_kit_add_collect "$_module" "$_answers" ${_sets[@]+"${_sets[@]}"})" || kit_die "collecting answers failed"
112
+ kit_add "$_module" "$_af" "$_target"; _rc=$?
113
+ rm -f "$_af"
114
+ exit $_rc
115
+ fi
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bash
2
+ # kit-adopt-test.sh — self-test for kit-adopt (#373). Runs under bash AND zsh.
3
+ # Run: bash scripts/kit-adopt-test.sh
4
+ PLUGIN_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
5
+
6
+ if [ -n "${KIT_ADOPT_TEST_INNER:-}" ]; then
7
+ set -u
8
+ fail=0
9
+ t() { if [ "$2" != "$3" ]; then echo "FAIL($KIT_ADOPT_TEST_INNER): $1 -> got '[$2]' want '[$3]'"; fail=1; else echo "ok($KIT_ADOPT_TEST_INNER): $1"; fi; }
10
+
11
+ proj="$(mktemp -d)"; trap 'rm -rf "$proj"' EXIT
12
+ cd "$proj"
13
+ . "$PLUGIN_DIR/scripts/kit-adopt.sh"
14
+ . "$PLUGIN_DIR/scripts/lib/kit-manifest.sh"
15
+
16
+ # A project that imported kit files by hand: skills + a rule + statusline (unmanaged) + user content.
17
+ mkdir -p .claude/skills/kit-task-pr .claude/rules knowledge
18
+ printf 'pr skill\n' > .claude/skills/kit-task-pr/SKILL.md
19
+ printf 'a rule\n' > .claude/rules/branch-naming.md
20
+ printf '#!/bin/sh\n' > .claude/statusline.sh
21
+ printf 'my notes\n' > knowledge/doc.md # user content — must NEVER be a candidate
22
+
23
+ # --check: 3 kit-shaped files unmanaged -> rc 1, knowledge/ excluded.
24
+ out="$(kit_adopt_check 2>&1)"; rc=$?
25
+ t "check rc=1 when unmanaged exist" "$rc" "1"
26
+ t "check counts the 3 kit files" "$(printf '%s' "$out" | grep -c '?')" "3"
27
+ t "check never lists knowledge/" "$(printf '%s' "$out" | grep -c 'knowledge/')" "0"
28
+
29
+ # dry-run records nothing.
30
+ KIT_DRY_RUN=1 kit_adopt_all >/dev/null 2>&1
31
+ t "dry-run writes no manifest" "$([ -f .claude/kit.manifest.json ] && echo yes || echo no)" "no"
32
+
33
+ # real adopt (assume-yes): all 3 recorded, knowledge untouched.
34
+ KIT_ASSUME_YES=1 kit_adopt_all >/dev/null 2>&1
35
+ t "skill now tracked" "$(kit_manifest_verify .claude/skills/kit-task-pr/SKILL.md)" "intact"
36
+ t "rule now tracked" "$(kit_manifest_verify .claude/rules/branch-naming.md)" "intact"
37
+ t "statusline now tracked" "$(kit_manifest_verify .claude/statusline.sh)" "intact"
38
+ t "statusline tier=B" "$(kit_manifest_get .claude/statusline.sh | jq -r .tier)" "B"
39
+ t "skill tier=A" "$(kit_manifest_get .claude/skills/kit-task-pr/SKILL.md | jq -r .tier)" "A"
40
+ t "op=adopt" "$(kit_manifest_get .claude/rules/branch-naming.md | jq -r .op)" "adopt"
41
+ t "knowledge NOT tracked" "$(kit_manifest_verify knowledge/doc.md)" "untracked"
42
+
43
+ # idempotent: a second adopt finds nothing new; --check is clean (rc 0).
44
+ out2="$(KIT_ASSUME_YES=1 kit_adopt_all 2>&1)"
45
+ t "2nd adopt no-op" "$(printf '%s' "$out2" | grep -c 'nothing to adopt')" "1"
46
+ kit_adopt_check >/dev/null 2>&1
47
+ t "check clean after adopt rc=0" "$?" "0"
48
+
49
+ [ "$fail" -eq 0 ] && echo "ALL OK($KIT_ADOPT_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_ADOPT_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,122 @@
1
+ #!/usr/bin/env bash
2
+ # kit-adopt.sh — adopt kit-shaped files a project ALREADY has into the ownership manifest (#373).
3
+ #
4
+ # THE MOMENT: a project imported the kit by hand (copied a .claude/ from another repo, ran an old
5
+ # init, or a teammate pasted skills/rules in) — the files are PRESENT but the manifest doesn't know
6
+ # them, so update/remove can't manage them safely (kit_manifest_verify says `untracked` -> "never
7
+ # touch"). kit-adopt walks the kit-shaped paths that exist on disk, shows what it would adopt
8
+ # (dry-run diff), and — on confirm — RECORDS each into the manifest at its current hash. It writes
9
+ # NO file content; it only takes ownership of what's already there. After adopting, /kit-update can
10
+ # refresh them and kit-remove can leave clean.
11
+ #
12
+ # It is the read-only counterpart to kit-wire: wire WRITES the kit's own files; adopt CLAIMS files
13
+ # the project brought. It never claims a file that is not kit-shaped (your knowledge/, plans/,
14
+ # drafts/, app code are off-limits) and never overwrites — adoption is hash-recording only.
15
+ #
16
+ # Run:
17
+ # scripts/kit-adopt.sh # interactive: list candidates, confirm, record
18
+ # scripts/kit-adopt.sh --dry-run # preview only
19
+ # scripts/kit-adopt.sh --check # report unmanaged kit-shaped files, write nothing (rc 1 if any)
20
+ # KIT_ASSUME_YES=1 scripts/kit-adopt.sh # non-interactive (init/CI/Cowork)
21
+ # Requires: jq + a sha256 tool.
22
+
23
+ set -uo pipefail
24
+ _adopt_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd)"
25
+ # shellcheck source=/dev/null
26
+ . "$_adopt_dir/lib/kit-operate.sh" # four-beat machine + kit-manifest.sh
27
+ # shellcheck source=/dev/null
28
+ . "$_adopt_dir/lib/kit-cli.sh" # kit_is_main
29
+
30
+ # Tier-classify a kit-shaped path. Statusline shim + settings + hooks are CLI-only (tier B); the
31
+ # portable skills/rules/agents/commands are tier A (work in Cowork/claude.ai too). Mirrors kit-wire.
32
+ _kit_adopt_tier() {
33
+ case "$1" in
34
+ .claude/statusline.sh|.claude/settings.json|.claude/hooks/*|.claude/lib/*) printf 'B' ;;
35
+ *) printf 'A' ;;
36
+ esac
37
+ }
38
+
39
+ # Emit the set of kit-shaped paths that EXIST under the project but are NOT yet in the manifest.
40
+ # "kit-shaped" = the directories the kit owns (skills/rules/agents/commands/hooks/lib) + the two
41
+ # wired singletons. Never the user's content trees (knowledge/, plans/, drafts/, apps/, src/).
42
+ # One relpath per line.
43
+ kit_adopt_candidates() {
44
+ local p verdict
45
+ # Files under the kit-owned .claude subtrees.
46
+ while IFS= read -r p; do
47
+ [ -n "$p" ] || continue
48
+ p="${p#./}"
49
+ verdict="$(kit_manifest_verify "$p" 2>/dev/null || true)"
50
+ [ "$verdict" = "untracked" ] && printf '%s\n' "$p"
51
+ done < <(
52
+ find .claude/skills .claude/rules .claude/agents .claude/commands \
53
+ .claude/hooks .claude/lib -type f 2>/dev/null
54
+ )
55
+ # The two wired singletons, if present.
56
+ for p in .claude/statusline.sh .claude/settings.json; do
57
+ [ -f "$p" ] || continue
58
+ verdict="$(kit_manifest_verify "$p" 2>/dev/null || true)"
59
+ [ "$verdict" = "untracked" ] && printf '%s\n' "$p"
60
+ done
61
+ }
62
+
63
+ # kit_adopt_all — record every unmanaged kit-shaped file into the manifest at its current hash.
64
+ # Honors KIT_DRY_RUN (report only) and KIT_ASSUME_YES (skip the confirm). Returns 0 always (a clean
65
+ # project with nothing to adopt is success).
66
+ kit_adopt_all() {
67
+ command -v jq >/dev/null 2>&1 || { echo "kit-adopt: jq required" >&2; return 1; }
68
+
69
+ # Collect candidates into an array first (a per-file confirm's read must not eat the loop's stdin).
70
+ local arr=() line
71
+ while IFS= read -r line; do [ -n "$line" ] && arr+=("$line"); done < <(kit_adopt_candidates)
72
+
73
+ local n="${#arr[@]}"
74
+ if [ "$n" -eq 0 ]; then _kit_op_say "kit-adopt: nothing to adopt — every kit-shaped file is already tracked (or none present)."; return 0; fi
75
+
76
+ _kit_op_say "kit-adopt: $n unmanaged kit-shaped file(s) found:"
77
+ local p; for p in ${arr[@]+"${arr[@]}"}; do _kit_op_say " + adopt $p ($(_kit_adopt_tier "$p"))"; done
78
+
79
+ if _kit_op_dry; then _kit_op_say " [dry-run] would record $n file(s) into the manifest"; return 0; fi
80
+
81
+ if [ -z "${KIT_ASSUME_YES:-}" ]; then
82
+ printf 'kit-adopt: take ownership of these %s file(s) in the manifest? [y/N] ' "$n" >&2
83
+ local _c=""; IFS= read -r _c </dev/tty 2>/dev/null || IFS= read -r _c 2>/dev/null || _c=""
84
+ case "$_c" in y|Y|yes|YES) ;; *) _kit_op_say "kit-adopt: aborted (nothing recorded)."; return 0;; esac
85
+ fi
86
+
87
+ kit_manifest_init || return 1
88
+ local adopted=0
89
+ for p in ${arr[@]+"${arr[@]}"}; do
90
+ kit_manifest_record "$p" "$(_kit_adopt_tier "$p")" adopt >/dev/null && {
91
+ adopted=$((adopted+1)); _kit_op_say " adopted: $p"
92
+ }
93
+ done
94
+ _kit_op_say "kit-adopt: $adopted file(s) now manifest-managed (update/remove can handle them safely)."
95
+ }
96
+
97
+ # --check: report unmanaged kit-shaped files, write nothing, rc 1 if any exist (SessionStart nudge).
98
+ kit_adopt_check() {
99
+ local arr=() line
100
+ while IFS= read -r line; do [ -n "$line" ] && arr+=("$line"); done < <(kit_adopt_candidates)
101
+ local n="${#arr[@]}"
102
+ [ "$n" -eq 0 ] && return 0
103
+ _kit_op_say "kit-adopt --check: $n kit-shaped file(s) present but unmanaged — run 'kit-adopt' to take ownership:"
104
+ local p; for p in ${arr[@]+"${arr[@]}"}; do _kit_op_say " ? $p"; done
105
+ return 1
106
+ }
107
+
108
+ # CLI (direct execution only)
109
+ if kit_is_main; then
110
+ _mode=run
111
+ while [ $# -gt 0 ]; do case "$1" in
112
+ --dry-run) export KIT_DRY_RUN=1; shift;;
113
+ --yes|-y) export KIT_ASSUME_YES=1; shift;;
114
+ --check) _mode=check; shift;;
115
+ -h|--help) echo "usage: kit-adopt.sh [--check] [--dry-run] [--yes] (env: KIT_ASSUME_YES, KIT_DRY_RUN)"; exit 0;;
116
+ *) echo "unknown arg: $1" >&2; exit 2;;
117
+ esac; done
118
+ case "$_mode" in
119
+ check) kit_adopt_check;;
120
+ *) kit_adopt_all;;
121
+ esac
122
+ fi
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env bash
2
+ # Bump the claude-kit plugin version in EVERY place that must stay in sync:
3
+ # <plugin>/.claude-plugin/plugin.json .version
4
+ # <plugin>/.claude-plugin/version.json .version + .channels.<beta|stable>.version
5
+ # <plugin>/package.json .version
6
+ # <plugin>/.claude-plugin/marketplace.json .metadata.version + .plugins[claude-kit].version
7
+ # <repo-root>/.claude-plugin/marketplace.json (when present) — same two fields
8
+ #
9
+ # Usage: kit-bump-version.sh [beta|patch|minor|<explicit-version>]
10
+ # beta (default) 0.7.0-beta.1 -> 0.7.0-beta.2 ; 0.7.0 -> 0.7.1-beta.1 (-> channels.beta)
11
+ # patch 0.7.0-beta.N -> 0.7.0 ; 0.7.0 -> 0.7.1 (-> channels.stable)
12
+ # minor 0.7.x[-*] -> 0.8.0 (-> channels.stable)
13
+ # 1.2.3-rc.1 set verbatim
14
+ #
15
+ # version.json is the canonical channel descriptor: a `-beta.N` build advances channels.beta,
16
+ # a clean release advances channels.stable. /kit-dev ship runs this so the installed plugin
17
+ # (deduped by version) sees every release.
18
+ set -euo pipefail
19
+
20
+ # Resolve the plugin dir from this script's location (BASH_SOURCE), so it works run from the
21
+ # plugin checkout OR the monorepo. Repo root is best-effort (for the optional root marketplace).
22
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23
+ plugin_dir="$(cd "$script_dir/.." && pwd)"
24
+ repo_root="$(git -C "$plugin_dir" rev-parse --show-toplevel 2>/dev/null || true)"
25
+
26
+ plugin_json="$plugin_dir/.claude-plugin/plugin.json"
27
+ version_json="$plugin_dir/.claude-plugin/version.json"
28
+ package_json="$plugin_dir/package.json"
29
+ sub_marketplace_json="$plugin_dir/.claude-plugin/marketplace.json"
30
+ root_marketplace_json="${repo_root:+$repo_root/.claude-plugin/marketplace.json}"
31
+ [[ -f $plugin_json ]] || { echo "✗ plugin.json not found at $plugin_json" >&2; exit 1; }
32
+
33
+ mode="${1:-beta}"
34
+ current="$(jq -r .version "$plugin_json")"
35
+
36
+ core="${current%%-*}"
37
+ pre=""; [[ $current == *-* ]] && pre="${current#*-}"
38
+ IFS=. read -r maj min pat <<<"$core"
39
+
40
+ channel="stable" # which channel this bump advances
41
+ case "$mode" in
42
+ beta)
43
+ channel="beta"
44
+ if [[ $pre == beta.* ]]; then
45
+ next="$core-beta.$(( ${pre#beta.} + 1 ))"
46
+ else
47
+ next="$maj.$min.$((pat + 1))-beta.1"
48
+ fi
49
+ ;;
50
+ patch)
51
+ if [[ -n $pre ]]; then next="$core"; else next="$maj.$min.$((pat + 1))"; fi
52
+ ;;
53
+ minor)
54
+ next="$maj.$((min + 1)).0"
55
+ ;;
56
+ *)
57
+ [[ $mode =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.]+)?$ ]] || { echo "✗ '$mode' is not beta|patch|minor or a semver" >&2; exit 1; }
58
+ next="$mode"
59
+ [[ $next == *-* ]] && channel="beta"
60
+ ;;
61
+ esac
62
+
63
+ _set() { local f="$1" filter="$2"; [[ -f "$f" ]] || return 0; local t; t="$(mktemp)"; jq "$filter" "$f" >"$t" && mv "$t" "$f"; }
64
+
65
+ _set "$plugin_json" '.version = "'"$next"'"'
66
+ _set "$package_json" '.version = "'"$next"'"'
67
+ # version.json: top-level version + the advancing channel's version.
68
+ _set "$version_json" '.version = "'"$next"'" | .channels.'"$channel"'.version = "'"$next"'"'
69
+
70
+ mkt_bumped=0
71
+ for mkt in "$root_marketplace_json" "$sub_marketplace_json"; do
72
+ [[ -n "$mkt" && -f "$mkt" ]] || continue
73
+ t="$(mktemp)"
74
+ jq --arg v "$next" '.metadata.version = $v | (.plugins[] | select(.name == "claude-kit") | .version) = $v' \
75
+ "$mkt" >"$t" && mv "$t" "$mkt"
76
+ mkt_bumped=$((mkt_bumped + 1))
77
+ done
78
+
79
+ echo "✓ claude-kit $current -> $next (channel: $channel; plugin.json + version.json + package.json + $mkt_bumped marketplace.json)"
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env bash
2
+ # kit-digest.sh — pre-digest long inputs (transcripts, CI logs, big files, YouTube URLs)
3
+ # with the LOCAL model (scripts/lib/kit-local.sh + mlx_lm.server) so the Claude session
4
+ # reads a <=1500-token digest + a pointer to the original instead of the full content.
5
+ #
6
+ # Usage:
7
+ # ./scripts/kit-digest.sh <path|url> [--focus "<topic>"] [--lang es|en]
8
+ #
9
+ # Exit codes: 0 digest printed · 2 local server down (caller falls back to reading the
10
+ # original directly) · 1 input/usage error.
11
+ #
12
+ # Chunking: inputs are split into ~2500-word chunks (map), each digested locally, then
13
+ # merged in a final reduce pass when there is more than one chunk.
14
+ set -uo pipefail
15
+
16
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17
+ # Plugin-direct: stay in the caller's CWD so the project's .claude/kit.config.json
18
+ # (.local model/port) is read; the helper + inputs resolve by absolute path.
19
+
20
+ # shellcheck source=lib/kit-local.sh
21
+ source "$SCRIPT_DIR/lib/kit-local.sh"
22
+
23
+ INPUT="${1:-}"
24
+ FOCUS=""
25
+ LANG_OUT="es"
26
+ shift || true
27
+ while [[ $# -gt 0 ]]; do
28
+ case "$1" in
29
+ --focus) FOCUS="$2"; shift 2 ;;
30
+ --lang) LANG_OUT="$2"; shift 2 ;;
31
+ *) echo "kit-digest: unknown flag $1" >&2; exit 1 ;;
32
+ esac
33
+ done
34
+
35
+ [[ -z "$INPUT" ]] && { echo "usage: kit-digest.sh <path|url> [--focus \"<topic>\"] [--lang es|en]" >&2; exit 1; }
36
+
37
+ if ! kit_local_alive; then
38
+ echo "kit-digest: local server down (mlx_lm.server :${KIT_LOCAL_PORT}) — read the original directly instead." >&2
39
+ echo " start it: mlx_lm.server --model ${KIT_LOCAL_MODEL} --port ${KIT_LOCAL_PORT}" >&2
40
+ exit 2
41
+ fi
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # Acquire text
45
+ # ---------------------------------------------------------------------------
46
+ WORK="$(mktemp -d /tmp/kit-digest.XXXXXX)" || exit 1
47
+ trap 'rm -rf "$WORK"' EXIT
48
+ TEXT="$WORK/input.txt"
49
+ SOURCE_DESC="$INPUT"
50
+
51
+ vtt_to_text() { # stdin: VTT -> stdout: deduped plain text
52
+ python3 -c '
53
+ import re, sys
54
+ out, prev = [], ""
55
+ for l in sys.stdin:
56
+ l = l.strip()
57
+ if not l or "-->" in l or l.startswith(("WEBVTT", "Kind:", "Language:")) or re.match(r"^\d+$", l):
58
+ continue
59
+ l = re.sub(r"<[^>]+>", "", l)
60
+ if l != prev:
61
+ out.append(l); prev = l
62
+ print(re.sub(r"\s+", " ", " ".join(out)))'
63
+ }
64
+
65
+ if [[ "$INPUT" =~ ^https?://(www\.)?(youtube\.com|youtu\.be)/ ]]; then
66
+ command -v yt-dlp >/dev/null 2>&1 || { echo "kit-digest: yt-dlp required for YouTube URLs (brew install yt-dlp)" >&2; exit 1; }
67
+ TITLE="$(yt-dlp --skip-download --print '%(title)s' "$INPUT" 2>/dev/null | head -1 || true)"
68
+ SOURCE_DESC="${TITLE:-$INPUT} ($INPUT)"
69
+ yt-dlp --skip-download --write-auto-subs --write-subs --sub-langs "en.*,es.*" --sub-format vtt \
70
+ -o "$WORK/yt" "$INPUT" >/dev/null 2>&1 || true
71
+ VTT="$(ls "$WORK"/yt*.vtt 2>/dev/null | head -1 || true)"
72
+ [[ -z "$VTT" ]] && { echo "kit-digest: no subtitles found for $INPUT" >&2; exit 1; }
73
+ vtt_to_text < "$VTT" > "$TEXT"
74
+ elif [[ "$INPUT" =~ ^https?:// ]]; then
75
+ curl -sfL -m 30 "$INPUT" 2>/dev/null \
76
+ | perl -0pe 's/<script\b.*?<\/script>//gs; s/<style\b.*?<\/style>//gs; s/<[^>]+>/ /gs' \
77
+ | tr -s ' \t\n' ' ' > "$TEXT" || { echo "kit-digest: fetch failed: $INPUT" >&2; exit 1; }
78
+ else
79
+ [[ -f "$INPUT" ]] || { echo "kit-digest: file not found: $INPUT" >&2; exit 1; }
80
+ case "$INPUT" in
81
+ *.vtt) vtt_to_text < "$INPUT" > "$TEXT" ;;
82
+ *) cat "$INPUT" > "$TEXT" ;;
83
+ esac
84
+ fi
85
+
86
+ WORDS=$(wc -w < "$TEXT" | tr -d ' ')
87
+ [[ "${WORDS:-0}" -eq 0 ]] && { echo "kit-digest: empty input" >&2; exit 1; }
88
+
89
+ # ---------------------------------------------------------------------------
90
+ # Map (digest per ~2500-word chunk) -> reduce (merge)
91
+ # ---------------------------------------------------------------------------
92
+ CHUNK_WORDS=2500
93
+ if [[ "$LANG_OUT" == "en" ]]; then
94
+ SYS_MAP="You summarize for an engineering audience. Extract the key points, decisions, numbers and names from this fragment as tight bullets. No intro, no closing."
95
+ SYS_REDUCE="Merge these partial digests into ONE digest of at most 1500 tokens: tight bullets grouped by theme, keep concrete numbers/names/refs. No intro, no closing."
96
+ else
97
+ SYS_MAP="Resumes para una audiencia de ingenieria. Extrae los puntos clave, decisiones, numeros y nombres de este fragmento en bullets apretados. Sin intro ni cierre."
98
+ SYS_REDUCE="Fusiona estos digests parciales en UN digest de maximo 1500 tokens: bullets apretados agrupados por tema, conserva numeros/nombres/refs concretos. Sin intro ni cierre."
99
+ fi
100
+ [[ -n "$FOCUS" ]] && SYS_MAP="$SYS_MAP Prioriza todo lo relacionado con: $FOCUS."
101
+
102
+ # One chunk per line, CHUNK_WORDS words each (awk: xargs would choke on quotes).
103
+ awk -v n="$CHUNK_WORDS" '{for(i=1;i<=NF;i++){printf "%s%s",$i,(++c%n==0?"\n":" ")}}END{if(c%n!=0)print ""}' \
104
+ "$TEXT" > "$WORK/wrapped.txt"
105
+ split -l 1 "$WORK/wrapped.txt" "$WORK/chunk." 2>/dev/null
106
+
107
+ PARTS=()
108
+ for c in "$WORK"/chunk.*; do
109
+ [[ -s "$c" ]] || continue
110
+ part="$(kit_local_chat "$SYS_MAP" "$(cat "$c")" 700)" || { echo "kit-digest: local chat failed mid-run" >&2; exit 2; }
111
+ PARTS+=("$part")
112
+ done
113
+
114
+ if [[ ${#PARTS[@]} -eq 1 ]]; then
115
+ DIGEST="${PARTS[0]}"
116
+ else
117
+ ALL="$(printf '%s\n\n' "${PARTS[@]}")"
118
+ DIGEST="$(kit_local_chat "$SYS_REDUCE" "$ALL" 1500)" || DIGEST="$ALL"
119
+ fi
120
+
121
+ echo "# Digest (local: $(kit_local_model_tag)) — $SOURCE_DESC"
122
+ echo
123
+ printf '%s\n' "$DIGEST"
124
+ echo
125
+ echo "---"
126
+ echo "Original: $INPUT ($WORDS palabras, ${#PARTS[@]} chunk(s)) — para deep-dive selectivo, lee el original."