feed-the-machine 1.7.1 → 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 +37 -11
- package/bin/install.mjs +57 -8
- package/docs/images/council-chat.png +0 -0
- 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/install.sh +83 -7
- package/package.json +2 -1
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
|
|
|
@@ -64,6 +76,8 @@ Not sure about an architecture choice or debugging approach? This sends the prob
|
|
|
64
76
|
```
|
|
65
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.
|
|
66
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
|
+
|
|
67
81
|
---
|
|
68
82
|
|
|
69
83
|
## Before / After
|
|
@@ -165,18 +179,28 @@ FTM ships with 26 skills and 15 automation hooks. You don't need to memorize the
|
|
|
165
179
|
| **ftm-pause / resume** | Save and restore. Pick up exactly where you left off in a new conversation |
|
|
166
180
|
| **ftm-upgrade** | Self-update. Stay current with one command |
|
|
167
181
|
|
|
168
|
-
### Hooks (
|
|
182
|
+
### Hooks (16 automations)
|
|
169
183
|
|
|
170
184
|
These run automatically in the background — no slash commands needed:
|
|
171
185
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
-
|
|
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 |
|
|
180
204
|
|
|
181
205
|
---
|
|
182
206
|
|
|
@@ -236,7 +260,9 @@ Everything else runs on Claude Code alone.
|
|
|
236
260
|
|
|
237
261
|
```bash
|
|
238
262
|
git clone https://github.com/kkudumu/feed-the-machine.git ~/feed-the-machine
|
|
239
|
-
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
|
|
240
266
|
```
|
|
241
267
|
|
|
242
268
|
**Uninstall:** `./uninstall.sh` (removes skills, keeps your memory data)
|
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
|
|
@@ -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
|
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "feed-the-machine",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.2",
|
|
4
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",
|
|
@@ -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",
|