@seanhogg/builderforce-memory-mcp 2026.6.28 → 2026.6.31

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
@@ -95,6 +95,24 @@ write tools; `--local=<dist/bin/stdio.js>` runs a checkout for development.
95
95
  The same launch spec is exported for programmatic use:
96
96
  `buildServerSpec()` / `installMemoryServer()` (see `src/install/`).
97
97
 
98
+ ### Claude Code: the self-driving "memory combo"
99
+
100
+ Registering the server gives Claude Code the memory *tools*. For Claude Code the
101
+ installer ALSO wires hooks so memory is **self-driving instead of advisory** (skip
102
+ with `--no-hooks`):
103
+
104
+ | Hook | Behaviour |
105
+ |------|-----------|
106
+ | `SessionStart` | Inject a digest of the top durable memories. |
107
+ | `PreCompact` | Flush durable learnings before the window is summarised. |
108
+ | `UserPromptSubmit` | **Contextual recall** — score memories against the prompt and inject the relevant ones, so a stored fact is retrieved at the *decision point*, not only at session start. |
109
+ | `Stop` | **Autonomous capture** — if the user's last message was a durable instruction/correction and it wasn't consolidated this turn, BLOCK the stop with a directive to `memory_remember`. Capture flows through the MCP write path (never racing the snapshot); a `stop_hook_active` guard means at most one nudge per turn. |
110
+
111
+ All four point at one generated, dependency-free hook script
112
+ (`~/.claude/builderforce-memory/bfmem-hook.mjs`, four modes) plus a companion
113
+ skill. Idempotent + `.bak`-safe. Source of truth: `src/install/claude-hooks.ts`
114
+ (`installClaudeCombo` / `bfmemHookSource`).
115
+
98
116
  ## Quick start — stdio (any process / language)
99
117
 
