feed-the-machine 1.6.1 → 1.7.1

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 (272) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +262 -170
  3. package/bin/__pycache__/tasks_db.cpython-314.pyc +0 -0
  4. package/bin/brain.py +1340 -0
  5. package/bin/convert_claude_skills_to_codex.py +490 -0
  6. package/bin/generate-manifest.mjs +463 -463
  7. package/bin/harden_codex_skills.py +141 -0
  8. package/bin/install.mjs +491 -491
  9. package/bin/migrate-eng-buddy-data.py +875 -0
  10. package/bin/playbook_engine/__init__.py +1 -0
  11. package/bin/playbook_engine/conftest.py +8 -0
  12. package/bin/playbook_engine/extractor.py +33 -0
  13. package/bin/playbook_engine/manager.py +102 -0
  14. package/bin/playbook_engine/models.py +84 -0
  15. package/bin/playbook_engine/registry.py +35 -0
  16. package/bin/playbook_engine/test_extractor.py +72 -0
  17. package/bin/playbook_engine/test_integration.py +129 -0
  18. package/bin/playbook_engine/test_manager.py +85 -0
  19. package/bin/playbook_engine/test_models.py +166 -0
  20. package/bin/playbook_engine/test_registry.py +67 -0
  21. package/bin/playbook_engine/test_tracer.py +86 -0
  22. package/bin/playbook_engine/tracer.py +93 -0
  23. package/bin/tasks_db.py +456 -0
  24. package/docs/HOOKS.md +243 -243
  25. package/docs/INBOX.md +233 -233
  26. package/ftm/SKILL.md +125 -122
  27. package/ftm-audit/SKILL.md +673 -623
  28. package/ftm-audit/references/protocols/PROJECT-PATTERNS.md +91 -91
  29. package/ftm-audit/references/protocols/RUNTIME-WIRING.md +66 -66
  30. package/ftm-audit/references/protocols/WIRING-CONTRACTS.md +135 -135
  31. package/ftm-audit/references/strategies/AUTO-FIX-STRATEGIES.md +69 -69
  32. package/ftm-audit/references/templates/REPORT-FORMAT.md +96 -96
  33. package/ftm-audit/scripts/run-knip.sh +23 -23
  34. package/ftm-audit.yml +2 -2
  35. package/ftm-brainstorm/SKILL.md +1003 -498
  36. package/ftm-brainstorm/evals/evals.json +180 -100
  37. package/ftm-brainstorm/evals/promptfoo.yaml +109 -109
  38. package/ftm-brainstorm/references/agent-prompts.md +552 -224
  39. package/ftm-brainstorm/references/plan-template.md +209 -121
  40. package/ftm-brainstorm.yml +2 -2
  41. package/ftm-browse/SKILL.md +454 -454
  42. package/ftm-browse/daemon/browser-manager.ts +206 -206
  43. package/ftm-browse/daemon/bun.lock +30 -30
  44. package/ftm-browse/daemon/cli.ts +347 -347
  45. package/ftm-browse/daemon/commands.ts +410 -410
  46. package/ftm-browse/daemon/main.ts +357 -357
  47. package/ftm-browse/daemon/package.json +17 -17
  48. package/ftm-browse/daemon/server.ts +189 -189
  49. package/ftm-browse/daemon/snapshot.ts +519 -519
  50. package/ftm-browse/daemon/tsconfig.json +22 -22
  51. package/ftm-browse.yml +4 -4
  52. package/ftm-capture/SKILL.md +370 -370
  53. package/ftm-capture.yml +4 -4
  54. package/ftm-codex-gate/SKILL.md +361 -361
  55. package/ftm-codex-gate.yml +2 -2
  56. package/ftm-config/SKILL.md +422 -345
  57. package/ftm-config.default.yml +125 -82
  58. package/ftm-config.yml +44 -2
  59. package/ftm-council/SKILL.md +416 -416
  60. package/ftm-council/references/prompts/CLAUDE-INVESTIGATION.md +60 -60
  61. package/ftm-council/references/prompts/CODEX-INVESTIGATION.md +58 -58
  62. package/ftm-council/references/prompts/GEMINI-INVESTIGATION.md +58 -58
  63. package/ftm-council/references/prompts/REBUTTAL-TEMPLATE.md +57 -57
  64. package/ftm-council/references/protocols/PREREQUISITES.md +47 -47
  65. package/ftm-council/references/protocols/STEP-0-FRAMING.md +46 -46
  66. package/ftm-council-chat.yml +2 -0
  67. package/ftm-council.yml +2 -2
  68. package/ftm-dashboard/SKILL.md +163 -163
  69. package/ftm-dashboard.yml +4 -4
  70. package/ftm-debug/SKILL.md +1037 -1037
  71. package/ftm-debug/references/phases/PHASE-0-INTAKE.md +58 -58
  72. package/ftm-debug/references/phases/PHASE-1-TRIAGE.md +46 -46
  73. package/ftm-debug/references/phases/PHASE-2-WAR-ROOM-AGENTS.md +279 -279
  74. package/ftm-debug/references/phases/PHASE-3-TO-6-EXECUTION.md +436 -436
  75. package/ftm-debug/references/protocols/BLACKBOARD.md +86 -86
  76. package/ftm-debug/references/protocols/EDGE-CASES.md +103 -103
  77. package/ftm-debug.yml +2 -2
  78. package/ftm-diagram/SKILL.md +277 -277
  79. package/ftm-diagram.yml +2 -2
  80. package/ftm-executor/SKILL.md +777 -777
  81. package/ftm-executor/references/STYLE-TEMPLATE.md +73 -73
  82. package/ftm-executor/references/phases/PHASE-0-VERIFICATION.md +62 -62
  83. package/ftm-executor/references/phases/PHASE-2-AGENT-ASSEMBLY.md +34 -34
  84. package/ftm-executor/references/phases/PHASE-3-WORKTREES.md +38 -38
  85. package/ftm-executor/references/phases/PHASE-4-5-AUDIT.md +81 -72
  86. package/ftm-executor/references/phases/PHASE-4-DISPATCH.md +66 -66
  87. package/ftm-executor/references/phases/PHASE-5-5-CODEX-GATE.md +73 -73
  88. package/ftm-executor/references/protocols/DOCUMENTATION-BOOTSTRAP.md +36 -36
  89. package/ftm-executor/references/protocols/MODEL-PROFILE.md +59 -59
  90. package/ftm-executor/references/protocols/PROGRESS-TRACKING.md +66 -66
  91. package/ftm-executor/runtime/ftm-runtime.mjs +252 -252
  92. package/ftm-executor/runtime/package.json +8 -8
  93. package/ftm-executor.yml +2 -2
  94. package/ftm-git/SKILL.md +441 -441
  95. package/ftm-git/evals/evals.json +26 -26
  96. package/ftm-git/evals/promptfoo.yaml +75 -75
  97. package/ftm-git/hooks/post-commit-experience.sh +92 -92
  98. package/ftm-git/references/patterns/SECRET-PATTERNS.md +104 -104
  99. package/ftm-git/references/protocols/REMEDIATION.md +139 -139
  100. package/ftm-git/scripts/pre-commit-secrets.sh +110 -110
  101. package/ftm-git.yml +2 -2
  102. package/ftm-inbox/backend/__pycache__/main.cpython-314.pyc +0 -0
  103. package/ftm-inbox/backend/adapters/_retry.py +64 -64
  104. package/ftm-inbox/backend/adapters/base.py +230 -230
  105. package/ftm-inbox/backend/adapters/freshservice.py +104 -104
  106. package/ftm-inbox/backend/adapters/gmail.py +125 -125
  107. package/ftm-inbox/backend/adapters/jira.py +136 -136
  108. package/ftm-inbox/backend/adapters/registry.py +192 -192
  109. package/ftm-inbox/backend/adapters/slack.py +110 -110
  110. package/ftm-inbox/backend/db/connection.py +54 -54
  111. package/ftm-inbox/backend/db/schema.py +78 -78
  112. package/ftm-inbox/backend/executor/__init__.py +7 -7
  113. package/ftm-inbox/backend/executor/engine.py +149 -149
  114. package/ftm-inbox/backend/executor/step_runner.py +98 -98
  115. package/ftm-inbox/backend/main.py +103 -103
  116. package/ftm-inbox/backend/models/__init__.py +1 -1
  117. package/ftm-inbox/backend/models/unified_task.py +36 -36
  118. package/ftm-inbox/backend/planner/__init__.py +6 -6
  119. package/ftm-inbox/backend/planner/__pycache__/__init__.cpython-314.pyc +0 -0
  120. package/ftm-inbox/backend/planner/__pycache__/generator.cpython-314.pyc +0 -0
  121. package/ftm-inbox/backend/planner/__pycache__/schema.cpython-314.pyc +0 -0
  122. package/ftm-inbox/backend/planner/generator.py +127 -127
  123. package/ftm-inbox/backend/planner/schema.py +34 -34
  124. package/ftm-inbox/backend/requirements.txt +5 -5
  125. package/ftm-inbox/backend/routes/__pycache__/plan.cpython-314.pyc +0 -0
  126. package/ftm-inbox/backend/routes/execute.py +186 -186
  127. package/ftm-inbox/backend/routes/health.py +52 -52
  128. package/ftm-inbox/backend/routes/inbox.py +68 -68
  129. package/ftm-inbox/backend/routes/plan.py +271 -271
  130. package/ftm-inbox/bin/launchagent.mjs +91 -91
  131. package/ftm-inbox/bin/setup.mjs +188 -188
  132. package/ftm-inbox/bin/start.sh +10 -10
  133. package/ftm-inbox/bin/status.sh +17 -17
  134. package/ftm-inbox/bin/stop.sh +8 -8
  135. package/ftm-inbox/config.example.yml +55 -55
  136. package/ftm-inbox/package-lock.json +2898 -2898
  137. package/ftm-inbox/package.json +26 -26
  138. package/ftm-inbox/postcss.config.js +6 -6
  139. package/ftm-inbox/src/app.css +199 -199
  140. package/ftm-inbox/src/app.html +18 -18
  141. package/ftm-inbox/src/lib/api.ts +166 -166
  142. package/ftm-inbox/src/lib/components/ExecutionLog.svelte +81 -81
  143. package/ftm-inbox/src/lib/components/InboxFeed.svelte +143 -143
  144. package/ftm-inbox/src/lib/components/PlanStep.svelte +271 -271
  145. package/ftm-inbox/src/lib/components/PlanView.svelte +206 -206
  146. package/ftm-inbox/src/lib/components/StreamPanel.svelte +99 -99
  147. package/ftm-inbox/src/lib/components/TaskCard.svelte +190 -190
  148. package/ftm-inbox/src/lib/components/ui/EmptyState.svelte +63 -63
  149. package/ftm-inbox/src/lib/components/ui/KawaiiCard.svelte +86 -86
  150. package/ftm-inbox/src/lib/components/ui/PillButton.svelte +106 -106
  151. package/ftm-inbox/src/lib/components/ui/StatusBadge.svelte +67 -67
  152. package/ftm-inbox/src/lib/components/ui/StreamDrawer.svelte +149 -149
  153. package/ftm-inbox/src/lib/components/ui/ThemeToggle.svelte +80 -80
  154. package/ftm-inbox/src/lib/theme.ts +47 -47
  155. package/ftm-inbox/src/routes/+layout.svelte +76 -76
  156. package/ftm-inbox/src/routes/+page.svelte +401 -401
  157. package/ftm-inbox/svelte.config.js +12 -12
  158. package/ftm-inbox/tailwind.config.ts +63 -63
  159. package/ftm-inbox/tsconfig.json +13 -13
  160. package/ftm-inbox/vite.config.ts +6 -6
  161. package/ftm-intent/SKILL.md +241 -241
  162. package/ftm-intent.yml +2 -2
  163. package/ftm-manifest.json +3794 -3794
  164. package/ftm-map/SKILL.md +291 -291
  165. package/ftm-map/scripts/db.py +712 -712
  166. package/ftm-map/scripts/index.py +415 -415
  167. package/ftm-map/scripts/parser.py +224 -224
  168. package/ftm-map/scripts/queries/go-tags.scm +20 -20
  169. package/ftm-map/scripts/queries/javascript-tags.scm +35 -35
  170. package/ftm-map/scripts/queries/python-tags.scm +31 -31
  171. package/ftm-map/scripts/queries/ruby-tags.scm +19 -19
  172. package/ftm-map/scripts/queries/rust-tags.scm +37 -37
  173. package/ftm-map/scripts/queries/typescript-tags.scm +41 -41
  174. package/ftm-map/scripts/query.py +301 -301
  175. package/ftm-map/scripts/ranker.py +377 -377
  176. package/ftm-map/scripts/requirements.txt +5 -5
  177. package/ftm-map/scripts/setup-hooks.sh +27 -27
  178. package/ftm-map/scripts/setup.sh +56 -56
  179. package/ftm-map/scripts/test_db.py +364 -364
  180. package/ftm-map/scripts/test_parser.py +174 -174
  181. package/ftm-map/scripts/test_query.py +183 -183
  182. package/ftm-map/scripts/test_ranker.py +199 -199
  183. package/ftm-map/scripts/views.py +591 -591
  184. package/ftm-map.yml +2 -2
  185. package/ftm-mind/SKILL.md +201 -1943
  186. package/ftm-mind/evals/promptfoo.yaml +142 -142
  187. package/ftm-mind/references/blackboard-protocol.md +110 -0
  188. package/ftm-mind/references/blackboard-schema.md +328 -328
  189. package/ftm-mind/references/complexity-guide.md +110 -110
  190. package/ftm-mind/references/complexity-sizing.md +138 -0
  191. package/ftm-mind/references/decide-act-protocol.md +172 -0
  192. package/ftm-mind/references/direct-execution.md +51 -0
  193. package/ftm-mind/references/environment-discovery.md +77 -0
  194. package/ftm-mind/references/event-registry.md +319 -319
  195. package/ftm-mind/references/mcp-inventory.md +300 -296
  196. package/ftm-mind/references/ops-routing.md +47 -0
  197. package/ftm-mind/references/orient-protocol.md +234 -0
  198. package/ftm-mind/references/personality.md +40 -0
  199. package/ftm-mind/references/protocols/COMPLEXITY-SIZING.md +72 -72
  200. package/ftm-mind/references/protocols/MCP-HEURISTICS.md +32 -32
  201. package/ftm-mind/references/protocols/PLAN-APPROVAL.md +80 -80
  202. package/ftm-mind/references/reflexion-protocol.md +249 -249
  203. package/ftm-mind/references/routing/SCENARIOS.md +22 -22
  204. package/ftm-mind/references/routing-scenarios.md +35 -35
  205. package/ftm-mind.yml +2 -2
  206. package/ftm-ops.yml +4 -0
  207. package/ftm-pause/SKILL.md +395 -395
  208. package/ftm-pause/references/protocols/SKILL-RESTORE-PROTOCOLS.md +186 -186
  209. package/ftm-pause/references/protocols/VALIDATION.md +80 -80
  210. package/ftm-pause.yml +2 -2
  211. package/ftm-researcher/SKILL.md +275 -275
  212. package/ftm-researcher/evals/agent-diversity.yaml +17 -17
  213. package/ftm-researcher/evals/synthesis-quality.yaml +12 -12
  214. package/ftm-researcher/evals/trigger-accuracy.yaml +39 -39
  215. package/ftm-researcher/references/adaptive-search.md +116 -116
  216. package/ftm-researcher/references/agent-prompts.md +193 -193
  217. package/ftm-researcher/references/council-integration.md +193 -193
  218. package/ftm-researcher/references/output-format.md +203 -203
  219. package/ftm-researcher/references/synthesis-pipeline.md +165 -165
  220. package/ftm-researcher/scripts/score_credibility.py +234 -234
  221. package/ftm-researcher/scripts/validate_research.py +92 -92
  222. package/ftm-researcher.yml +2 -2
  223. package/ftm-resume/SKILL.md +518 -518
  224. package/ftm-resume/references/protocols/VALIDATION.md +172 -172
  225. package/ftm-resume.yml +2 -2
  226. package/ftm-retro/SKILL.md +380 -380
  227. package/ftm-retro/references/protocols/SCORING-RUBRICS.md +89 -89
  228. package/ftm-retro/references/templates/REPORT-FORMAT.md +109 -109
  229. package/ftm-retro.yml +2 -2
  230. package/ftm-routine/SKILL.md +170 -170
  231. package/ftm-routine.yml +4 -4
  232. package/ftm-state/blackboard/capabilities.json +5 -5
  233. package/ftm-state/blackboard/capabilities.schema.json +27 -27
  234. package/ftm-state/blackboard/context.json +37 -23
  235. package/ftm-state/blackboard/experiences/doom-statusline-fix.json +26 -0
  236. package/ftm-state/blackboard/experiences/hackathon-pages-site.json +26 -0
  237. package/ftm-state/blackboard/experiences/hindsight-sso-kickoff.json +42 -0
  238. package/ftm-state/blackboard/experiences/index.json +58 -9
  239. package/ftm-state/blackboard/experiences/learning-ragnarok-api-access.json +23 -0
  240. package/ftm-state/blackboard/experiences/nordlayer-members-auto-assign.json +26 -0
  241. package/ftm-state/blackboard/experiences/saml2aws-stale-session-fix.json +41 -0
  242. package/ftm-state/blackboard/patterns.json +6 -6
  243. package/ftm-state/schemas/context.schema.json +130 -130
  244. package/ftm-state/schemas/experience-index.schema.json +77 -77
  245. package/ftm-state/schemas/experience.schema.json +78 -78
  246. package/ftm-state/schemas/patterns.schema.json +44 -44
  247. package/ftm-upgrade/SKILL.md +194 -194
  248. package/ftm-upgrade/scripts/check-version.sh +76 -76
  249. package/ftm-upgrade/scripts/upgrade.sh +143 -143
  250. package/ftm-upgrade.yml +2 -2
  251. package/ftm-verify.yml +2 -2
  252. package/ftm.yml +2 -2
  253. package/hooks/ftm-auto-log.sh +137 -0
  254. package/hooks/ftm-blackboard-enforcer.sh +93 -93
  255. package/hooks/ftm-discovery-reminder.sh +90 -90
  256. package/hooks/ftm-drafts-gate.sh +61 -61
  257. package/hooks/ftm-event-logger.mjs +107 -107
  258. package/hooks/ftm-install-hooks.sh +240 -0
  259. package/hooks/ftm-learning-capture.sh +117 -0
  260. package/hooks/ftm-map-autodetect.sh +79 -79
  261. package/hooks/ftm-pending-sync-check.sh +22 -22
  262. package/hooks/ftm-plan-gate.sh +92 -92
  263. package/hooks/ftm-post-commit-trigger.sh +57 -57
  264. package/hooks/ftm-post-compaction.sh +138 -0
  265. package/hooks/ftm-pre-compaction.sh +147 -0
  266. package/hooks/ftm-session-end.sh +52 -0
  267. package/hooks/ftm-session-snapshot.sh +213 -0
  268. package/hooks/ftm-task-loader.sh +100 -0
  269. package/hooks/settings-template.json +91 -81
  270. package/install.sh +363 -363
  271. package/package.json +84 -84
  272. package/uninstall.sh +25 -25
