feed-the-machine 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +170 -170
  3. package/bin/generate-manifest.mjs +463 -463
  4. package/bin/install.mjs +491 -491
  5. package/docs/HOOKS.md +243 -243
  6. package/docs/INBOX.md +233 -233
  7. package/ftm/SKILL.md +122 -122
  8. package/ftm-audit/SKILL.md +623 -541
  9. package/ftm-audit/references/protocols/PROJECT-PATTERNS.md +91 -91
  10. package/ftm-audit/references/protocols/RUNTIME-WIRING.md +66 -66
  11. package/ftm-audit/references/protocols/WIRING-CONTRACTS.md +135 -135
  12. package/ftm-audit/references/strategies/AUTO-FIX-STRATEGIES.md +69 -69
  13. package/ftm-audit/references/templates/REPORT-FORMAT.md +96 -96
  14. package/ftm-audit/scripts/run-knip.sh +23 -23
  15. package/ftm-audit.yml +2 -2
  16. package/ftm-brainstorm/SKILL.md +498 -498
  17. package/ftm-brainstorm/evals/evals.json +100 -100
  18. package/ftm-brainstorm/evals/promptfoo.yaml +109 -109
  19. package/ftm-brainstorm/references/agent-prompts.md +224 -224
  20. package/ftm-brainstorm/references/plan-template.md +121 -121
  21. package/ftm-brainstorm.yml +2 -2
  22. package/ftm-browse/SKILL.md +454 -454
  23. package/ftm-browse/daemon/browser-manager.ts +206 -206
  24. package/ftm-browse/daemon/bun.lock +30 -30
  25. package/ftm-browse/daemon/cli.ts +347 -347
  26. package/ftm-browse/daemon/commands.ts +410 -410
  27. package/ftm-browse/daemon/main.ts +357 -357
  28. package/ftm-browse/daemon/package.json +17 -17
  29. package/ftm-browse/daemon/server.ts +189 -189
  30. package/ftm-browse/daemon/snapshot.ts +519 -519
  31. package/ftm-browse/daemon/tsconfig.json +22 -22
  32. package/ftm-browse.yml +4 -4
  33. package/ftm-capture/SKILL.md +370 -370
  34. package/ftm-capture.yml +4 -4
  35. package/ftm-codex-gate/SKILL.md +361 -361
  36. package/ftm-codex-gate.yml +2 -2
  37. package/ftm-config/SKILL.md +345 -345
  38. package/ftm-config.default.yml +82 -80
  39. package/ftm-config.yml +2 -2
  40. package/ftm-council/SKILL.md +416 -416
  41. package/ftm-council/references/prompts/CLAUDE-INVESTIGATION.md +60 -60
  42. package/ftm-council/references/prompts/CODEX-INVESTIGATION.md +58 -58
  43. package/ftm-council/references/prompts/GEMINI-INVESTIGATION.md +58 -58
  44. package/ftm-council/references/prompts/REBUTTAL-TEMPLATE.md +57 -57
  45. package/ftm-council/references/protocols/PREREQUISITES.md +47 -47
  46. package/ftm-council/references/protocols/STEP-0-FRAMING.md +46 -46
  47. package/ftm-council.yml +2 -2
  48. package/ftm-dashboard/SKILL.md +163 -163
  49. package/ftm-dashboard.yml +4 -4
  50. package/ftm-debug/SKILL.md +1037 -1037
  51. package/ftm-debug/references/phases/PHASE-0-INTAKE.md +58 -58
  52. package/ftm-debug/references/phases/PHASE-1-TRIAGE.md +46 -46
  53. package/ftm-debug/references/phases/PHASE-2-WAR-ROOM-AGENTS.md +279 -279
  54. package/ftm-debug/references/phases/PHASE-3-TO-6-EXECUTION.md +436 -436
  55. package/ftm-debug/references/protocols/BLACKBOARD.md +86 -86
  56. package/ftm-debug/references/protocols/EDGE-CASES.md +103 -103
  57. package/ftm-debug.yml +2 -2
  58. package/ftm-diagram/SKILL.md +277 -277
  59. package/ftm-diagram.yml +2 -2
  60. package/ftm-executor/SKILL.md +777 -767
  61. package/ftm-executor/references/STYLE-TEMPLATE.md +73 -73
  62. package/ftm-executor/references/phases/PHASE-0-VERIFICATION.md +62 -62
  63. package/ftm-executor/references/phases/PHASE-2-AGENT-ASSEMBLY.md +34 -34
  64. package/ftm-executor/references/phases/PHASE-3-WORKTREES.md +38 -38
  65. package/ftm-executor/references/phases/PHASE-4-5-AUDIT.md +72 -72
  66. package/ftm-executor/references/phases/PHASE-4-DISPATCH.md +66 -66
  67. package/ftm-executor/references/phases/PHASE-5-5-CODEX-GATE.md +73 -73
  68. package/ftm-executor/references/protocols/DOCUMENTATION-BOOTSTRAP.md +36 -36
  69. package/ftm-executor/references/protocols/MODEL-PROFILE.md +59 -44
  70. package/ftm-executor/references/protocols/PROGRESS-TRACKING.md +66 -66
  71. package/ftm-executor/runtime/ftm-runtime.mjs +252 -252
  72. package/ftm-executor/runtime/package.json +8 -8
  73. package/ftm-executor.yml +2 -2
  74. package/ftm-git/SKILL.md +441 -441
  75. package/ftm-git/evals/evals.json +26 -26
  76. package/ftm-git/evals/promptfoo.yaml +75 -75
  77. package/ftm-git/hooks/post-commit-experience.sh +92 -92
  78. package/ftm-git/references/patterns/SECRET-PATTERNS.md +104 -104
  79. package/ftm-git/references/protocols/REMEDIATION.md +139 -139
  80. package/ftm-git/scripts/pre-commit-secrets.sh +110 -110
  81. package/ftm-git.yml +2 -2
  82. package/ftm-inbox/backend/adapters/_retry.py +64 -64
  83. package/ftm-inbox/backend/adapters/base.py +230 -230
  84. package/ftm-inbox/backend/adapters/freshservice.py +104 -104
  85. package/ftm-inbox/backend/adapters/gmail.py +125 -125
  86. package/ftm-inbox/backend/adapters/jira.py +136 -136
  87. package/ftm-inbox/backend/adapters/registry.py +192 -192
  88. package/ftm-inbox/backend/adapters/slack.py +110 -110
  89. package/ftm-inbox/backend/db/connection.py +54 -54
  90. package/ftm-inbox/backend/db/schema.py +78 -78
  91. package/ftm-inbox/backend/executor/__init__.py +7 -7
  92. package/ftm-inbox/backend/executor/engine.py +149 -149
  93. package/ftm-inbox/backend/executor/step_runner.py +98 -98
  94. package/ftm-inbox/backend/main.py +103 -103
  95. package/ftm-inbox/backend/models/__init__.py +1 -1
  96. package/ftm-inbox/backend/models/unified_task.py +36 -36
  97. package/ftm-inbox/backend/planner/__init__.py +6 -6
  98. package/ftm-inbox/backend/planner/generator.py +127 -127
  99. package/ftm-inbox/backend/planner/schema.py +34 -34
  100. package/ftm-inbox/backend/requirements.txt +5 -5
  101. package/ftm-inbox/backend/routes/execute.py +186 -186
  102. package/ftm-inbox/backend/routes/health.py +52 -52
  103. package/ftm-inbox/backend/routes/inbox.py +68 -68
  104. package/ftm-inbox/backend/routes/plan.py +271 -271
  105. package/ftm-inbox/bin/launchagent.mjs +91 -91
  106. package/ftm-inbox/bin/setup.mjs +188 -188
  107. package/ftm-inbox/bin/start.sh +10 -10
  108. package/ftm-inbox/bin/status.sh +17 -17
  109. package/ftm-inbox/bin/stop.sh +8 -8
  110. package/ftm-inbox/config.example.yml +55 -55
  111. package/ftm-inbox/package-lock.json +2898 -2898
  112. package/ftm-inbox/package.json +26 -26
  113. package/ftm-inbox/postcss.config.js +6 -6
  114. package/ftm-inbox/src/app.css +199 -199
  115. package/ftm-inbox/src/app.html +18 -18
  116. package/ftm-inbox/src/lib/api.ts +166 -166
  117. package/ftm-inbox/src/lib/components/ExecutionLog.svelte +81 -81
  118. package/ftm-inbox/src/lib/components/InboxFeed.svelte +143 -143
  119. package/ftm-inbox/src/lib/components/PlanStep.svelte +271 -271
  120. package/ftm-inbox/src/lib/components/PlanView.svelte +206 -206
  121. package/ftm-inbox/src/lib/components/StreamPanel.svelte +99 -99
  122. package/ftm-inbox/src/lib/components/TaskCard.svelte +190 -190
  123. package/ftm-inbox/src/lib/components/ui/EmptyState.svelte +63 -63
  124. package/ftm-inbox/src/lib/components/ui/KawaiiCard.svelte +86 -86
  125. package/ftm-inbox/src/lib/components/ui/PillButton.svelte +106 -106
  126. package/ftm-inbox/src/lib/components/ui/StatusBadge.svelte +67 -67
  127. package/ftm-inbox/src/lib/components/ui/StreamDrawer.svelte +149 -149
  128. package/ftm-inbox/src/lib/components/ui/ThemeToggle.svelte +80 -80
  129. package/ftm-inbox/src/lib/theme.ts +47 -47
  130. package/ftm-inbox/src/routes/+layout.svelte +76 -76
  131. package/ftm-inbox/src/routes/+page.svelte +401 -401
  132. package/ftm-inbox/svelte.config.js +12 -12
  133. package/ftm-inbox/tailwind.config.ts +63 -63
  134. package/ftm-inbox/tsconfig.json +13 -13
  135. package/ftm-inbox/vite.config.ts +6 -6
  136. package/ftm-intent/SKILL.md +241 -241
  137. package/ftm-intent.yml +2 -2
  138. package/ftm-manifest.json +3794 -3794
  139. package/ftm-map/SKILL.md +291 -291
  140. package/ftm-map/scripts/db.py +712 -712
  141. package/ftm-map/scripts/index.py +415 -415
  142. package/ftm-map/scripts/parser.py +224 -224
  143. package/ftm-map/scripts/queries/go-tags.scm +20 -20
  144. package/ftm-map/scripts/queries/javascript-tags.scm +35 -35
  145. package/ftm-map/scripts/queries/python-tags.scm +31 -31
  146. package/ftm-map/scripts/queries/ruby-tags.scm +19 -19
  147. package/ftm-map/scripts/queries/rust-tags.scm +37 -37
  148. package/ftm-map/scripts/queries/typescript-tags.scm +41 -41
  149. package/ftm-map/scripts/query.py +301 -301
  150. package/ftm-map/scripts/ranker.py +377 -377
  151. package/ftm-map/scripts/requirements.txt +5 -5
  152. package/ftm-map/scripts/setup-hooks.sh +27 -27
  153. package/ftm-map/scripts/setup.sh +56 -56
  154. package/ftm-map/scripts/test_db.py +364 -364
  155. package/ftm-map/scripts/test_parser.py +174 -174
  156. package/ftm-map/scripts/test_query.py +183 -183
  157. package/ftm-map/scripts/test_ranker.py +199 -199
  158. package/ftm-map/scripts/views.py +591 -591
  159. package/ftm-map.yml +2 -2
  160. package/ftm-mind/SKILL.md +1943 -1943
  161. package/ftm-mind/evals/promptfoo.yaml +142 -142
  162. package/ftm-mind/references/blackboard-schema.md +328 -328
  163. package/ftm-mind/references/complexity-guide.md +110 -110
  164. package/ftm-mind/references/event-registry.md +319 -319
  165. package/ftm-mind/references/mcp-inventory.md +296 -296
  166. package/ftm-mind/references/protocols/COMPLEXITY-SIZING.md +72 -72
  167. package/ftm-mind/references/protocols/MCP-HEURISTICS.md +32 -32
  168. package/ftm-mind/references/protocols/PLAN-APPROVAL.md +80 -80
  169. package/ftm-mind/references/reflexion-protocol.md +249 -249
  170. package/ftm-mind/references/routing/SCENARIOS.md +22 -22
  171. package/ftm-mind/references/routing-scenarios.md +35 -35
  172. package/ftm-mind.yml +2 -2
  173. package/ftm-pause/SKILL.md +395 -395
  174. package/ftm-pause/references/protocols/SKILL-RESTORE-PROTOCOLS.md +186 -186
  175. package/ftm-pause/references/protocols/VALIDATION.md +80 -80
  176. package/ftm-pause.yml +2 -2
  177. package/ftm-researcher/SKILL.md +275 -275
  178. package/ftm-researcher/evals/agent-diversity.yaml +17 -17
  179. package/ftm-researcher/evals/synthesis-quality.yaml +12 -12
  180. package/ftm-researcher/evals/trigger-accuracy.yaml +39 -39
  181. package/ftm-researcher/references/adaptive-search.md +116 -116
  182. package/ftm-researcher/references/agent-prompts.md +193 -193
  183. package/ftm-researcher/references/council-integration.md +193 -193
  184. package/ftm-researcher/references/output-format.md +203 -203
  185. package/ftm-researcher/references/synthesis-pipeline.md +165 -165
  186. package/ftm-researcher/scripts/score_credibility.py +234 -234
  187. package/ftm-researcher/scripts/validate_research.py +92 -92
  188. package/ftm-researcher.yml +2 -2
  189. package/ftm-resume/SKILL.md +518 -518
  190. package/ftm-resume/references/protocols/VALIDATION.md +172 -172
  191. package/ftm-resume.yml +2 -2
  192. package/ftm-retro/SKILL.md +380 -380
  193. package/ftm-retro/references/protocols/SCORING-RUBRICS.md +89 -89
  194. package/ftm-retro/references/templates/REPORT-FORMAT.md +109 -109
  195. package/ftm-retro.yml +2 -2
  196. package/ftm-routine/SKILL.md +170 -170
  197. package/ftm-routine.yml +4 -4
  198. package/ftm-state/blackboard/capabilities.json +5 -5
  199. package/ftm-state/blackboard/capabilities.schema.json +27 -27
  200. package/ftm-state/blackboard/context.json +23 -23
  201. package/ftm-state/blackboard/experiences/index.json +9 -9
  202. package/ftm-state/blackboard/patterns.json +6 -6
  203. package/ftm-state/schemas/context.schema.json +130 -130
  204. package/ftm-state/schemas/experience-index.schema.json +77 -77
  205. package/ftm-state/schemas/experience.schema.json +78 -78
  206. package/ftm-state/schemas/patterns.schema.json +44 -44
  207. package/ftm-upgrade/SKILL.md +194 -194
  208. package/ftm-upgrade/scripts/check-version.sh +76 -76
  209. package/ftm-upgrade/scripts/upgrade.sh +143 -143
  210. package/ftm-upgrade.yml +2 -2
  211. package/ftm-verify.yml +2 -2
  212. package/ftm.yml +2 -2
  213. package/hooks/ftm-blackboard-enforcer.sh +93 -93
  214. package/hooks/ftm-discovery-reminder.sh +90 -90
  215. package/hooks/ftm-drafts-gate.sh +61 -61
  216. package/hooks/ftm-event-logger.mjs +107 -107
  217. package/hooks/ftm-map-autodetect.sh +79 -79
  218. package/hooks/ftm-pending-sync-check.sh +22 -22
  219. package/hooks/ftm-plan-gate.sh +92 -92
  220. package/hooks/ftm-post-commit-trigger.sh +57 -57
  221. package/hooks/settings-template.json +81 -81
  222. package/install.sh +363 -363
  223. package/package.json +84 -84
  224. package/uninstall.sh +25 -25