100
118
  ```bash
@@ -9,6 +9,8 @@
9
9
  * BUILDERFORCE_MEMORY_TOKEN Bearer token required on every request (recommended).
10
10
  * BUILDERFORCE_MEMORY_DB IndexedDB database name.
11
11
  * BUILDERFORCE_MEMORY_READONLY '1' to disable remember/forget tools.
12
+ * BUILDERFORCE_GATEWAY_URL Gateway base URL (default https://api.builderforce.ai).
13
+ * BUILDERFORCE_API_KEY `bfk_*` tenant key. When set, exposes the cost tools.
12
14
  */
13
15
  export {};
14
16
  //# sourceMappingURL=http.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/bin/http.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/bin/http.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG"}
package/dist/bin/http.js CHANGED
@@ -9,6 +9,8 @@
9
9
  * BUILDERFORCE_MEMORY_TOKEN Bearer token required on every request (recommended).
10
10
  * BUILDERFORCE_MEMORY_DB IndexedDB database name.
11
11
  * BUILDERFORCE_MEMORY_READONLY '1' to disable remember/forget tools.
12
+ * BUILDERFORCE_GATEWAY_URL Gateway base URL (default https://api.builderforce.ai).
13
+ * BUILDERFORCE_API_KEY `bfk_*` tenant key. When set, exposes the cost tools.
12
14
  */
13
15
  import http from "node:http";
14
16
  import { createLocalMemoryStoreBackend } from "../backends/memory-store.js";
@@ -19,6 +21,10 @@ const backend = await createLocalMemoryStoreBackend({
19
21
  const handler = createMemoryHttpHandler(backend, {
20
22
  authToken: process.env["BUILDERFORCE_MEMORY_TOKEN"],
21
23
  writable: process.env["BUILDERFORCE_MEMORY_READONLY"] !== "1",
24
+ // Optional gateway-backed cost tools (token_usage, model_efficiency). Only
25
+ // exposed when an API key is present; URL defaults to the public gateway.
26
+ gatewayUrl: process.env["BUILDERFORCE_GATEWAY_URL"] ?? "https://api.builderforce.ai",
27
+ gatewayApiKey: process.env["BUILDERFORCE_API_KEY"],
22
28
  });
23
29
  const port = Number(process.env["PORT"] ?? 8787);
24
30
  http.createServer((req, res) => {
@@ -1 +1 @@
1
- {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/bin/http.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,MAAM,OAAO,GAAG,MAAM,6BAA6B,CAAC;IAChD,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;CAChD,CAAC,CAAC;AAEH,MAAM,OAAO,GAAG,uBAAuB,CAAC,OAAO,EAAE;IAC7C,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC;IACnD,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,KAAK,GAAG;CAChE,CAAC,CAAC;AAEH,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC;AAEjD,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IAC3B,KAAK,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACjC,IAAI,CAAC,GAAG,CAAC,WAAW;YAAE,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QACjF,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;IACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gDAAgD,IAAI,IAAI,CAAC,CAAC;AACnF,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/bin/http.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,MAAM,OAAO,GAAG,MAAM,6BAA6B,CAAC;IAChD,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;CAChD,CAAC,CAAC;AAEH,MAAM,OAAO,GAAG,uBAAuB,CAAC,OAAO,EAAE;IAC7C,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC;IACnD,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,KAAK,GAAG;IAC7D,2EAA2E;IAC3E,0EAA0E;IAC1E,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,IAAI,6BAA6B;IACpF,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;CACrD,CAAC,CAAC;AAEH,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC;AAEjD,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IAC3B,KAAK,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACjC,IAAI,CAAC,GAAG,CAAC,WAAW;YAAE,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QACjF,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;IACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gDAAgD,IAAI,IAAI,CAAC,CAAC;AACnF,CAAC,CAAC,CAAC"}
@@ -17,8 +17,10 @@
17
17
  */
18
18
  import os from "node:os";
19
19
  import path from "node:path";
20
+ import nodeFs from "node:fs";
20
21
  import { HOSTS } from "../install/hosts.js";
21
22
  import { installMemoryServer } from "../install/install.js";
23
+ import { installClaudeCombo } from "../install/claude-hooks.js";
22
24
  function argValue(name) {
23
25
  const hit = process.argv.find((a) => a.startsWith(`--${name}=`));
24
26
  return hit ? hit.slice(name.length + 3) : undefined;
@@ -73,6 +75,26 @@ for (const r of results) {
73
75
  if (r.status === "installed" || r.status === "updated")
74
76
  wrote += 1;
75
77
  }
78
+ // ── Claude Code memory combo (hooks + companion skill) ───────────────────────
79
+ // Registering the server above gives Claude Code the TOOLS; this adds the
80
+ // self-driving behaviour (SessionStart digest, contextual recall, autonomous Stop
81
+ // capture). Only for Claude Code, only when present, and skippable via --no-hooks.
82
+ const claudeResult = results.find((r) => r.host === "claude-code");
83
+ const claudePresent = claudeResult && claudeResult.status !== "unsupported" && claudeResult.status !== "error";
84
+ if (claudePresent && !hasFlag("no-hooks")) {
85
+ try {
86
+ const combo = installClaudeCombo({
87
+ fs: nodeFs,
88
+ claudeDir: path.join(os.homedir(), ".claude"),
89
+ memoryFile,
90
+ });
91
+ const what = combo.addedHooks.length ? `added ${combo.addedHooks.join(", ")}` : "already current";
92
+ process.stdout.write(`✓ Claude Code memory combo: hooks ${what}; recall + autonomous capture wired\n`);
93
+ }
94
+ catch (err) {
95
+ process.stderr.write(`✗ Claude Code hooks: ${String(err.message ?? err)}\n`);
96
+ }
97
+ }
76
98
  if (results.length === 0) {
77
99
  process.stdout.write("No MCP hosts detected. Pass --host=all to write configs anyway, or --host=cursor (etc.).\n");
78
100
  }
@@ -1 +1 @@
1
- {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/bin/install.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/bin/install.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAqB,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAEhE,SAAS,QAAQ,CAAC,IAAY;IAC1B,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC;IACjE,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACxD,CAAC;AACD,MAAM,OAAO,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;AAE9E,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;IAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB;QACI,8EAA8E;QAC9E,EAAE;QACF,yEAAyE;QACzE,yFAAyF;QACzF,yDAAyD;QACzD,qEAAqE;QACrE,EAAE;QACF,gBAAgB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACnD,EAAE;KACL,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;AACjC,MAAM,KAAK,GACP,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,MAAM;IACvC,CAAC,CAAC,MAAM;IACR,CAAC,CAAC,OAAO,KAAK,KAAK;QACjB,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAEpE,MAAM,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,sBAAsB,EAAE,aAAa,CAAC,CAAC;AAE7G,IAAI,OAAO,CAAC;AACZ,IAAI,CAAC;IACD,OAAO,GAAG,mBAAmB,CAAC;QAC1B,KAAK;QACL,UAAU;QACV,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC;QAC7B,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC;KAC9B,CAAC,CAAC;AACP,CAAC;AAAC,OAAO,GAAG,EAAE,CAAC;IACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,MAAM,CAAE,GAAa,CAAC,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;IACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,MAAM,IAAI,GAA2B;IACjC,SAAS,EAAE,GAAG;IACd,OAAO,EAAE,GAAG;IACZ,OAAO,EAAE,GAAG;IACZ,WAAW,EAAE,GAAG;IAChB,KAAK,EAAE,GAAG;CACb,CAAC;AAEF,IAAI,KAAK,GAAG,CAAC,CAAC;AACd,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;IACtB,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1C,MAAM,GAAG,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,MAAM,GAAG,GAAG,GAAG,KAAK,IAAI,CAAC,CAAC;IACzF,IAAI,CAAC,CAAC,MAAM,KAAK,WAAW,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS;QAAE,KAAK,IAAI,CAAC,CAAC;AACvE,CAAC;AAED,gFAAgF;AAChF,0EAA0E;AAC1E,kFAAkF;AAClF,mFAAmF;AACnF,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC;AACnE,MAAM,aAAa,GAAG,YAAY,IAAI,YAAY,CAAC,MAAM,KAAK,aAAa,IAAI,YAAY,CAAC,MAAM,KAAK,OAAO,CAAC;AAC/G,IAAI,aAAa,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;IACxC,IAAI,CAAC;QACD,MAAM,KAAK,GAAG,kBAAkB,CAAC;YAC7B,EAAE,EAAE,MAAM;YACV,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC;YAC7C,UAAU;SACb,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC;QAClG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qCAAqC,IAAI,uCAAuC,CAAC,CAAC;IAC3G,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wBAAwB,MAAM,CAAE,GAAa,CAAC,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5F,CAAC;AACL,CAAC;AAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;IACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,4FAA4F,CAC/F,CAAC;AACN,CAAC;KAAM,CAAC;IACJ,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,YAAY,KAAK,8BAA8B,UAAU,iIAAiI,CAC7L,CAAC;AACN,CAAC"}
@@ -9,6 +9,9 @@
9
9
  * persists across process restarts — REQUIRED for an
10
10
  * MCP client that respawns this server each session
11
11
  * (otherwise fake-indexeddb loses everything on exit).
12
+ * BUILDERFORCE_GATEWAY_URL Gateway base URL (default https://api.builderforce.ai).
13
+ * BUILDERFORCE_API_KEY `bfk_*` tenant key. When set, exposes the cost tools
14
+ * (token_usage, model_efficiency).
12
15
  *
13
16
  * Recall is lexical (Jaccard) here — this headless binary does not stand up the
14
17
  * SSM runtime/GPU. For SSM-embedding recall, embed the package in-process and
@@ -1 +1 @@
1
- {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/bin/stdio.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG"}
1
+ {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/bin/stdio.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG"}
package/dist/bin/stdio.js CHANGED
@@ -9,6 +9,9 @@
9
9
  * persists across process restarts — REQUIRED for an
10
10
  * MCP client that respawns this server each session
11
11
  * (otherwise fake-indexeddb loses everything on exit).
12
+ * BUILDERFORCE_GATEWAY_URL Gateway base URL (default https://api.builderforce.ai).
13
+ * BUILDERFORCE_API_KEY `bfk_*` tenant key. When set, exposes the cost tools
14
+ * (token_usage, model_efficiency).
12
15
  *
13
16
  * Recall is lexical (Jaccard) here — this headless binary does not stand up the
14
17
  * SSM runtime/GPU. For SSM-embedding recall, embed the package in-process and
@@ -20,5 +23,11 @@ const backend = await createLocalMemoryStoreBackend({
20
23
  dbName: process.env["BUILDERFORCE_MEMORY_DB"],
21
24
  persistFile: process.env["BUILDERFORCE_MEMORY_FILE"],
22
25
  });
23
- await runStdio(backend, { writable: process.env["BUILDERFORCE_MEMORY_READONLY"] !== "1" });
26
+ await runStdio(backend, {
27
+ writable: process.env["BUILDERFORCE_MEMORY_READONLY"] !== "1",
28
+ // Optional gateway-backed cost tools (token_usage, model_efficiency). Only
29
+ // exposed when an API key is present; URL defaults to the public gateway.
30
+ gatewayUrl: process.env["BUILDERFORCE_GATEWAY_URL"] ?? "https://api.builderforce.ai",
31
+ gatewayApiKey: process.env["BUILDERFORCE_API_KEY"],
32
+ });
24
33
  //# sourceMappingURL=stdio.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/bin/stdio.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,MAAM,OAAO,GAAG,MAAM,6BAA6B,CAAC;IAChD,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;IAC7C,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC;CACvD,CAAC,CAAC;AAEH,MAAM,QAAQ,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC"}
1
+ {"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/bin/stdio.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,MAAM,OAAO,GAAG,MAAM,6BAA6B,CAAC;IAChD,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;IAC7C,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC;CACvD,CAAC,CAAC;AAEH,MAAM,QAAQ,CAAC,OAAO,EAAE;IACpB,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,KAAK,GAAG;IAC7D,2EAA2E;IAC3E,0EAA0E;IAC1E,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,IAAI,6BAA6B;IACpF,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;CACrD,CAAC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -24,4 +24,6 @@ export { installMemoryServer } from "./install/install.js";
24
24
  export type { InstallOptions, InstallResult, InstallStatus, HostSelector, FsLike } from "./install/install.js";
25
25
  export { HOSTS, SERVER_KEY, findHost } from "./install/hosts.js";
26
26
  export type { HostAdapter, HostEnv, ConfigFormat } from "./install/hosts.js";
27
+ export { installClaudeCombo, bfmemHookSource, companionSkillMd, claudeComboPaths } from "./install/claude-hooks.js";
28
+ export type { ClaudeComboPaths } from "./install/claude-hooks.js";
27
29
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG5E,OAAO,EAAE,kBAAkB,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC;AAC/F,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEhF,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,YAAY,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,YAAY,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG/D,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAChG,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACnF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC/G,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACjE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG5E,OAAO,EAAE,kBAAkB,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC;AAC/F,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEhF,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,YAAY,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,YAAY,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG/D,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAChG,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACnF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC/G,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACjE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAG7E,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACpH,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC"}
package/dist/index.js CHANGED
@@ -19,4 +19,7 @@ export { createMemoryHttpHandler } from "./transports/http.js";
19
19
  export { buildServerSpec, MCP_PACKAGE, MCP_BIN, RUNTIME_PEERS } from "./install/server-spec.js";
20
20
  export { installMemoryServer } from "./install/install.js";
21
21
  export { HOSTS, SERVER_KEY, findHost } from "./install/hosts.js";
22
+ // Claude Code "memory combo" — hooks (SessionStart/PreCompact/UserPromptSubmit/Stop)
23
+ // + companion skill that make memory self-driving (contextual recall + autonomous capture).
24
+ export { installClaudeCombo, bfmemHookSource, companionSkillMd, claudeComboPaths } from "./install/claude-hooks.js";
22
25
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,0FAA0F;AAC1F,OAAO,EAAE,kBAAkB,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC;AAG/F,iFAAiF;AACjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9C,+EAA+E;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAG5D,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG5D,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAG/D,iFAAiF;AACjF,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEhG,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,0FAA0F;AAC1F,OAAO,EAAE,kBAAkB,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC;AAG/F,iFAAiF;AACjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9C,+EAA+E;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAG5D,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG5D,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAG/D,iFAAiF;AACjF,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEhG,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEjE,qFAAqF;AACrF,4FAA4F;AAC5F,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Claude Code "memory combo" — the hooks + companion skill half of the install.
3
+ *
4
+ * Registering the MCP server (install.ts) gives Claude Code the memory TOOLS.
5
+ * This module adds the behaviour that makes memory SELF-DRIVING rather than
6
+ * advisory, so the agent doesn't have to be reminded to use it:
7
+ *
8
+ * SessionStart → inject a digest of the top durable memories.
9
+ * PreCompact → flush durable learnings before the window is summarised.
10
+ * UserPromptSubmit → CONTEXTUAL recall: score memories against the prompt and
11
+ * inject the relevant ones (so a stored fact is retrieved at
12
+ * the decision point, not just at session start).
13
+ * Stop → AUTONOMOUS capture: detect a durable instruction/correction
14
+ * in the user's last message and, if it wasn't consolidated
15
+ * this turn, BLOCK the stop with a directive to memory_remember
16
+ * (capture flows through the MCP write path — never racing the
17
+ * server's snapshot).
18
+ *
19
+ * All four point at one generated, dependency-free hook script (DRY). The combo
20
+ * is idempotent and backup-safe, matching install.ts.
21
+ */
22
+ import type { FsLike } from "./install.js";
23
+ /** The single hook script, four modes. `memoryFile` is the JSON snapshot path. */
24
+ export declare function bfmemHookSource(memoryFile: string): string;
25
+ /** The companion skill doc that teaches the recall/remember discipline. */
26
+ export declare function companionSkillMd(): string;
27
+ /** Files/paths the combo manages, derived from the Claude config dir (~/.claude). */
28
+ export interface ClaudeComboPaths {
29
+ claudeDir: string;
30
+ settingsFile: string;
31
+ hookScript: string;
32
+ companionSkill: string;
33
+ }
34
+ export declare function claudeComboPaths(claudeDir: string): ClaudeComboPaths;
35
+ /**
36
+ * Write the hook script + companion skill and register all four hooks in
37
+ * settings.json. Idempotent (skips a hook command already present) and
38
+ * backup-safe (.bak before overwriting settings). Returns the events it added.
39
+ */
40
+ export declare function installClaudeCombo(opts: {
41
+ fs: FsLike;
42
+ claudeDir: string;
43
+ memoryFile: string;
44
+ }): {
45
+ hookScript: string;
46
+ addedHooks: string[];
47
+ settingsFile: string;
48
+ };
49
+ //# sourceMappingURL=claude-hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-hooks.d.ts","sourceRoot":"","sources":["../../src/install/claude-hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA6I1D;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,IAAI,MAAM,CA0BzC;AAED,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAOpE;AASD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACtB,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAmCrE"}
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Claude Code "memory combo" — the hooks + companion skill half of the install.
3
+ *
4
+ * Registering the MCP server (install.ts) gives Claude Code the memory TOOLS.
5
+ * This module adds the behaviour that makes memory SELF-DRIVING rather than
6
+ * advisory, so the agent doesn't have to be reminded to use it:
7
+ *
8
+ * SessionStart → inject a digest of the top durable memories.
9
+ * PreCompact → flush durable learnings before the window is summarised.
10
+ * UserPromptSubmit → CONTEXTUAL recall: score memories against the prompt and
11
+ * inject the relevant ones (so a stored fact is retrieved at
12
+ * the decision point, not just at session start).
13
+ * Stop → AUTONOMOUS capture: detect a durable instruction/correction
14
+ * in the user's last message and, if it wasn't consolidated
15
+ * this turn, BLOCK the stop with a directive to memory_remember
16
+ * (capture flows through the MCP write path — never racing the
17
+ * server's snapshot).
18
+ *
19
+ * All four point at one generated, dependency-free hook script (DRY). The combo
20
+ * is idempotent and backup-safe, matching install.ts.
21
+ */
22
+ import nodePath from "node:path";
23
+ /** The single hook script, four modes. `memoryFile` is the JSON snapshot path. */
24
+ export function bfmemHookSource(memoryFile) {
25
+ return `#!/usr/bin/env node
26
+ /**
27
+ * builderforce-memory hook — generated by builderforce-memory-install. Dependency-free:
28
+ * reads only the JSON snapshot, never stands up the MCP server.
29
+ * --session SessionStart: inject a small digest of durable memories.
30
+ * --precompact PreCompact: remind to flush learnings before summarisation.
31
+ * --recall UserPromptSubmit: contextual recall — inject memories relevant to the prompt.
32
+ * --capture Stop: autonomous consolidation — block the stop until a detected
33
+ * durable correction is persisted via memory_remember.
34
+ */
35
+ import fs from "node:fs";
36
+
37
+ const FILE = ${JSON.stringify(memoryFile)};
38
+ const mode = process.argv[2] || "--session";
39
+
40
+ function readStdin() {
41
+ try { return fs.readFileSync(0, "utf8"); } catch { return ""; }
42
+ }
43
+ function loadEntries() {
44
+ try { const p = JSON.parse(fs.readFileSync(FILE, "utf8")); return Array.isArray(p) ? p : []; }
45
+ catch { return []; }
46
+ }
47
+ function tokens(s) {
48
+ return String(s || "").toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 3);
49
+ }
50
+
51
+ if (mode === "--precompact") {
52
+ process.stdout.write(
53
+ "[builderforce-memory] Context is about to be compacted. Persist any durable learnings from this session — decisions, fixes, project facts, user preferences — by calling memory_remember with concise one-line facts. Recall them later with memory_recall instead of re-reading files.",
54
+ );
55
+ process.exit(0);
56
+ }
57
+
58
+ if (mode === "--recall") {
59
+ let prompt = "";
60
+ try { const j = JSON.parse(readStdin() || "{}"); prompt = typeof j.prompt === "string" ? j.prompt : ""; } catch { /* no input */ }
61
+ if (!prompt) process.exit(0);
62
+ const entries = loadEntries();
63
+ if (entries.length === 0) process.exit(0);
64
+ const q = new Set(tokens(prompt));
65
+ if (q.size === 0) process.exit(0);
66
+ const scored = entries
67
+ .map((e) => {
68
+ const hay = new Set(tokens([e.key, e.content, (e.tags || []).join(" ")].join(" ")));
69
+ let score = 0;
70
+ for (const t of q) if (hay.has(t)) score++;
71
+ return { e, score };
72
+ })
73
+ .filter((x) => x.score >= 2)
74
+ .sort((a, b) => b.score - a.score)
75
+ .slice(0, 3);
76
+ if (scored.length === 0) process.exit(0);
77
+ const lines = scored.map((x) => "• " + x.e.key + ": " + String(x.e.content || "").slice(0, 240)).join("\\n");
78
+ process.stdout.write(
79
+ "[builderforce-memory] Relevant stored memories for this request — use these instead of re-deriving or re-reading:\\n" + lines,
80
+ );
81
+ process.exit(0);
82
+ }
83
+
84
+ if (mode === "--capture") {
85
+ let input = {};
86
+ try { input = JSON.parse(readStdin() || "{}"); } catch { /* no input */ }
87
+ if (input.stop_hook_active) process.exit(0);
88
+ const tpath = input.transcript_path;
89
+ if (!tpath || typeof tpath !== "string") process.exit(0);
90
+
91
+ let raw = "";
92
+ try {
93
+ const stat = fs.statSync(tpath);
94
+ const want = Math.min(stat.size, 400000);
95
+ const fd = fs.openSync(tpath, "r");
96
+ const buf = Buffer.alloc(want);
97
+ fs.readSync(fd, buf, 0, want, stat.size - want);
98
+ fs.closeSync(fd);
99
+ raw = buf.toString("utf8");
100
+ } catch { process.exit(0); }
101
+
102
+ const msgs = [];
103
+ for (const ln of raw.split("\\n")) { if (ln.trim()) { try { msgs.push(JSON.parse(ln)); } catch { /* partial */ } } }
104
+
105
+ let lastUserText = "";
106
+ let rememberedThisTurn = false;
107
+ for (let i = msgs.length - 1; i >= 0; i--) {
108
+ const m = msgs[i] || {};
109
+ const inner = m.message || m;
110
+ const role = inner.role;
111
+ const content = inner.content;
112
+ if (role === "assistant" && !lastUserText) {
113
+ if (Array.isArray(content)) for (const c of content) {
114
+ if (c && c.type === "tool_use" && typeof c.name === "string" && c.name.includes("memory_remember")) rememberedThisTurn = true;
115
+ }
116
+ }
117
+ if (role === "user" && !lastUserText) {
118
+ let txt = "";
119
+ if (typeof content === "string") txt = content;
120
+ else if (Array.isArray(content)) for (const c of content) {
121
+ if (typeof c === "string") txt += c + " ";
122
+ else if (c && c.type === "text" && typeof c.text === "string") txt += c.text + " ";
123
+ }
124
+ if (txt.trim()) lastUserText = txt;
125
+ }
126
+ }
127
+ if (!lastUserText || rememberedThisTurn) process.exit(0);
128
+
129
+ const lower = lastUserText.toLowerCase();
130
+ const SIGNALS = [
131
+ "you have to", "you should", "you need to", "you must", "from now on", "going forward",
132
+ "always ", "never ", "make sure", "remember to", "don't forget", "do not forget",
133
+ "i keep telling", "i already told", "shouldn't have to", "keep having to remind",
134
+ "you forgot", "you keep", "that's wrong", "you're wrong", "this is wrong", "stop ",
135
+ ];
136
+ const hit = SIGNALS.find((s) => lower.includes(s));
137
+ if (!hit) process.exit(0);
138
+
139
+ const snippet = lastUserText.trim().slice(0, 240);
140
+ const reason =
141
+ "[builderforce-memory] A durable instruction/correction was detected in the user's last message and you did NOT consolidate it this turn. " +
142
+ "Before stopping, call memory_remember with a concise, stable-keyed fact capturing it (reuse the same key to replace a superseded one, or memory_forget it). " +
143
+ "User said: '" + snippet + "'. If it is genuinely not durable (a one-off, or already stored), briefly say why and then stop.";
144
+ process.stdout.write(JSON.stringify({ decision: "block", reason }));
145
+ process.exit(0);
146
+ }
147
+
148
+ const entries = loadEntries();
149
+ if (entries.length === 0) {
150
+ process.stdout.write(
151
+ "[builderforce-memory] Active (memory empty). Store durable facts with memory_remember and retrieve them with memory_recall instead of re-reading files or chat history — that is how token usage stays low.",
152
+ );
153
+ process.exit(0);
154
+ }
155
+ const top = entries
156
+ .map((e) => ({ key: e.key, content: String(e.content ?? ""), importance: typeof e.importance === "number" ? e.importance : 0.5 }))
157
+ .sort((a, b) => b.importance - a.importance)
158
+ .slice(0, 8)
159
+ .map((e) => "• " + e.key + ": " + e.content.slice(0, 200))
160
+ .join("\\n");
161
+ process.stdout.write(
162
+ "[builderforce-memory] Active — token-saving memory loaded. Before re-reading files or history, call memory_recall. Store durable facts with memory_remember.\\nTop memories:\\n" + top,
163
+ );
164
+ `;
165
+ }
166
+ /** The companion skill doc that teaches the recall/remember discipline. */
167
+ export function companionSkillMd() {
168
+ return `---
169
+ name: builderforce-memory
170
+ description: Token-saving persistent memory. Recall durable facts (decisions, fixes, project context, user preferences) instead of re-reading files or chat history, and remember new durable facts as you learn them.
171
+ ---
172
+
173
+ # builderforce-memory — the conductor
174
+
175
+ Persistent memory backed by the \`builderforce-memory\` MCP server. The point is
176
+ **token reduction**: pull a small relevant slice from memory instead of re-reading
177
+ the full file/history. The install also wires hooks so this is largely automatic:
178
+ the SessionStart digest, contextual recall on each prompt, and an autonomous Stop
179
+ capture that won't let a durable correction go unrecorded.
180
+
181
+ ## Tools
182
+ - \`memory_recall(query, [k])\` — top-K relevant memories. First move on a task.
183
+ - \`memory_get(key)\` — exact lookup by key.
184
+ - \`memory_recall_by_tag(tag)\` — everything under a tag.
185
+ - \`memory_remember(key, content, [tags], [importance])\` — store ONE durable fact.
186
+ - \`memory_forget(key)\` — drop a superseded fact.
187
+
188
+ ## Discipline
189
+ Reuse a stable key to REPLACE a fact (write-through), don't append duplicates.
190
+ Store decisions, non-obvious fixes, project constraints, user preferences — one
191
+ tight line each. Not things git already records or one-off turn details.
192
+ `;
193
+ }
194
+ export function claudeComboPaths(claudeDir) {
195
+ return {
196
+ claudeDir,
197
+ settingsFile: nodePath.join(claudeDir, "settings.json"),
198
+ hookScript: nodePath.join(claudeDir, "builderforce-memory", "bfmem-hook.mjs"),
199
+ companionSkill: nodePath.join(claudeDir, "skills", "builderforce-memory", "SKILL.md"),
200
+ };
201
+ }
202
+ const HOOK_EVENTS = [
203
+ { event: "SessionStart", mode: "--session" },
204
+ { event: "PreCompact", mode: "--precompact" },
205
+ { event: "UserPromptSubmit", mode: "--recall" },
206
+ { event: "Stop", mode: "--capture" },
207
+ ];
208
+ /**
209
+ * Write the hook script + companion skill and register all four hooks in
210
+ * settings.json. Idempotent (skips a hook command already present) and
211
+ * backup-safe (.bak before overwriting settings). Returns the events it added.
212
+ */
213
+ export function installClaudeCombo(opts) {
214
+ const { fs, memoryFile } = opts;
215
+ const paths = claudeComboPaths(opts.claudeDir);
216
+ // 1) Hook script.
217
+ const hookDir = nodePath.dirname(paths.hookScript);
218
+ if (!fs.existsSync(hookDir))
219
+ fs.mkdirSync(hookDir, { recursive: true });
220
+ fs.writeFileSync(paths.hookScript, bfmemHookSource(memoryFile));
221
+ // 2) Companion skill.
222
+ const skillDir = nodePath.dirname(paths.companionSkill);
223
+ if (!fs.existsSync(skillDir))
224
+ fs.mkdirSync(skillDir, { recursive: true });
225
+ fs.writeFileSync(paths.companionSkill, companionSkillMd());
226
+ // 3) Merge hooks into settings.json.
227
+ let settings = {};
228
+ if (fs.existsSync(paths.settingsFile)) {
229
+ try {
230
+ settings = JSON.parse(fs.readFileSync(paths.settingsFile, "utf8"));
231
+ }
232
+ catch {
233
+ throw new Error("existing settings.json is not valid JSON");
234
+ }
235
+ fs.copyFileSync(paths.settingsFile, `${paths.settingsFile}.bak`);
236
+ }
237
+ const hooks = (settings.hooks && typeof settings.hooks === "object" ? settings.hooks : {});
238
+ const added = [];
239
+ for (const { event, mode } of HOOK_EVENTS) {
240
+ const command = `node "${paths.hookScript}" ${mode}`;
241
+ const arr = (Array.isArray(hooks[event]) ? hooks[event] : (hooks[event] = []));
242
+ const exists = arr.some((g) => (g.hooks || []).some((h) => h.command === command));
243
+ if (!exists) {
244
+ arr.push({ hooks: [{ type: "command", command }] });
245
+ added.push(event);
246
+ }
247
+ }
248
+ settings.hooks = hooks;
249
+ const settingsDir = nodePath.dirname(paths.settingsFile);
250
+ if (!fs.existsSync(settingsDir))
251
+ fs.mkdirSync(settingsDir, { recursive: true });
252
+ fs.writeFileSync(paths.settingsFile, `${JSON.stringify(settings, null, 2)}\n`);
253
+ return { hookScript: paths.hookScript, addedHooks: added, settingsFile: paths.settingsFile };
254
+ }
255
+ //# sourceMappingURL=claude-hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-hooks.js","sourceRoot":"","sources":["../../src/install/claude-hooks.ts"],"names":[],"mappings":"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"}
package/dist/tools.d.ts CHANGED
@@ -38,6 +38,13 @@ export interface MemoryToolsOptions {
38
38
  maxContentChars?: number;
39
39
  /** Expose write tools (remember/forget). Default true; forced false if the backend is read-only. */
40
40
  writable?: boolean;
41
+ /**
42
+ * BuilderForce gateway base URL (e.g. https://api.builderforce.ai). When set
43
+ * together with {@link gatewayApiKey}, the cost/efficiency tools are exposed.
44
+ */
45
+ gatewayUrl?: string;
46
+ /** A `bfk_*` tenant API key. Required (with {@link gatewayUrl}) for cost tools. */
47
+ gatewayApiKey?: string;
41
48
  }