@@ -0,0 +1,147 @@
1
+ #!/bin/bash
2
+ # ftm-pre-compaction.sh
3
+ # Hook: Pre-compaction memory flush for ftm
4
+ # Trigger: UserPromptSubmit
5
+ #
6
+ # Fires on every user message during an active ftm session.
7
+ # Reads the session JSONL to track token usage. When context approaches
8
+ # the limit (default: 150K of 200K tokens), injects a flush-first prefix
9
+ # telling Claude to write daily log + task state before responding.
10
+ #
11
+ # This approximates OpenClaw's pre-compaction flush, adapted for Claude Code's
12
+ # hook system (which can inject text but can't inject silent LLM turns).
13
+ #
14
+ # THRESHOLD: 150K tokens = 75% of 200K context window
15
+ # COOLDOWN: Only re-fires after 15K more tokens consumed (prevents spamming)
16
+ #
17
+ # FILES:
18
+ # ~/.claude/ftm-state/blackboard/context.json - session gate
19
+ # ~/.claude/ftm-state/.last-flush-tokens - token count at last flush (cooldown)
20
+
21
+ FTM_STATE="$HOME/.claude/ftm-state"
22
+ CONTEXT_JSON="$FTM_STATE/blackboard/context.json"
23
+ LAST_FLUSH_FILE="$FTM_STATE/.last-flush-tokens"
24
+
25
+ THRESHOLD=150000 # Flush when total context exceeds this
26
+ COOLDOWN=15000 # Only re-flush after this many additional tokens
27
+
28
+ # 1. Only run during active ftm sessions
29
+ FTM_ACTIVE=$(python3 -c "
30
+ import json, sys
31
+ try:
32
+ with open('$CONTEXT_JSON') as f:
33
+ d = json.load(f)
34
+ task = d.get('current_task', {})
35
+ status = task.get('status', '')
36
+ print('1' if status not in ('', 'completed', 'none') else '0')
37
+ except Exception:
38
+ print('0')
39
+ " 2>/dev/null)
40
+
41
+ if [ "$FTM_ACTIVE" != "1" ]; then
42
+ exit 0
43
+ fi
44
+
45
+ # 2. Read hook payload from stdin (JSON: { session_id, prompt, transcript_path, ... })
46
+ PAYLOAD=$(cat)
47
+ if [ -z "$PAYLOAD" ]; then
48
+ exit 0
49
+ fi
50
+
51
+ # 3. Extract session_id and transcript_path from payload
52
+ SESSION_ID=$(echo "$PAYLOAD" | python3 -c "
53
+ import sys, json
54
+ try:
55
+ d = json.load(sys.stdin)
56
+ print(d.get('session_id', ''))
57
+ except:
58
+ print('')
59
+ " 2>/dev/null)
60
+
61
+ TRANSCRIPT_PATH=$(echo "$PAYLOAD" | python3 -c "
62
+ import sys, json
63
+ try:
64
+ d = json.load(sys.stdin)
65
+ print(d.get('transcript_path', ''))
66
+ except:
67
+ print('')
68
+ " 2>/dev/null)
69
+
70
+ # 4. Find the session JSONL (try transcript_path first, then search by session_id)
71
+ JSONL_FILE=""
72
+ if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
73
+ JSONL_FILE="$TRANSCRIPT_PATH"
74
+ elif [ -n "$SESSION_ID" ]; then
75
+ JSONL_FILE=$(find "$HOME/.claude/projects/" -name "${SESSION_ID}.jsonl" 2>/dev/null | head -1)
76
+ fi
77
+
78
+ if [ -z "$JSONL_FILE" ] || [ ! -f "$JSONL_FILE" ]; then
79
+ exit 0
80
+ fi
81
+
82
+ # 5. Parse JSONL to find latest total token usage
83
+ TOTAL_TOKENS=$(python3 -c "
84
+ import json, sys, os
85
+
86
+ path = sys.argv[1]
87
+ try:
88
+ with open(path) as f:
89
+ lines = f.readlines()
90
+ except:
91
+ print(0)
92
+ sys.exit(0)
93
+
94
+ # Scan in reverse for the latest usage entry
95
+ for line in reversed(lines):
96
+ try:
97
+ obj = json.loads(line)
98
+ msg = obj.get('message', {})
99
+ if not isinstance(msg, dict):
100
+ continue
101
+ usage = msg.get('usage', {})
102
+ if isinstance(usage, dict) and 'input_tokens' in usage:
103
+ total = (
104
+ usage.get('input_tokens', 0) +
105
+ usage.get('cache_read_input_tokens', 0) +
106
+ usage.get('cache_creation_input_tokens', 0)
107
+ )
108
+ print(total)
109
+ sys.stdout.flush()
110
+ os._exit(0)
111
+ except Exception:
112
+ pass
113
+
114
+ print(0)
115
+ " "$JSONL_FILE" 2>/dev/null)
116
+
117
+ TOTAL_TOKENS=${TOTAL_TOKENS:-0}
118
+
119
+ # 6. Check if we're over the threshold
120
+ if [ "$TOTAL_TOKENS" -lt "$THRESHOLD" ] 2>/dev/null; then
121
+ exit 0
122
+ fi
123
+
124
+ # 7. Cooldown check: only flush if we've consumed COOLDOWN more tokens since last flush
125
+ LAST_FLUSH_TOKENS=$(cat "$LAST_FLUSH_FILE" 2>/dev/null || echo "0")
126
+ TOKENS_SINCE_FLUSH=$(( TOTAL_TOKENS - LAST_FLUSH_TOKENS ))
127
+
128
+ if [ "$TOKENS_SINCE_FLUSH" -lt "$COOLDOWN" ] 2>/dev/null; then
129
+ exit 0
130
+ fi
131
+
132
+ # 8. Record this flush point
133
+ echo "$TOTAL_TOKENS" > "$LAST_FLUSH_FILE"
134
+
135
+ # 9. Inject the flush-first prefix into the user's message
136
+ TODAY=$(date +%Y-%m-%d)
137
+ PCT=$((TOTAL_TOKENS * 100 / 200000))
138
+
139
+ echo ""
140
+ echo "[PRE-COMPACTION MEMORY FLUSH — ${TOTAL_TOKENS} tokens (~${PCT}% of 200K) — ${TODAY}]:"
141
+ echo "Context is getting full. BEFORE responding: write any unsaved state to disk."
142
+ echo "1. Append unsaved session progress to the ftm daily log for ${TODAY}"
143
+ echo "2. Update ~/.claude/ftm-state/blackboard/context.json with current task status"
144
+ echo "3. If important facts/solutions discovered this session, append to relevant knowledge files"
145
+ echo "Do this silently — do NOT announce the flush to the user. Then respond normally."
146
+ echo "If there is genuinely nothing new to record, skip the writes and proceed directly."
147
+ echo ""
@@ -0,0 +1,52 @@
1
+ #!/bin/bash
2
+ # ftm-session-end.sh
3
+ # Hook: Deactivate ftm session tracking when conversation ends
4
+ # Trigger: SessionEnd
5
+ #
6
+ # IMPORTANT: Must be listed AFTER ftm-session-snapshot.sh in settings.json
7
+ # (in a separate matcher entry). The snapshot hook reads context.json status
8
+ # to gate itself — this hook marks the session completed.
9
+
10
+ FTM_STATE="$HOME/.claude/ftm-state"
11
+ CONTEXT_JSON="$FTM_STATE/blackboard/context.json"
12
+
13
+ # Check if an active session exists
14
+ IS_ACTIVE=$(python3 -c "
15
+ import json, sys
16
+ try:
17
+ with open('$CONTEXT_JSON') as f:
18
+ d = json.load(f)
19
+ task = d.get('current_task', {})
20
+ status = task.get('status', '')
21
+ print('1' if status not in ('', 'completed', 'none') else '0')
22
+ except Exception:
23
+ print('0')
24
+ " 2>/dev/null)
25
+
26
+ if [ "$IS_ACTIVE" != "1" ]; then
27
+ exit 0
28
+ fi
29
+
30
+ # Mark session as completed in context.json
31
+ python3 -c "
32
+ import json, sys
33
+ from datetime import datetime
34
+
35
+ try:
36
+ with open('$CONTEXT_JSON') as f:
37
+ d = json.load(f)
38
+
39
+ if 'current_task' in d and isinstance(d['current_task'], dict):
40
+ d['current_task']['status'] = 'completed'
41
+
42
+ if 'session_metadata' in d and isinstance(d['session_metadata'], dict):
43
+ d['session_metadata']['last_updated'] = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
44
+
45
+ with open('$CONTEXT_JSON', 'w') as f:
46
+ json.dump(d, f, indent=2)
47
+ except Exception as e:
48
+ sys.stderr.write(f'ftm-session-end: failed to update context.json: {e}\n')
49
+ sys.exit(1)
50
+ " 2>/dev/null
51
+
52
+ echo "ftm session tracking deactivated (session ended)"
@@ -0,0 +1,213 @@
1
+ #!/bin/bash
2
+ # ftm-session-snapshot.sh
3
+ # Hook: Session snapshot on conversation end
4
+ # Trigger: SessionEnd
5
+ #
6
+ # Captures the last 15 meaningful user/assistant exchanges as a markdown snapshot
7
+ # when an ftm session ends. Fills the gap where sessions that never hit the
8
+ # compaction token threshold would lose all conversational context.
9
+ #
10
+ # Inspired by OpenClaw's session snapshot mechanism, which fires on /new or /reset.
11
+ # We fire on SessionEnd since Claude Code doesn't expose /new or /reset as hook events.
12
+ #
13
+ # Filtering:
14
+ # - Keeps: user and assistant messages with text content
15
+ # - Skips: tool_use blocks, tool_result blocks, system messages, slash commands
16
+ # - Truncates: messages > 2000 chars (long pastes don't need full capture)
17
+ #
18
+ # Filename: YYYY-MM-DDTHH-MM-<topic-slug>.md (topic derived from last user message)
19
+ #
20
+ # IMPORTANT: Must be listed BEFORE ftm-session-end.sh in settings.json.
21
+ # ftm-session-end.sh updates context.json status — this hook reads it.
22
+ #
23
+ # OUTPUT: ~/.claude/ftm-state/sessions/
24
+ # MINIMUM: 3 meaningful messages required to write a snapshot (skip trivial sessions)
25
+ #
26
+ # FILES:
27
+ # ~/.claude/ftm-state/blackboard/context.json - session gate (read only, not modified)
28
+ # ~/.claude/ftm-state/sessions/ - snapshot output directory
29
+
30
+ FTM_STATE="$HOME/.claude/ftm-state"
31
+ CONTEXT_JSON="$FTM_STATE/blackboard/context.json"
32
+ SESSIONS_DIR="$FTM_STATE/sessions"
33
+ MAX_MESSAGES=15
34
+ MIN_MESSAGES=3
35
+
36
+ # 1. Only run during active ftm sessions
37
+ FTM_ACTIVE=$(python3 -c "
38
+ import json, sys
39
+ try:
40
+ with open('$CONTEXT_JSON') as f:
41
+ d = json.load(f)
42
+ task = d.get('current_task', {})
43
+ status = task.get('status', '')
44
+ print('1' if status not in ('', 'completed', 'none') else '0')
45
+ except Exception:
46
+ print('0')
47
+ " 2>/dev/null)
48
+
49
+ if [ "$FTM_ACTIVE" != "1" ]; then
50
+ exit 0
51
+ fi
52
+
53
+ # 2. Read hook payload from stdin
54
+ PAYLOAD=$(cat)
55
+ if [ -z "$PAYLOAD" ]; then
56
+ exit 0
57
+ fi
58
+
59
+ # 3. Extract session_id and transcript_path
60
+ SESSION_ID=$(echo "$PAYLOAD" | python3 -c "
61
+ import sys, json
62
+ try:
63
+ d = json.load(sys.stdin)
64
+ print(d.get('session_id', ''))
65
+ except:
66
+ print('')
67
+ " 2>/dev/null)
68
+
69
+ TRANSCRIPT_PATH=$(echo "$PAYLOAD" | python3 -c "
70
+ import sys, json
71
+ try:
72
+ d = json.load(sys.stdin)
73
+ print(d.get('transcript_path', ''))
74
+ except:
75
+ print('')
76
+ " 2>/dev/null)
77
+
78
+ # 4. Find session JSONL
79
+ JSONL_FILE=""
80
+ if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
81
+ JSONL_FILE="$TRANSCRIPT_PATH"
82
+ elif [ -n "$SESSION_ID" ]; then
83
+ JSONL_FILE=$(find "$HOME/.claude/projects/" -name "${SESSION_ID}.jsonl" 2>/dev/null | head -1)
84
+ fi
85
+
86
+ if [ -z "$JSONL_FILE" ] || [ ! -f "$JSONL_FILE" ]; then
87
+ exit 0
88
+ fi
89
+
90
+ # 5. Ensure sessions directory exists
91
+ mkdir -p "$SESSIONS_DIR"
92
+
93
+ # 6. Parse JSONL, build snapshot, write file — all in one Python block
94
+ export JSONL_FILE SESSION_ID SESSIONS_DIR MAX_MESSAGES MIN_MESSAGES
95
+ python3 << 'PYEOF'
96
+ import json, sys, os, re
97
+ from datetime import datetime
98
+
99
+ jsonl_path = os.environ['JSONL_FILE']
100
+ session_id = os.environ.get('SESSION_ID', 'unknown')
101
+ sessions_dir = os.environ['SESSIONS_DIR']
102
+ max_msgs = int(os.environ.get('MAX_MESSAGES', '15'))
103
+ min_msgs = int(os.environ.get('MIN_MESSAGES', '3'))
104
+
105
+ # --- Parse JSONL ---
106
+ try:
107
+ with open(jsonl_path) as f:
108
+ lines = f.readlines()
109
+ except Exception:
110
+ sys.exit(0)
111
+
112
+ messages = []
113
+ for line in lines:
114
+ line = line.strip()
115
+ if not line:
116
+ continue
117
+ try:
118
+ obj = json.loads(line)
119
+ except Exception:
120
+ continue
121
+
122
+ msg = obj.get('message', {})
123
+ if not isinstance(msg, dict):
124
+ continue
125
+
126
+ role = msg.get('role', '')
127
+ if role not in ('user', 'assistant'):
128
+ continue
129
+
130
+ content = msg.get('content', '')
131
+
132
+ # Extract text content only — skip tool_use, tool_result, image blocks
133
+ if isinstance(content, list):
134
+ text_parts = []
135
+ for block in content:
136
+ if isinstance(block, dict):
137
+ if block.get('type') == 'text':
138
+ t = block.get('text', '').strip()
139
+ if t:
140
+ text_parts.append(t)
141
+ elif isinstance(block, str):
142
+ t = block.strip()
143
+ if t:
144
+ text_parts.append(t)
145
+ text = '\n'.join(text_parts)
146
+ elif isinstance(content, str):
147
+ text = content.strip()
148
+ else:
149
+ continue
150
+
151
+ if not text:
152
+ continue
153
+
154
+ # Skip slash commands
155
+ if role == 'user' and text.lstrip().startswith('/'):
156
+ continue
157
+
158
+ messages.append({'role': role, 'text': text})
159
+
160
+ # Take last max_msgs meaningful messages
161
+ recent = messages[-max_msgs:]
162
+
163
+ # Require minimum message count — skip trivial sessions
164
+ if len(recent) < min_msgs:
165
+ sys.exit(0)
166
+
167
+ # --- Derive topic slug from last user message with some substance ---
168
+ topic = 'session'
169
+ for m in reversed(recent):
170
+ if m['role'] == 'user' and len(m['text']) > 15:
171
+ raw = m['text'][:80].lower()
172
+ raw = re.sub(r'[^a-z0-9\s]', ' ', raw)
173
+ raw = re.sub(r'\s+', '-', raw.strip())
174
+ raw = re.sub(r'-+', '-', raw).strip('-')[:50].rstrip('-')
175
+ if raw:
176
+ topic = raw
177
+ break
178
+
179
+ # --- Build filename and output path ---
180
+ now = datetime.now()
181
+ timestamp_file = now.strftime('%Y-%m-%dT%H-%M')
182
+ timestamp_display = now.strftime('%Y-%m-%d %H:%M')
183
+ filename = f'{timestamp_file}-{topic}.md'
184
+ output_path = os.path.join(sessions_dir, filename)
185
+
186
+ # --- Write snapshot markdown ---
187
+ lines_out = [
188
+ f'# Session Snapshot — {timestamp_display}',
189
+ '',
190
+ f'**Messages captured**: {len(recent)} (last {len(recent)} of session)',
191
+ f'**Session**: {session_id[:8]}...',
192
+ '',
193
+ '---',
194
+ '',
195
+ ]
196
+
197
+ for msg in recent:
198
+ role_label = '**You**' if msg['role'] == 'user' else '**Claude**'
199
+ text = msg['text']
200
+ # Truncate very long messages to keep snapshot lean
201
+ if len(text) > 2000:
202
+ text = text[:2000] + '\n\n[... truncated — full content in daily log ...]'
203
+ lines_out.append(f'{role_label}: {text}')
204
+ lines_out.append('')
205
+
206
+ try:
207
+ with open(output_path, 'w') as f:
208
+ f.write('\n'.join(lines_out))
209
+ except Exception:
210
+ pass
211
+ PYEOF
212
+
213
+ exit 0
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env bash
2
+ # ftm-task-loader.sh
3
+ # PostToolUse hook that fires after the Skill tool is invoked.
4
+ # When ftm-ops or ftm is the invoked skill, loads tasks from brain.py
5
+ # and injects TaskCreate instructions as additionalContext.
6
+ #
7
+ # This is deterministic — brain.py runs in the hook (fast), and the
8
+ # model receives pre-parsed TaskCreate calls it must execute.
9
+ #
10
+ # Hook: PostToolUse (matcher: Skill)
11
+
12
+ set -euo pipefail
13
+
14
+ INPUT=$(cat)
15
+
16
+ # Extract the tool name and check it's the Skill tool
17
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
18
+ if [[ "$TOOL_NAME" != "Skill" ]]; then
19
+ exit 0
20
+ fi
21
+
22
+ # Extract which skill was invoked from the tool input
23
+ SKILL_NAME=$(echo "$INPUT" | jq -r '.tool_input.skill // ""' 2>/dev/null)
24
+
25
+ # Only fire for ftm-ops, ftm, or ftm-mind (which routes to ftm-ops for task requests)
26
+ case "$SKILL_NAME" in
27
+ ftm-ops|ftm|ftm-mind|eng-buddy) ;;
28
+ *) exit 0 ;;
29
+ esac
30
+
31
+ # Find brain.py — check multiple locations
32
+ BRAIN_PY=""
33
+ for candidate in \
34
+ "$HOME/.claude/skills/ftm/bin/brain.py" \
35
+ "$HOME/.claude/skills/ftm-ops/../bin/brain.py" \
36
+ "$HOME/Documents/Code/feed-the-machine/bin/brain.py"; do
37
+ if [[ -f "$candidate" ]]; then
38
+ BRAIN_PY="$candidate"
39
+ break
40
+ fi
41
+ done
42
+
43
+ if [[ -z "$BRAIN_PY" ]]; then
44
+ # brain.py not found — skip silently
45
+ exit 0
46
+ fi
47
+
48
+ # Run brain.py and capture task JSON
49
+ TASKS_JSON=$(python3 "$BRAIN_PY" --tasks --task-json 2>/dev/null) || exit 0
50
+
51
+ # Count active tasks
52
+ TASK_COUNT=$(echo "$TASKS_JSON" | python3 -c "
53
+ import sys, json
54
+ try:
55
+ tasks = json.load(sys.stdin)
56
+ active = [t for t in tasks if t.get('status') in ('pending', 'in_progress')]
57
+ print(len(active))
58
+ except:
59
+ print(0)
60
+ " 2>/dev/null)
61
+
62
+ if [[ "$TASK_COUNT" == "0" ]]; then
63
+ exit 0
64
+ fi
65
+
66
+ # Generate TaskCreate instructions from the task data
67
+ TASK_INSTRUCTIONS=$(echo "$TASKS_JSON" | python3 -c "
68
+ import sys, json
69
+ try:
70
+ tasks = json.load(sys.stdin)
71
+ active = [t for t in tasks if t.get('status') in ('pending', 'in_progress')]
72
+ lines = []
73
+ for t in active:
74
+ tid = t.get('id', t.get('legacy_number', '?'))
75
+ title = t.get('title', 'Untitled')
76
+ status = t.get('status', 'pending')
77
+ priority = t.get('priority', 'medium')
78
+ jira = t.get('jira_key', '')
79
+ desc_parts = [status, priority]
80
+ if jira:
81
+ desc_parts.append(jira)
82
+ desc = ' | '.join(desc_parts)
83
+ lines.append(f'TaskCreate(subject: \"#{tid} {title}\", description: \"{desc}\")')
84
+ print('\n'.join(lines))
85
+ except:
86
+ pass
87
+ " 2>/dev/null)
88
+
89
+ if [[ -z "$TASK_INSTRUCTIONS" ]]; then
90
+ exit 0
91
+ fi
92
+
93
+ # Output as additionalContext — the model MUST process this
94
+ cat <<EOF
95
+ [ftm-task-loader] Loaded $TASK_COUNT active tasks from tasks.db. You MUST now call TaskCreate for each one so they appear in Claude Code's sidebar task list. Do NOT skip this — rendering a table is not enough. Call each one:
96
+
97
+ $TASK_INSTRUCTIONS
98
+
99
+ Call all of these TaskCreate invocations NOW, before doing anything else. This is non-negotiable.
100
+ EOF
@@ -1,81 +1,91 @@
1
- {
2
- "$schema": "https://json.schemastore.org/claude-code-settings.json",
3
- "_comment": "FTM hooks configuration template. Merge these entries into your ~/.claude/settings.json under the 'hooks' key. install.sh can do this automatically with --setup-hooks.",
4
- "hooks": {
5
- "PreToolUse": [
6
- {
7
- "matcher": "Edit|Write",
8
- "hooks": [
9
- {
10
- "type": "command",
11
- "command": "~/.claude/hooks/ftm-plan-gate.sh",
12
- "timeout": 5
13
- }
14
- ]
15
- },
16
- {
17
- "matcher": "mcp__slack__slack_post_message|mcp__slack__slack_reply_to_thread|mcp__gmail__send_email",
18
- "hooks": [
19
- {
20
- "type": "command",
21
- "command": "~/.claude/hooks/ftm-drafts-gate.sh",
22
- "timeout": 5
23
- }
24
- ]
25
- }
26
- ],
27
- "UserPromptSubmit": [
28
- {
29
- "hooks": [
30
- {
31
- "type": "command",
32
- "command": "~/.claude/hooks/ftm-discovery-reminder.sh",
33
- "timeout": 5
34
- },
35
- {
36
- "type": "command",
37
- "command": "~/.claude/hooks/ftm-pending-sync-check.sh",
38
- "timeout": 5
39
- },
40
- {
41
- "type": "command",
42
- "command": "~/.claude/hooks/ftm-map-autodetect.sh",
43
- "timeout": 5
44
- }
45
- ]
46
- }
47
- ],
48
- "PostToolUse": [
49
- {
50
- "matcher": "",
51
- "hooks": [
52
- {
53
- "type": "command",
54
- "command": "node ~/.claude/hooks/ftm-event-logger.mjs"
55
- }
56
- ]
57
- },
58
- {
59
- "matcher": "Bash|mcp__git__git_commit",
60
- "hooks": [
61
- {
62
- "type": "command",
63
- "command": "~/.claude/hooks/ftm-post-commit-trigger.sh",
64
- "timeout": 5
65
- }
66
- ]
67
- }
68
- ],
69
- "Stop": [
70
- {
71
- "hooks": [
72
- {
73
- "type": "command",
74
- "command": "~/.claude/hooks/ftm-blackboard-enforcer.sh",
75
- "timeout": 5
76
- }
77
- ]
78
- }
79
- ]
80
- }
81
- }
1
+ {
2
+ "$schema": "https://json.schemastore.org/claude-code-settings.json",
3
+ "_comment": "FTM hooks configuration template. Merge these entries into your ~/.claude/settings.json under the 'hooks' key. install.sh can do this automatically with --setup-hooks.",
4
+ "hooks": {
5
+ "PreToolUse": [
6
+ {
7
+ "matcher": "Edit|Write",
8
+ "hooks": [
9
+ {
10
+ "type": "command",
11
+ "command": "~/.claude/hooks/ftm-plan-gate.sh",
12
+ "timeout": 5
13
+ }
14
+ ]
15
+ },
16
+ {
17
+ "matcher": "mcp__slack__slack_post_message|mcp__slack__slack_reply_to_thread|mcp__gmail__send_email",
18
+ "hooks": [
19
+ {
20
+ "type": "command",
21
+ "command": "~/.claude/hooks/ftm-drafts-gate.sh",
22
+ "timeout": 5
23
+ }
24
+ ]
25
+ }
26
+ ],
27
+ "UserPromptSubmit": [
28
+ {
29
+ "hooks": [
30
+ {
31
+ "type": "command",
32
+ "command": "~/.claude/hooks/ftm-discovery-reminder.sh",
33
+ "timeout": 5
34
+ },
35
+ {
36
+ "type": "command",
37
+ "command": "~/.claude/hooks/ftm-pending-sync-check.sh",
38
+ "timeout": 5
39
+ },
40
+ {
41
+ "type": "command",
42
+ "command": "~/.claude/hooks/ftm-map-autodetect.sh",
43
+ "timeout": 5
44
+ }
45
+ ]
46
+ }
47
+ ],
48
+ "PostToolUse": [
49
+ {
50
+ "matcher": "",
51
+ "hooks": [
52
+ {
53
+ "type": "command",
54
+ "command": "node ~/.claude/hooks/ftm-event-logger.mjs"
55
+ }
56
+ ]
57
+ },
58
+ {
59
+ "matcher": "Skill",
60
+ "hooks": [
61
+ {
62
+ "type": "command",
63
+ "command": "~/.claude/hooks/ftm-task-loader.sh",
64
+ "timeout": 10
65
+ }
66
+ ]
67
+ },
68
+ {
69
+ "matcher": "Bash|mcp__git__git_commit",
70
+ "hooks": [
71
+ {
72
+ "type": "command",
73
+ "command": "~/.claude/hooks/ftm-post-commit-trigger.sh",
74
+ "timeout": 5
75
+ }
76
+ ]
77
+ }
78
+ ],
79
+ "Stop": [
80
+ {
81
+ "hooks": [
82
+ {
83
+ "type": "command",
84
+ "command": "~/.claude/hooks/ftm-blackboard-enforcer.sh",
85
+ "timeout": 5
86
+ }
87
+ ]
88
+ }
89
+ ]
90
+ }
91
+ }