infernoflow 0.44.10 โ†’ 0.44.12

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 CHANGED
@@ -1,6 +1,8 @@
1
1
  # ๐Ÿ”ฅ infernoflow
2
2
 
3
- > Persistent memory for AI coding sessions. Capture what agents can't infer from code โ€” the gotchas, the decisions, the failed approaches โ€” and replay it into your next chat so you stop re-deriving context every time you open Cursor / Claude Code / Copilot.
3
+ > **Every new AI session starts cold โ€” the gotchas you found, the decisions you made, don't survive. infernoflow makes them stick.**
4
+ >
5
+ > Persistent memory for AI coding sessions. Captures the gotchas, decisions and dead ends your code can't tell an agent โ€” and replays them into your next Cursor / Claude Code / Copilot chat so the same wrong turn never happens twice.
4
6
 
5
7
  [![npm version](https://img.shields.io/npm/v/infernoflow.svg?color=orange)](https://www.npmjs.com/package/infernoflow)
6
8
  [![npm downloads](https://img.shields.io/npm/dw/infernoflow.svg?color=orange)](https://www.npmjs.com/package/infernoflow)
@@ -8,16 +10,45 @@
8
10
  [![VS Code Marketplace](https://img.shields.io/visual-studio-marketplace/v/infernoflow.infernoflow?label=VS%20Code&color=orange)](https://marketplace.visualstudio.com/items?itemName=infernoflow.infernoflow)
9
11
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
10
12
 
13
+ You spent 90 minutes yesterday teaching Claude that your auth API returns `200` even on errors โ€” check the response body, not the status code. Today you ask the same kind of question and watch it write `if (response.ok) { return data }` all over again. The AI didn't get worse overnight. **It just doesn't have memory.**
14
+
15
+ infernoflow is a local-first CLI + VS Code extension + open protocol (AMP) that gives your AI persistent context across sessions. No SaaS. JSONL on disk. Three rule files your IDE already reads.
16
+
17
+ ---
18
+
19
+ ## What's new โ€” ๐Ÿ†• v0.44.10: session bookmarks
20
+
21
+ Drop a **named resume point** mid-session. When the context window fills up or you're about to make a risky change, one command captures the current session's transcript and stores it as a jumpable checkpoint:
22
+
23
+ ```bash
24
+ infernoflow bookmark "before the SP refactor"
25
+ ```
26
+
27
+ Or just say it in chat โ€” the Cursor `beforeSubmitPrompt` hook catches phrases like `"bookmark this"` / `"mark this point"` / `"save this checkpoint"` and drops the bookmark itself, no AI cooperation required. Or let the AI do it via the `amp_bookmark` MCP tool when it notices the session is getting long.
28
+
29
+ **No `--note`?** infernoflow reads Claude Code's own on-disk transcript (`~/.claude/projects/โ€ฆ/*.jsonl`), distills the last 40 turns to markdown, and stores it as the bookmark's context. Fully deterministic โ€” no model call. Recall any time:
30
+
31
+ ```bash
32
+ infernoflow bookmark list # โ— has context, โ—‹ marker only
33
+ infernoflow bookmark show "SP refactor" # jump back โ€” see the whole snapshot
34
+ ```
35
+
36
+ Bookmarks are **never auto-pruned**, and they surface in `infernoflow switch` under a `## ๐Ÿ”– Bookmarks โ€” Resume Points` section so the next session opens right where work paused.
37
+
38
+ ---
39
+
11
40
  ## The loop
12
41
 
13
- Every new AI session today starts cold. The agent re-reads your code, re-derives the obvious, and often re-makes the same wrong move someone else made yesterday. infernoflow closes that loop in four stages:
42
+ Every new AI session today starts cold. The agent re-reads your code, re-derives the obvious, and re-makes the same wrong move someone else made yesterday. infernoflow closes that loop in four stages:
14
43
 
15
- 1. **Capture** โ€” while you and the agent work, certain moments are worth saving: a gotcha hit, a decision made, an attempted fix that failed, a pattern noticed. The agent writes them down automatically via the `amp_write` MCP tool โ€” no copy/paste, no `git commit -m`. A protocol block injected into the rule files teaches the AI exactly when to log โ€” e.g. when you type `!!`, `retry`, `not working`, `still broken`, or describe a `Plan:` โ€” and a Cursor `beforeSubmitPrompt` hook backstops the AI by scanning your prompt for those triggers and writing the entry deterministically when the AI doesn't.
16
- 2. **Link** โ€” each captured moment becomes a structured AMP entry (`gotcha | decision | attempt | note | detection | pattern`) with timestamp, file:line, tags, and a stable AMP id. Linked into the project, not your scratchpad.
44
+ 1. **Capture** โ€” while you and the agent work, moments worth saving get logged automatically: a gotcha hit, a decision made, an attempted fix that failed, a pattern noticed, a resume point marked. The AI writes them via the `amp_write` MCP tool. A protocol block injected into the rule files teaches it exactly when. A Cursor `beforeSubmitPrompt` hook backstops the AI by scanning your prompt for triggers (`!!`, `retry`, `not working`, `still broken`, `bookmark this`) and writing the entry deterministically when the AI doesn't.
45
+ 2. **Link** โ€” each captured moment becomes a structured AMP entry (`gotcha | decision | attempt | note | detection | pattern | bookmark`) with timestamp, `file:line`, tags, and a stable AMP id.
17
46
  3. **Persist** โ€” entries land in `.ai-memory/branches/<branch>.jsonl` (git-tracked, travels with your branch โ€” teammates inherit it) plus `.ai-memory/global.jsonl` (personal preferences, gitignored, synced across your machines via any OS-synced folder).
18
- 4. **Restore** โ€” when a new session starts, the agent reads `CLAUDE.md` / `.cursorrules` / `copilot-instructions.md` at boot. The most relevant entries are already there. Warm start; no cold derivation.
47
+ 4. **Restore** โ€” when a new session starts, the agent reads `CLAUDE.md` / `.cursorrules` / `copilot-instructions.md` at boot. The most relevant entries are already there. **Warm start; no cold derivation.**
19
48
 
20
- That's it. No service to log into. No SaaS. JSONL on disk, an MCP server, and three rule files your IDE already reads.
49
+ No service to log into. No SaaS. JSONL on disk, an MCP server, and three rule files your IDE already reads.
50
+
51
+ ---
21
52
 
22
53
  ## Install
23
54
 
@@ -28,13 +59,15 @@ infernoflow init --yes
28
59
 
29
60
  Zero runtime dependencies. Works on Node โ‰ฅ 18 โ€” macOS, Linux, Windows.
30
61
 
31
- `init --yes` does the whole setup: creates `.ai-memory/`, writes rule files for every supported IDE, wires the MCP server for Cursor / VS Code Copilot / Claude Code in one shot, applies the clean-tree git policy, and drops a visible demo entry so you can confirm the loop is alive with one command:
62
+ `init --yes` does the whole setup: creates `.ai-memory/`, writes rule files for every supported IDE, wires the MCP server for Cursor / VS Code Copilot / Claude Code in one shot, applies the clean-tree git policy, and drops a visible demo entry so you can confirm the loop is alive with:
32
63
 
33
64
  ```bash
34
65
  infernoflow status
35
66
  ```
36
67
 
37
- ## The 5-command core + 1
68
+ ---
69
+
70
+ ## The 5-command core + 4
38
71
 
39
72
  These cover 95% of usage:
40
73
 
@@ -45,18 +78,30 @@ These cover 95% of usage:
45
78
  | `infernoflow switch` | Generate a handoff for the next session. `--copy` puts it on your clipboard |
46
79
  | `infernoflow recap` | End-of-session summary with health score + unlogged-change detection |
47
80
  | `infernoflow status` | Quick health check โ€” entries, gotchas, decisions, last activity |
81
+ | `infernoflow bookmark "..."` | **๐Ÿ†•** Drop a named resume point โ€” auto-captures the session transcript as its context. `list` / `show <id\|label>` / `rm` round it out. Surfaces in `switch`. Never auto-pruned. |
48
82
  | `infernoflow refresh` | Manually rebuild `CLAUDE.md` / `.cursorrules` / `copilot-instructions.md` from memory |
49
- | `infernoflow forget <id|prefix>` | Delete a memory entry without hand-editing JSONL. `--last` for the newest. |
50
- | `infernoflow prune` | Archive stale `note` / `attempt` entries older than 30 days. Gotchas/decisions never auto-pruned. Default dry-run; `--apply` to act. |
51
- | `infernoflow bookmark "..."` | Drop a named **resume point** โ€” auto-captures the session transcript as its context. `list` / `show <id\|label>` to jump back; surfaces in `switch`. Never auto-pruned. |
83
+ | `infernoflow forget <id\|prefix>` | Delete a memory entry without hand-editing JSONL. `--last` for the newest |
84
+ | `infernoflow prune` | Archive stale `note` / `attempt` entries older than 30 days. Gotchas/decisions/bookmarks never auto-pruned. Default dry-run; `--apply` to act |
52
85
 
53
86
  In practice you barely run any of these โ€” the MCP-aware AI does it for you. The CLI is for grep-style introspection.
54
87
 
55
- `infernoflow commands` shows the full list (~20 commands, grouped by purpose).
88
+ `infernoflow commands` shows the full list (~23 commands, grouped by purpose).
89
+
90
+ ---
91
+
92
+ ## Works with GitHub Copilot Chat (via VS Code LMT โ€” not MCP)
93
+
94
+ **GitHub Copilot Chat doesn't support MCP.** Every other AI memory tool assumes MCP is the transport, so none of them work with Copilot Chat.
95
+
96
+ The infernoflow **VS Code extension** registers `amp_write` and `amp_read` as native **VS Code Language Model Tools** โ€” Copilot's supported extension surface. So Copilot can log a gotcha or recall a past decision **on its own**, mid-conversation, the moment it notices something worth remembering. Nothing to wire up: install the extension and Copilot's tool picker shows `๐Ÿ”ฅ amp_write`. Call by hand with `#amp_write` / `#amp_read` in the chat box.
97
+
98
+ Cursor and Claude Code get the same capability through the MCP server the CLI installs. **Same protocol, same disk file, three transports** โ€” MCP for Cursor/Claude, LMT for Copilot, native Node in the extension.
99
+
100
+ ---
56
101
 
57
102
  ## Keeping it lean: token budget + rotation
58
103
 
59
- The injected memory block is paid for on every AI turn (and twice when a tool loads both `CLAUDE.md` and `copilot-instructions.md`). infernoflow ships lean defaults โ€” 4 entries, 5 commits, 200-char per-entry truncation โ€” and gives you knobs to tune further. Set once in `.ai-memory/amp.json`:
104
+ The injected memory block is paid for on every AI turn (and twice when a tool loads both `CLAUDE.md` and `copilot-instructions.md`). infernoflow ships lean defaults โ€” 4 entries, 5 commits, 200-char truncation, and a **compact ~3-line protocol** (the full trigger table is redundant with the `amp_*` tool descriptions, so it's off by default: ~430 tokens/file/turn saved). Tune further in `.ai-memory/amp.json`:
60
105
 
61
106
  ```jsonc
62
107
  "config": {
@@ -65,7 +110,7 @@ The injected memory block is paid for on every AI turn (and twice when a tool lo
65
110
  "maxCommits": 5, // git commits injected
66
111
  "maxEntryChars": 200, // per-entry truncation
67
112
  "targets": ["CLAUDE.md", ".cursorrules"], // drop a file from the list and its stale block is stripped automatically
68
- "includeProtocol": true // false drops the ~17-line capture protocol (advanced)
113
+ "protocolStyle": "compact" // "compact" (default) ยท "full" (restore the trigger table) ยท "off"
69
114
  },
70
115
  "rotation": {
71
116
  "archiveAfterDays": 30,
@@ -83,7 +128,11 @@ infernoflow refresh --max-memory 3 # same; persists into amp.j
83
128
  infernoflow prune --apply --max-age-days 14 # one-off cleanup
84
129
  ```
85
130
 
86
- **Rotation** archives stale `note` / `attempt` / `detection` entries to `.ai-memory/archive/sessions-YYYY-MM.jsonl` โ€” invisible to the merged read (so the AI, sidebar, `ask`, and `refresh` stop surfacing them) but still on disk if you want them back. `gotcha`, `decision`, and `pattern` entries are **never auto-pruned** โ€” that's the knowledge you logged infernoflow FOR.
131
+ **Rotation** archives stale `note` / `attempt` / `detection` entries to `.ai-memory/archive/sessions-YYYY-MM.jsonl` โ€” invisible to the merged read (so the AI, sidebar, `ask`, and `refresh` stop surfacing them) but still on disk if you want them back. `gotcha`, `decision`, `pattern`, and `bookmark` entries are **never auto-pruned** โ€” that's the knowledge you logged infernoflow FOR.
132
+
133
+ **Two-tier bodies (new in 0.44.10):** any entry can carry a rich `detail` โ€” stored in `.ai-memory/details/<id>.md`, loaded on demand via `readDetail()`, and **never injected into rule files**. The lean index stays lean; you pay for the body only when you open it. `log --detail`, `--detail-file`, MCP `amp_write` `detail`, and the new `amp_bookmark` tool all feed it.
134
+
135
+ ---
87
136
 
88
137
  ## Branch-aware memory + cross-machine sync
89
138
 
@@ -94,6 +143,8 @@ infernoflow prune --apply --max-age-days 14 # one-off cleanup
94
143
  โ”œโ”€โ”€ branches/
95
144
  โ”‚ โ”œโ”€โ”€ main.jsonl โ† project-wide truths (git-tracked)
96
145
  โ”‚ โ””โ”€โ”€ feature-auth.jsonl โ† your current branch's work (git-tracked)
146
+ โ”œโ”€โ”€ details/ โ† Tier-2 rich bodies (loaded on demand)
147
+ โ”‚ โ””โ”€โ”€ amp_01HXYZ....md
97
148
  โ”œโ”€โ”€ global.jsonl โ† your personal preferences (gitignored)
98
149
  โ””โ”€โ”€ sessions.jsonl โ† legacy flat file (still read)
99
150
  ```
@@ -107,17 +158,21 @@ infernoflow prune --apply --max-age-days 14 # one-off cleanup
107
158
  - **`merge=union` on branch JSONLs** means concurrent commits from different machines merge cleanly โ€” no manual conflict resolution.
108
159
  - **Branch switching never blocked.** Rule files refresh only at MCP server boot or via explicit `infernoflow refresh`, not on every entry โ€” your working tree stays clean while you log.
109
160
 
161
+ ---
162
+
110
163
  ## Cross-IDE โ€” same memory, every tool
111
164
 
112
- The rule files at the top level of your project are what every AI agent reads on boot. infernoflow writes the same canonical block to all three:
165
+ | Tool | Reads from | Writes via |
166
+ |---|---|---|
167
+ | Claude Code | `CLAUDE.md` | MCP (`amp_write`) |
168
+ | Cursor | `.cursorrules` | MCP (`amp_write`) + `beforeSubmitPrompt` hook |
169
+ | GitHub Copilot Chat (VS Code) | `.github/copilot-instructions.md` | **VS Code LMT** (`amp_write`) โ€” extension only, no MCP |
170
+ | GitHub Copilot (JetBrains) | `.github/copilot-instructions.md` | rule files only (read-only surface) |
171
+ | Windsurf | `.windsurfrules` | MCP (planned) |
113
172
 
114
- | Tool | Reads from |
115
- |---|---|
116
- | Claude Code | `CLAUDE.md` |
117
- | Cursor | `.cursorrules` |
118
- | GitHub Copilot (VS Code, JetBrains) | `.github/copilot-instructions.md` |
173
+ The MCP server is wired by `infernoflow setup` / `init` into each tool's config file. No per-tool setup.
119
174
 
120
- Plus MCP for tools that speak it โ€” Cursor, Claude Code, VS Code Copilot Chat. The MCP server is wired by `infernoflow setup` / `init` into each tool's config file. No per-tool setup.
175
+ ---
121
176
 
122
177
  ## MCP tools (for AI agents)
123
178
 
@@ -125,9 +180,10 @@ When the MCP server is wired, your AI agent can call these directly in chat:
125
180
 
126
181
  | Tool | What it does |
127
182
  |---|---|
128
- | `amp_write` | Log an entry (`type`, `msg`, optional `file` / `line` / `tags`) |
183
+ | `amp_write` | Log an entry (`type`, `msg`, optional `file` / `line` / `tags` / `detail`) |
129
184
  | `amp_read` | Read entries with optional filters |
130
185
  | `amp_search` | Keyword search across entries |
186
+ | `amp_bookmark` | **๐Ÿ†•** Drop a named resume point โ€” auto-captures the current session transcript when no `note` is given |
131
187
  | `amp_handoff` | Generate the handoff document for the next AI session |
132
188
  | `amp_health` | Session health score (Aโ€“F) |
133
189
  | `infernoflow_status` | Memory + project health at a glance |
@@ -135,9 +191,11 @@ When the MCP server is wired, your AI agent can call these directly in chat:
135
191
  | `infernoflow_context` | Generate AI-ready context for a task |
136
192
  | `infernoflow_git_drift` | Detect which capabilities recent commits affected |
137
193
 
138
- The `amp_*` tools follow the [AMP MCP spec ยง7.3](docs/protocol/PROTOCOL.md#73-mcp-tool-interface) โ€” vendor-neutral. Any AMP-Full client only needs to know those five names. The same five are also available as CLI aliases (`infernoflow amp read | write | search | handoff | health`) so the CLI and MCP surfaces match name-for-name.
194
+ The `amp_*` tools follow the [AMP MCP spec ยง7.3](docs/protocol/PROTOCOL.md#73-mcp-tool-interface) โ€” vendor-neutral. Any AMP-Full client only needs to know those six names. The same six are also available as CLI aliases (`infernoflow amp read | write | search | bookmark | handoff | health`) so the CLI and MCP surfaces match name-for-name.
195
+
196
+ Every memory line injected into the rule files is prefixed with `๐Ÿ”ฅ` so the AI (and you) can tell at a glance that a line came from infernoflow even when it's quoted out of the managed block. When the AI uses one, the protocol tells it to briefly cite the source โ€” e.g. *๐Ÿ”ฅ (from infernoflow memory) gotcha at src/api.js:42: API returns 202 not 200*.
139
197
 
140
- Every memory line injected into the rule files is prefixed with `๐Ÿ”ฅ` so the AI (and you) can tell at a glance that a line came from infernoflow even when it's quoted out of the managed block.
198
+ ---
141
199
 
142
200
  ## What it has caught (real dogfood)
143
201
 
@@ -152,9 +210,17 @@ infernoflow was developed by building a multi-tenant kanban (`infernotest_01`) a
152
210
 
153
211
  These are the things you'd otherwise forget by next Tuesday and re-derive at 11pm on a Friday. They live in `.ai-memory/branches/*.jsonl` forever.
154
212
 
213
+ ---
214
+
155
215
  ## VS Code extension
156
216
 
157
- The companion extension renders your memory as a live sidebar โ€” ranked-by-relevance gotchas/decisions for whatever file you're editing, status bar health score, inline CodeLens annotations at gotcha locations.
217
+ The companion extension is the visual surface over your memory:
218
+
219
+ - **Live sidebar** โ€” ranked-by-relevance gotchas / decisions / attempts for whatever file you're editing.
220
+ - **Gotchas as Problems** โ€” logged with a `file:line`? They appear as yellow squigglies in the editor and rows in the **Problems panel**, right next to your TypeScript errors. Both *you* and *Copilot* see the warning before making the same mistake again.
221
+ - **Status bar health score** โ€” always visible: `๐Ÿ”ฅ B 65 ยท โš 3 ยท โœ“2 ยท โŒ1 ยท ๐Ÿ“‹ Switch`. Click `Switch` to copy the handoff.
222
+ - **Copilot Chat integration** โ€” `#amp_write` / `#amp_read` in the chat box; Copilot picks them up automatically via VS Code LMT (see above).
223
+ - **Keyboard-first logging** โ€” `Ctrl+Alt+G` (gotcha) / `Ctrl+Alt+D` (decision) / `Ctrl+Alt+A` (ask) / `Ctrl+Alt+S` (switch) / `Ctrl+Alt+R` (recap). Right-click in the editor to log a gotcha for the current line.
158
224
 
159
225
  ```
160
226
  ext install infernoflow.infernoflow
@@ -164,19 +230,43 @@ Or in the Marketplace: [infernoflow.infernoflow](https://marketplace.visualstudi
164
230
 
165
231
  The extension is **window only** in v0.7.9+ โ€” the CLI is the single canonical writer of rule files. No race between extension and CLI; the extension watches `.ai-memory/**/*.jsonl` and renders.
166
232
 
233
+ ---
234
+
167
235
  ## Troubleshooting
168
236
 
169
237
  - **I upgraded infernoflow but `amp_write` entries still look wrong.** Your IDE's MCP server is loaded into memory at session start and doesn't reload from disk. **Quit and reopen Cursor / Claude Code / VS Code.** `infernoflow doctor` will flag this with a "MCP runtime vโ€ฆ but CLI vโ€ฆ" warning.
170
238
  - **`infernoflow` not found.** Use `npx infernoflow` until the global install resolves on your PATH.
171
239
  - **PowerShell script execution blocked.** `Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass`.
172
- - **Box-drawing chars look broken.** Should auto-fall-back to ASCII on legacy PowerShell. If not, open an issue.
240
+ - **Box-drawing chars look broken.** Force UTF-8 first: `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8`. Should auto-fall-back to ASCII on legacy PowerShell. If not, open an issue.
173
241
  - **`infernoflow doctor`** โ€” full diagnostic if anything looks wrong. Includes the MCP runtime stamp check + AI provider detection + git hooks status.
174
242
 
243
+ ---
244
+
175
245
  ## Why this matters
176
246
 
177
247
  Code changes daily. What the system *actually does* under all those edits โ€” the invariants, the constraints, the things that bit you last week โ€” code can't tell you. infernoflow keeps that current and feeds it to the agent so the agent stops re-deriving from scratch.
178
248
 
179
- That's the whole product. No vendor lock-in (it's JSONL on disk). No SaaS. One CLI, one MCP server, three rule files your IDE was reading anyway.
249
+ That's the whole product. No vendor lock-in (it's JSONL on disk). No SaaS. One CLI, one VS Code extension, one open protocol, three rule files your IDE was reading anyway.
250
+
251
+ ---
252
+
253
+ ## Security & privacy
254
+
255
+ Local-first by design:
256
+
257
+ - ๐Ÿšซ **No telemetry.** No analytics, no error reporting, no install pings.
258
+ - ๐Ÿšซ **No `postinstall` script.** `npm install -g infernoflow` runs no code โ€” it only copies files.
259
+ - ๐Ÿšซ **No network calls in any default command path.** Everything runs on your machine.
260
+ - ๐Ÿšซ **No auto-updates, no background processes, no cloud sync.**
261
+ - โœ… **Reads and writes only inside your project directory** (`.ai-memory/`, plus the three rule files at repo root).
262
+ - โœ… **Auto-injected content is wrapped in markers** (`<!-- infernoflow:start -->` / `<!-- infernoflow:end -->`) โ€” your manual edits outside the block are never touched.
263
+ - โœ… **Secret patterns rejected on capture** โ€” entries matching `sk-`, `ghp_`, `-----BEGIN` are refused at the AMP writer.
264
+
265
+ The optional `infernoflow ai setup` command wires an AI provider (Anthropic / OpenAI / Google / Ollama) for a few enrichment commands โ€” same trust model as using that provider directly. Off by default.
266
+
267
+ Full policy: [SECURITY.md](./SECURITY.md). Vulnerability reports: `hello@infernoflow.dev` or [GitHub Security Advisory](https://github.com/ronmiz/infernoflow/security/advisories/new).
268
+
269
+ ---
180
270
 
181
271
  ## License
182
272
 
@@ -184,6 +274,7 @@ MIT
184
274
 
185
275
  ## Links
186
276
 
187
- - [GitHub](https://github.com/ronmiz/infernoflow) ยท [npm](https://www.npmjs.com/package/infernoflow) ยท [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=infernoflow.infernoflow) ยท [Issues](https://github.com/ronmiz/infernoflow/issues)
277
+ - [GitHub](https://github.com/ronmiz/infernoflow) ยท [npm](https://www.npmjs.com/package/infernoflow) ยท [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=infernoflow.infernoflow) ยท [Issues](https://github.com/ronmiz/infernoflow/issues) ยท [infernoflow.dev](https://www.infernoflow.dev)
188
278
  - [AMP protocol spec](docs/protocol/PROTOCOL.md) โ€” vendor-neutral memory format
189
279
  - [Dogfood: what infernoflow caught while building infernotest_01](docs/dogfood-infernotest_01.md)
280
+ - [Why your AI coding assistant has amnesia](docs/BLOG_POST.md) โ€” the case for the protocol
@@ -8,7 +8,7 @@ import*as s from"node:fs";import*as J from"node:os";import*as r from"node:path";
8
8
  `)+`
9
9
  `:"","utf8"),f.files.push(p)):v>0&&f.files.push(p)}if(!t.dryRun&&l&&h.size>0)for(const[p,b]of h)try{s.mkdirSync(r.dirname(p),{recursive:!0}),s.appendFileSync(p,b.join(`
10
10
  `)+`
11
- `,"utf8")}catch{}return f}function X(e){const{config:t}=m(e);try{return JSON.parse(s.readFileSync(t,"utf8"))}catch{return null}}function ae(e,t={}){x(e);const{config:o}=m(e,{forWrite:!0});if(s.existsSync(o))return!1;const n={amp:T,project:t.project||r.basename(e),stack:t.stack||{},config:{autoCapture:!0,maxEntries:1e3,rotationStrategy:"archive",inject:["all"],injection:{maxEntries:4,maxCommits:5,maxEntryChars:200},rotation:{archiveAfterDays:30,archivableTypes:["note","attempt","detection"],auto:!1},...t.config||{}}};return s.writeFileSync(o,JSON.stringify(n,null,2)+`
11
+ `,"utf8")}catch{}return f}function X(e){const{config:t}=m(e);try{return JSON.parse(s.readFileSync(t,"utf8"))}catch{return null}}function ae(e,t={}){x(e);const{config:o}=m(e,{forWrite:!0});if(s.existsSync(o))return!1;const n={amp:T,project:t.project||r.basename(e),stack:t.stack||{},config:{autoCapture:!0,maxEntries:1e3,rotationStrategy:"archive",inject:["all"],injection:{maxEntries:4,maxCommits:5,maxEntryChars:200,protocolStyle:"compact"},rotation:{archiveAfterDays:30,archivableTypes:["note","attempt","detection"],auto:!1},...t.config||{}}};return s.writeFileSync(o,JSON.stringify(n,null,2)+`
12
12
  `,"utf8"),!0}function fe(e,t={}){x(e);const{config:o}=m(e,{forWrite:!0});let n={};try{n=JSON.parse(s.readFileSync(o,"utf8"))}catch{n={}}(!n||typeof n!="object")&&(n={}),n.amp||(n.amp=T),n.project||(n.project=r.basename(e)),(!n.config||typeof n.config!="object")&&(n.config={});const c=n.config.injection&&typeof n.config.injection=="object"?n.config.injection:{};return n.config.injection={...c,...t},s.writeFileSync(o,JSON.stringify(n,null,2)+`
13
13
  `,"utf8"),n.config.injection}function le(e){const t=r.join(e,"inferno"),o=r.join(t,"sessions.jsonl");if(!s.existsSync(o))return{migrated:0,reason:"no legacy sessions.jsonl"};const n=r.join(e,".ai-memory"),c=r.join(n,"sessions.jsonl");if(s.existsSync(c))return{migrated:0,reason:".ai-memory/sessions.jsonl already exists"};x(e);const l=s.readFileSync(o,"utf8").split(`
14
14
  `).filter(Boolean);let a=0;for(const i of l)try{const u=JSON.parse(i),f=N(u);s.appendFileSync(c,JSON.stringify(f)+`
@@ -1,13 +1,14 @@
1
- import*as l from"node:fs";import*as d from"node:path";import{execSync as S}from"node:child_process";import{readEntries as I,readConfig as j}from"./amp/io.mjs";const y="<!-- infernoflow:start -->",h="<!-- infernoflow:end -->",w=[".cursorrules","CLAUDE.md",d.join(".github","copilot-instructions.md")],b=4,A=5,C=200;function _(e){const n=e&&e.config||{},t=n.injection&&typeof n.injection=="object"?n.injection:{},o=Array.isArray(n.inject)&&!n.inject.includes("all")?n.inject:null;return{maxEntries:Number.isInteger(t.maxEntries)&&t.maxEntries>=0?t.maxEntries:b,maxCommits:Number.isInteger(t.maxCommits)&&t.maxCommits>=0?t.maxCommits:A,maxEntryChars:Number.isInteger(t.maxEntryChars)&&t.maxEntryChars>0?t.maxEntryChars:C,targets:Array.isArray(t.targets)&&t.targets.length?t.targets:o||w,includeProtocol:t.includeProtocol!==!1}}function v(e){if(!l.existsSync(e))return{stripped:!1};const n=l.readFileSync(e,"utf8"),t=n.indexOf(y),o=n.indexOf(h);if(t===-1||o===-1||o<=t)return{stripped:!1};const r=n.slice(0,t).replace(/\s+$/,""),s=n.slice(o+h.length).replace(/^\s+/,""),i=r&&s?r+`
1
+ import*as u from"node:fs";import*as y from"node:path";import{execSync as j}from"node:child_process";import{readEntries as v,readConfig as x}from"./amp/io.mjs";const g="<!-- infernoflow:start -->",m="<!-- infernoflow:end -->",w=[".cursorrules","CLAUDE.md",y.join(".github","copilot-instructions.md")],k=4,C=5,S=200;function A(e){const o=e&&e.config||{},t=o.injection&&typeof o.injection=="object"?o.injection:{},n=Array.isArray(o.inject)&&!o.inject.includes("all")?o.inject:null,r=["full","compact","off"].includes(t.protocolStyle)?t.protocolStyle:"compact",s=t.includeProtocol===!1?"off":r;return{maxEntries:Number.isInteger(t.maxEntries)&&t.maxEntries>=0?t.maxEntries:k,maxCommits:Number.isInteger(t.maxCommits)&&t.maxCommits>=0?t.maxCommits:C,maxEntryChars:Number.isInteger(t.maxEntryChars)&&t.maxEntryChars>0?t.maxEntryChars:S,targets:Array.isArray(t.targets)&&t.targets.length?t.targets:n||w,protocolStyle:s,includeProtocol:s!=="off"}}function T(e){if(!u.existsSync(e))return{stripped:!1};const o=u.readFileSync(e,"utf8"),t=o.indexOf(g),n=o.indexOf(m);if(t===-1||n===-1||n<=t)return{stripped:!1};const r=o.slice(0,t).replace(/\s+$/,""),s=o.slice(n+m.length).replace(/^\s+/,""),i=r&&s?r+`
2
2
 
3
- `+s:r||s;return l.writeFileSync(e,i?i.replace(/\s*$/,"")+`
4
- `:"","utf8"),{stripped:!0}}function E(){const e=[];return e.push("### Memory protocol \u2014 capture as you go"),e.push(""),e.push("You have the `amp_write` MCP tool. **Use it proactively** (without the user asking) whenever you detect any of these moments. Capture the lesson in one sentence so the next AI session inherits it."),e.push(""),e.push("**If `amp_write` is not in your visible tools**, your client uses deferred MCP loading \u2014 call your tool-discovery mechanism (Claude Code: `ToolSearch` with query `infernoflow`) to load the `amp_*` tools BEFORE proceeding. Don't silently skip the protocol because the tool isn't visible yet."),e.push(""),e.push("| When you see\u2026 | Call `amp_write` with\u2026 |"),e.push("|---|---|"),e.push("| User types `!!`, `!!!`, `not working`, `still broken`, `no change`, `same error`, `retry` / `retrying`, repeated frustration | `{type: 'attempt', msg: '<one-line: what was tried and how it failed>', file: '<active file>'}` |"),e.push("| You generate a plan, numbered steps, or `Plan:` / `Approach:` block | `{type: 'note', msg: 'Plan: <one-line summary>', file: '<focus file>'}` |"),e.push("| User picks X over Y, decides an architectural approach, or you both agree on a direction | `{type: 'decision', msg: '<X> chosen over <Y> because <reason>', file: '<file>'}` |"),e.push("| Discovered non-obvious behavior, API quirk, or `aha \u2014 turns out\u2026` moment | `{type: 'gotcha', msg: '<one-line>', file: '<file>', line: <line if known>}` |"),e.push("| User says `switching` / `back to X`, or ~30 min passed without logging | `{type: 'note', msg: 'Session snapshot \u2014 worked on: <X>. Open threads: <Y>.'}` |"),e.push(""),e.push("**Do NOT log:** typos, syntax errors, obvious bugs visible in the code, anything the next AI can infer by re-reading the file."),e.push("**Do log:** anything that taught you something non-derivable from code."),e.push("**Be concise:** entries should be one sentence. Future-you will skim them."),e.push("**Bookmark resume points:** when the user says `bookmark this` / `mark this point`, or the context window is filling up while work is mid-flight, call `amp_bookmark` with a short `label` (omit `note` to auto-capture the session transcript) \u2014 so they can jump back to that exact point in the next session."),e.push("**When you use a \u{1F525} memory entry in your reply, briefly cite it** \u2014 e.g. `\u{1F525} (from infernoflow memory) gotcha at src/api.js:42: ...` \u2014 so the user can see which guidance came from project memory vs. your own inference."),e.join(`
5
- `)}function T(){return[y,"<!-- Auto-managed by infernoflow. Don't edit between these markers. -->","## Project memory (infernoflow)","",E(),"",'_No entries yet. They\'ll appear here as you and your AI tools log them \u2014 run `infernoflow log "..."` or call `amp_write` from any MCP-aware AI._',h].join(`
6
- `)}function F(e){const n=e.indexOf("<!-- AMP:START -->"),t=e.indexOf("<!-- AMP:END -->");if(n===-1||t===-1||t<=n)return e;const o=e.slice(0,n).replace(/\s+$/,""),r=e.slice(t+16).replace(/^\s+/,"");return(o?o+(r?`
3
+ `+s:r||s;return u.writeFileSync(e,i?i.replace(/\s*$/,"")+`
4
+ `:"","utf8"),{stripped:!0}}function E(e="compact"){return e==="off"?"":e==="full"?M():F()}function F(){return["### Memory protocol","",'Use the `amp_write` and `amp_bookmark` MCP tools **proactively** (without being asked): log a one-sentence entry when the user hits frustration (`!!` / "not working" / "retry" \u2192 `attempt`), a `decision` is made, or you learn a non-obvious `gotcha`; drop a bookmark on "bookmark this". Don\'t log what\'s re-derivable from the code. The full trigger list and field shapes are in the tool descriptions \u2014 if the `amp_*` tools aren\'t visible, load them first (Claude Code: `ToolSearch` with query `infernoflow`).'].join(`
5
+ `)}function M(){const e=[];return e.push("### Memory protocol \u2014 capture as you go"),e.push(""),e.push("You have the `amp_write` MCP tool. **Use it proactively** (without the user asking) whenever you detect any of these moments. Capture the lesson in one sentence so the next AI session inherits it."),e.push(""),e.push("**If `amp_write` is not in your visible tools**, your client uses deferred MCP loading \u2014 call your tool-discovery mechanism (Claude Code: `ToolSearch` with query `infernoflow`) to load the `amp_*` tools BEFORE proceeding. Don't silently skip the protocol because the tool isn't visible yet."),e.push(""),e.push("| When you see\u2026 | Call `amp_write` with\u2026 |"),e.push("|---|---|"),e.push("| User types `!!`, `!!!`, `not working`, `still broken`, `no change`, `same error`, `retry` / `retrying`, repeated frustration | `{type: 'attempt', msg: '<one-line: what was tried and how it failed>', file: '<active file>'}` |"),e.push("| You generate a plan, numbered steps, or `Plan:` / `Approach:` block | `{type: 'note', msg: 'Plan: <one-line summary>', file: '<focus file>'}` |"),e.push("| User picks X over Y, decides an architectural approach, or you both agree on a direction | `{type: 'decision', msg: '<X> chosen over <Y> because <reason>', file: '<file>'}` |"),e.push("| Discovered non-obvious behavior, API quirk, or `aha \u2014 turns out\u2026` moment | `{type: 'gotcha', msg: '<one-line>', file: '<file>', line: <line if known>}` |"),e.push("| User says `switching` / `back to X`, or ~30 min passed without logging | `{type: 'note', msg: 'Session snapshot \u2014 worked on: <X>. Open threads: <Y>.'}` |"),e.push(""),e.push("**Do NOT log:** typos, syntax errors, obvious bugs visible in the code, anything the next AI can infer by re-reading the file."),e.push("**Do log:** anything that taught you something non-derivable from code."),e.push("**Be concise:** entries should be one sentence. Future-you will skim them."),e.push("**Bookmark resume points:** when the user says `bookmark this` / `mark this point`, or the context window is filling up while work is mid-flight, call `amp_bookmark` with a short `label` (omit `note` to auto-capture the session transcript) \u2014 so they can jump back to that exact point in the next session."),e.push("**When you use a \u{1F525} memory entry in your reply, briefly cite it** \u2014 e.g. `\u{1F525} (from infernoflow memory) gotcha at src/api.js:42: ...` \u2014 so the user can see which guidance came from project memory vs. your own inference."),e.join(`
6
+ `)}function D(e="compact"){const o=E(e);return[g,"<!-- Auto-managed by infernoflow. Don't edit between these markers. -->","## Project memory (infernoflow)","",...o?[o,""]:[],'_No entries yet. They\'ll appear here as you and your AI tools log them \u2014 run `infernoflow log "..."` or call `amp_write` from any MCP-aware AI._',m].join(`
7
+ `)}function R(e){const o=e.indexOf("<!-- AMP:START -->"),t=e.indexOf("<!-- AMP:END -->");if(o===-1||t===-1||t<=o)return e;const n=e.slice(0,o).replace(/\s+$/,""),r=e.slice(t+16).replace(/^\s+/,"");return(n?n+(r?`
7
8
 
8
- `:""):"")+r}function k(e,n){const t=d.dirname(e);if(l.existsSync(t)||l.mkdirSync(t,{recursive:!0}),!l.existsSync(e))return l.writeFileSync(e,n+`
9
- `,"utf8"),{created:!0,updated:!1};let o=l.readFileSync(e,"utf8");o=F(o);const r=o.indexOf(y),s=o.indexOf(h);if(r===-1||s===-1){const g=n+`
9
+ `:""):"")+r}function I(e,o){const t=y.dirname(e);if(u.existsSync(t)||u.mkdirSync(t,{recursive:!0}),!u.existsSync(e))return u.writeFileSync(e,o+`
10
+ `,"utf8"),{created:!0,updated:!1};let n=u.readFileSync(e,"utf8");n=R(n);const r=n.indexOf(g),s=n.indexOf(m);if(r===-1||s===-1){const d=o+`
10
11
 
11
- `+o;return l.writeFileSync(e,g,"utf8"),{created:!1,updated:!0}}const i=o.slice(0,r),p=o.slice(s+h.length),u=i+n+p;return u===o?{created:!1,updated:!1}:(l.writeFileSync(e,u,"utf8"),{created:!1,updated:!0})}function $(e){const n=T(),t=[];for(const o of w){const r=d.join(e,o);try{const s=k(r,n);t.push({rel:o,...s})}catch(s){t.push({rel:o,error:s.message})}}return t}function D(e){return I(e)}function M(e,n=10){try{return S(`git log --pretty=format:"%h%x09%ad%x09%s" --date=short -n ${n}`,{cwd:e,encoding:"utf8",stdio:["ignore","pipe","ignore"]}).split(`
12
- `).filter(Boolean).map(o=>{const[r,s,i]=o.split(" ");return{hash:(r||"").slice(0,7),date:s||"",subject:i||""}})}catch{return[]}}function R(e,n={}){const{maxEntries:t=b,maxCommits:o=A,maxEntryChars:r=C,includeProtocol:s=!0}=n,i=D(e),p=M(e,o);i.sort((a,m)=>{const f=typeof a.ts=="number"?a.ts:Date.parse(a.ts||0);return(typeof m.ts=="number"?m.ts:Date.parse(m.ts||0))-f});const u=i.slice(0,t),g={gotcha:"\u26A0",decision:"\u2713",attempt:"\u2717",note:"\xB7",detection:"\u25CB",pattern:"\u25C7"},c=[];if(c.push(y),c.push("<!-- Auto-managed by infernoflow. Don't edit between these markers. -->"),c.push("## Project memory (infernoflow)"),c.push(""),s&&(c.push(E()),c.push("")),p.length>0){c.push("### Recent commits");for(const a of p)c.push(`- \`${a.hash}\` _${a.date}_ ${a.subject}`);c.push("")}if(u.length>0){c.push("### Recent memory");for(const a of u){const m=a.file?` (\`${a.file}${a.line?":"+a.line:""}\`)`:"",f=(a.msg||a.summary||"").replace(/\n/g," "),x=r&&f.length>r?f.slice(0,r).trimEnd()+"\u2026":f;c.push(`- \u{1F525} ${g[a.type]||"\xB7"} **${a.type||"note"}**${m}: ${x}`)}c.push("")}return i.length===0&&p.length===0&&c.push('_No entries yet. They\'ll appear here as you and your AI tools log them \u2014 run `infernoflow log "..."` or call `amp_write` from any MCP-aware AI._'),c.push(h),c.join(`
13
- `)}function P(e){const n=_(j(e)),t=R(e,n),o=i=>String(i).replace(/\\/g,"/"),r=new Set(n.targets.map(o)),s=[];for(const i of w){const p=d.join(e,i);try{if(r.has(o(i))){const u=k(p,t);s.push({rel:i,...u})}else v(p).stripped&&s.push({rel:i,stripped:!0})}catch(u){s.push({rel:i,error:u.message})}}return s}export{P as refreshRuleFilesFromMemory,_ as resolveInjectionSettings,$ as writeInitRuleFiles};
12
+ `+n;return u.writeFileSync(e,d,"utf8"),{created:!1,updated:!0}}const i=n.slice(0,r),p=n.slice(s+m.length),l=i+o+p;return l===n?{created:!1,updated:!1}:(u.writeFileSync(e,l,"utf8"),{created:!1,updated:!0})}function B(e){const{protocolStyle:o}=A(x(e)),t=D(o),n=[];for(const r of w){const s=y.join(e,r);try{const i=I(s,t);n.push({rel:r,...i})}catch(i){n.push({rel:r,error:i.message})}}return n}function N(e){return v(e)}function P(e,o=10){try{return j(`git log --pretty=format:"%h%x09%ad%x09%s" --date=short -n ${o}`,{cwd:e,encoding:"utf8",stdio:["ignore","pipe","ignore"]}).split(`
13
+ `).filter(Boolean).map(n=>{const[r,s,i]=n.split(" ");return{hash:(r||"").slice(0,7),date:s||"",subject:i||""}})}catch{return[]}}function O(e,o={}){const{maxEntries:t=k,maxCommits:n=C,maxEntryChars:r=S,protocolStyle:s="compact",includeProtocol:i=!0}=o,p=N(e),l=P(e,n);p.sort((a,h)=>{const f=typeof a.ts=="number"?a.ts:Date.parse(a.ts||0);return(typeof h.ts=="number"?h.ts:Date.parse(h.ts||0))-f});const d=p.slice(0,t),_={gotcha:"\u26A0",decision:"\u2713",attempt:"\u2717",note:"\xB7",detection:"\u25CB",pattern:"\u25C7"},c=[];if(c.push(g),c.push("<!-- Auto-managed by infernoflow. Don't edit between these markers. -->"),c.push("## Project memory (infernoflow)"),c.push(""),i&&s!=="off"&&(c.push(E(s)),c.push("")),l.length>0){c.push("### Recent commits");for(const a of l)c.push(`- \`${a.hash}\` _${a.date}_ ${a.subject}`);c.push("")}if(d.length>0){c.push("### Recent memory");for(const a of d){const h=a.file?` (\`${a.file}${a.line?":"+a.line:""}\`)`:"",f=(a.msg||a.summary||"").replace(/\n/g," "),b=r&&f.length>r?f.slice(0,r).trimEnd()+"\u2026":f;c.push(`- \u{1F525} ${_[a.type]||"\xB7"} **${a.type||"note"}**${h}: ${b}`)}c.push("")}return p.length===0&&l.length===0&&c.push('_No entries yet. They\'ll appear here as you and your AI tools log them \u2014 run `infernoflow log "..."` or call `amp_write` from any MCP-aware AI._'),c.push(m),c.join(`
14
+ `)}function L(e){const o=A(x(e)),t=O(e,o),n=i=>String(i).replace(/\\/g,"/"),r=new Set(o.targets.map(n)),s=[];for(const i of w){const p=y.join(e,i);try{if(r.has(n(i))){const l=I(p,t);s.push({rel:i,...l})}else T(p).stripped&&s.push({rel:i,stripped:!0})}catch(l){s.push({rel:i,error:l.message})}}return s}export{L as refreshRuleFilesFromMemory,A as resolveInjectionSettings,B as writeInitRuleFiles};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infernoflow",
3
- "version": "0.44.10",
3
+ "version": "0.44.12",
4
4
  "description": "Persistent memory for AI coding sessions โ€” captures what agents can't infer from code alone. Works with Copilot, Cursor, Claude, and Windsurf.",
5
5
  "type": "module",
6
6
  "bin": {