42
49
  /**
43
50
  * Builds the memory tool set bound to `backend`. Write tools are included only
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,cAAc,CAAC;AAE7D,kEAAkE;AAClE,MAAM,WAAW,UAAU;IACvB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,2FAA2F;AAC3F,MAAM,WAAW,UAAU;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,WAAW,EAAE,CAAC,CAAC,WAAW,CAAC;IAC3B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;CACnE;AAED,MAAM,WAAW,kBAAkB;IAC/B,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oGAAoG;IACpG,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AA4BD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,GAAE,kBAAuB,GAAG,UAAU,EAAE,CA4IpG"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,cAAc,CAAC;AAE7D,kEAAkE;AAClE,MAAM,WAAW,UAAU;IACvB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,2FAA2F;AAC3F,MAAM,WAAW,UAAU;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,WAAW,EAAE,CAAC,CAAC,WAAW,CAAC;IAC3B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;CACnE;AAED,MAAM,WAAW,kBAAkB;IAC/B,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oGAAoG;IACpG,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AA4BD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,GAAE,kBAAuB,GAAG,UAAU,EAAE,CAkJpG"}
package/dist/tools.js CHANGED
@@ -181,6 +181,75 @@ export function buildMemoryTools(backend, opts = {}) {
181
181
  },
182
182
  });
183
183
  }
184
+ // Gateway-backed cost tools — only when BOTH a gateway URL and key are
185
+ // present, so memory-only deployments are unchanged.
186
+ if (opts.gatewayUrl && opts.gatewayApiKey) {
187
+ appendGatewayCostTools(tools, opts.gatewayUrl.replace(/\/+$/, ""), opts.gatewayApiKey);
188
+ }
184
189
  return tools;
185
190
  }
191
+ async function gatewayGet(gatewayUrl, path, apiKey) {
192
+ const res = await fetch(`${gatewayUrl}${path}`, {
193
+ headers: { authorization: `Bearer ${apiKey}` },
194
+ });
195
+ if (!res.ok)
196
+ throw new Error(`gateway ${path} → HTTP ${res.status}`);
197
+ return (await res.json());
198
+ }
199
+ function appendGatewayCostTools(tools, gatewayUrl, apiKey) {
200
+ tools.push({
201
+ name: "token_usage",
202
+ description: "Current token spend + budget for the workspace (today).",
203
+ inputSchema: {},
204
+ handler: async () => {
205
+ try {
206
+ const s = await gatewayGet(gatewayUrl, "/llm/v1/builder-insights", apiKey);
207
+ const lines = [
208
+ `Token usage (${s.windowLabel ?? "today"}):`,
209
+ `• Tokens: ${(s.todayTokens ?? 0).toLocaleString()}`,
210
+ `• Cost: $${(s.todayCostUsd ?? 0).toFixed(2)}`,
211
+ `• % of daily cap: ${s.pctOfDailyCap == null
212
+ ? "no cap"
213
+ : `${s.pctOfDailyCap}%${s.dailyCapTokens ? ` of ${s.dailyCapTokens.toLocaleString()}` : ""}`}`,
214
+ `• Top model: ${s.topModel ? `${s.topModel.model} (${s.topModel.tokens.toLocaleString()} tok)` : "—"}`,
215
+ ];
216
+ if (s.costPerMergedPrUsd != null)
217
+ lines.push(`• Cost / merged PR: $${s.costPerMergedPrUsd.toFixed(2)}`);
218
+ if (s.tip)
219
+ lines.push(`• Tip: ${s.tip}`);
220
+ return ok(lines.join("\n"));
221
+ }
222
+ catch (err) {
223
+ return fail(`token_usage failed: ${String(err)}`);
224
+ }
225
+ },
226
+ });
227
+ tools.push({
228
+ name: "model_efficiency",
229
+ description: "Which models performed best/cheapest for this workspace's recent work.",
230
+ inputSchema: {},
231
+ handler: async () => {
232
+ try {
233
+ const a = await gatewayGet(gatewayUrl, "/llm/v1/model-analytics", apiKey);
234
+ const groups = a.byAction ?? [];
235
+ if (groups.length === 0)
236
+ return ok("No model efficiency data yet for this workspace.");
237
+ const out = ["Model efficiency by action type (best first):"];
238
+ for (const g of groups) {
239
+ out.push(`\n${g.label ?? g.actionType ?? "action"}:`);
240
+ for (const m of (g.models ?? []).slice(0, 3)) {
241
+ const cost = m.avgCostMillicents != null ? ` $${(m.avgCostMillicents / 100_000).toFixed(4)}/call` : "";
242
+ const merge = m.mergeRate != null ? ` merge=${Math.round(m.mergeRate * 100)}%` : "";
243
+ const score = m.avgScore != null ? ` score=${m.avgScore}` : "";
244
+ out.push(` • ${m.model}${score}${merge}${cost} (n=${m.samples ?? 0})`);
245
+ }
246
+ }
247
+ return ok(out.join("\n"));
248
+ }
249
+ catch (err) {
250
+ return fail(`model_efficiency failed: ${String(err)}`);
251
+ }
252
+ },
253
+ });
254
+ }
186
255
  //# sourceMappingURL=tools.js.map
package/dist/tools.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seanhogg/builderforce-memory-mcp",
3
- "version": "2026.6.28",
3
+ "version": "2026.6.31",
4
4
  "description": "BuilderForce Agent Memory — MCP transport. Exposes @seanhogg/builderforce-memory to any MCP client (Claude Agent SDK in-process, stdio, or HTTP) over a pluggable MemoryBackend, with token-saving fetch-on-demand recall.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "peerDependencies": {
52
52
  "@anthropic-ai/claude-agent-sdk": ">=0.1.0",
53
- "@seanhogg/builderforce-memory": "^2026.6.28",
53
+ "@seanhogg/builderforce-memory": "^2026.6.29",
54
54
  "fake-indexeddb": "^6.0.0"
55
55
  },
56
56
  "peerDependenciesMeta": {
@@ -71,8 +71,9 @@
71
71
  "@typescript-eslint/parser": "^7.0.0",
72
72
  "eslint": "^8.57.0",
73
73
  "fake-indexeddb": "^6.0.0",
74
- "typescript": "^5.0.0",
75
- "@seanhogg/builderforce-memory": "2026.6.28"
74
+ "typescript": "^6.0.3",
75
+ "@typescript/native-preview": "7.0.0-dev.20260627.2",
76
+ "@seanhogg/builderforce-memory": "2026.6.31"
76
77
  },
77
78
  "engines": {
78
79
  "node": ">=18.0.0"
@@ -82,7 +83,8 @@
82
83
  },
83
84
  "scripts": {
84
85
  "build": "tsc",
85
- "test": "tsc && node --test tests/install.test.mjs",
86
- "lint": "eslint src/"
86
+ "test": "tsc && node --test tests/install.test.mjs tests/tools.test.mjs",
87
+ "lint": "eslint src/",
88
+ "typecheck:native": "tsgo --noEmit"
87
89
  }
88
90
  }
package/src/bin/http.ts CHANGED
@@ -9,6 +9,8 @@
9
9
  * BUILDERFORCE_MEMORY_TOKEN Bearer token required on every request (recommended).
10
10
  * BUILDERFORCE_MEMORY_DB IndexedDB database name.
11
11
  * BUILDERFORCE_MEMORY_READONLY '1' to disable remember/forget tools.
12
+ * BUILDERFORCE_GATEWAY_URL Gateway base URL (default https://api.builderforce.ai).
13
+ * BUILDERFORCE_API_KEY `bfk_*` tenant key. When set, exposes the cost tools.
12
14
  */
