@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,322 @@
1
+ #!/usr/bin/env bash
2
+ # kit-routines.sh — suggested-routines cron catalogue: accept / remove / list / verify (#377).
3
+ #
4
+ # THE PHILOSOPHY: the kit SUGGESTS routines; the user accepts; nothing is created without opt-in.
5
+ # Accepted routines are stored in kit.config.json (.routines[]) and wired as OS cron jobs
6
+ # (crontab entries) scoped to the project directory. Each entry is tagged with the kit's sentinel
7
+ # comment so it can be identified and removed cleanly. The script NEVER auto-accepts anything.
8
+ #
9
+ # Operations:
10
+ # kit-routines.sh list # show the catalogue with status (accepted / available)
11
+ # kit-routines.sh accept <id> [--dir D] # suggest -> add cron + record in kit.config.json
12
+ # kit-routines.sh remove <id> [--dir D] # remove cron + clear from kit.config.json
13
+ # kit-routines.sh verify [--dir D] # check: config routines have matching cron entries
14
+ # kit-routines.sh remove-all [--dir D] # remove every kit-managed cron for this project (used by kit-remove)
15
+ # KIT_DRY_RUN=1 kit-routines.sh ... # preview any write op, write nothing
16
+ # KIT_ASSUME_YES=1 kit-routines.sh ... # non-interactive (CI / batch)
17
+ #
18
+ # Cron sentinel format — one line per routine:
19
+ # # kit-managed:<project_slug>:<routine_id> <cron_expr> <command>
20
+ # This makes grep-based remove exact and never matches unrelated crontab lines.
21
+ #
22
+ # Requires: jq (catalogue parse + config patch), crontab (OS-level cron).
23
+
24
+ set -uo pipefail
25
+ _rt_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd)"
26
+ # shellcheck source=/dev/null
27
+ . "$_rt_dir/lib/kit-cli.sh" # kit_is_main, kit_say, kit_warn, kit_die
28
+
29
+ # Plugin root — prefer CLAUDE_PLUGIN_ROOT, fall back to two levels up.
30
+ _kit_rt_plugin_root() {
31
+ if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then printf '%s\n' "$CLAUDE_PLUGIN_ROOT"; return 0; fi
32
+ ( CDPATH='' cd -- "$_rt_dir/.." && pwd )
33
+ }
34
+
35
+ _kit_rt_catalogue() {
36
+ local f; f="$(_kit_rt_plugin_root)/routines/catalogue.json"
37
+ [ -f "$f" ] || kit_die "routines catalogue not found: $f"
38
+ cat "$f"
39
+ }
40
+
41
+ _kit_rt_dry() { [ -n "${KIT_DRY_RUN:-}" ]; }
42
+ _kit_rt_yes() { [ -n "${KIT_ASSUME_YES:-}" ]; }
43
+ _kit_rt_say() { printf '%s\n' "$*" >&2; }
44
+
45
+ # ── project-slug helper ─────────────────────────────────────────────────────
46
+ # Derive a stable slug for cron sentinel tagging: use kit.config.json project.slug,
47
+ # fall back to the current directory name.
48
+ _kit_rt_slug() {
49
+ local dir="${1:-$PWD}" slug
50
+ slug="$(jq -r '.project.slug // empty' "$dir/.claude/kit.config.json" 2>/dev/null || true)"
51
+ [ -n "$slug" ] && { printf '%s\n' "$slug"; return 0; }
52
+ basename "$dir"
53
+ }
54
+
55
+ # ── catalogue helpers ────────────────────────────────────────────────────────
56
+ # kit_routines_get_by_id <id> -> JSON object or empty
57
+ kit_routines_get_by_id() {
58
+ _kit_rt_catalogue | jq --arg id "$1" '.routines[] | select(.id==$id)'
59
+ }
60
+
61
+ # kit_routines_accepted <dir> -> space-separated list of accepted routine ids from kit.config.json
62
+ kit_routines_accepted() {
63
+ local dir="${1:-$PWD}" cfg
64
+ cfg="$dir/.claude/kit.config.json"
65
+ [ -f "$cfg" ] || { printf ''; return 0; }
66
+ jq -r '(.routines // [])[]' "$cfg" 2>/dev/null || true
67
+ }
68
+
69
+ # ── crontab helpers ──────────────────────────────────────────────────────────
70
+ # Sentinel comment identifies a kit-managed line: "# kit-managed:<slug>:<id>"
71
+ _kit_rt_sentinel() { printf '# kit-managed:%s:%s' "$1" "$2"; }
72
+
73
+ # _kit_rt_cron_line <sentinel> <cron_expr> <command_expanded> -> the full crontab line pair
74
+ _kit_rt_cron_line() {
75
+ local sentinel="$1" expr="$2" cmd="$3"
76
+ printf '%s\n%s %s\n' "$sentinel" "$expr" "$cmd"
77
+ }
78
+
79
+ # _kit_rt_expand_command <command_template> <scripts_dir> -> expanded command string
80
+ _kit_rt_expand_command() {
81
+ local tmpl="$1" scripts="$2"
82
+ printf '%s' "$tmpl" | sed "s|\${KIT_SCRIPTS}|$scripts|g"
83
+ }
84
+
85
+ # kit_routines_cron_has <sentinel> -> rc 0 if present in crontab
86
+ kit_routines_cron_has() {
87
+ local sentinel="$1"
88
+ crontab -l 2>/dev/null | grep -qF "$sentinel"
89
+ }
90
+
91
+ # kit_routines_cron_add <sentinel> <cron_line_pair>
92
+ # Appends the two-line pair (sentinel + cron) to the crontab if not already present.
93
+ kit_routines_cron_add() {
94
+ local sentinel="$1" pair="$2" tmp
95
+ if kit_routines_cron_has "$sentinel"; then
96
+ _kit_rt_say " = cron already present ($sentinel)"; return 0
97
+ fi
98
+ if _kit_rt_dry; then
99
+ _kit_rt_say " [dry-run] would add cron: $sentinel"; return 0
100
+ fi
101
+ tmp="$(mktemp)"
102
+ { crontab -l 2>/dev/null; printf '\n%s\n' "$pair"; } > "$tmp" || { rm -f "$tmp"; return 1; }
103
+ crontab "$tmp"; local rc=$?; rm -f "$tmp"
104
+ [ "$rc" -eq 0 ] && _kit_rt_say " + cron added: $sentinel" || { _kit_rt_say " error: crontab write failed"; return 1; }
105
+ }
106
+
107
+ # kit_routines_cron_remove <sentinel>
108
+ # Removes every line whose PRECEDING line is the sentinel comment (the comment + cron line pair).
109
+ kit_routines_cron_remove() {
110
+ local sentinel="$1" tmp
111
+ if ! kit_routines_cron_has "$sentinel"; then
112
+ _kit_rt_say " = cron not found (already removed): $sentinel"; return 0
113
+ fi
114
+ if _kit_rt_dry; then
115
+ _kit_rt_say " [dry-run] would remove cron: $sentinel"; return 0
116
+ fi
117
+ tmp="$(mktemp)"
118
+ # Strip the sentinel line AND the following non-empty cron line.
119
+ crontab -l 2>/dev/null | awk -v s="$sentinel" '
120
+ /^[[:space:]]*$/ { print; next }
121
+ $0 == s { skip=1; next }
122
+ skip { skip=0; next }
123
+ { print }
124
+ ' > "$tmp" || { rm -f "$tmp"; return 1; }
125
+ crontab "$tmp"; local rc=$?; rm -f "$tmp"
126
+ [ "$rc" -eq 0 ] && _kit_rt_say " - cron removed: $sentinel" || { _kit_rt_say " error: crontab write failed"; return 1; }
127
+ }
128
+
129
+ # ── config helpers ───────────────────────────────────────────────────────────
130
+ # kit_routines_config_add <id> <dir>
131
+ # Adds the routine id to kit.config.json .routines[] (idempotent).
132
+ kit_routines_config_add() {
133
+ local id="$1" dir="${2:-$PWD}" cfg tmp
134
+ cfg="$dir/.claude/kit.config.json"
135
+ _kit_rt_dry && { _kit_rt_say " [dry-run] would add '$id' to $cfg .routines[]"; return 0; }
136
+ [ -f "$cfg" ] || printf '{}\n' > "$cfg"
137
+ tmp="$(mktemp)"
138
+ jq --arg id "$id" '.routines = ((.routines // []) + [$id] | unique)' "$cfg" > "$tmp" \
139
+ && mv "$tmp" "$cfg" || { rm -f "$tmp"; return 1; }
140
+ _kit_rt_say " + recorded '$id' in $cfg"
141
+ }
142
+
143
+ # kit_routines_config_remove <id> <dir>
144
+ kit_routines_config_remove() {
145
+ local id="$1" dir="${2:-$PWD}" cfg tmp
146
+ cfg="$dir/.claude/kit.config.json"
147
+ [ -f "$cfg" ] || return 0
148
+ _kit_rt_dry && { _kit_rt_say " [dry-run] would remove '$id' from $cfg .routines[]"; return 0; }
149
+ tmp="$(mktemp)"
150
+ jq --arg id "$id" '.routines = ((.routines // []) | map(select(. != $id)))' "$cfg" > "$tmp" \
151
+ && mv "$tmp" "$cfg" || { rm -f "$tmp"; return 1; }
152
+ _kit_rt_say " - removed '$id' from $cfg"
153
+ }
154
+
155
+ # ── public operations ────────────────────────────────────────────────────────
156
+
157
+ # kit_routines_list [dir] — print the catalogue table with acceptance status.
158
+ kit_routines_list() {
159
+ local dir="${1:-$PWD}"
160
+ command -v jq >/dev/null 2>&1 || kit_die "jq required"
161
+ local cat accepted
162
+ cat="$(_kit_rt_catalogue)"
163
+ accepted="$(kit_routines_accepted "$dir")"
164
+
165
+ printf '%-22s %-8s %-8s %-9s %s\n' "ID" "CADENCE" "STATUS" "COST" "DESCRIPTION" >&2
166
+ printf '%s\n' "-----------------------------------------------------------------------" >&2
167
+
168
+ local n; n="$(printf '%s' "$cat" | jq '.routines | length')"
169
+ local i=0
170
+ while [ "$i" -lt "$n" ]; do
171
+ local id cad cost desc status
172
+ id="$(printf '%s' "$cat" | jq -r ".routines[$i].id")"
173
+ cad="$(printf '%s' "$cat" | jq -r ".routines[$i].cadence")"
174
+ cost="$(printf '%s' "$cat" | jq -r ".routines[$i].cost")"
175
+ desc="$(printf '%s' "$cat" | jq -r ".routines[$i].description" | cut -c1-50)"
176
+ if printf '%s\n' "$accepted" | grep -qx "$id" 2>/dev/null; then
177
+ status="accepted"
178
+ else
179
+ status="available"
180
+ fi
181
+ printf '%-22s %-8s %-8s %-9s %s\n' "$id" "$cad" "$status" "$cost" "$desc" >&2
182
+ i=$((i+1))
183
+ done
184
+ }
185
+
186
+ # kit_routines_accept <id> [dir]
187
+ # Opt-in: look up the routine in the catalogue, add cron entry, record in config.
188
+ kit_routines_accept() {
189
+ local id="$1" dir="${2:-$PWD}"
190
+ command -v jq >/dev/null 2>&1 || kit_die "jq required"
191
+ command -v crontab >/dev/null 2>&1 || kit_die "crontab required"
192
+
193
+ dir="$(cd "$dir" 2>/dev/null && pwd)" || kit_die "no such dir: $dir"
194
+
195
+ local entry; entry="$(kit_routines_get_by_id "$id")"
196
+ [ -n "$entry" ] || kit_die "unknown routine '$id' (run: kit-routines.sh list)"
197
+
198
+ local scripts; scripts="$(cd "$_rt_dir" && pwd)"
199
+ local cron_expr cmd_tmpl cmd_expanded sentinel pair slug
200
+ cron_expr="$(printf '%s' "$entry" | jq -r '.cron')"
201
+ cmd_tmpl="$(printf '%s' "$entry" | jq -r '.command')"
202
+ cmd_expanded="$(_kit_rt_expand_command "$cmd_tmpl" "$scripts")"
203
+ slug="$(_kit_rt_slug "$dir")"
204
+ sentinel="$(_kit_rt_sentinel "$slug" "$id")"
205
+ pair="$(_kit_rt_cron_line "$sentinel" "$cron_expr" "$cmd_expanded")"
206
+
207
+ # One confirm unless non-interactive.
208
+ if ! _kit_rt_dry && ! _kit_rt_yes; then
209
+ local name; name="$(printf '%s' "$entry" | jq -r '.name')"
210
+ printf 'Accept routine "%s" (%s, %s)? [y/N] ' "$name" "$id" "$cron_expr" >&2
211
+ local reply=""; IFS= read -r reply || true
212
+ case "$reply" in y|Y|yes|YES) ;; *) _kit_rt_say " skipped."; return 0;; esac
213
+ fi
214
+
215
+ kit_routines_cron_add "$sentinel" "$pair" || return 1
216
+ kit_routines_config_add "$id" "$dir" || return 1
217
+ _kit_rt_say "routine '$id' accepted."
218
+ }
219
+
220
+ # kit_routines_remove <id> [dir]
221
+ kit_routines_remove() {
222
+ local id="$1" dir="${2:-$PWD}"
223
+ command -v jq >/dev/null 2>&1 || kit_die "jq required"
224
+ command -v crontab >/dev/null 2>&1 || kit_die "crontab required"
225
+
226
+ dir="$(cd "$dir" 2>/dev/null && pwd)" || kit_die "no such dir: $dir"
227
+ local slug; slug="$(_kit_rt_slug "$dir")"
228
+ local sentinel; sentinel="$(_kit_rt_sentinel "$slug" "$id")"
229
+
230
+ kit_routines_cron_remove "$sentinel" || return 1
231
+ kit_routines_config_remove "$id" "$dir" || return 1
232
+ _kit_rt_say "routine '$id' removed."
233
+ }
234
+
235
+ # kit_routines_verify [dir]
236
+ # Check: every id in kit.config.json .routines[] has a matching crontab entry.
237
+ # rc 0 = in sync; rc 1 = drift (missing or orphan crons).
238
+ kit_routines_verify() {
239
+ local dir="${1:-$PWD}"
240
+ command -v jq >/dev/null 2>&1 || kit_die "jq required"
241
+
242
+ dir="$(cd "$dir" 2>/dev/null && pwd)" || kit_die "no such dir: $dir"
243
+ local accepted; accepted="$(kit_routines_accepted "$dir")"
244
+ local slug; slug="$(_kit_rt_slug "$dir")"
245
+
246
+ if [ -z "$accepted" ]; then
247
+ _kit_rt_say "kit-routines verify: no routines accepted — nothing to check."
248
+ return 0
249
+ fi
250
+
251
+ local drift=0 id sentinel
252
+ while IFS= read -r id; do
253
+ [ -z "$id" ] && continue
254
+ sentinel="$(_kit_rt_sentinel "$slug" "$id")"
255
+ if ! kit_routines_cron_has "$sentinel"; then
256
+ _kit_rt_say " ! cron missing for routine '$id' (sentinel: $sentinel)"
257
+ drift=1
258
+ else
259
+ _kit_rt_say " = cron present: $id"
260
+ fi
261
+ done <<EOF
262
+ $accepted
263
+ EOF
264
+
265
+ if [ "$drift" -eq 1 ]; then
266
+ _kit_rt_say "kit-routines verify: drift detected — run 'kit-routines.sh accept <id>' to repair."
267
+ return 1
268
+ fi
269
+ _kit_rt_say "kit-routines verify: all crons in sync."
270
+ return 0
271
+ }
272
+
273
+ # kit_routines_remove_all [dir]
274
+ # Remove every kit-managed cron for this project. Called by kit-remove on uninstall.
275
+ kit_routines_remove_all() {
276
+ local dir="${1:-$PWD}"
277
+ command -v jq >/dev/null 2>&1 || kit_die "jq required"
278
+ command -v crontab >/dev/null 2>&1 || { _kit_rt_say "kit-routines: crontab not found — skip cron cleanup."; return 0; }
279
+
280
+ dir="$(cd "$dir" 2>/dev/null && pwd)" || kit_die "no such dir: $dir"
281
+ local accepted; accepted="$(kit_routines_accepted "$dir")"
282
+ local slug; slug="$(_kit_rt_slug "$dir")"
283
+
284
+ if [ -z "$accepted" ]; then
285
+ _kit_rt_say "kit-routines remove-all: no routines to remove."; return 0
286
+ fi
287
+
288
+ local id
289
+ while IFS= read -r id; do
290
+ [ -z "$id" ] && continue
291
+ kit_routines_cron_remove "$(_kit_rt_sentinel "$slug" "$id")"
292
+ done <<EOF
293
+ $accepted
294
+ EOF
295
+ _kit_rt_say "kit-routines remove-all: done."
296
+ }
297
+
298
+ # ── CLI ──────────────────────────────────────────────────────────────────────
299
+ if kit_is_main; then
300
+ command -v jq >/dev/null 2>&1 || { echo "kit-routines: jq required" >&2; exit 1; }
301
+ _sub="${1:-}"; shift 2>/dev/null || true
302
+ _id=""; _dir="$PWD"
303
+ while [ $# -gt 0 ]; do case "$1" in
304
+ --dir) _dir="$2"; shift 2;;
305
+ --dry-run) export KIT_DRY_RUN=1; shift;;
306
+ --yes|-y) export KIT_ASSUME_YES=1; shift;;
307
+ -h|--help)
308
+ echo "usage: kit-routines.sh list|accept <id>|remove <id>|verify|remove-all [--dir D] [--dry-run] [--yes]"
309
+ exit 0;;
310
+ -*) kit_die "unknown flag: $1";;
311
+ *) [ -z "$_id" ] && _id="$1" || kit_die "unexpected arg: $1"; shift;;
312
+ esac; done
313
+ case "$_sub" in
314
+ list) kit_routines_list "$_dir";;
315
+ accept) [ -n "$_id" ] || kit_die "usage: kit-routines.sh accept <id>"; kit_routines_accept "$_id" "$_dir";;
316
+ remove) [ -n "$_id" ] || kit_die "usage: kit-routines.sh remove <id>"; kit_routines_remove "$_id" "$_dir";;
317
+ verify) kit_routines_verify "$_dir";;
318
+ remove-all) kit_routines_remove_all "$_dir";;
319
+ -h|--help) echo "usage: kit-routines.sh list|accept <id>|remove <id>|verify|remove-all [--dir D] [--dry-run] [--yes]"; exit 0;;
320
+ *) kit_die "usage: kit-routines.sh list|accept <id>|remove <id>|verify|remove-all [--dir D] [--dry-run] [--yes]";;
321
+ esac
322
+ fi
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env bash
2
+ # claude-kit — CLI-style update check. Compares a kit-initialized project's recorded kitVersion
3
+ # against the installed plugin version, and prints a one-line notice when the project is behind.
4
+ # Safe no-op when it can't determine either side. Used by the SessionStart hook and /kit-update.
5
+ #
6
+ # Usage: kit-version-check.sh [--target DIR] [--plugin-root DIR] [--quiet]
7
+ set -uo pipefail
8
+
9
+ TARGET="$PWD"; PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-}"; QUIET=0
10
+ while [[ $# -gt 0 ]]; do
11
+ case "$1" in
12
+ --target) TARGET="$2"; shift 2 ;;
13
+ --plugin-root) PLUGIN_ROOT="$2"; shift 2 ;;
14
+ --quiet) QUIET=1; shift ;;
15
+ *) shift ;;
16
+ esac
17
+ done
18
+
19
+ command -v jq >/dev/null 2>&1 || exit 0
20
+ cfg="$TARGET/.claude/kit.config.json"
21
+ [[ -f "$cfg" ]] || exit 0
22
+
23
+ # Locate the installed plugin's plugin.json: explicit root → env → best-effort glob.
24
+ pj=""
25
+ [[ -n "$PLUGIN_ROOT" && -f "$PLUGIN_ROOT/.claude-plugin/plugin.json" ]] && pj="$PLUGIN_ROOT/.claude-plugin/plugin.json"
26
+ if [[ -z "$pj" ]]; then
27
+ pj="$(ls -t "$HOME"/.claude/plugins/*/claude-kit*/.claude-plugin/plugin.json \
28
+ "$HOME"/.claude/plugins/*/*/claude-kit*/.claude-plugin/plugin.json \
29
+ "$HOME"/.claude/plugins/*/*/claude-kit*/*/.claude-plugin/plugin.json \
30
+ "$HOME"/.claude/plugins/*/*/*/claude-kit*/.claude-plugin/plugin.json 2>/dev/null | head -1 || true)"
31
+ fi
32
+ [[ -n "$pj" && -f "$pj" ]] || exit 0
33
+
34
+ have="$(jq -r '.kitVersion // "0.0.0"' "$cfg" 2>/dev/null || echo 0.0.0)"
35
+ latest="$(jq -r '.version // "0.0.0"' "$pj" 2>/dev/null || echo 0.0.0)"
36
+
37
+ # Branded one-line status — ALWAYS shown (the banner is never silenced unless --quiet).
38
+ # Renders the seed-head sigil when the brand lib is present; plain text otherwise.
39
+ SIGIL="$TARGET/.claude/lib/kit-sigil.sh"
40
+ banner() { # banner <skill-slot> <note>
41
+ [[ $QUIET -eq 1 ]] && return 0
42
+ if [[ -f "$SIGIL" ]]; then
43
+ # shellcheck source=/dev/null
44
+ source "$SIGIL"; kit_sigil "$1" "$2"
45
+ else
46
+ echo "claude-kit${1:+ · $1}${2:+ · $2}"
47
+ fi
48
+ }
49
+
50
+ # Up to date — still announce it (no longer a silent no-op).
51
+ if [[ "$have" == "$latest" ]]; then
52
+ banner "v$latest" "al día · up to date"
53
+ exit 0
54
+ fi
55
+
56
+ # Is `latest` strictly newer than `have`? We can't lean on `sort -V` for the whole string:
57
+ # neither GNU nor BSD `sort -V` implements SemVer pre-release precedence (both rank
58
+ # `0.7.0-beta.1` ABOVE `0.7.0`, which is wrong — a pre-release must be LOWER than its release).
59
+ # So we split each version into a numeric CORE (x.y.z) and an optional PRE (after the first `-`),
60
+ # and compare per SemVer §11:
61
+ # 1. cores differ → the higher core wins (sort -V is reliable on pure numeric cores)
62
+ # 2. cores equal:
63
+ # stable vs pre → stable is newer (a release outranks any pre-release of same core)
64
+ # pre vs pre → compare the pre identifiers (sort -V, e.g. beta.1 < beta.2)
65
+ # POSIX-ish: only bash parameter expansion + a tiny `sort -V` on suffix-free strings.
66
+ ver_is_newer() { # ver_is_newer HAVE LATEST → exit 0 if LATEST strictly newer than HAVE
67
+ local h="$1" l="$2"
68
+ [[ "$h" == "$l" ]] && return 1
69
+ local hcore="${h%%-*}" lcore="${l%%-*}" hpre="" lpre="" top
70
+ [[ "$h" == *-* ]] && hpre="${h#*-}"
71
+ [[ "$l" == *-* ]] && lpre="${l#*-}"
72
+ if [[ "$hcore" != "$lcore" ]]; then
73
+ top="$(printf '%s\n%s\n' "$hcore" "$lcore" | sort -V | tail -1)"
74
+ [[ "$top" == "$lcore" ]]; return
75
+ fi
76
+ # cores equal — decide on the pre-release component
77
+ [[ -z "$lpre" && -n "$hpre" ]] && return 0 # latest = release, have = pre → newer
78
+ [[ -n "$lpre" && -z "$hpre" ]] && return 1 # latest = pre, have = release → not newer
79
+ # both pre-release on the same core
80
+ top="$(printf '%s\n%s\n' "$hpre" "$lpre" | sort -V | tail -1)"
81
+ [[ "$top" == "$lpre" && "$hpre" != "$lpre" ]]
82
+ }
83
+
84
+ if ver_is_newer "$have" "$latest"; then
85
+ banner "update available" "$have -> $latest · run /kit-update to merge new features (your edits are preserved)"
86
+ exit 10 # signal: behind
87
+ fi
88
+
89
+ # have is NEWER than the installed plugin — local dev ahead of the release.
90
+ banner "v$have" "ahead of plugin $latest"
91
+ exit 0
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env bash
2
+ # kit-wire-test.sh — self-test for kit-wire (#369). Runs under bash AND zsh.
3
+ # Run: bash scripts/kit-wire-test.sh
4
+ PLUGIN_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
5
+
6
+ if [ -n "${KIT_WIRE_TEST_INNER:-}" ]; then
7
+ set -u
8
+ fail=0
9
+ t() { if [ "$2" != "$3" ]; then echo "FAIL($KIT_WIRE_TEST_INNER): $1 -> got '[$2]' want '[$3]'"; fail=1; else echo "ok($KIT_WIRE_TEST_INNER): $1"; fi; }
10
+
11
+ proj="$(mktemp -d)"; trap 'rm -rf "$proj"' EXIT
12
+ cd "$proj"
13
+ export CLAUDE_PLUGIN_ROOT="$PLUGIN_DIR" # so the shim + plugin-root resolve to our statusline
14
+ export KIT_ASSUME_YES=1
15
+
16
+ . "$PLUGIN_DIR/scripts/kit-wire.sh"
17
+
18
+ # converge
19
+ kit_wire >/dev/null 2>&1
20
+ t "shim created" "$([ -f .claude/statusline.sh ] && echo yes || echo no)" "yes"
21
+ t "shim is a shim" "$(grep -c 'claude-kit statusline shim' .claude/statusline.sh)" "1"
22
+ t "settings wired" "$(jq -r '.statusLine.command' .claude/settings.json 2>/dev/null)" '$CLAUDE_PROJECT_DIR/.claude/statusline.sh'
23
+ t "shim tracked" "$(. "$PLUGIN_DIR/scripts/lib/kit-manifest.sh"; kit_manifest_verify .claude/statusline.sh)" "intact"
24
+ t "settings tracked" "$(. "$PLUGIN_DIR/scripts/lib/kit-manifest.sh"; kit_manifest_verify .claude/settings.json)" "intact"
25
+
26
+ # idempotent: second converge changes nothing, shim still intact
27
+ kit_wire >/dev/null 2>&1
28
+ t "idempotent shim" "$(. "$PLUGIN_DIR/scripts/lib/kit-manifest.sh"; kit_manifest_verify .claude/statusline.sh)" "intact"
29
+
30
+ # --check is clean after converge (rc 0)
31
+ if kit_wire_check >/dev/null 2>&1; then t "check clean rc" "0" "0"; else t "check clean rc" "$?" "0"; fi
32
+
33
+ # hook executability: drop a non-exec hook, converge, assert it became executable
34
+ mkdir -p .claude/hooks; printf '#!/usr/bin/env bash\n:\n' > .claude/hooks/demo.sh; chmod -x .claude/hooks/demo.sh
35
+ kit_wire >/dev/null 2>&1
36
+ t "hook chmod +x" "$([ -x .claude/hooks/demo.sh ] && echo yes || echo no)" "yes"
37
+
38
+ # drift detection: user removes the shim -> --check reports drift (rc 1)
39
+ rm -f .claude/statusline.sh
40
+ if kit_wire_check >/dev/null 2>&1; then t "check detects drift" "0" "1"; else t "check detects drift" "1" "1"; fi
41
+
42
+ [ "$fail" -eq 0 ] && echo "ALL OK($KIT_WIRE_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_WIRE_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,132 @@
1
+ #!/usr/bin/env bash
2
+ # kit-wire.sh — idempotent "converge" that installs/repairs everything the kit must WIRE into a
3
+ # project, and keeps it wired across updates (#369). Fixes the live class of bug where /kit-update
4
+ # refreshes files but never re-runs the wiring, so settings drift and engine assets go stale.
5
+ #
6
+ # WHAT IT CONVERGES (each recorded in the F0 ownership manifest, so update/uninstall stay safe):
7
+ # 1. statusline shim .claude/statusline.sh -> a STABLE file that exec's the plugin's
8
+ # versioned kit-statusline.sh. The shim never changes, so a plugin bump
9
+ # updates the statusline everywhere with zero local edits (kills the
10
+ # "template drifted older than the project copy" bug).
11
+ # 2. settings statusLine ensures .claude/settings.json points its statusLine at the shim.
12
+ # 3. hook executability chmod +x on managed hooks so they actually run.
13
+ #
14
+ # Run: scripts/kit-wire.sh # converge (interactive confirms unless KIT_ASSUME_YES)
15
+ # scripts/kit-wire.sh --check # report drift only, write nothing (rc 1 if drift) — self-heal
16
+ # KIT_ASSUME_YES=1 scripts/kit-wire.sh # non-interactive (init/update/CI)
17
+ #
18
+ # Idempotent: a second run is a no-op. Honors KIT_DRY_RUN. Requires: jq.
19
+
20
+ set -uo pipefail
21
+ _wire_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd)"
22
+ # shellcheck source=/dev/null
23
+ . "$_wire_dir/lib/kit-operate.sh" # pulls in kit-manifest.sh too
24
+ # shellcheck source=/dev/null
25
+ . "$_wire_dir/lib/kit-cli.sh" # kit_is_main
26
+
27
+ # Where the running engine lives. As a Claude Code plugin, CLAUDE_PLUGIN_ROOT is set; otherwise
28
+ # fall back to this script's plugin dir (two levels up from scripts/).
29
+ kit_plugin_root() {
30
+ if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then printf '%s\n' "$CLAUDE_PLUGIN_ROOT"; return 0; fi
31
+ ( CDPATH='' cd -- "$_wire_dir/.." && pwd )
32
+ }
33
+
34
+ # The shim is intentionally tiny and STABLE — its content never encodes a version, so it survives
35
+ # plugin bumps. It resolves the newest installed kit plugin by glob and exec's its statusline.
36
+ # (Falls back to a co-located plugin root for dev/source checkouts.)
37
+ _kit_shim_content() {
38
+ cat <<'SHIM'
39
+ #!/usr/bin/env bash
40
+ # claude-kit statusline shim — DO NOT EDIT. Managed by `kit-wire` (kit.manifest.json).
41
+ # Stable indirection: resolves the installed kit plugin and exec's its versioned statusline,
42
+ # so a plugin update changes the statusline with no edit here. Fails soft (exit 0).
43
+ set -uo pipefail
44
+ _p="${CLAUDE_PLUGIN_ROOT:-}"
45
+ if [ -z "$_p" ]; then
46
+ _p="$(ls -d "$HOME"/.claude/plugins/cache/*/claude-kit*/ 2>/dev/null | sort -V | tail -1)"
47
+ fi
48
+ _sl="$_p/statusline/kit-statusline.sh"
49
+ [ -f "$_sl" ] || _sl="$_p/templates/statusline/kit-statusline.sh"
50
+ if [ -f "$_sl" ]; then exec bash "$_sl" "$@"; fi
51
+ cat >/dev/null 2>&1; exit 0 # no engine found: consume stdin, render nothing, never error
52
+ SHIM
53
+ }
54
+
55
+ # Ensure .claude/settings.json has statusLine -> the shim. Uses jq; creates the file if absent.
56
+ _kit_wire_settings() {
57
+ local settings=".claude/settings.json" shim=".claude/statusline.sh" cmd tmp cur want
58
+ cmd="\$CLAUDE_PROJECT_DIR/$shim"
59
+ if _kit_op_dry; then
60
+ cur="$(jq -r '.statusLine.command // empty' "$settings" 2>/dev/null || true)"
61
+ [ "$cur" = "$cmd" ] && { _kit_op_say "= settings.statusLine already wired"; return 0; }
62
+ _kit_op_say "~ would set settings.statusLine -> $cmd"; return 10
63
+ fi
64
+ [ -f "$settings" ] || printf '{}\n' > "$settings"
65
+ cur="$(jq -r '.statusLine.command // empty' "$settings" 2>/dev/null || true)"
66
+ if [ "$cur" = "$cmd" ]; then _kit_op_say "= settings.statusLine already wired"; return 0; fi
67
+ tmp="$(mktemp)" || return 1
68
+ jq --arg c "$cmd" '.statusLine = {type:"command", command:$c}' "$settings" > "$tmp" \
69
+ && mv "$tmp" "$settings" || { rm -f "$tmp"; return 1; }
70
+ kit_manifest_record "$settings" B wire >/dev/null 2>&1 || true
71
+ _kit_op_say "+ wired settings.statusLine -> $cmd"
72
+ }
73
+
74
+ # chmod +x any managed hooks present under .claude/hooks (so they actually run).
75
+ _kit_wire_hook_exec() {
76
+ local h changed=0
77
+ [ -d ".claude/hooks" ] || return 0
78
+ for h in .claude/hooks/*.sh; do
79
+ [ -f "$h" ] || continue
80
+ if [ ! -x "$h" ]; then
81
+ _kit_op_dry && { _kit_op_say "~ would chmod +x $h"; changed=1; continue; }
82
+ chmod +x "$h" && _kit_op_say "+ chmod +x $h" && changed=1
83
+ fi
84
+ done
85
+ return 0
86
+ }
87
+
88
+ kit_wire() {
89
+ command -v jq >/dev/null 2>&1 || { echo "kit-wire: jq required" >&2; return 1; }
90
+ local proot shim=".claude/statusline.sh"
91
+ proot="$(kit_plugin_root)"
92
+ _kit_op_say "kit-wire: plugin root = $proot"
93
+ mkdir -p .claude
94
+ # 1. statusline shim (tier B — CLI only). kit_op_write_content runs the conffiles machine.
95
+ _kit_shim_content | kit_op_write_content "$shim" B wire || return 1
96
+ [ -f "$shim" ] && [ -z "${KIT_DRY_RUN:-}" ] && chmod +x "$shim"
97
+ # 2. settings statusLine -> shim
98
+ _kit_wire_settings || true
99
+ # 3. hook executability
100
+ _kit_wire_hook_exec || true
101
+ }
102
+
103
+ # --check: report drift, write nothing, rc 1 if anything is out of sync (for SessionStart self-heal)
104
+ # Also verifies that every accepted routine in kit.config.json has a matching cron entry.
105
+ kit_wire_check() {
106
+ local drift=0
107
+ # 1. standard wire drift
108
+ KIT_DRY_RUN=1 kit_wire >/tmp/.kitwire.$$ 2>&1
109
+ if grep -qE '^[~+]|would' /tmp/.kitwire.$$; then
110
+ sed 's/^/ /' /tmp/.kitwire.$$ >&2; drift=1
111
+ fi
112
+ rm -f /tmp/.kitwire.$$
113
+ # 2. routines: check accepted crons are installed (fail-soft — missing crontab is not an error)
114
+ if command -v crontab >/dev/null 2>&1; then
115
+ local rt_script; rt_script="$(kit_plugin_root)/scripts/kit-routines.sh"
116
+ if [ -f "$rt_script" ]; then
117
+ bash "$rt_script" verify 2>/tmp/.kiwire-rt.$$ || { sed 's/^/ /' /tmp/.kiwire-rt.$$ >&2; drift=1; }
118
+ rm -f /tmp/.kiwire-rt.$$
119
+ fi
120
+ fi
121
+ return "$drift"
122
+ }
123
+
124
+ # CLI (direct execution only; zsh-safe guard)
125
+ # CLI (direct execution only)
126
+ if kit_is_main; then
127
+ case "${1:-}" in
128
+ --check) kit_wire_check;;
129
+ -h|--help) echo "usage: kit-wire.sh [--check] (env: KIT_ASSUME_YES, KIT_DRY_RUN)";;
130
+ *) kit_wire;;
131
+ esac
132
+ fi