package/install.sh CHANGED
@@ -1,363 +1,363 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- # FTM Skills Installer
5
- # Creates symlinks from this repo into ~/.claude/skills/ so slash commands work.
6
- # Installs hooks, merges them into settings.json, and verifies the result.
7
- # Safe to re-run — idempotent.
8
- #
9
- # Usage:
10
- # ./install.sh # Full install (skills + hooks + settings merge)
11
- # ./install.sh --no-hooks # Skills and state only, skip hooks entirely
12
- # ./install.sh --skip-merge # Install hook files but don't touch settings.json
13
-
14
- REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
15
- SKILLS_DIR="$HOME/.claude/skills"
16
- STATE_DIR="$HOME/.claude/ftm-state"
17
- CONFIG_DIR="$HOME/.claude"
18
- HOOKS_DIR="$HOME/.claude/hooks"
19
- SETTINGS_FILE="$CONFIG_DIR/settings.json"
20
-
21
- NO_HOOKS=false
22
- SKIP_MERGE=false
23
- for arg in "$@"; do
24
- case "$arg" in
25
- --no-hooks) NO_HOOKS=true ;;
26
- --skip-merge) SKIP_MERGE=true ;;
27
- # Keep --setup-hooks for backwards compat (now a no-op since merge is default)
28
- --setup-hooks) ;;
29
- esac
30
- done
31
-
32
- WARN_COUNT=0
33
- warn() {
34
- echo " WARN: $1"
35
- WARN_COUNT=$((WARN_COUNT + 1))
36
- }
37
-
38
- # --- Preflight Checks ---
39
-
40
- echo "Preflight checks..."
41
-
42
- # Check jq (required for hooks and settings merge)
43
- if ! command -v jq &>/dev/null; then
44
- if [ "$NO_HOOKS" = true ]; then
45
- echo " jq not found (ok — hooks skipped)"
46
- else
47
- echo ""
48
- echo " ERROR: jq is required for FTM hooks."
49
- echo ""
50
- echo " Install it:"
51
- echo " macOS: brew install jq"
52
- echo " Ubuntu: sudo apt-get install jq"
53
- echo " Alpine: apk add jq"
54
- echo ""
55
- echo " Or skip hooks: ./install.sh --no-hooks"
56
- exit 1
57
- fi
58
- else
59
- echo " jq: $(jq --version)"
60
- fi
61
-
62
- # Check node (required for event logger hook)
63
- if ! command -v node &>/dev/null; then
64
- if [ "$NO_HOOKS" = true ]; then
65
- echo " node not found (ok — hooks skipped)"
66
- else
67
- echo ""
68
- echo " ERROR: Node.js is required for the FTM event logger hook."
69
- echo ""
70
- echo " Install it: https://nodejs.org/"
71
- echo " Or skip hooks: ./install.sh --no-hooks"
72
- exit 1
73
- fi
74
- else
75
- echo " node: $(node --version)"
76
- fi
77
-
78
- echo ""
79
- echo "Installing FTM skills from: $REPO_DIR"
80
- echo "Linking into: $SKILLS_DIR"
81
- echo ""
82
-
83
- mkdir -p "$SKILLS_DIR"
84
-
85
- # --- Skills ---
86
-
87
- # Link all ftm*.yml files
88
- for yml in "$REPO_DIR"/ftm*.yml; do
89
- [ -f "$yml" ] || continue
90
- name=$(basename "$yml")
91
- # Skip ftm-config.default.yml — it's a template, not a skill
92
- [[ "$name" == *".default."* ]] && continue
93
- target="$SKILLS_DIR/$name"
94
- if [ -L "$target" ]; then
95
- rm "$target"
96
- elif [ -f "$target" ]; then
97
- echo " SKIP $name (real file exists — back it up first)"
98
- continue
99
- fi
100
- ln -s "$yml" "$target"
101
- echo " LINK $name"
102
- done
103
-
104
- # Link all ftm* directories (skills with SKILL.md)
105
- for dir in "$REPO_DIR"/ftm*/; do
106
- [ -d "$dir" ] || continue
107
- name=$(basename "$dir")
108
- [ "$name" = "ftm-state" ] && continue # state is handled separately
109
- target="$SKILLS_DIR/$name"
110
- if [ -L "$target" ]; then
111
- rm "$target"
112
- elif [ -d "$target" ]; then
113
- echo " SKIP $name/ (real directory exists — back it up first)"
114
- continue
115
- fi
116
- ln -s "$dir" "$target"
117
- echo " LINK $name/"
118
- done
119
-
120
- SKILL_COUNT=0
121
- for _f in "$REPO_DIR"/ftm*.yml; do
122
- [ -e "$_f" ] || continue
123
- case "$_f" in *.default.*) continue ;; esac
124
- SKILL_COUNT=$((SKILL_COUNT + 1))
125
- done
126
- echo ""
127
- echo " $SKILL_COUNT skills linked."
128
-
129
- # --- Blackboard State ---
130
-
131
- if [ -d "$REPO_DIR/ftm-state" ]; then
132
- echo ""
133
- mkdir -p "$STATE_DIR/blackboard/experiences"
134
- for f in "$REPO_DIR/ftm-state/blackboard"/*.json; do
135
- [ -f "$f" ] || continue
136
- name=$(basename "$f")
137
- target="$STATE_DIR/blackboard/$name"
138
- if [ ! -f "$target" ]; then
139
- cp "$f" "$target"
140
- echo " INIT $name (blackboard template)"
141
- fi
142
- done
143
- idx="$STATE_DIR/blackboard/experiences/index.json"
144
- if [ ! -f "$idx" ]; then
145
- cp "$REPO_DIR/ftm-state/blackboard/experiences/index.json" "$idx"
146
- echo " INIT experiences/index.json (blackboard template)"
147
- fi
148
- fi
149
-
150
- # --- Config ---
151
-
152
- if [ ! -f "$CONFIG_DIR/ftm-config.yml" ] && [ -f "$REPO_DIR/ftm-config.default.yml" ]; then
153
- cp "$REPO_DIR/ftm-config.default.yml" "$CONFIG_DIR/ftm-config.yml"
154
- echo " INIT ftm-config.yml (from default template)"
155
- fi
156
-
157
- # --- Hooks ---
158
-
159
- HOOK_COUNT=0
160
-
161
- if [ "$NO_HOOKS" = true ]; then
162
- echo ""
163
- echo "Skipping hooks (--no-hooks)."
164
- else
165
- echo ""
166
- echo "Installing hooks..."
167
-
168
- if [ -d "$REPO_DIR/hooks" ]; then
169
- mkdir -p "$HOOKS_DIR"
170
-
171
- # Install shell hooks
172
- for hook in "$REPO_DIR/hooks"/ftm-*.sh; do
173
- [ -f "$hook" ] || continue
174
- name=$(basename "$hook")
175
- target="$HOOKS_DIR/$name"
176
- if [ -f "$target" ]; then
177
- cp "$hook" "$target"
178
- chmod +x "$target"
179
- echo " UPDATE $name"
180
- else
181
- cp "$hook" "$target"
182
- chmod +x "$target"
183
- echo " INSTALL $name"
184
- fi
185
- HOOK_COUNT=$((HOOK_COUNT + 1))
186
- done
187
-
188
- # Install Node.js hooks
189
- for hook in "$REPO_DIR/hooks"/ftm-*.mjs; do
190
- [ -f "$hook" ] || continue
191
- name=$(basename "$hook")
192
- target="$HOOKS_DIR/$name"
193
- if [ -f "$target" ]; then
194
- cp "$hook" "$target"
195
- echo " UPDATE $name"
196
- else
197
- cp "$hook" "$target"
198
- echo " INSTALL $name"
199
- fi
200
- HOOK_COUNT=$((HOOK_COUNT + 1))
201
- done
202
-
203
- echo ""
204
- echo " $HOOK_COUNT hooks installed to $HOOKS_DIR"
205
- fi
206
-
207
- # --- Hook Config Merge (default behavior now) ---
208
-
209
- if [ "$SKIP_MERGE" = true ]; then
210
- echo ""
211
- echo " Skipping settings.json merge (--skip-merge)."
212
- echo " Add entries from hooks/settings-template.json to ~/.claude/settings.json manually."
213
- else
214
- echo ""
215
- echo "Registering hooks in settings.json..."
216
-
217
- TEMPLATE="$REPO_DIR/hooks/settings-template.json"
218
- if [ ! -f "$TEMPLATE" ]; then
219
- warn "hooks/settings-template.json not found — hooks installed but not registered"
220
- else
221
- # Expand ~ to $HOME in the template (jq doesn't expand shell paths)
222
- EXPANDED_TEMPLATE=$(sed "s|~/.claude|$HOME/.claude|g" "$TEMPLATE")
223
-
224
- if [ ! -f "$SETTINGS_FILE" ]; then
225
- # No settings.json — create one from the template hooks section
226
- echo "$EXPANDED_TEMPLATE" | jq '{hooks: .hooks}' > "$SETTINGS_FILE"
227
- echo " CREATED $SETTINGS_FILE with FTM hooks"
228
- else
229
- # Merge FTM hooks into existing settings.json
230
- BACKUP="$SETTINGS_FILE.ftm-backup-$(date +%Y%m%d%H%M%S)"
231
- cp "$SETTINGS_FILE" "$BACKUP"
232
- echo " BACKUP $BACKUP"
233
-
234
- # Extract the hooks section from the template
235
- TEMPLATE_HOOKS=$(echo "$EXPANDED_TEMPLATE" | jq '.hooks')
236
-
237
- # Read existing settings
238
- EXISTING=$(cat "$SETTINGS_FILE")
239
-
240
- # Ensure hooks key exists
241
- if echo "$EXISTING" | jq -e '.hooks' >/dev/null 2>&1; then
242
- : # hooks key exists
243
- else
244
- EXISTING=$(echo "$EXISTING" | jq '. + {hooks: {}}')
245
- fi
246
-
247
- # Merge each hook event type
248
- for EVENT in PreToolUse UserPromptSubmit PostToolUse Stop; do
249
- TEMPLATE_ENTRIES=$(echo "$TEMPLATE_HOOKS" | jq --arg e "$EVENT" '.[$e] // []')
250
- EXISTING_ENTRIES=$(echo "$EXISTING" | jq --arg e "$EVENT" '.hooks[$e] // []')
251
-
252
- # Check if any FTM hooks are already present (by checking command paths)
253
- FTM_COMMANDS=$(echo "$TEMPLATE_ENTRIES" | jq -r '.[].hooks[]?.command // empty' 2>/dev/null)
254
- ALREADY_PRESENT=false
255
-
256
- for cmd in $FTM_COMMANDS; do
257
- cmd_basename=$(basename "$cmd")
258
- if echo "$EXISTING_ENTRIES" | jq -r '.[].hooks[]?.command // empty' 2>/dev/null | grep -q "$cmd_basename"; then
259
- ALREADY_PRESENT=true
260
- break
261
- fi
262
- done
263
-
264
- if [ "$ALREADY_PRESENT" = true ]; then
265
- echo " SKIP $EVENT hooks (already configured)"
266
- continue
267
- fi
268
-
269
- # Append template entries to existing
270
- MERGED=$(jq -n --argjson existing "$EXISTING_ENTRIES" --argjson template "$TEMPLATE_ENTRIES" '$existing + $template')
271
- EXISTING=$(echo "$EXISTING" | jq --arg e "$EVENT" --argjson m "$MERGED" '.hooks[$e] = $m')
272
- echo " MERGE $EVENT hooks"
273
- done
274
-
275
- echo "$EXISTING" | jq '.' > "$SETTINGS_FILE"
276
- echo " UPDATED $SETTINGS_FILE"
277
- fi
278
-
279
- echo ""
280
- echo " Hooks are active."
281
- fi
282
- fi
283
- fi
284
-
285
- # --- Verification ---
286
-
287
- echo ""
288
- echo "Verifying installation..."
289
-
290
- ERRORS=0
291
-
292
- # Check skill symlinks resolve
293
- BROKEN_LINKS=0
294
- for link in "$SKILLS_DIR"/ftm*; do
295
- [ -L "$link" ] || continue
296
- if [ ! -e "$link" ]; then
297
- warn "broken symlink: $link"
298
- BROKEN_LINKS=$((BROKEN_LINKS + 1))
299
- fi
300
- done
301
- if [ "$BROKEN_LINKS" -eq 0 ]; then
302
- echo " Skills: $SKILL_COUNT linked, all symlinks valid"
303
- else
304
- ERRORS=$((ERRORS + 1))
305
- fi
306
-
307
- # Check blackboard state
308
- if [ -f "$STATE_DIR/blackboard/context.json" ] && [ -f "$STATE_DIR/blackboard/patterns.json" ]; then
309
- echo " Blackboard: initialized"
310
- else
311
- warn "blackboard state incomplete"
312
- ERRORS=$((ERRORS + 1))
313
- fi
314
-
315
- # Check config
316
- if [ -f "$CONFIG_DIR/ftm-config.yml" ]; then
317
- echo " Config: present"
318
- else
319
- warn "ftm-config.yml missing"
320
- ERRORS=$((ERRORS + 1))
321
- fi
322
-
323
- # Check hooks (if installed)
324
- if [ "$NO_HOOKS" = false ] && [ "$HOOK_COUNT" -gt 0 ]; then
325
- # Verify hook files exist and are executable
326
- HOOK_OK=true
327
- for hook in "$HOOKS_DIR"/ftm-*.sh; do
328
- [ -f "$hook" ] || continue
329
- if [ ! -x "$hook" ]; then
330
- warn "$(basename "$hook") not executable"
331
- HOOK_OK=false
332
- fi
333
- done
334
-
335
- if [ "$HOOK_OK" = true ]; then
336
- echo " Hooks: $HOOK_COUNT installed, all executable"
337
- else
338
- ERRORS=$((ERRORS + 1))
339
- fi
340
-
341
- # Verify settings.json has FTM hooks registered
342
- if [ "$SKIP_MERGE" = false ] && [ -f "$SETTINGS_FILE" ]; then
343
- FTM_REGISTERED=$(grep -c 'ftm-' "$SETTINGS_FILE" 2>/dev/null || echo "0")
344
- if [ "$FTM_REGISTERED" -gt 0 ]; then
345
- echo " Settings: $FTM_REGISTERED FTM entries in settings.json"
346
- else
347
- warn "no FTM hooks found in settings.json"
348
- ERRORS=$((ERRORS + 1))
349
- fi
350
- fi
351
- fi
352
-
353
- # --- Summary ---
354
-
355
- echo ""
356
- if [ "$ERRORS" -eq 0 ] && [ "$WARN_COUNT" -eq 0 ]; then
357
- echo "Done. $SKILL_COUNT skills, $HOOK_COUNT hooks. Everything checks out."
358
- else
359
- echo "Done. $SKILL_COUNT skills, $HOOK_COUNT hooks. $WARN_COUNT warning(s)."
360
- fi
361
- echo ""
362
- echo "Restart Claude Code (or start a new session) to pick up the skills."
363
- echo "Try: /ftm help"
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # FTM Skills Installer
5
+ # Creates symlinks from this repo into ~/.claude/skills/ so slash commands work.
6
+ # Installs hooks, merges them into settings.json, and verifies the result.
7
+ # Safe to re-run — idempotent.
8
+ #
9
+ # Usage:
10
+ # ./install.sh # Full install (skills + hooks + settings merge)
11
+ # ./install.sh --no-hooks # Skills and state only, skip hooks entirely
12
+ # ./install.sh --skip-merge # Install hook files but don't touch settings.json
13
+
14
+ REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
15
+ SKILLS_DIR="$HOME/.claude/skills"
16
+ STATE_DIR="$HOME/.claude/ftm-state"
17
+ CONFIG_DIR="$HOME/.claude"
18
+ HOOKS_DIR="$HOME/.claude/hooks"
19
+ SETTINGS_FILE="$CONFIG_DIR/settings.json"
20
+
21
+ NO_HOOKS=false
22
+ SKIP_MERGE=false
23
+ for arg in "$@"; do
24
+ case "$arg" in
25
+ --no-hooks) NO_HOOKS=true ;;
26
+ --skip-merge) SKIP_MERGE=true ;;
27
+ # Keep --setup-hooks for backwards compat (now a no-op since merge is default)
28
+ --setup-hooks) ;;
29
+ esac
30
+ done
31
+
32
+ WARN_COUNT=0
33
+ warn() {
34
+ echo " WARN: $1"
35
+ WARN_COUNT=$((WARN_COUNT + 1))
36
+ }
37
+
38
+ # --- Preflight Checks ---
39
+
40
+ echo "Preflight checks..."
41
+
42
+ # Check jq (required for hooks and settings merge)
43
+ if ! command -v jq &>/dev/null; then
44
+ if [ "$NO_HOOKS" = true ]; then
45
+ echo " jq not found (ok — hooks skipped)"
46
+ else
47
+ echo ""
48
+ echo " ERROR: jq is required for FTM hooks."
49
+ echo ""
50
+ echo " Install it:"
51
+ echo " macOS: brew install jq"
52
+ echo " Ubuntu: sudo apt-get install jq"
53
+ echo " Alpine: apk add jq"
54
+ echo ""
55
+ echo " Or skip hooks: ./install.sh --no-hooks"
56
+ exit 1
57
+ fi
58
+ else
59
+ echo " jq: $(jq --version)"
60
+ fi
61
+
62
+ # Check node (required for event logger hook)
63
+ if ! command -v node &>/dev/null; then
64
+ if [ "$NO_HOOKS" = true ]; then
65
+ echo " node not found (ok — hooks skipped)"
66
+ else
67
+ echo ""
68
+ echo " ERROR: Node.js is required for the FTM event logger hook."
69
+ echo ""
70
+ echo " Install it: https://nodejs.org/"
71
+ echo " Or skip hooks: ./install.sh --no-hooks"
72
+ exit 1
73
+ fi
74
+ else
75
+ echo " node: $(node --version)"
76
+ fi
77
+
78
+ echo ""
79
+ echo "Installing FTM skills from: $REPO_DIR"
80
+ echo "Linking into: $SKILLS_DIR"
81
+ echo ""
82
+
83
+ mkdir -p "$SKILLS_DIR"
84
+
85
+ # --- Skills ---
86
+
87
+ # Link all ftm*.yml files
88
+ for yml in "$REPO_DIR"/ftm*.yml; do
89
+ [ -f "$yml" ] || continue
90
+ name=$(basename "$yml")
91
+ # Skip ftm-config.default.yml — it's a template, not a skill
92
+ [[ "$name" == *".default."* ]] && continue
93
+ target="$SKILLS_DIR/$name"
94
+ if [ -L "$target" ]; then
95
+ rm "$target"
96
+ elif [ -f "$target" ]; then
97
+ echo " SKIP $name (real file exists — back it up first)"
98
+ continue
99
+ fi
100
+ ln -s "$yml" "$target"
101
+ echo " LINK $name"
102
+ done
103
+
104
+ # Link all ftm* directories (skills with SKILL.md)
105
+ for dir in "$REPO_DIR"/ftm*/; do
106
+ [ -d "$dir" ] || continue
107
+ name=$(basename "$dir")
108
+ [ "$name" = "ftm-state" ] && continue # state is handled separately
109
+ target="$SKILLS_DIR/$name"
110
+ if [ -L "$target" ]; then
111
+ rm "$target"
112
+ elif [ -d "$target" ]; then
113
+ echo " SKIP $name/ (real directory exists — back it up first)"
114
+ continue
115
+ fi
116
+ ln -s "$dir" "$target"
117
+ echo " LINK $name/"
118
+ done
119
+
120
+ SKILL_COUNT=0
121
+ for _f in "$REPO_DIR"/ftm*.yml; do
122
+ [ -e "$_f" ] || continue
123
+ case "$_f" in *.default.*) continue ;; esac
124
+ SKILL_COUNT=$((SKILL_COUNT + 1))
125
+ done
126
+ echo ""
127
+ echo " $SKILL_COUNT skills linked."
128
+
129
+ # --- Blackboard State ---
130
+
131
+ if [ -d "$REPO_DIR/ftm-state" ]; then
132
+ echo ""
133
+ mkdir -p "$STATE_DIR/blackboard/experiences"
134
+ for f in "$REPO_DIR/ftm-state/blackboard"/*.json; do
135
+ [ -f "$f" ] || continue
136
+ name=$(basename "$f")
137
+ target="$STATE_DIR/blackboard/$name"
138
+ if [ ! -f "$target" ]; then
139
+ cp "$f" "$target"
140
+ echo " INIT $name (blackboard template)"
141
+ fi
142
+ done
143
+ idx="$STATE_DIR/blackboard/experiences/index.json"
144
+ if [ ! -f "$idx" ]; then
145
+ cp "$REPO_DIR/ftm-state/blackboard/experiences/index.json" "$idx"
146
+ echo " INIT experiences/index.json (blackboard template)"
147
+ fi
148
+ fi
149
+
150
+ # --- Config ---
151
+
152
+ if [ ! -f "$CONFIG_DIR/ftm-config.yml" ] && [ -f "$REPO_DIR/ftm-config.default.yml" ]; then
153
+ cp "$REPO_DIR/ftm-config.default.yml" "$CONFIG_DIR/ftm-config.yml"
154
+ echo " INIT ftm-config.yml (from default template)"
155
+ fi
156
+
157
+ # --- Hooks ---
158
+
159
+ HOOK_COUNT=0
160
+
161
+ if [ "$NO_HOOKS" = true ]; then
162
+ echo ""
163
+ echo "Skipping hooks (--no-hooks)."
164
+ else
165
+ echo ""
166
+ echo "Installing hooks..."
167
+
168
+ if [ -d "$REPO_DIR/hooks" ]; then
169
+ mkdir -p "$HOOKS_DIR"
170
+
171
+ # Install shell hooks
172
+ for hook in "$REPO_DIR/hooks"/ftm-*.sh; do
173
+ [ -f "$hook" ] || continue
174
+ name=$(basename "$hook")
175
+ target="$HOOKS_DIR/$name"
176
+ if [ -f "$target" ]; then
177
+ cp "$hook" "$target"
178
+ chmod +x "$target"
179
+ echo " UPDATE $name"
180
+ else
181
+ cp "$hook" "$target"
182
+ chmod +x "$target"
183
+ echo " INSTALL $name"
184
+ fi
185
+ HOOK_COUNT=$((HOOK_COUNT + 1))
186
+ done
187
+
188
+ # Install Node.js hooks
189
+ for hook in "$REPO_DIR/hooks"/ftm-*.mjs; do
190
+ [ -f "$hook" ] || continue
191
+ name=$(basename "$hook")
192
+ target="$HOOKS_DIR/$name"
193
+ if [ -f "$target" ]; then
194
+ cp "$hook" "$target"
195
+ echo " UPDATE $name"
196
+ else
197
+ cp "$hook" "$target"
198
+ echo " INSTALL $name"
199
+ fi
200
+ HOOK_COUNT=$((HOOK_COUNT + 1))
201
+ done
202
+
203
+ echo ""
204
+ echo " $HOOK_COUNT hooks installed to $HOOKS_DIR"
205
+ fi
206
+
207
+ # --- Hook Config Merge (default behavior now) ---
208
+
209
+ if [ "$SKIP_MERGE" = true ]; then
210
+ echo ""
211
+ echo " Skipping settings.json merge (--skip-merge)."
212
+ echo " Add entries from hooks/settings-template.json to ~/.claude/settings.json manually."
213
+ else
214
+ echo ""
215
+ echo "Registering hooks in settings.json..."
216
+
217
+ TEMPLATE="$REPO_DIR/hooks/settings-template.json"
218
+ if [ ! -f "$TEMPLATE" ]; then
219
+ warn "hooks/settings-template.json not found — hooks installed but not registered"
220
+ else
221
+ # Expand ~ to $HOME in the template (jq doesn't expand shell paths)
222
+ EXPANDED_TEMPLATE=$(sed "s|~/.claude|$HOME/.claude|g" "$TEMPLATE")
223
+
224
+ if [ ! -f "$SETTINGS_FILE" ]; then
225
+ # No settings.json — create one from the template hooks section
226
+ echo "$EXPANDED_TEMPLATE" | jq '{hooks: .hooks}' > "$SETTINGS_FILE"
227
+ echo " CREATED $SETTINGS_FILE with FTM hooks"
228
+ else
229
+ # Merge FTM hooks into existing settings.json
230
+ BACKUP="$SETTINGS_FILE.ftm-backup-$(date +%Y%m%d%H%M%S)"
231
+ cp "$SETTINGS_FILE" "$BACKUP"
232
+ echo " BACKUP $BACKUP"
233
+
234
+ # Extract the hooks section from the template
235
+ TEMPLATE_HOOKS=$(echo "$EXPANDED_TEMPLATE" | jq '.hooks')
236
+
237
+ # Read existing settings
238
+ EXISTING=$(cat "$SETTINGS_FILE")
239
+
240
+ # Ensure hooks key exists
241
+ if echo "$EXISTING" | jq -e '.hooks' >/dev/null 2>&1; then
242
+ : # hooks key exists
243
+ else
244
+ EXISTING=$(echo "$EXISTING" | jq '. + {hooks: {}}')
245
+ fi
246
+
247
+ # Merge each hook event type
248
+ for EVENT in PreToolUse UserPromptSubmit PostToolUse Stop; do
249
+ TEMPLATE_ENTRIES=$(echo "$TEMPLATE_HOOKS" | jq --arg e "$EVENT" '.[$e] // []')
250
+ EXISTING_ENTRIES=$(echo "$EXISTING" | jq --arg e "$EVENT" '.hooks[$e] // []')
251
+
252
+ # Check if any FTM hooks are already present (by checking command paths)
253
+ FTM_COMMANDS=$(echo "$TEMPLATE_ENTRIES" | jq -r '.[].hooks[]?.command // empty' 2>/dev/null)
254
+ ALREADY_PRESENT=false
255
+
256
+ for cmd in $FTM_COMMANDS; do
257
+ cmd_basename=$(basename "$cmd")
258
+ if echo "$EXISTING_ENTRIES" | jq -r '.[].hooks[]?.command // empty' 2>/dev/null | grep -q "$cmd_basename"; then
259
+ ALREADY_PRESENT=true
260
+ break
261
+ fi
262
+ done
263
+
264
+ if [ "$ALREADY_PRESENT" = true ]; then
265
+ echo " SKIP $EVENT hooks (already configured)"
266
+ continue
267
+ fi
268
+
269
+ # Append template entries to existing
270
+ MERGED=$(jq -n --argjson existing "$EXISTING_ENTRIES" --argjson template "$TEMPLATE_ENTRIES" '$existing + $template')
271
+ EXISTING=$(echo "$EXISTING" | jq --arg e "$EVENT" --argjson m "$MERGED" '.hooks[$e] = $m')
272
+ echo " MERGE $EVENT hooks"
273
+ done
274
+
275
+ echo "$EXISTING" | jq '.' > "$SETTINGS_FILE"
276
+ echo " UPDATED $SETTINGS_FILE"
277
+ fi
278
+
279
+ echo ""
280
+ echo " Hooks are active."
281
+ fi
282
+ fi
283
+ fi
284
+
285
+ # --- Verification ---
286
+
287
+ echo ""
288
+ echo "Verifying installation..."
289
+
290
+ ERRORS=0
291
+
292
+ # Check skill symlinks resolve
293
+ BROKEN_LINKS=0
294
+ for link in "$SKILLS_DIR"/ftm*; do
295
+ [ -L "$link" ] || continue
296
+ if [ ! -e "$link" ]; then
297
+ warn "broken symlink: $link"
298
+ BROKEN_LINKS=$((BROKEN_LINKS + 1))
299
+ fi
300
+ done
301
+ if [ "$BROKEN_LINKS" -eq 0 ]; then
302
+ echo " Skills: $SKILL_COUNT linked, all symlinks valid"
303
+ else
304
+ ERRORS=$((ERRORS + 1))
305
+ fi
306
+
307
+ # Check blackboard state
308
+ if [ -f "$STATE_DIR/blackboard/context.json" ] && [ -f "$STATE_DIR/blackboard/patterns.json" ]; then
309
+ echo " Blackboard: initialized"
310
+ else
311
+ warn "blackboard state incomplete"
312
+ ERRORS=$((ERRORS + 1))
313
+ fi
314
+
315
+ # Check config
316
+ if [ -f "$CONFIG_DIR/ftm-config.yml" ]; then
317
+ echo " Config: present"
318
+ else
319
+ warn "ftm-config.yml missing"
320
+ ERRORS=$((ERRORS + 1))
321
+ fi
322
+
323
+ # Check hooks (if installed)
324
+ if [ "$NO_HOOKS" = false ] && [ "$HOOK_COUNT" -gt 0 ]; then
325
+ # Verify hook files exist and are executable
326
+ HOOK_OK=true
327
+ for hook in "$HOOKS_DIR"/ftm-*.sh; do
328
+ [ -f "$hook" ] || continue
329
+ if [ ! -x "$hook" ]; then
330
+ warn "$(basename "$hook") not executable"
331
+ HOOK_OK=false
332
+ fi
333
+ done
334
+
335
+ if [ "$HOOK_OK" = true ]; then
336
+ echo " Hooks: $HOOK_COUNT installed, all executable"
337
+ else
338
+ ERRORS=$((ERRORS + 1))
339
+ fi
340
+
341
+ # Verify settings.json has FTM hooks registered
342
+ if [ "$SKIP_MERGE" = false ] && [ -f "$SETTINGS_FILE" ]; then
343
+ FTM_REGISTERED=$(grep -c 'ftm-' "$SETTINGS_FILE" 2>/dev/null || echo "0")
344
+ if [ "$FTM_REGISTERED" -gt 0 ]; then
345
+ echo " Settings: $FTM_REGISTERED FTM entries in settings.json"
346
+ else
347
+ warn "no FTM hooks found in settings.json"
348
+ ERRORS=$((ERRORS + 1))
349
+ fi
350
+ fi
351
+ fi
352
+
353
+ # --- Summary ---
354
+
355
+ echo ""
356
+ if [ "$ERRORS" -eq 0 ] && [ "$WARN_COUNT" -eq 0 ]; then
357
+ echo "Done. $SKILL_COUNT skills, $HOOK_COUNT hooks. Everything checks out."
358
+ else
359
+ echo "Done. $SKILL_COUNT skills, $HOOK_COUNT hooks. $WARN_COUNT warning(s)."
360
+ fi
361
+ echo ""
362
+ echo "Restart Claude Code (or start a new session) to pick up the skills."
363
+ echo "Try: /ftm help"