13
15
 
14
16
  import http from "node:http";
@@ -22,6 +24,10 @@ const backend = await createLocalMemoryStoreBackend({
22
24
  const handler = createMemoryHttpHandler(backend, {
23
25
  authToken: process.env["BUILDERFORCE_MEMORY_TOKEN"],
24
26
  writable: process.env["BUILDERFORCE_MEMORY_READONLY"] !== "1",
27
+ // Optional gateway-backed cost tools (token_usage, model_efficiency). Only
28
+ // exposed when an API key is present; URL defaults to the public gateway.
29
+ gatewayUrl: process.env["BUILDERFORCE_GATEWAY_URL"] ?? "https://api.builderforce.ai",
30
+ gatewayApiKey: process.env["BUILDERFORCE_API_KEY"],
25
31
  });
26
32
 
27
33
  const port = Number(process.env["PORT"] ?? 8787);
@@ -18,8 +18,10 @@
18
18
 
19
19
  import os from "node:os";
20
20
  import path from "node:path";
21
+ import nodeFs from "node:fs";
21
22
  import { HOSTS } from "../install/hosts.js";
22
23
  import { installMemoryServer, type HostSelector } from "../install/install.js";
24
+ import { installClaudeCombo } from "../install/claude-hooks.js";
23
25
 
24
26
  function argValue(name: string): string | undefined {
25
27
  const hit = process.argv.find((a) => a.startsWith(`--${name}=`));
@@ -83,6 +85,26 @@ for (const r of results) {
83
85
  if (r.status === "installed" || r.status === "updated") wrote += 1;
84
86
  }
85
87
 
88
+ // ── Claude Code memory combo (hooks + companion skill) ───────────────────────
89
+ // Registering the server above gives Claude Code the TOOLS; this adds the
90
+ // self-driving behaviour (SessionStart digest, contextual recall, autonomous Stop
91
+ // capture). Only for Claude Code, only when present, and skippable via --no-hooks.
92
+ const claudeResult = results.find((r) => r.host === "claude-code");
93
+ const claudePresent = claudeResult && claudeResult.status !== "unsupported" && claudeResult.status !== "error";
94
+ if (claudePresent && !hasFlag("no-hooks")) {
95
+ try {
96
+ const combo = installClaudeCombo({
97
+ fs: nodeFs,
98
+ claudeDir: path.join(os.homedir(), ".claude"),
99
+ memoryFile,
100
+ });
101
+ const what = combo.addedHooks.length ? `added ${combo.addedHooks.join(", ")}` : "already current";
102
+ process.stdout.write(`✓ Claude Code memory combo: hooks ${what}; recall + autonomous capture wired\n`);
103
+ } catch (err) {
104
+ process.stderr.write(`✗ Claude Code hooks: ${String((err as Error).message ?? err)}\n`);
105
+ }
106
+ }
107
+
86
108
  if (results.length === 0) {
87
109
  process.stdout.write(
88
110
  "No MCP hosts detected. Pass --host=all to write configs anyway, or --host=cursor (etc.).\n",
package/src/bin/stdio.ts CHANGED
@@ -9,6 +9,9 @@
9
9
  * persists across process restarts — REQUIRED for an
10
10
  * MCP client that respawns this server each session
11
11
  * (otherwise fake-indexeddb loses everything on exit).
12
+ * BUILDERFORCE_GATEWAY_URL Gateway base URL (default https://api.builderforce.ai).
13
+ * BUILDERFORCE_API_KEY `bfk_*` tenant key. When set, exposes the cost tools
14
+ * (token_usage, model_efficiency).
12
15
  *
13
16
  * Recall is lexical (Jaccard) here — this headless binary does not stand up the
14
17
  * SSM runtime/GPU. For SSM-embedding recall, embed the package in-process and
@@ -23,4 +26,10 @@ const backend = await createLocalMemoryStoreBackend({
23
26
  persistFile: process.env["BUILDERFORCE_MEMORY_FILE"],
24
27
  });
25
28
 
26
- await runStdio(backend, { writable: process.env["BUILDERFORCE_MEMORY_READONLY"] !== "1" });
29
+ await runStdio(backend, {
30
+ writable: process.env["BUILDERFORCE_MEMORY_READONLY"] !== "1",
31
+ // Optional gateway-backed cost tools (token_usage, model_efficiency). Only
32
+ // exposed when an API key is present; URL defaults to the public gateway.
33
+ gatewayUrl: process.env["BUILDERFORCE_GATEWAY_URL"] ?? "https://api.builderforce.ai",
34
+ gatewayApiKey: process.env["BUILDERFORCE_API_KEY"],
35
+ });
package/src/index.ts CHANGED
@@ -37,3 +37,7 @@ export { installMemoryServer } from "./install/install.js";
37
37
  export type { InstallOptions, InstallResult, InstallStatus, HostSelector, FsLike } from "./install/install.js";
38
38
  export { HOSTS, SERVER_KEY, findHost } from "./install/hosts.js";
39
39
  export type { HostAdapter, HostEnv, ConfigFormat } from "./install/hosts.js";
40
+ // Claude Code "memory combo" — hooks (SessionStart/PreCompact/UserPromptSubmit/Stop)
41
+ // + companion skill that make memory self-driving (contextual recall + autonomous capture).
42
+ export { installClaudeCombo, bfmemHookSource, companionSkillMd, claudeComboPaths } from "./install/claude-hooks.js";
43
+ export type { ClaudeComboPaths } from "./install/claude-hooks.js";
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Claude Code "memory combo" — the hooks + companion skill half of the install.
3
+ *
4
+ * Registering the MCP server (install.ts) gives Claude Code the memory TOOLS.
5
+ * This module adds the behaviour that makes memory SELF-DRIVING rather than
6
+ * advisory, so the agent doesn't have to be reminded to use it:
7
+ *
8
+ * SessionStart → inject a digest of the top durable memories.
9
+ * PreCompact → flush durable learnings before the window is summarised.
10
+ * UserPromptSubmit → CONTEXTUAL recall: score memories against the prompt and
11
+ * inject the relevant ones (so a stored fact is retrieved at
12
+ * the decision point, not just at session start).
13
+ * Stop → AUTONOMOUS capture: detect a durable instruction/correction
14
+ * in the user's last message and, if it wasn't consolidated
15
+ * this turn, BLOCK the stop with a directive to memory_remember
16
+ * (capture flows through the MCP write path — never racing the
17
+ * server's snapshot).
18
+ *
19
+ * All four point at one generated, dependency-free hook script (DRY). The combo
20
+ * is idempotent and backup-safe, matching install.ts.
21
+ */
22
+
23
+ import nodePath from "node:path";
24
+ import type { FsLike } from "./install.js";
25
+
26
+ /** The single hook script, four modes. `memoryFile` is the JSON snapshot path. */
27
+ export function bfmemHookSource(memoryFile: string): string {
28
+ return `#!/usr/bin/env node
29
+ /**
30
+ * builderforce-memory hook — generated by builderforce-memory-install. Dependency-free:
31
+ * reads only the JSON snapshot, never stands up the MCP server.
32
+ * --session SessionStart: inject a small digest of durable memories.
33
+ * --precompact PreCompact: remind to flush learnings before summarisation.
34
+ * --recall UserPromptSubmit: contextual recall — inject memories relevant to the prompt.
35
+ * --capture Stop: autonomous consolidation — block the stop until a detected
36
+ * durable correction is persisted via memory_remember.
37
+ */
38
+ import fs from "node:fs";
39
+
40
+ const FILE = ${JSON.stringify(memoryFile)};
41
+ const mode = process.argv[2] || "--session";
42
+
43
+ function readStdin() {
44
+ try { return fs.readFileSync(0, "utf8"); } catch { return ""; }
45
+ }
46
+ function loadEntries() {
47
+ try { const p = JSON.parse(fs.readFileSync(FILE, "utf8")); return Array.isArray(p) ? p : []; }
48
+ catch { return []; }
49
+ }
50
+ function tokens(s) {
51
+ return String(s || "").toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 3);
52
+ }
53
+
54
+ if (mode === "--precompact") {
55
+ process.stdout.write(
56
+ "[builderforce-memory] Context is about to be compacted. Persist any durable learnings from this session — decisions, fixes, project facts, user preferences — by calling memory_remember with concise one-line facts. Recall them later with memory_recall instead of re-reading files.",
57
+ );
58
+ process.exit(0);
59
+ }
60
+
61
+ if (mode === "--recall") {
62
+ let prompt = "";
63
+ try { const j = JSON.parse(readStdin() || "{}"); prompt = typeof j.prompt === "string" ? j.prompt : ""; } catch { /* no input */ }
64
+ if (!prompt) process.exit(0);
65
+ const entries = loadEntries();
66
+ if (entries.length === 0) process.exit(0);
67
+ const q = new Set(tokens(prompt));
68
+ if (q.size === 0) process.exit(0);
69
+ const scored = entries
70
+ .map((e) => {
71
+ const hay = new Set(tokens([e.key, e.content, (e.tags || []).join(" ")].join(" ")));
72
+ let score = 0;
73
+ for (const t of q) if (hay.has(t)) score++;
74
+ return { e, score };
75
+ })
76
+ .filter((x) => x.score >= 2)
77
+ .sort((a, b) => b.score - a.score)
78
+ .slice(0, 3);
79
+ if (scored.length === 0) process.exit(0);
80
+ const lines = scored.map((x) => "• " + x.e.key + ": " + String(x.e.content || "").slice(0, 240)).join("\\n");
81
+ process.stdout.write(
82
+ "[builderforce-memory] Relevant stored memories for this request — use these instead of re-deriving or re-reading:\\n" + lines,
83
+ );
84
+ process.exit(0);
85
+ }
86
+
87
+ if (mode === "--capture") {
88
+ let input = {};
89
+ try { input = JSON.parse(readStdin() || "{}"); } catch { /* no input */ }
90
+ if (input.stop_hook_active) process.exit(0);
91
+ const tpath = input.transcript_path;
92
+ if (!tpath || typeof tpath !== "string") process.exit(0);
93
+
94
+ let raw = "";
95
+ try {
96
+ const stat = fs.statSync(tpath);
97
+ const want = Math.min(stat.size, 400000);
98
+ const fd = fs.openSync(tpath, "r");
99
+ const buf = Buffer.alloc(want);
100
+ fs.readSync(fd, buf, 0, want, stat.size - want);
101
+ fs.closeSync(fd);
102
+ raw = buf.toString("utf8");
103
+ } catch { process.exit(0); }
104
+
105
+ const msgs = [];
106
+ for (const ln of raw.split("\\n")) { if (ln.trim()) { try { msgs.push(JSON.parse(ln)); } catch { /* partial */ } } }
107
+
108
+ let lastUserText = "";
109
+ let rememberedThisTurn = false;
110
+ for (let i = msgs.length - 1; i >= 0; i--) {
111
+ const m = msgs[i] || {};
112
+ const inner = m.message || m;
113
+ const role = inner.role;
114
+ const content = inner.content;
115
+ if (role === "assistant" && !lastUserText) {
116
+ if (Array.isArray(content)) for (const c of content) {
117
+ if (c && c.type === "tool_use" && typeof c.name === "string" && c.name.includes("memory_remember")) rememberedThisTurn = true;
118
+ }
119
+ }
120
+ if (role === "user" && !lastUserText) {
121
+ let txt = "";
122
+ if (typeof content === "string") txt = content;
123
+ else if (Array.isArray(content)) for (const c of content) {
124
+ if (typeof c === "string") txt += c + " ";
125
+ else if (c && c.type === "text" && typeof c.text === "string") txt += c.text + " ";
126
+ }
127
+ if (txt.trim()) lastUserText = txt;
128
+ }
129
+ }
130
+ if (!lastUserText || rememberedThisTurn) process.exit(0);
131
+
132
+ const lower = lastUserText.toLowerCase();
133
+ const SIGNALS = [
134
+ "you have to", "you should", "you need to", "you must", "from now on", "going forward",
135
+ "always ", "never ", "make sure", "remember to", "don't forget", "do not forget",
136
+ "i keep telling", "i already told", "shouldn't have to", "keep having to remind",
137
+ "you forgot", "you keep", "that's wrong", "you're wrong", "this is wrong", "stop ",
138
+ ];
139
+ const hit = SIGNALS.find((s) => lower.includes(s));
140
+ if (!hit) process.exit(0);
141
+
142
+ const snippet = lastUserText.trim().slice(0, 240);
143
+ const reason =
144
+ "[builderforce-memory] A durable instruction/correction was detected in the user's last message and you did NOT consolidate it this turn. " +
145
+ "Before stopping, call memory_remember with a concise, stable-keyed fact capturing it (reuse the same key to replace a superseded one, or memory_forget it). " +
146
+ "User said: '" + snippet + "'. If it is genuinely not durable (a one-off, or already stored), briefly say why and then stop.";
147
+ process.stdout.write(JSON.stringify({ decision: "block", reason }));
148
+ process.exit(0);
149
+ }
150
+
151
+ const entries = loadEntries();
152
+ if (entries.length === 0) {
153
+ process.stdout.write(
154
+ "[builderforce-memory] Active (memory empty). Store durable facts with memory_remember and retrieve them with memory_recall instead of re-reading files or chat history — that is how token usage stays low.",
155
+ );
156
+ process.exit(0);
157
+ }
158
+ const top = entries
159
+ .map((e) => ({ key: e.key, content: String(e.content ?? ""), importance: typeof e.importance === "number" ? e.importance : 0.5 }))
160
+ .sort((a, b) => b.importance - a.importance)
161
+ .slice(0, 8)
162
+ .map((e) => "• " + e.key + ": " + e.content.slice(0, 200))
163
+ .join("\\n");
164
+ process.stdout.write(
165
+ "[builderforce-memory] Active — token-saving memory loaded. Before re-reading files or history, call memory_recall. Store durable facts with memory_remember.\\nTop memories:\\n" + top,
166
+ );
167
+ `;
168
+ }
169
+
170
+ /** The companion skill doc that teaches the recall/remember discipline. */
171
+ export function companionSkillMd(): string {
172
+ return `---
173
+ name: builderforce-memory
174
+ description: Token-saving persistent memory. Recall durable facts (decisions, fixes, project context, user preferences) instead of re-reading files or chat history, and remember new durable facts as you learn them.
175
+ ---
176
+
177
+ # builderforce-memory — the conductor
178
+
179
+ Persistent memory backed by the \`builderforce-memory\` MCP server. The point is
180
+ **token reduction**: pull a small relevant slice from memory instead of re-reading
181
+ the full file/history. The install also wires hooks so this is largely automatic:
182
+ the SessionStart digest, contextual recall on each prompt, and an autonomous Stop
183
+ capture that won't let a durable correction go unrecorded.
184
+
185
+ ## Tools
186
+ - \`memory_recall(query, [k])\` — top-K relevant memories. First move on a task.
187
+ - \`memory_get(key)\` — exact lookup by key.
188
+ - \`memory_recall_by_tag(tag)\` — everything under a tag.
189
+ - \`memory_remember(key, content, [tags], [importance])\` — store ONE durable fact.
190
+ - \`memory_forget(key)\` — drop a superseded fact.
191
+
192
+ ## Discipline
193
+ Reuse a stable key to REPLACE a fact (write-through), don't append duplicates.
194
+ Store decisions, non-obvious fixes, project constraints, user preferences — one
195
+ tight line each. Not things git already records or one-off turn details.
196
+ `;
197
+ }
198
+
199
+ /** Files/paths the combo manages, derived from the Claude config dir (~/.claude). */
200
+ export interface ClaudeComboPaths {
201
+ claudeDir: string;
202
+ settingsFile: string;
203
+ hookScript: string;
204
+ companionSkill: string;
205
+ }
206
+
207
+ export function claudeComboPaths(claudeDir: string): ClaudeComboPaths {
208
+ return {
209
+ claudeDir,
210
+ settingsFile: nodePath.join(claudeDir, "settings.json"),
211
+ hookScript: nodePath.join(claudeDir, "builderforce-memory", "bfmem-hook.mjs"),
212
+ companionSkill: nodePath.join(claudeDir, "skills", "builderforce-memory", "SKILL.md"),
213
+ };
214
+ }
215
+
216
+ const HOOK_EVENTS = [
217
+ { event: "SessionStart", mode: "--session" },
218
+ { event: "PreCompact", mode: "--precompact" },
219
+ { event: "UserPromptSubmit", mode: "--recall" },
220
+ { event: "Stop", mode: "--capture" },
221
+ ] as const;
222
+
223
+ /**
224
+ * Write the hook script + companion skill and register all four hooks in
225
+ * settings.json. Idempotent (skips a hook command already present) and
226
+ * backup-safe (.bak before overwriting settings). Returns the events it added.
227
+ */
228
+ export function installClaudeCombo(opts: {
229
+ fs: FsLike;
230
+ claudeDir: string;
231
+ memoryFile: string;
232
+ }): { hookScript: string; addedHooks: string[]; settingsFile: string } {
233
+ const { fs, memoryFile } = opts;
234
+ const paths = claudeComboPaths(opts.claudeDir);
235
+
236
+ // 1) Hook script.
237
+ const hookDir = nodePath.dirname(paths.hookScript);
238
+ if (!fs.existsSync(hookDir)) fs.mkdirSync(hookDir, { recursive: true });
239
+ fs.writeFileSync(paths.hookScript, bfmemHookSource(memoryFile));
240
+
241
+ // 2) Companion skill.
242
+ const skillDir = nodePath.dirname(paths.companionSkill);
243
+ if (!fs.existsSync(skillDir)) fs.mkdirSync(skillDir, { recursive: true });
244
+ fs.writeFileSync(paths.companionSkill, companionSkillMd());
245
+
246
+ // 3) Merge hooks into settings.json.
247
+ let settings: Record<string, unknown> = {};
248
+ if (fs.existsSync(paths.settingsFile)) {
249
+ try { settings = JSON.parse(fs.readFileSync(paths.settingsFile, "utf8")) as Record<string, unknown>; }
250
+ catch { throw new Error("existing settings.json is not valid JSON"); }
251
+ fs.copyFileSync(paths.settingsFile, `${paths.settingsFile}.bak`);
252
+ }
253
+ const hooks = (settings.hooks && typeof settings.hooks === "object" ? settings.hooks : {}) as Record<string, unknown>;
254
+ const added: string[] = [];
255
+ for (const { event, mode } of HOOK_EVENTS) {
256
+ const command = `node "${paths.hookScript}" ${mode}`;
257
+ const arr = (Array.isArray(hooks[event]) ? hooks[event] : (hooks[event] = [])) as Array<{ hooks?: Array<{ type?: string; command?: string }> }>;
258
+ const exists = arr.some((g) => (g.hooks || []).some((h) => h.command === command));
259
+ if (!exists) { arr.push({ hooks: [{ type: "command", command }] }); added.push(event); }
260
+ }
261
+ settings.hooks = hooks;
262
+ const settingsDir = nodePath.dirname(paths.settingsFile);
263
+ if (!fs.existsSync(settingsDir)) fs.mkdirSync(settingsDir, { recursive: true });
264
+ fs.writeFileSync(paths.settingsFile, `${JSON.stringify(settings, null, 2)}\n`);
265
+
266
+ return { hookScript: paths.hookScript, addedHooks: added, settingsFile: paths.settingsFile };
267
+ }
package/src/tools.ts CHANGED
@@ -39,6 +39,13 @@ export interface MemoryToolsOptions {
39
39
  maxContentChars?: number;
40
40
  /** Expose write tools (remember/forget). Default true; forced false if the backend is read-only. */
41
41
  writable?: boolean;
42
+ /**
43
+ * BuilderForce gateway base URL (e.g. https://api.builderforce.ai). When set
44
+ * together with {@link gatewayApiKey}, the cost/efficiency tools are exposed.
45
+ */
46
+ gatewayUrl?: string;
47
+ /** A `bfk_*` tenant API key. Required (with {@link gatewayUrl}) for cost tools. */
48
+ gatewayApiKey?: string;
42
49
  }
43
50
 
44
51
  const DEFAULT_MAX_RESULTS = 5;
@@ -210,5 +217,101 @@ export function buildMemoryTools(backend: MemoryBackend, opts: MemoryToolsOption
210
217
  });
211
218
  }
212
219
 
220
+ // Gateway-backed cost tools — only when BOTH a gateway URL and key are
221
+ // present, so memory-only deployments are unchanged.
222
+ if (opts.gatewayUrl && opts.gatewayApiKey) {
223
+ appendGatewayCostTools(tools, opts.gatewayUrl.replace(/\/+$/, ""), opts.gatewayApiKey);
224
+ }
225
+
213
226
  return tools;
214
227
  }
228
+
229
+ /** Snapshot shape returned by GET /llm/v1/builder-insights. */
230
+ interface BuilderInsightsSnapshot {
231
+ windowLabel?: string;
232
+ todayTokens?: number;
233
+ todayCostUsd?: number;
234
+ dailyCapTokens?: number | null;
235
+ pctOfDailyCap?: number | null;
236
+ topModel?: { model: string; tokens: number } | null;
237
+ costPerMergedPrUsd?: number | null;
238
+ tip?: string | null;
239
+ }
240
+
241
+ /** Ranking shape returned by GET /llm/v1/model-analytics. */
242
+ interface ModelAnalytics {
243
+ byAction?: Array<{
244
+ actionType?: string;
245
+ label?: string;
246
+ models?: Array<{
247
+ model: string;
248
+ samples?: number;
249
+ avgScore?: number;
250
+ mergeRate?: number;
251
+ avgCostMillicents?: number;
252
+ }>;
253
+ }>;
254
+ }
255
+
256
+ async function gatewayGet<T>(gatewayUrl: string, path: string, apiKey: string): Promise<T> {
257
+ const res = await fetch(`${gatewayUrl}${path}`, {
258
+ headers: { authorization: `Bearer ${apiKey}` },
259
+ });
260
+ if (!res.ok) throw new Error(`gateway ${path} → HTTP ${res.status}`);
261
+ return (await res.json()) as T;
262
+ }
263
+
264
+ function appendGatewayCostTools(tools: MemoryTool[], gatewayUrl: string, apiKey: string): void {
265
+ tools.push({
266
+ name: "token_usage",
267
+ description: "Current token spend + budget for the workspace (today).",
268
+ inputSchema: {},
269
+ handler: async () => {
270
+ try {
271
+ const s = await gatewayGet<BuilderInsightsSnapshot>(gatewayUrl, "/llm/v1/builder-insights", apiKey);
272
+ const lines = [
273
+ `Token usage (${s.windowLabel ?? "today"}):`,
274
+ `• Tokens: ${(s.todayTokens ?? 0).toLocaleString()}`,
275
+ `• Cost: $${(s.todayCostUsd ?? 0).toFixed(2)}`,
276
+ `• % of daily cap: ${
277
+ s.pctOfDailyCap == null
278
+ ? "no cap"
279
+ : `${s.pctOfDailyCap}%${s.dailyCapTokens ? ` of ${s.dailyCapTokens.toLocaleString()}` : ""}`
280
+ }`,
281
+ `• Top model: ${s.topModel ? `${s.topModel.model} (${s.topModel.tokens.toLocaleString()} tok)` : "—"}`,
282
+ ];
283
+ if (s.costPerMergedPrUsd != null) lines.push(`• Cost / merged PR: $${s.costPerMergedPrUsd.toFixed(2)}`);
284
+ if (s.tip) lines.push(`• Tip: ${s.tip}`);
285
+ return ok(lines.join("\n"));
286
+ } catch (err) {
287
+ return fail(`token_usage failed: ${String(err)}`);
288
+ }
289
+ },
290
+ });
291
+
292
+ tools.push({
293
+ name: "model_efficiency",
294
+ description: "Which models performed best/cheapest for this workspace's recent work.",
295
+ inputSchema: {},
296
+ handler: async () => {
297
+ try {
298
+ const a = await gatewayGet<ModelAnalytics>(gatewayUrl, "/llm/v1/model-analytics", apiKey);
299
+ const groups = a.byAction ?? [];
300
+ if (groups.length === 0) return ok("No model efficiency data yet for this workspace.");
301
+ const out: string[] = ["Model efficiency by action type (best first):"];
302
+ for (const g of groups) {
303
+ out.push(`\n${g.label ?? g.actionType ?? "action"}:`);
304
+ for (const m of (g.models ?? []).slice(0, 3)) {
305
+ const cost = m.avgCostMillicents != null ? ` $${(m.avgCostMillicents / 100_000).toFixed(4)}/call` : "";
306
+ const merge = m.mergeRate != null ? ` merge=${Math.round(m.mergeRate * 100)}%` : "";
307
+ const score = m.avgScore != null ? ` score=${m.avgScore}` : "";
308
+ out.push(` • ${m.model}${score}${merge}${cost} (n=${m.samples ?? 0})`);
309
+ }
310
+ }
311
+ return ok(out.join("\n"));
312
+ } catch (err) {
313
+ return fail(`model_efficiency failed: ${String(err)}`);
314
+ }
315
+ },
316
+ });
317
+ }