feed-the-machine 1.7.0 → 1.7.2
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.
- package/README.md +137 -19
- package/bin/__pycache__/tasks_db.cpython-314.pyc +0 -0
- package/bin/install.mjs +57 -8
- package/docs/images/council-chat.png +0 -0
- package/ftm-audit/SKILL.md +51 -1
- package/ftm-council-chat.yml +2 -0
- package/ftm-executor/references/phases/PHASE-4-5-AUDIT.md +10 -1
- package/ftm-mind/references/blackboard-protocol.md +19 -0
- package/ftm-mind/references/decide-act-protocol.md +1 -5
- package/ftm-mind/references/protocols/PLAN-APPROVAL.md +37 -3
- package/ftm-state/schemas/experience.schema.json +50 -0
- package/hooks/ftm-learning-capture.sh +133 -0
- package/hooks/ftm-task-loader.sh +100 -0
- package/hooks/settings-template.json +10 -0
- package/install.sh +83 -7
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -24,11 +24,23 @@ Think of it like this: regular AI is a blank whiteboard every time you walk into
|
|
|
24
24
|
|
|
25
25
|
## Install
|
|
26
26
|
|
|
27
|
+
**Everything** (26 skills + 15 hooks):
|
|
27
28
|
```bash
|
|
28
29
|
npx feed-the-machine@latest
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
**Just the skills you want:**
|
|
33
|
+
```bash
|
|
34
|
+
npx feed-the-machine --only ftm-council-chat,ftm-mind
|
|
35
|
+
```
|
|
36
|
+
This always includes `ftm` (the router) and `ftm-config` as base dependencies.
|
|
37
|
+
|
|
38
|
+
**See what's available:**
|
|
39
|
+
```bash
|
|
40
|
+
npx feed-the-machine --list
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Works with any existing Claude Code setup. After install, restart Claude Code or start a new session.
|
|
32
44
|
|
|
33
45
|
---
|
|
34
46
|
|
|
@@ -52,6 +64,20 @@ Describe something you're trying to figure out. It researches the web and GitHub
|
|
|
52
64
|
```
|
|
53
65
|
Paste an error or describe weird behavior. It attacks the problem from multiple angles at once — way faster than stepping through it manually.
|
|
54
66
|
|
|
67
|
+
**Get a second opinion:**
|
|
68
|
+
```
|
|
69
|
+
/ftm-council
|
|
70
|
+
```
|
|
71
|
+
Not sure about an architecture choice or debugging approach? This sends the problem to Claude, GPT, and Gemini independently. They debate in rounds until at least two agree. Like calling three contractors instead of trusting the first quote.
|
|
72
|
+
|
|
73
|
+
**Open the chatroom:**
|
|
74
|
+
```
|
|
75
|
+
/ftm-council-chat
|
|
76
|
+
```
|
|
77
|
+
An AIM-styled browser chatroom where you, Claude, Codex, and Gemini all talk in real time. You're a full participant, not just watching.
|
|
78
|
+
|
|
79
|
+
<img src="docs/images/council-chat.png" alt="ftm-council-chat screenshot — AIM-styled chatroom with Claude, Codex, and Gemini debating Jira token types" width="800" />
|
|
80
|
+
|
|
55
81
|
---
|
|
56
82
|
|
|
57
83
|
## Before / After
|
|
@@ -98,26 +124,84 @@ Every task makes it smarter. Fix a bug? It remembers the root cause. Ship a feat
|
|
|
98
124
|
|
|
99
125
|
## What's Included
|
|
100
126
|
|
|
101
|
-
FTM
|
|
127
|
+
FTM ships with 26 skills and 15 automation hooks. You don't need to memorize them — just use `/ftm` and it picks the right ones automatically. But here's what's under the hood:
|
|
128
|
+
|
|
129
|
+
### Core
|
|
130
|
+
|
|
131
|
+
| Skill | Plain English |
|
|
132
|
+
|-------|--------------|
|
|
133
|
+
| **ftm-mind** | The brain. Reads your input, pulls memory, sizes complexity, picks the right approach |
|
|
134
|
+
| **ftm-executor** | The hands. Takes the plan and does the work with parallel agents in isolated worktrees |
|
|
135
|
+
| **ftm-ops** | Your ops dashboard. Task management, capacity tracking, stakeholder comms, meeting intel, burnout detection |
|
|
136
|
+
| **ftm-config** | Settings. Choose which AI models handle planning vs execution vs review |
|
|
137
|
+
|
|
138
|
+
### Thinking & Research
|
|
102
139
|
|
|
103
140
|
| Skill | Plain English |
|
|
104
141
|
|-------|--------------|
|
|
105
|
-
| **ftm-
|
|
106
|
-
| **ftm-
|
|
107
|
-
| **ftm-
|
|
108
|
-
| **ftm-
|
|
109
|
-
|
|
110
|
-
|
|
142
|
+
| **ftm-brainstorm** | Thinking partner. Researches the web and GitHub in parallel, challenges assumptions, surfaces options |
|
|
143
|
+
| **ftm-researcher** | Deep research engine. 7 specialized finder agents search in parallel, then adversarially review each other's findings |
|
|
144
|
+
| **ftm-council** | Second opinions. Sends the problem to Claude, GPT, and Gemini — goes with the majority |
|
|
145
|
+
| **ftm-council-chat** | AIM-styled chatroom. You, Claude, Codex, and Gemini all talking in a browser window |
|
|
146
|
+
|
|
147
|
+
### Building & Debugging
|
|
148
|
+
|
|
149
|
+
| Skill | Plain English |
|
|
150
|
+
|-------|--------------|
|
|
151
|
+
| **ftm-debug** | Bug war room. Attacks problems from multiple angles simultaneously |
|
|
152
|
+
| **ftm-audit** | Wiring check. Makes sure all code is actually connected and working — static analysis + LLM audit |
|
|
153
|
+
| **ftm-codex-gate** | Code validation gate. Sends your code to GPT for adversarial review before it ships |
|
|
154
|
+
| **ftm-browse** | Web automation. Opens browsers, fills forms, takes screenshots, inspects pages |
|
|
111
155
|
| **ftm-git** | Safety net. Scans for leaked passwords/keys before you push code |
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
|
116
|
-
|
|
117
|
-
| **ftm-
|
|
118
|
-
| **ftm-
|
|
156
|
+
|
|
157
|
+
### Codebase Intelligence
|
|
158
|
+
|
|
159
|
+
| Skill | Plain English |
|
|
160
|
+
|-------|--------------|
|
|
161
|
+
| **ftm-map** | Code knowledge graph. Knows what depends on what and what breaks if you change something |
|
|
162
|
+
| **ftm-intent** | Living docs. Keeps function-level documentation updated automatically when code changes |
|
|
163
|
+
| **ftm-diagram** | Architecture diagrams. Auto-generated mermaid diagrams that stay current |
|
|
164
|
+
|
|
165
|
+
### Quality & Learning
|
|
166
|
+
|
|
167
|
+
| Skill | Plain English |
|
|
168
|
+
|-------|--------------|
|
|
169
|
+
| **ftm-verify** | Post-execution verification. Two independent AI models audit the completed work, then auto-fix anything they find |
|
|
170
|
+
| **ftm-retro** | Self-assessment. Reviews its own execution and scores it across 5 dimensions |
|
|
171
|
+
| **ftm-capture** | Knowledge extraction. Turns what you just did into reusable routines and playbooks |
|
|
172
|
+
| **ftm-dashboard** | Analytics. Shows which skills you use, approval rates, and session stats |
|
|
173
|
+
|
|
174
|
+
### Workflow & Session
|
|
175
|
+
|
|
176
|
+
| Skill | Plain English |
|
|
177
|
+
|-------|--------------|
|
|
178
|
+
| **ftm-routine** | Recurring workflows. Define multi-step routines in YAML and run them by name |
|
|
179
|
+
| **ftm-pause / resume** | Save and restore. Pick up exactly where you left off in a new conversation |
|
|
119
180
|
| **ftm-upgrade** | Self-update. Stay current with one command |
|
|
120
181
|
|
|
182
|
+
### Hooks (16 automations)
|
|
183
|
+
|
|
184
|
+
These run automatically in the background — no slash commands needed:
|
|
185
|
+
|
|
186
|
+
| Hook | What It Does |
|
|
187
|
+
|------|-------------|
|
|
188
|
+
| **event-logger** | Logs every tool use to a JSONL file with debouncing and 30-day rotation |
|
|
189
|
+
| **auto-log** | Detects when you report completing work and reminds Claude to log progress |
|
|
190
|
+
| **learning-capture** | Captures edits and task completions into a learning database for pattern recognition |
|
|
191
|
+
| **session-snapshot** | Saves the last 15 exchanges as a markdown snapshot when a session ends — crash recovery |
|
|
192
|
+
| **session-end** | Marks the current session as completed in the blackboard |
|
|
193
|
+
| **blackboard-enforcer** | Nudges Claude to record meaningful work to the blackboard before a session ends undocumented |
|
|
194
|
+
| **plan-gate** | Prevents code edits without a plan — soft-warns at first, blocks after 3+ edits |
|
|
195
|
+
| **task-loader** | Loads active tasks from your task database when a session starts |
|
|
196
|
+
| **pre-compaction** | Monitors token usage and flushes state to disk before context hits 75% capacity |
|
|
197
|
+
| **post-compaction** | After context compresses, reloads critical state from disk before responding |
|
|
198
|
+
| **map-autodetect** | Detects unmapped projects on first use and bootstraps the code knowledge graph |
|
|
199
|
+
| **post-commit-trigger** | After git commits, triggers code map sync and documentation updates |
|
|
200
|
+
| **pending-sync-check** | Surfaces out-of-session commits that need code map syncing |
|
|
201
|
+
| **discovery-reminder** | Reminds Claude to run a discovery interview for tasks involving external systems or migrations |
|
|
202
|
+
| **drafts-gate** | Blocks Slack and Gmail sends unless a draft file exists first — no accidental messages |
|
|
203
|
+
| **install-hooks** | Self-installer that registers all hooks into Claude Code's settings.json |
|
|
204
|
+
|
|
121
205
|
---
|
|
122
206
|
|
|
123
207
|
## The Secret Sauce
|
|
@@ -140,15 +224,33 @@ Three things make FTM different from "just using Claude Code":
|
|
|
140
224
|
profile: balanced # quality | balanced | budget
|
|
141
225
|
|
|
142
226
|
profiles:
|
|
143
|
-
|
|
227
|
+
quality:
|
|
144
228
|
planning: opus # the deep thinker
|
|
145
|
-
execution:
|
|
229
|
+
execution: opus # thorough but slower
|
|
146
230
|
review: sonnet # the checker
|
|
231
|
+
|
|
232
|
+
balanced:
|
|
233
|
+
planning: opus
|
|
234
|
+
execution: sonnet # fast worker
|
|
235
|
+
review: sonnet
|
|
236
|
+
|
|
237
|
+
budget:
|
|
238
|
+
planning: sonnet
|
|
239
|
+
execution: sonnet
|
|
240
|
+
review: haiku # cheapest
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Execution settings:**
|
|
244
|
+
|
|
245
|
+
```yaml
|
|
246
|
+
execution:
|
|
247
|
+
agent_mode: bypassPermissions # how much autonomy agents get
|
|
248
|
+
max_parallel_agents: 5 # simultaneous agent workers
|
|
147
249
|
```
|
|
148
250
|
|
|
149
251
|
**Optional extras** for the full experience:
|
|
150
252
|
|
|
151
|
-
- [Codex CLI](https://github.com/openai/codex) — Powers the
|
|
253
|
+
- [Codex CLI](https://github.com/openai/codex) — Powers the council, code validation, and adversarial review
|
|
152
254
|
- [Gemini CLI](https://github.com/google/gemini-cli) — Third voice in the council
|
|
153
255
|
- Playwright MCP server (`npx @playwright/mcp@latest`) — Powers browser automation
|
|
154
256
|
|
|
@@ -158,13 +260,29 @@ Everything else runs on Claude Code alone.
|
|
|
158
260
|
|
|
159
261
|
```bash
|
|
160
262
|
git clone https://github.com/kkudumu/feed-the-machine.git ~/feed-the-machine
|
|
161
|
-
cd ~/feed-the-machine && ./install.sh
|
|
263
|
+
cd ~/feed-the-machine && ./install.sh # everything
|
|
264
|
+
# or: ./install.sh --only ftm-council-chat # just one skill
|
|
265
|
+
# or: ./install.sh --list # see what's available
|
|
162
266
|
```
|
|
163
267
|
|
|
164
268
|
**Uninstall:** `./uninstall.sh` (removes skills, keeps your memory data)
|
|
165
269
|
|
|
166
270
|
---
|
|
167
271
|
|
|
272
|
+
## Current Version
|
|
273
|
+
|
|
274
|
+
**v1.7.0** (April 2026) — [Full changelog](CHANGELOG.md)
|
|
275
|
+
|
|
276
|
+
Recent highlights:
|
|
277
|
+
- **ftm-ops**: Personal operations intelligence (task management, capacity tracking, burnout detection, stakeholder comms)
|
|
278
|
+
- **ftm-mind slimmed from 87KB to 10KB** via progressive disclosure
|
|
279
|
+
- **ftm-verify**: Dual-model adversarial verification (Codex + Gemini audit your completed work independently)
|
|
280
|
+
- **ftm-council-chat**: AIM-styled browser chatroom for multi-AI conversations
|
|
281
|
+
- **15 automation hooks** for event logging, learning capture, secret scanning, and session management
|
|
282
|
+
- **Configurable agent permissions** and parallel agent limits
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
168
286
|
## License
|
|
169
287
|
|
|
170
288
|
MIT
|
|
Binary file
|
package/bin/install.mjs
CHANGED
|
@@ -7,9 +7,12 @@
|
|
|
7
7
|
* Safe to re-run — idempotent.
|
|
8
8
|
*
|
|
9
9
|
* Flags:
|
|
10
|
-
* --
|
|
11
|
-
* --
|
|
12
|
-
* --
|
|
10
|
+
* --only skill1,skill2 Install specific skills (always includes ftm + ftm-config)
|
|
11
|
+
* --list List available skills with descriptions
|
|
12
|
+
* --with-inbox Also install the inbox service
|
|
13
|
+
* --no-hooks Skip hooks entirely
|
|
14
|
+
* --with-hooks Include hooks even with --only
|
|
15
|
+
* --skip-merge Install hook files but don't touch settings.json
|
|
13
16
|
*/
|
|
14
17
|
|
|
15
18
|
import { existsSync, mkdirSync, readdirSync, lstatSync, readFileSync, writeFileSync, copyFileSync, symlinkSync, unlinkSync, chmodSync, cpSync } from "fs";
|
|
@@ -31,8 +34,25 @@ const INBOX_INSTALL_DIR = join(HOME, ".claude", "ftm-inbox");
|
|
|
31
34
|
|
|
32
35
|
const ARGS = process.argv.slice(2);
|
|
33
36
|
const WITH_INBOX = ARGS.includes("--with-inbox");
|
|
34
|
-
const NO_HOOKS = ARGS.includes("--no-hooks");
|
|
35
37
|
const SKIP_MERGE = ARGS.includes("--skip-merge");
|
|
38
|
+
const LIST_MODE = ARGS.includes("--list");
|
|
39
|
+
const WITH_HOOKS_FLAG = ARGS.includes("--with-hooks");
|
|
40
|
+
|
|
41
|
+
// Parse --only (supports --only=x,y and --only x,y)
|
|
42
|
+
const ONLY_RAW = ARGS.find(a => a.startsWith("--only="))?.split("=")[1]
|
|
43
|
+
|| (ARGS.includes("--only") ? ARGS[ARGS.indexOf("--only") + 1] : null);
|
|
44
|
+
|
|
45
|
+
const ONLY_SKILLS = ONLY_RAW
|
|
46
|
+
? new Set(["ftm", "ftm-config", ...ONLY_RAW.split(",").map(s => s.trim())])
|
|
47
|
+
: null;
|
|
48
|
+
|
|
49
|
+
// When --only is used, skip hooks unless --with-hooks or explicit --no-hooks
|
|
50
|
+
const NO_HOOKS = ARGS.includes("--no-hooks") || (ONLY_SKILLS && !WITH_HOOKS_FLAG);
|
|
51
|
+
|
|
52
|
+
function skillWanted(name) {
|
|
53
|
+
if (!ONLY_SKILLS) return true;
|
|
54
|
+
return ONLY_SKILLS.has(name);
|
|
55
|
+
}
|
|
36
56
|
|
|
37
57
|
let warnCount = 0;
|
|
38
58
|
|
|
@@ -268,29 +288,58 @@ function verify(skillCount, hookCount) {
|
|
|
268
288
|
return { errors };
|
|
269
289
|
}
|
|
270
290
|
|
|
291
|
+
// --- List Mode ---
|
|
292
|
+
|
|
293
|
+
function listSkills() {
|
|
294
|
+
console.log("\nAvailable FTM skills:\n");
|
|
295
|
+
const ymlFiles = readdirSync(REPO_DIR).filter(
|
|
296
|
+
(f) => f.startsWith("ftm-") && f.endsWith(".yml") && !f.includes("config.default")
|
|
297
|
+
);
|
|
298
|
+
for (const yml of ymlFiles) {
|
|
299
|
+
const name = yml.replace(".yml", "");
|
|
300
|
+
const content = readFileSync(join(REPO_DIR, yml), "utf8");
|
|
301
|
+
const descMatch = content.match(/^description:\s*(.+)/m);
|
|
302
|
+
const desc = descMatch ? descMatch[1].slice(0, 80) : "";
|
|
303
|
+
console.log(` ${name.padEnd(22)} ${desc}`);
|
|
304
|
+
}
|
|
305
|
+
console.log("\nInstall specific skills: npx feed-the-machine --only ftm-council-chat,ftm-mind");
|
|
306
|
+
console.log("Install everything: npx feed-the-machine\n");
|
|
307
|
+
process.exit(0);
|
|
308
|
+
}
|
|
309
|
+
|
|
271
310
|
// --- Main ---
|
|
272
311
|
|
|
273
312
|
function main() {
|
|
313
|
+
if (LIST_MODE) {
|
|
314
|
+
listSkills();
|
|
315
|
+
}
|
|
316
|
+
|
|
274
317
|
preflight();
|
|
275
318
|
|
|
276
|
-
|
|
319
|
+
if (ONLY_SKILLS) {
|
|
320
|
+
const requested = [...ONLY_SKILLS].filter(s => s !== "ftm" && s !== "ftm-config").join(", ");
|
|
321
|
+
console.log(`Installing selected FTM skills: ${requested} (+ ftm, ftm-config)`);
|
|
322
|
+
} else {
|
|
323
|
+
console.log(`Installing all FTM skills from: ${REPO_DIR}`);
|
|
324
|
+
}
|
|
277
325
|
console.log(`Linking into: ${SKILLS_DIR}`);
|
|
278
326
|
console.log("");
|
|
279
327
|
|
|
280
328
|
ensureDir(SKILLS_DIR);
|
|
281
329
|
|
|
282
|
-
// Link
|
|
330
|
+
// Link ftm*.yml files (filtered by --only if set)
|
|
283
331
|
const ymlFiles = readdirSync(REPO_DIR).filter(
|
|
284
332
|
(f) => f.startsWith("ftm") && f.endsWith(".yml") && !f.includes("config.default")
|
|
285
|
-
);
|
|
333
|
+
).filter((f) => skillWanted(f.replace(".yml", "")));
|
|
286
334
|
for (const yml of ymlFiles) {
|
|
287
335
|
safeSymlink(join(REPO_DIR, yml), join(SKILLS_DIR, yml));
|
|
288
336
|
}
|
|
289
337
|
|
|
290
|
-
// Link
|
|
338
|
+
// Link ftm* directories (filtered by --only if set)
|
|
291
339
|
const dirs = readdirSync(REPO_DIR).filter((f) => {
|
|
292
340
|
if (!f.startsWith("ftm")) return false;
|
|
293
341
|
if (f === "ftm-state") return false;
|
|
342
|
+
if (!skillWanted(f)) return false;
|
|
294
343
|
const fullPath = join(REPO_DIR, f);
|
|
295
344
|
try {
|
|
296
345
|
return lstatSync(fullPath).isDirectory();
|
|
Binary file
|
package/ftm-audit/SKILL.md
CHANGED
|
@@ -194,6 +194,51 @@ Append a row to the root INTENT.md module map table:
|
|
|
194
194
|
|
|
195
195
|
---
|
|
196
196
|
|
|
197
|
+
## Layer 1.75: Filesystem Path Resolution Check
|
|
198
|
+
|
|
199
|
+
**Purpose:** Verify that all file paths and CLI commands referenced in SKILL.md and reference files actually resolve from the installed skill location. This catches the most dangerous class of wiring bug — code that passes every static check but fails at runtime because a path doesn't exist where the skill expects it.
|
|
200
|
+
|
|
201
|
+
**When to run:** After Layer 1.5, before Layer 2. Runs on ANY project that has `.md` files with path references (not just code projects). This is especially critical after tasks that change path references, move files, or update directory structures.
|
|
202
|
+
|
|
203
|
+
**Scope:** Scan all `.md` files in the changed diff for path references.
|
|
204
|
+
|
|
205
|
+
**Check protocol:**
|
|
206
|
+
|
|
207
|
+
1. **Find changed markdown files:** `git diff HEAD~1 --name-only -- '*.md' '*.yml'`
|
|
208
|
+
2. **For each changed file, extract path references** matching these patterns:
|
|
209
|
+
- Absolute paths: `~/.claude/`, `/Users/`, `/home/`
|
|
210
|
+
- CLI commands: `python3 <path>`, `bash <path>`, `node <path>`
|
|
211
|
+
- Skill-relative paths: `bin/`, `references/`, `scripts/`
|
|
212
|
+
- Config-referenced paths: any path that appears after a `paths.` config key
|
|
213
|
+
3. **Resolve each path from the installed location:**
|
|
214
|
+
- For skill files (in `~/.claude/skills/<name>/`): resolve relative to the skill's install directory
|
|
215
|
+
- For absolute paths (`~/.claude/...`): resolve directly
|
|
216
|
+
- For CLI commands: extract the path argument, resolve it, then verify execution with `--help` or `--version`
|
|
217
|
+
4. **Verify existence:** `test -e <resolved-path>` for each
|
|
218
|
+
5. **For CLI commands:** additionally verify `python3 <path> --help 2>&1` exits without "No such file or directory"
|
|
219
|
+
|
|
220
|
+
**Finding types:**
|
|
221
|
+
|
|
222
|
+
| Finding | Severity | Auto-fixable? |
|
|
223
|
+
|---------|----------|---------------|
|
|
224
|
+
| `BROKEN_PATH` — path in .md file doesn't resolve from installed location | HARD FAIL | No — requires human decision on correct path |
|
|
225
|
+
| `BROKEN_CLI` — CLI command path doesn't exist or errors on execution | HARD FAIL | No — requires fixing the path or creating a symlink |
|
|
226
|
+
| `HARDCODED_USER_PATH` — absolute path contains a specific username (e.g., `/Users/kioja.kudumu/`) | WARN | Yes — replace with `~/` or `$HOME/` equivalent |
|
|
227
|
+
| `STALE_PATH_REFERENCE` — path references a directory/file that was moved or renamed in this diff | HARD FAIL | Yes — update to new path |
|
|
228
|
+
|
|
229
|
+
**Output format:**
|
|
230
|
+
```
|
|
231
|
+
Layer 1.75 findings:
|
|
232
|
+
- [BROKEN_PATH] ftm-ops/SKILL.md:53 — `~/.claude/skills/ftm/bin/brain.py` does not exist (ftm/ points to router subdirectory, not repo root)
|
|
233
|
+
- [BROKEN_CLI] ftm-mind/references/orient-protocol.md:210 — `python3 ~/.claude/skills/eng-buddy/bin/brain.py` → file not found
|
|
234
|
+
- [HARDCODED_USER_PATH] ftm-mind/references/ops-routing.md:44 — `/Users/kioja.kudumu/.claude/eng-buddy/drafts/` contains hardcoded username
|
|
235
|
+
- [STALE_PATH_REFERENCE] ftm-ops/references/task-management.md:15 — `~/.claude/eng-buddy/active-tasks.md` was moved to `~/.claude/ftm-ops/active-tasks.md`
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Why this layer exists:** In the v1.7.0 merge, 160+ path references were updated across 23 files. Every static check passed. The integration test verified brain.py worked from the repo path. But when a user invoked `/ftm-ops`, it called `python3 ~/.claude/skills/ftm/bin/brain.py` — which didn't exist because the `ftm` skill symlink pointed to the `ftm/` subdirectory (the router), not the repo root. A 2-second `test -e` would have caught this. This layer ensures it always does.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
197
242
|
## Layer 2: LLM Adversarial Audit
|
|
198
243
|
|
|
199
244
|
**Mindset:** You are an adversary trying to PROVE code is dead. Not "confirm it works" — PROVE it's dead. Every new/modified export is guilty until proven innocent. You must find a complete chain from app entry point to the code in question, or it's flagged.
|
|
@@ -508,7 +553,8 @@ When invoked (manually via `/ftm-audit` or automatically post-task):
|
|
|
508
553
|
1. Run Phase 0 (detect project patterns — framework, router, state, API layer)
|
|
509
554
|
2. Run Layer 1 (knip static analysis)
|
|
510
555
|
3. Run Layer 1.5 (documentation coverage check — INTENT.md entries for changed functions)
|
|
511
|
-
4. Run Layer
|
|
556
|
+
4. Run Layer 1.75 (filesystem path resolution — verify all referenced paths exist from installed location)
|
|
557
|
+
5. Run Layer 2 (LLM adversarial audit, calibrated to detected patterns)
|
|
512
558
|
5. Combine findings, deduplicate
|
|
513
559
|
6. Run Layer 3 (auto-fix) for each finding (including missing INTENT.md entries)
|
|
514
560
|
7. Re-verify (re-run Layers 1+1.5+2)
|
|
@@ -540,6 +586,10 @@ After completing, update the blackboard:
|
|
|
540
586
|
- Findings: [N]
|
|
541
587
|
- [list each finding — missing entries, stale entries, missing module docs]
|
|
542
588
|
|
|
589
|
+
### Layer 1.75: Path Resolution
|
|
590
|
+
- Findings: [N]
|
|
591
|
+
- [list each finding — broken paths, broken CLI commands, hardcoded user paths]
|
|
592
|
+
|
|
543
593
|
### Layer 2: Adversarial Audit
|
|
544
594
|
- Findings: [N]
|
|
545
595
|
- [list each finding with file:line and evidence]
|
|
@@ -19,7 +19,16 @@ Before running ftm-audit, verify these four checks for every task:
|
|
|
19
19
|
|
|
20
20
|
**Graceful degradation**: If ftm-browse binary is not installed, skip visual checks with a note: "Visual smoke test skipped — ftm-browse not installed." Do not fail the task.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
6. **Path resolution check** — If the task changed any file/CLI path references in SKILL.md or reference files:
|
|
23
|
+
- Extract all paths matching patterns: `~/.claude/`, `bin/`, `python3 `, `bash `
|
|
24
|
+
- Resolve each path from the installed skill location (`~/.claude/skills/<skill-name>/`)
|
|
25
|
+
- Verify the target exists: `test -e <resolved-path>`
|
|
26
|
+
- For CLI commands (`python3 <path>`), verify execution: `python3 <path> --help` exits 0
|
|
27
|
+
- Flag any path that doesn't resolve as a BLOCKER — the skill will fail at runtime
|
|
28
|
+
|
|
29
|
+
**Why this exists**: In the v1.7.0 eng-buddy merge, all brain.py references were updated from `~/.claude/skills/eng-buddy/bin/brain.py` to `~/.claude/skills/ftm/bin/brain.py`. The path existed in the repo but `~/.claude/skills/ftm/` pointed to the `ftm/` subdirectory (the router skill), not the repo root — so the path didn't resolve at runtime. This was caught by a user, not by the audit. A 2-second `test -e` check would have caught it.
|
|
30
|
+
|
|
31
|
+
A task is NOT marked complete until checks 1–4 pass (checks 5-6 are conditional).
|
|
23
32
|
|
|
24
33
|
**Failure handling:**
|
|
25
34
|
- Test failures → agent must fix before task completes
|
|
@@ -108,3 +108,22 @@ If `experiences/index.json` has no usable matches:
|
|
|
108
108
|
- continue normally
|
|
109
109
|
- lean harder on current repo state and direct inspection
|
|
110
110
|
- record the resulting experience aggressively after completion
|
|
111
|
+
|
|
112
|
+
## Recording Code Patterns and API Gotchas
|
|
113
|
+
|
|
114
|
+
When writing an experience after task completion, actively check for these:
|
|
115
|
+
|
|
116
|
+
**code_patterns** — If during the task you wrote code that interacts with an API, library, or module, save the **final working version** (not the failed attempts). Include imports, setup, and the actual call. This is the copy-pasteable snippet future sessions will use.
|
|
117
|
+
|
|
118
|
+
**api_gotchas** — If you hit errors because an API behaved differently than expected (wrong return type, unexpected method name, None instead of Response, objects instead of dicts), record each one. Format: what the module is, what's surprising, and what you'd wrongly assume.
|
|
119
|
+
|
|
120
|
+
**playbook_ref** — If a brain.py playbook was created (via ftm-capture or auto-playbook trigger), record the path so the experience and playbook cross-reference each other.
|
|
121
|
+
|
|
122
|
+
**When to populate these fields:**
|
|
123
|
+
- You hit 2+ errors on the same module before getting it right → record code_patterns + api_gotchas
|
|
124
|
+
- You used an API/library for the first time in this project → record code_patterns
|
|
125
|
+
- The auto-playbook hook fired → record all three fields
|
|
126
|
+
|
|
127
|
+
**When to skip:**
|
|
128
|
+
- Pure file editing, config changes, or git operations — no API interaction worth capturing
|
|
129
|
+
- The code pattern is already in an existing experience (check by module name before duplicating)
|
|
@@ -37,11 +37,7 @@ Going ahead unless you say otherwise.
|
|
|
37
37
|
|
|
38
38
|
**Step 0: Discovery Interview (if applicable).** Before generating the plan, check whether a Discovery Interview is needed (see Orient reference). If the task involves external systems, stakeholder coordination, or unfamiliar code, run the interview FIRST.
|
|
39
39
|
|
|
40
|
-
**Step 1: Generate the plan.** Build a numbered list
|
|
41
|
-
- A number
|
|
42
|
-
- A one-line description
|
|
43
|
-
- The files that will be touched
|
|
44
|
-
- The verification method
|
|
40
|
+
**Step 1: Generate the plan.** Build a numbered checkbox list. This format is **mandatory** — no narrative steps, no prose paragraphs. Every plan MUST use: `N. [ ] One-line action → target`. See `references/protocols/PLAN-APPROVAL.md` for the full format spec, examples for code/ops/comms/infra tasks, and the list of NEVER-produce anti-patterns.
|
|
45
41
|
|
|
46
42
|
**Step 2: Parse the user's response.**
|
|
47
43
|
|
|
@@ -10,11 +10,30 @@ For **medium and large** tasks, present a numbered task list and wait for the us
|
|
|
10
10
|
|
|
11
11
|
**Step 1: Generate the plan.**
|
|
12
12
|
|
|
13
|
-
Build a numbered list
|
|
13
|
+
Build a numbered checkbox list. This format is **mandatory** — no narrative steps, no prose paragraphs, no bullet-point summaries. Every plan, whether it's code, ops, comms, or infrastructure, MUST use this exact format:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
N. [ ] One-line action → target (file, channel, system, or person)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Each step must have:
|
|
14
20
|
- A number
|
|
21
|
+
- A `[ ]` checkbox (literal characters, not rendered)
|
|
15
22
|
- A one-line description of what will be done
|
|
16
|
-
-
|
|
17
|
-
-
|
|
23
|
+
- An arrow `→` pointing to the target: file path for code, channel/email for comms, system name for infra, or "self-evident" for simple actions
|
|
24
|
+
- If applicable, a verification method after the target: `verify: test / lint / visual check / confirmation`
|
|
25
|
+
|
|
26
|
+
**This applies to ALL task types, not just code:**
|
|
27
|
+
- Code tasks: `3. [ ] Add OAuth validation → src/middleware/oauth.ts verify: npm test`
|
|
28
|
+
- Ops/comms tasks: `1. [ ] Reply to Everett requesting domain mapping → Braintrust support thread`
|
|
29
|
+
- Infra tasks: `2. [ ] Create Freshservice webhook trigger → freshservice admin / workflows`
|
|
30
|
+
- Admin tasks: `4. [ ] Close out ITWORK2-9772 → Jira`
|
|
31
|
+
|
|
32
|
+
**NEVER produce:**
|
|
33
|
+
- Narrative paragraphs describing steps
|
|
34
|
+
- Numbered steps without `[ ]` checkboxes
|
|
35
|
+
- Steps with sub-bullets explaining details (put that in execution, not the plan)
|
|
36
|
+
- Headers like "Step 1:" or "### Step 1" — use the numbered checkbox format only
|
|
18
37
|
|
|
19
38
|
Present it like this:
|
|
20
39
|
|
|
@@ -36,6 +55,21 @@ Approve all? Or tell me what to change.
|
|
|
36
55
|
- "deny" or "stop" → cancel entirely
|
|
37
56
|
```
|
|
38
57
|
|
|
58
|
+
**Ops example:**
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
Here's my plan for the Braintrust post-SSO setup:
|
|
62
|
+
|
|
63
|
+
1. [ ] Reply to Everett requesting domain mapping + group mappings → Braintrust support thread
|
|
64
|
+
2. [ ] Reply to Spencer with admin process answer → #proj-braintrust
|
|
65
|
+
3. [ ] Request API key from Everett or Spencer → Braintrust org settings
|
|
66
|
+
4. [ ] Build Freshservice webhook → Braintrust API integration → freshservice workflows + Lambda
|
|
67
|
+
5. [ ] Reconcile existing users vs Okta groups → Braintrust API + Okta
|
|
68
|
+
6. [ ] Close out ITWORK2-9772 → Jira
|
|
69
|
+
|
|
70
|
+
Approve all? Or tell me what to change.
|
|
71
|
+
```
|
|
72
|
+
|
|
39
73
|
**Step 2: Parse the user's response.**
|
|
40
74
|
|
|
41
75
|
| User says | Action |
|
|
@@ -73,6 +73,56 @@
|
|
|
73
73
|
"items": {
|
|
74
74
|
"type": "string"
|
|
75
75
|
}
|
|
76
|
+
},
|
|
77
|
+
"code_patterns": {
|
|
78
|
+
"type": "array",
|
|
79
|
+
"description": "Working code snippets discovered during this task — the final correct version, not the failed attempts. Include imports, setup, and the actual call pattern.",
|
|
80
|
+
"items": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"required": ["module", "code"],
|
|
83
|
+
"additionalProperties": false,
|
|
84
|
+
"properties": {
|
|
85
|
+
"module": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"description": "The primary module/library this pattern uses (e.g. shared_services.okta.groups)"
|
|
88
|
+
},
|
|
89
|
+
"code": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
"description": "The working code snippet — complete and copy-pasteable"
|
|
92
|
+
},
|
|
93
|
+
"description": {
|
|
94
|
+
"type": "string",
|
|
95
|
+
"description": "One-line description of what this pattern does"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"api_gotchas": {
|
|
101
|
+
"type": "array",
|
|
102
|
+
"description": "Surprising API behaviors, wrong assumptions, and type mismatches discovered during this task. Things that caused errors and would cause them again without this note.",
|
|
103
|
+
"items": {
|
|
104
|
+
"type": "object",
|
|
105
|
+
"required": ["module", "gotcha"],
|
|
106
|
+
"additionalProperties": false,
|
|
107
|
+
"properties": {
|
|
108
|
+
"module": {
|
|
109
|
+
"type": "string",
|
|
110
|
+
"description": "The module/API where the gotcha was discovered"
|
|
111
|
+
},
|
|
112
|
+
"gotcha": {
|
|
113
|
+
"type": "string",
|
|
114
|
+
"description": "What's surprising or wrong — e.g. 'list_members() returns OktaUser objects, not dicts'"
|
|
115
|
+
},
|
|
116
|
+
"wrong_assumption": {
|
|
117
|
+
"type": "string",
|
|
118
|
+
"description": "What you'd naturally assume that turns out to be wrong"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"playbook_ref": {
|
|
124
|
+
"type": "string",
|
|
125
|
+
"description": "Path to the brain.py playbook entry if one was created from this experience, for cross-referencing"
|
|
76
126
|
}
|
|
77
127
|
}
|
|
78
128
|
}
|
|
@@ -100,6 +100,139 @@ except Exception:
|
|
|
100
100
|
fi
|
|
101
101
|
fi
|
|
102
102
|
|
|
103
|
+
# --- Auto-Playbook Trigger: detect repeated errors then success on same module ---
|
|
104
|
+
ERROR_TRACKER="$FTM_STATE/.error-tracker.jsonl"
|
|
105
|
+
|
|
106
|
+
if [ "$TOOL_NAME" = "Bash" ]; then
|
|
107
|
+
# Extract module/import and error status from the tool result
|
|
108
|
+
ANALYSIS=$(printf '%s' "$PAYLOAD" | python3 -c '
|
|
109
|
+
import json, sys
|
|
110
|
+
try:
|
|
111
|
+
d = json.load(sys.stdin)
|
|
112
|
+
output = d.get("tool_result", "") or ""
|
|
113
|
+
tool_input = d.get("tool_input", {})
|
|
114
|
+
command = tool_input.get("command", "") if isinstance(tool_input, dict) else ""
|
|
115
|
+
is_error = "Error:" in output or "Traceback" in output or "AttributeError" in output or "TypeError" in output or "ImportError" in output or "ModuleNotFoundError" in output
|
|
116
|
+
# Extract the primary module from import statements in the command
|
|
117
|
+
module = ""
|
|
118
|
+
for line in command.split("\n"):
|
|
119
|
+
line = line.strip()
|
|
120
|
+
if line.startswith("from ") and " import " in line:
|
|
121
|
+
module = line.split("from ")[1].split(" import")[0].strip()
|
|
122
|
+
break
|
|
123
|
+
elif line.startswith("import "):
|
|
124
|
+
module = line.split("import ")[1].split()[0].strip()
|
|
125
|
+
break
|
|
126
|
+
import json as j
|
|
127
|
+
j.dump({"is_error": is_error, "module": module}, sys.stdout)
|
|
128
|
+
except Exception:
|
|
129
|
+
import json as j
|
|
130
|
+
j.dump({"is_error": False, "module": ""}, sys.stdout)
|
|
131
|
+
' 2>/dev/null)
|
|
132
|
+
|
|
133
|
+
IS_ERROR=$(printf '%s' "$ANALYSIS" | python3 -c 'import json,sys; print("1" if json.load(sys.stdin).get("is_error") else "0")' 2>/dev/null)
|
|
134
|
+
MODULE=$(printf '%s' "$ANALYSIS" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("module",""))' 2>/dev/null)
|
|
135
|
+
|
|
136
|
+
if [ -n "$MODULE" ]; then
|
|
137
|
+
TIMESTAMP=$(date +%s)
|
|
138
|
+
if [ "$IS_ERROR" = "1" ]; then
|
|
139
|
+
# Append error event
|
|
140
|
+
echo "{\"ts\":$TIMESTAMP,\"module\":\"$MODULE\",\"type\":\"error\"}" >> "$ERROR_TRACKER"
|
|
141
|
+
else
|
|
142
|
+
# Success — check if this module had 3+ recent errors
|
|
143
|
+
if [ -f "$ERROR_TRACKER" ]; then
|
|
144
|
+
ERROR_COUNT=$(python3 -c "
|
|
145
|
+
import json, sys
|
|
146
|
+
count = 0
|
|
147
|
+
cutoff = $TIMESTAMP - 600 # last 10 minutes
|
|
148
|
+
for line in open('$ERROR_TRACKER'):
|
|
149
|
+
line = line.strip()
|
|
150
|
+
if not line: continue
|
|
151
|
+
try:
|
|
152
|
+
ev = json.loads(line)
|
|
153
|
+
if ev.get('module') == '$MODULE' and ev.get('type') == 'error' and ev.get('ts', 0) >= cutoff:
|
|
154
|
+
count += 1
|
|
155
|
+
except: pass
|
|
156
|
+
print(count)
|
|
157
|
+
" 2>/dev/null)
|
|
158
|
+
|
|
159
|
+
if [ "$ERROR_COUNT" -ge 3 ]; then
|
|
160
|
+
# Write a blackboard experience with the module and error count
|
|
161
|
+
SLUG=$(echo "$MODULE" | tr '.' '-' | tr '[:upper:]' '[:lower:]')
|
|
162
|
+
DATE_STAMP=$(date +%Y-%m-%d)
|
|
163
|
+
EXP_FILE="$FTM_STATE/blackboard/experiences/${DATE_STAMP}_auto-playbook-${SLUG}.json"
|
|
164
|
+
python3 -c "
|
|
165
|
+
import json, os, datetime
|
|
166
|
+
exp = {
|
|
167
|
+
'task_type': 'api-discovery',
|
|
168
|
+
'task_description': 'Auto-captured: $ERROR_COUNT errors on $MODULE before finding the working pattern',
|
|
169
|
+
'lessons_learned': [
|
|
170
|
+
'$MODULE required $ERROR_COUNT attempts to get right — working pattern saved as playbook',
|
|
171
|
+
'Check experience code_patterns before using this module in future sessions'
|
|
172
|
+
],
|
|
173
|
+
'code_patterns': [],
|
|
174
|
+
'api_gotchas': [],
|
|
175
|
+
'playbook_ref': None,
|
|
176
|
+
'timestamp': datetime.datetime.utcnow().isoformat() + 'Z',
|
|
177
|
+
'confidence': 0.7,
|
|
178
|
+
'tags': ['auto-playbook', '$(echo "$MODULE" | tr "." " ")']
|
|
179
|
+
}
|
|
180
|
+
os.makedirs(os.path.dirname('$EXP_FILE'), exist_ok=True)
|
|
181
|
+
with open('$EXP_FILE', 'w') as f:
|
|
182
|
+
json.dump(exp, f, indent=2)
|
|
183
|
+
|
|
184
|
+
# Update index
|
|
185
|
+
idx_path = '$FTM_STATE/blackboard/experiences/index.json'
|
|
186
|
+
try:
|
|
187
|
+
with open(idx_path) as f:
|
|
188
|
+
idx = json.load(f)
|
|
189
|
+
except:
|
|
190
|
+
idx = {'entries': [], 'metadata': {'total_count': 0, 'last_updated': None, 'max_entries': 200, 'pruning_strategy': 'remove_oldest_low_confidence'}}
|
|
191
|
+
|
|
192
|
+
idx['entries'].append({
|
|
193
|
+
'id': 'auto-playbook-${SLUG}-${DATE_STAMP}',
|
|
194
|
+
'file': os.path.basename('$EXP_FILE'),
|
|
195
|
+
'task_type': 'api-discovery',
|
|
196
|
+
'tags': ['auto-playbook', '$(echo "$MODULE" | tr "." " ")'],
|
|
197
|
+
'timestamp': exp['timestamp'],
|
|
198
|
+
'confidence': 0.7
|
|
199
|
+
})
|
|
200
|
+
idx['metadata']['total_count'] = len(idx['entries'])
|
|
201
|
+
idx['metadata']['last_updated'] = exp['timestamp']
|
|
202
|
+
with open(idx_path, 'w') as f:
|
|
203
|
+
json.dump(idx, f, indent=2)
|
|
204
|
+
" 2>/dev/null
|
|
205
|
+
|
|
206
|
+
echo ""
|
|
207
|
+
echo "[Auto-Playbook] Detected $ERROR_COUNT errors on '$MODULE' followed by a success in this session."
|
|
208
|
+
echo "A skeleton experience has been saved to the blackboard at: $EXP_FILE"
|
|
209
|
+
echo "NOW you MUST do two things:"
|
|
210
|
+
echo "1. Proactively invoke ftm-capture to save the working code pattern as a playbook"
|
|
211
|
+
echo "2. Update the experience file at $EXP_FILE — fill in the code_patterns array with the working snippet and api_gotchas with every wrong assumption you hit"
|
|
212
|
+
echo "Tell the user: \"That was rough — I'm saving the working pattern so next time it's one clean shot.\""
|
|
213
|
+
echo ""
|
|
214
|
+
|
|
215
|
+
# Clear tracked errors for this module so we don't re-trigger
|
|
216
|
+
python3 -c "
|
|
217
|
+
import json
|
|
218
|
+
lines = []
|
|
219
|
+
for line in open('$ERROR_TRACKER'):
|
|
220
|
+
line = line.strip()
|
|
221
|
+
if not line: continue
|
|
222
|
+
try:
|
|
223
|
+
ev = json.loads(line)
|
|
224
|
+
if ev.get('module') != '$MODULE':
|
|
225
|
+
lines.append(line)
|
|
226
|
+
except: pass
|
|
227
|
+
with open('$ERROR_TRACKER', 'w') as f:
|
|
228
|
+
f.write('\n'.join(lines) + '\n' if lines else '')
|
|
229
|
+
" 2>/dev/null
|
|
230
|
+
fi
|
|
231
|
+
fi
|
|
232
|
+
fi
|
|
233
|
+
fi
|
|
234
|
+
fi
|
|
235
|
+
|
|
103
236
|
# --- Feed Playbook Tracer if active trace exists ---
|
|
104
237
|
ACTIVE_TRACE_FILE="$FTM_STATE/.active-trace-id"
|
|
105
238
|
if [ -f "$ACTIVE_TRACE_FILE" ] && [ -f "$BRAIN_PY" ]; then
|
|
@@ -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
|
package/install.sh
CHANGED
|
@@ -7,9 +7,13 @@ set -euo pipefail
|
|
|
7
7
|
# Safe to re-run — idempotent.
|
|
8
8
|
#
|
|
9
9
|
# Usage:
|
|
10
|
-
# ./install.sh
|
|
11
|
-
# ./install.sh --
|
|
12
|
-
# ./install.sh --
|
|
10
|
+
# ./install.sh # Full install (all skills + hooks)
|
|
11
|
+
# ./install.sh --only ftm-council-chat # Install specific skill(s)
|
|
12
|
+
# ./install.sh --only ftm-mind,ftm-debug # Install multiple specific skills
|
|
13
|
+
# ./install.sh --list # List available skills
|
|
14
|
+
# ./install.sh --no-hooks # Skills only, skip hooks
|
|
15
|
+
# ./install.sh --only ftm-mind --with-hooks # Specific skills + all hooks
|
|
16
|
+
# ./install.sh --skip-merge # Install hook files but don't touch settings.json
|
|
13
17
|
|
|
14
18
|
REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
15
19
|
SKILLS_DIR="$HOME/.claude/skills"
|
|
@@ -20,21 +24,85 @@ SETTINGS_FILE="$CONFIG_DIR/settings.json"
|
|
|
20
24
|
|
|
21
25
|
NO_HOOKS=false
|
|
22
26
|
SKIP_MERGE=false
|
|
23
|
-
|
|
27
|
+
WITH_HOOKS=false
|
|
28
|
+
LIST_MODE=false
|
|
29
|
+
ONLY_SKILLS=""
|
|
30
|
+
|
|
31
|
+
i=1
|
|
32
|
+
while [ "$i" -le "$#" ]; do
|
|
33
|
+
arg="${!i}"
|
|
24
34
|
case "$arg" in
|
|
25
35
|
--no-hooks) NO_HOOKS=true ;;
|
|
26
36
|
--skip-merge) SKIP_MERGE=true ;;
|
|
37
|
+
--with-hooks) WITH_HOOKS=true ;;
|
|
38
|
+
--list) LIST_MODE=true ;;
|
|
39
|
+
--only=*) ONLY_SKILLS="${arg#--only=}" ;;
|
|
40
|
+
--only)
|
|
41
|
+
i=$((i + 1))
|
|
42
|
+
ONLY_SKILLS="${!i}"
|
|
43
|
+
;;
|
|
27
44
|
# Keep --setup-hooks for backwards compat (now a no-op since merge is default)
|
|
28
45
|
--setup-hooks) ;;
|
|
29
46
|
esac
|
|
47
|
+
i=$((i + 1))
|
|
30
48
|
done
|
|
31
49
|
|
|
50
|
+
# When --only is used, skip hooks by default unless --with-hooks
|
|
51
|
+
if [ -n "$ONLY_SKILLS" ] && [ "$WITH_HOOKS" != true ]; then
|
|
52
|
+
NO_HOOKS=true
|
|
53
|
+
fi
|
|
54
|
+
|
|
32
55
|
WARN_COUNT=0
|
|
33
56
|
warn() {
|
|
34
57
|
echo " WARN: $1"
|
|
35
58
|
WARN_COUNT=$((WARN_COUNT + 1))
|
|
36
59
|
}
|
|
37
60
|
|
|
61
|
+
# --- List Mode ---
|
|
62
|
+
|
|
63
|
+
if [ "$LIST_MODE" = true ]; then
|
|
64
|
+
echo ""
|
|
65
|
+
echo "Available FTM skills:"
|
|
66
|
+
echo ""
|
|
67
|
+
for yml in "$REPO_DIR"/ftm-*.yml; do
|
|
68
|
+
[ -f "$yml" ] || continue
|
|
69
|
+
name=$(basename "$yml" .yml)
|
|
70
|
+
[[ "$name" == *".default"* ]] && continue
|
|
71
|
+
desc=$(grep '^description:' "$yml" | head -1 | sed 's/^description: *//' | cut -c1-80)
|
|
72
|
+
printf " %-22s %s\n" "$name" "$desc"
|
|
73
|
+
done
|
|
74
|
+
echo ""
|
|
75
|
+
echo "Install specific skills: ./install.sh --only ftm-council-chat,ftm-mind"
|
|
76
|
+
echo "Install everything: ./install.sh"
|
|
77
|
+
exit 0
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# --- Skill Filter ---
|
|
81
|
+
|
|
82
|
+
declare -a SKILL_FILTER=()
|
|
83
|
+
if [ -n "$ONLY_SKILLS" ]; then
|
|
84
|
+
# Always include base dependencies
|
|
85
|
+
SKILL_FILTER+=("ftm" "ftm-config")
|
|
86
|
+
IFS=',' read -ra REQUESTED <<< "$ONLY_SKILLS"
|
|
87
|
+
for s in "${REQUESTED[@]}"; do
|
|
88
|
+
s=$(echo "$s" | xargs) # trim whitespace
|
|
89
|
+
SKILL_FILTER+=("$s")
|
|
90
|
+
done
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
skill_wanted() {
|
|
94
|
+
local name="$1"
|
|
95
|
+
if [ ${#SKILL_FILTER[@]} -eq 0 ]; then
|
|
96
|
+
return 0 # no filter = install all
|
|
97
|
+
fi
|
|
98
|
+
for wanted in "${SKILL_FILTER[@]}"; do
|
|
99
|
+
if [ "$name" = "$wanted" ]; then
|
|
100
|
+
return 0
|
|
101
|
+
fi
|
|
102
|
+
done
|
|
103
|
+
return 1
|
|
104
|
+
}
|
|
105
|
+
|
|
38
106
|
# --- Preflight Checks ---
|
|
39
107
|
|
|
40
108
|
echo "Preflight checks..."
|
|
@@ -76,7 +144,11 @@ else
|
|
|
76
144
|
fi
|
|
77
145
|
|
|
78
146
|
echo ""
|
|
79
|
-
|
|
147
|
+
if [ -n "$ONLY_SKILLS" ]; then
|
|
148
|
+
echo "Installing selected FTM skills: $ONLY_SKILLS (+ ftm, ftm-config)"
|
|
149
|
+
else
|
|
150
|
+
echo "Installing all FTM skills from: $REPO_DIR"
|
|
151
|
+
fi
|
|
80
152
|
echo "Linking into: $SKILLS_DIR"
|
|
81
153
|
echo ""
|
|
82
154
|
|
|
@@ -84,12 +156,13 @@ mkdir -p "$SKILLS_DIR"
|
|
|
84
156
|
|
|
85
157
|
# --- Skills ---
|
|
86
158
|
|
|
87
|
-
# Link
|
|
159
|
+
# Link ftm*.yml files (filtered by --only if set)
|
|
88
160
|
for yml in "$REPO_DIR"/ftm*.yml; do
|
|
89
161
|
[ -f "$yml" ] || continue
|
|
90
162
|
name=$(basename "$yml")
|
|
91
163
|
# Skip ftm-config.default.yml — it's a template, not a skill
|
|
92
164
|
[[ "$name" == *".default."* ]] && continue
|
|
165
|
+
skill_wanted "${name%.yml}" || continue
|
|
93
166
|
target="$SKILLS_DIR/$name"
|
|
94
167
|
if [ -L "$target" ]; then
|
|
95
168
|
rm "$target"
|
|
@@ -101,11 +174,12 @@ for yml in "$REPO_DIR"/ftm*.yml; do
|
|
|
101
174
|
echo " LINK $name"
|
|
102
175
|
done
|
|
103
176
|
|
|
104
|
-
# Link
|
|
177
|
+
# Link ftm* directories (filtered by --only if set)
|
|
105
178
|
for dir in "$REPO_DIR"/ftm*/; do
|
|
106
179
|
[ -d "$dir" ] || continue
|
|
107
180
|
name=$(basename "$dir")
|
|
108
181
|
[ "$name" = "ftm-state" ] && continue # state is handled separately
|
|
182
|
+
skill_wanted "$name" || continue
|
|
109
183
|
target="$SKILLS_DIR/$name"
|
|
110
184
|
if [ -L "$target" ]; then
|
|
111
185
|
rm "$target"
|
|
@@ -121,6 +195,8 @@ SKILL_COUNT=0
|
|
|
121
195
|
for _f in "$REPO_DIR"/ftm*.yml; do
|
|
122
196
|
[ -e "$_f" ] || continue
|
|
123
197
|
case "$_f" in *.default.*) continue ;; esac
|
|
198
|
+
_name=$(basename "$_f" .yml)
|
|
199
|
+
skill_wanted "$_name" || continue
|
|
124
200
|
SKILL_COUNT=$((SKILL_COUNT + 1))
|
|
125
201
|
done
|
|
126
202
|
echo ""
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "feed-the-machine",
|
|
3
|
-
"version": "1.7.
|
|
4
|
-
"description": "A
|
|
3
|
+
"version": "1.7.2",
|
|
4
|
+
"description": "A brain upgrade for Claude Code — 26 skills that teach it how to think before acting, remember across conversations, debug like a war room, run plans on autopilot with agent teams, and get second opinions from GPT & Gemini. Plus 15 hooks that automate the boring stuff.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "kkudumu",
|
|
7
7
|
"type": "module",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"bin/",
|
|
27
27
|
"hooks/",
|
|
28
28
|
"docs/HOOKS.md",
|
|
29
|
+
"docs/images/",
|
|
29
30
|
"install.sh",
|
|
30
31
|
"uninstall.sh",
|
|
31
32
|
"ftm.yml",
|