agent-working-memory 0.14.6 → 0.15.4

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.
@@ -44,15 +44,27 @@ import { existsSync, statSync } from 'node:fs';
44
44
  function detectBackendFromDisk() {
45
45
  // If AWM_DB_PATH is set, infer from its shape.
46
46
  const explicitPath = process.env.AWM_DB_PATH;
47
- if (explicitPath && existsSync(explicitPath)) {
48
- try {
49
- const stat = statSync(explicitPath);
50
- if (stat.isDirectory())
51
- return 'pglite'; // PGlite uses a directory
52
- if (stat.isFile())
53
- return 'sqlite'; // SQLite is a single file
47
+ if (explicitPath) {
48
+ if (existsSync(explicitPath)) {
49
+ try {
50
+ const stat = statSync(explicitPath);
51
+ if (stat.isDirectory())
52
+ return 'pglite'; // PGlite uses a directory
53
+ if (stat.isFile())
54
+ return 'sqlite'; // SQLite is a single file
55
+ }
56
+ catch { /* fall through to the default below */ }
54
57
  }
55
- catch { /* fall through */ }
58
+ // The caller named a path that does not exist yet — a brand-new store. Default it to
59
+ // SQLite and STOP: falling through to the cwd conventions below would pick the backend
60
+ // from files that have nothing to do with the path that was asked for.
61
+ //
62
+ // Not hypothetical. Running from a checkout containing a `memory-pglite/` directory
63
+ // made a fresh AWM_DB_PATH=/tmp/new-store.db resolve to PGlite, which is
64
+ // single-process — so two surfaces pointed at 'the same store' would have had the
65
+ // second refuse to start. Found by the cross-surface end-to-end test.
66
+ // PGlite at a new path is still reachable via AWM_STORE_BACKEND=pglite.
67
+ return 'sqlite';
56
68
  }
57
69
  // Otherwise look for the conventional defaults in cwd.
58
70
  // PGlite directory wins over SQLite file when both exist — assume the
@@ -1 +1 @@
1
- {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/storage/factory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/storage/factory.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-working-memory",
3
- "version": "0.14.6",
3
+ "version": "0.15.4",
4
4
  "description": "Persistent, local-first memory for AI agents. Selective by design: filters what's worth keeping, links what's related, and says nothing when it doesn't know. SQLite + ONNX, no cloud, no API keys.",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -27,7 +27,10 @@
27
27
  },
28
28
  "scripts": {
29
29
  "build": "tsc",
30
- "prepublishOnly": "npm run build && npm run check:release",
30
+ "build:plugin": "node scripts/build-plugin.mjs",
31
+ "build:mcpb": "node scripts/build-mcpb.mjs",
32
+ "pack:mcpb": "node scripts/build-mcpb.mjs --pack",
33
+ "prepublishOnly": "npm run build && npm run build:plugin && npm run build:mcpb && npm run check:release",
31
34
  "dev": "tsx watch src/index.ts",
32
35
  "start": "node dist/index.js",
33
36
  "test": "vitest",
@@ -51,7 +54,8 @@
51
54
  "check:release": "node scripts/check-release.mjs",
52
55
  "bench": "node scripts/bench-current.mjs",
53
56
  "test:docker": "bash scripts/docker-release-test.sh",
54
- "test:linux": "bash scripts/docker-release-test.sh linux"
57
+ "test:linux": "bash scripts/docker-release-test.sh linux",
58
+ "test:desktop": "bash scripts/docker-release-test.sh desktop"
55
59
  },
56
60
  "engines": {
57
61
  "node": ">=22.0.0"
@@ -60,6 +64,8 @@
60
64
  "files": [
61
65
  "dist/",
62
66
  "src/",
67
+ "plugin/",
68
+ ".claude-plugin/",
63
69
  "package.json",
64
70
  "README.md",
65
71
  "LICENSE"
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "awm",
3
+ "displayName": "Agent Working Memory",
4
+ "version": "0.15.4",
5
+ "description": "Persistent, local-first memory for AI agents. Selective by design: filters what's worth keeping, links what's related, and says nothing when it doesn't know. SQLite + ONNX, no cloud, no API keys.",
6
+ "author": {
7
+ "name": "Robert Winter"
8
+ },
9
+ "homepage": "https://completeideas.github.io/agent-working-memory/",
10
+ "repository": "https://github.com/CompleteIdeas/agent-working-memory",
11
+ "license": "Apache-2.0",
12
+ "keywords": [
13
+ "memory",
14
+ "mcp",
15
+ "recall",
16
+ "agent"
17
+ ],
18
+ "skills": "./skills/",
19
+ "hooks": "./hooks/hooks.json",
20
+ "mcpServers": {
21
+ "agent-working-memory": {
22
+ "command": "node",
23
+ "args": [
24
+ "${CLAUDE_PLUGIN_ROOT}/bin/awm-mcp-launcher.cjs"
25
+ ],
26
+ "env": {
27
+ "AWM_RERANK2": "1",
28
+ "AWM_RERANK_WINDOW": "query",
29
+ "AWM_RERANK_TAGS": "1",
30
+ "AWM_CLIENT": "claude-code"
31
+ }
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,36 @@
1
+ # AWM — Claude Code plugin
2
+
3
+ Installs Agent Working Memory as a plugin: the MCP server, the session hooks, and the usage
4
+ guidance, all versioned together. This is the plugin equivalent of `awm setup --global`.
5
+
6
+ **This directory is generated.** `npm run build:plugin` emits it from the same modules the
7
+ installer uses, and `npm run check:release` fails if the committed output has drifted. Edit
8
+ `src/adapters/`, not these files.
9
+
10
+ ## What it installs
11
+
12
+ | | |
13
+ |---|---|
14
+ | MCP server | 1 server, 19 memory tools, via `bin/awm-mcp-launcher.cjs` |
15
+ | Hooks | Stop, PreCompact, SessionEnd, UserPromptSubmit (prime), PostToolUse — hooks v0.14.6 |
16
+ | Skill | `awm-memory` — when to recall, when to write, how to tag |
17
+
18
+ ## The store
19
+
20
+ The launcher defaults `AWM_DB_PATH` to `~/.awm/memory.db` — **the same file
21
+ `awm setup --global` uses** — so a plugin install shares memory with a CLI install rather
22
+ than quietly starting an empty one. Set `AWM_DB_PATH` yourself to override.
23
+
24
+ It prefers an installed copy of the package (global, or a local `node_modules`) and falls
25
+ back to `npx -p agent-working-memory@0.15.4`. The npx path rebuilds
26
+ `better-sqlite3` on a cold cache, which is slow enough to look like a hang, so a real
27
+ install is worth having:
28
+
29
+ ```bash
30
+ npm install -g agent-working-memory
31
+ ```
32
+
33
+ ## Turning prime off
34
+
35
+ `UserPromptSubmit` primes relevant memory into context. To silence it without uninstalling,
36
+ create an empty `~/.claude/hooks/awm-prime.disabled`.
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ // Copyright 2026 Robert Winter / Complete Ideas
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ //
5
+ // Entry point for the Claude Code plugin's MCP server.
6
+ //
7
+ // This is a REAL source file, not a string inside the generator, because it is real code:
8
+ // it gets syntax-checked, linted and tested like everything else. `npm run build:plugin`
9
+ // copies it to plugin/bin/ and substitutes 0.15.4.
10
+ //
11
+ // Two jobs a bare `mcpServers` entry in plugin.json cannot do:
12
+ //
13
+ // 1. Default the store to ~/.awm/memory.db — the SAME file `awm setup --global` writes.
14
+ // Without this the plugin opens its own empty database and "your memory follows you
15
+ // between Claude Code, Desktop and Cowork" is simply false.
16
+ //
17
+ // 2. Find the server. A plugin does not know whether the package is installed globally,
18
+ // sits in a local node_modules, or is a checkout on disk.
19
+ //
20
+ // Fails loudly on stderr. A silent exit here looks like "the memory tools just aren't there",
21
+ // which is the least debuggable failure this could have.
22
+ 'use strict';
23
+
24
+ const { join, resolve, sep } = require('node:path');
25
+ const { homedir } = require('node:os');
26
+ const { existsSync } = require('node:fs');
27
+ const { pathToFileURL } = require('node:url');
28
+ const { spawn, execFileSync } = require('node:child_process');
29
+
30
+ const NL = '\n';
31
+
32
+ // Claude Desktop substitutes user_config values into env, and a field the user cleared
33
+ // arrives as an empty string rather than being absent. An empty AWM_AGENT_ID would override
34
+ // the server's directory-derived default with nothing, so drop empties before anything reads
35
+ // them and let the normal defaults apply.
36
+ for (const k of ['AWM_DB_PATH', 'AWM_AGENT_ID', 'AWM_HOOK_PORT', 'AWM_PACKAGE_ROOT']) {
37
+ if (process.env[k] !== undefined && String(process.env[k]).trim() === '') delete process.env[k];
38
+ }
39
+
40
+ // An explicit AWM_DB_PATH always wins, so per-project pools still work.
41
+ if (!process.env.AWM_DB_PATH) {
42
+ process.env.AWM_DB_PATH = join(homedir(), '.awm', 'memory.db').split(sep).join('/');
43
+ }
44
+
45
+ const tried = [];
46
+ function candidate(p) {
47
+ if (!p) return null;
48
+ tried.push(p);
49
+ return existsSync(p) ? p : null;
50
+ }
51
+
52
+ // In priority order. npx is last: on a cold cache it rebuilds better-sqlite3, which takes
53
+ // minutes and is indistinguishable from a hang.
54
+ let found =
55
+ // 1. An explicit override.
56
+ candidate(process.env.AWM_PACKAGE_ROOT && join(process.env.AWM_PACKAGE_ROOT, 'dist', 'mcp.js')) ||
57
+ // 2. The plugin sitting inside an AWM checkout (plugin/bin -> ../../dist).
58
+ candidate(resolve(__dirname, '..', '..', 'dist', 'mcp.js')) ||
59
+ // 3. A local dependency of the project being worked on.
60
+ candidate(join(process.cwd(), 'node_modules', 'agent-working-memory', 'dist', 'mcp.js'));
61
+
62
+ // 4. A global install. require.resolve does not search global roots, so ask npm once.
63
+ if (!found) {
64
+ try {
65
+ const isWin = process.platform === 'win32';
66
+ const root = execFileSync(isWin ? 'npm.cmd' : 'npm', ['root', '-g'], {
67
+ encoding: 'utf-8',
68
+ shell: isWin,
69
+ stdio: ['ignore', 'pipe', 'ignore'],
70
+ }).trim();
71
+ found = candidate(root && join(root, 'agent-working-memory', 'dist', 'mcp.js'));
72
+ } catch {
73
+ /* npm not on PATH — fall through */
74
+ }
75
+ }
76
+
77
+ // 5. Anywhere node can resolve it from here.
78
+ if (!found) {
79
+ try {
80
+ found = require.resolve('agent-working-memory/dist/mcp.js');
81
+ } catch {
82
+ /* not installed where this script can see it */
83
+ }
84
+ }
85
+
86
+ if (found) {
87
+ // dist/ is ESM ("type": "module"), so import() — require() throws ERR_REQUIRE_ESM.
88
+ import(pathToFileURL(found).href).catch((e) => {
89
+ process.stderr.write('[awm] failed to load ' + found + ': ' + e.message + NL);
90
+ process.exit(1);
91
+ });
92
+ } else {
93
+ // Last resort, pinned so the tools match the hook scripts shipped beside them.
94
+ // shell:true on Windows: spawning npx.cmd directly throws EINVAL since Node 18.20/20.12.
95
+ const isWin = process.platform === 'win32';
96
+ const child = spawn(
97
+ isWin ? 'npx.cmd' : 'npx',
98
+ ['-y', '-p', 'agent-working-memory@0.15.4', 'awm', 'mcp'],
99
+ { stdio: 'inherit', env: process.env, shell: isWin }
100
+ );
101
+ child.on('error', (e) => {
102
+ process.stderr.write('[awm] could not start the MCP server: ' + e.message + NL);
103
+ process.stderr.write('[awm] looked in:' + NL);
104
+ for (const t of tried) process.stderr.write(' ' + t + NL);
105
+ process.stderr.write('[awm] fix: npm install -g agent-working-memory' + NL);
106
+ process.exit(1);
107
+ });
108
+ child.on('exit', (code) => process.exit(code == null ? 0 : code));
109
+ }
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ // awm-checkpoint.cjs — installed by `awm setup` (hooks v0.14.6). PreCompact / SessionEnd: checkpoint this session into ITS agent's AWM.
3
+ // Re-running `awm setup` rewrites this file; put local changes elsewhere.
4
+ // Fails open: on any problem it prints nothing and exits 0.
5
+
6
+ // Forwards the whole hook payload (hook_event_name, transcript_path, cwd,
7
+ // session_id) to POST /hooks/checkpoint on the sidecar that belongs to this
8
+ // session's agent. transcript_path matters: the sidecar parses it for the
9
+ // current task and active files, so the checkpoint is a real one, not a stub.
10
+ 'use strict';
11
+ const http = require('http');
12
+
13
+ let finder = null;
14
+ try { finder = require('./awm-find-sidecar.cjs'); } catch (e) { process.exit(0); }
15
+
16
+ const TIMEOUT_MS = 5000;
17
+
18
+ let raw = '';
19
+ process.stdin.on('data', function (d) { raw += d; });
20
+ process.stdin.on('end', function () {
21
+ let payload = {};
22
+ try { payload = JSON.parse(raw || '{}'); } catch (e) { payload = {}; }
23
+ if (!payload.hook_event_name) payload.hook_event_name = 'PreCompact';
24
+ main(payload).catch(function () { process.exit(0); });
25
+ });
26
+ // A hook with nothing on stdin must still exit.
27
+ process.stdin.on('error', function () { process.exit(0); });
28
+
29
+ async function main(payload) {
30
+ const found = await finder.findSidecar(payload.cwd);
31
+ const sc = found.sidecar;
32
+ if (!sc) {
33
+ finder.debugLog('checkpoint', { event: payload.hook_event_name, agent: found.config.agentId, sidecar: null, live: found.all.length });
34
+ process.exit(0);
35
+ }
36
+ const body = JSON.stringify(payload);
37
+ const req = http.request({
38
+ host: '127.0.0.1', port: sc.port, path: '/hooks/checkpoint', method: 'POST',
39
+ headers: {
40
+ 'Content-Type': 'application/json',
41
+ 'Content-Length': Buffer.byteLength(body),
42
+ 'Authorization': 'Bearer ' + found.config.secret,
43
+ },
44
+ timeout: TIMEOUT_MS,
45
+ }, function (res) {
46
+ res.resume();
47
+ res.on('end', function () {
48
+ finder.debugLog('checkpoint', { event: payload.hook_event_name, agent: found.config.agentId, port: sc.port, status: res.statusCode });
49
+ process.exit(0);
50
+ });
51
+ });
52
+ req.on('error', function () { process.exit(0); });
53
+ req.on('timeout', function () { req.destroy(); process.exit(0); });
54
+ req.end(body);
55
+ }
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ // AWM DB-mutation reminder hook (PostToolUse on Bash|PowerShell). Installed by awm setup.
3
+ let raw = '';
4
+ process.stdin.on('data', (d) => (raw += d));
5
+ process.stdin.on('end', () => {
6
+ try {
7
+ const j = JSON.parse(raw || '{}');
8
+ const cmd = (j.tool_input && j.tool_input.command) || '';
9
+ const isDbClient = /\b(sqlcmd|mysql|psql)\b/i.test(cmd) || /(sqlcmd|mysql|psql)\.exe/i.test(cmd);
10
+ const isMutation = /\b(INSERT|UPDATE|DELETE|ALTER|TRUNCATE|DROP|MERGE)\b/i.test(cmd);
11
+ if (isDbClient && isMutation) {
12
+ console.log(JSON.stringify({
13
+ hookSpecificOutput: {
14
+ hookEventName: 'PostToolUse',
15
+ additionalContext:
16
+ 'AWM REMINDER: this command contains a production DB mutation (INSERT/UPDATE/DELETE/ALTER/DROP/TRUNCATE/MERGE). ' +
17
+ 'The change is NOT complete until it is recorded: call memory_write NOW with the table, what changed, row counts, date, and why ' +
18
+ '(memory_class canonical if other agents must recall it), and confirm a rollback/backup exists. ' +
19
+ 'If the keywords were only in a string/comment or on temp tables (#...), no write is needed.',
20
+ },
21
+ }));
22
+ }
23
+ } catch (e) { /* never block the tool result */ }
24
+ process.exit(0);
25
+ });
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+ // awm-find-sidecar.cjs — installed by `awm setup` (hooks v0.14.6). Shared resolver: which AWM sidecar belongs to THIS session.
3
+ // Re-running `awm setup` rewrites this file; put local changes elsewhere.
4
+ // Fails open: on any problem it prints nothing and exits 0.
5
+
6
+ // Since AWM 0.14.2 each MCP process binds the first free port in
7
+ // [AWM_HOOK_PORT, +AWM_HOOK_PORT_RANGE), so "the sidecar is on 8401" is not a
8
+ // safe assumption: with N sessions open there are N sidecars and the one on
9
+ // 8401 may serve a different agent pool. This module (1) works out which agent
10
+ // a session started in `cwd` belongs to, from the MCP config that governs that
11
+ // directory, (2) probes the whole range in parallel (local, ~10 ms), and
12
+ // (3) returns the responder whose agentId matches — newest version first, then
13
+ // lowest port. No match → null, and callers fail open.
14
+ 'use strict';
15
+ const fs = require('fs');
16
+ const os = require('os');
17
+ const path = require('path');
18
+ const http = require('http');
19
+
20
+ const SERVER_NAME = 'agent-working-memory';
21
+ const PROBE_TIMEOUT_MS = 400;
22
+
23
+ function norm(p) { return String(p || '').replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase(); }
24
+ function readJson(p) { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch (e) { return null; } }
25
+ function readText(p) { try { return fs.readFileSync(p, 'utf8').trim(); } catch (e) { return ''; } }
26
+
27
+ // MIRROR of deriveAgentFromDir() in src/core/agent-id.ts — the server's own
28
+ // fallback when AWM_AGENT_ID is unset. Kept in sync by tests/adapters/hook-scripts.test.ts.
29
+ function deriveAgentFromDir(dir) {
30
+ const d = String(dir || '').replace(/\\/g, '/');
31
+ if (/\/AgentSynapse\//i.test(d)) return 'work';
32
+ return /\/Personal-Projects(\/|$)/i.test(d) ? 'personal' : 'work';
33
+ }
34
+
35
+ // The AWM entry inside one MCP config file, if any. ~/.claude.json keeps
36
+ // project-scoped servers under projects[<path>].mcpServers.
37
+ function awmEntry(json, cwd) {
38
+ if (!json) return null;
39
+ if (json.mcpServers && json.mcpServers[SERVER_NAME]) return json.mcpServers[SERVER_NAME];
40
+ if (json.projects && cwd) {
41
+ for (const k of Object.keys(json.projects)) {
42
+ if (norm(k) !== norm(cwd)) continue;
43
+ const s = json.projects[k] && json.projects[k].mcpServers;
44
+ if (s && s[SERVER_NAME]) return s[SERVER_NAME];
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+
50
+ /**
51
+ * Resolve the configuration that governs a session started in `cwd`:
52
+ * { agentId, secret, port, range, dbPath, source }.
53
+ *
54
+ * Order: the nearest .mcp.json / .claude/mcp.json walking up from cwd, then
55
+ * ~/.mcp.json, then ~/.claude.json, then the record `awm setup` left in
56
+ * ~/.claude/hooks/awm-hooks.json. Process env always wins for a given key.
57
+ */
58
+ function resolveConfig(cwd) {
59
+ cwd = cwd || process.env.CLAUDE_PROJECT_DIR || process.cwd();
60
+ const home = os.homedir();
61
+ const candidates = [];
62
+ let dir = path.resolve(cwd);
63
+ for (let i = 0; i < 16; i++) {
64
+ candidates.push(path.join(dir, '.mcp.json'), path.join(dir, '.claude', 'mcp.json'));
65
+ const parent = path.dirname(dir);
66
+ if (parent === dir) break;
67
+ dir = parent;
68
+ }
69
+ candidates.push(path.join(home, '.mcp.json'), path.join(home, '.claude.json'));
70
+
71
+ let entry = null, source = null;
72
+ for (const c of candidates) {
73
+ const e = awmEntry(readJson(c), cwd);
74
+ if (e) { entry = e; source = c; break; }
75
+ }
76
+ const env = (entry && entry.env) || {};
77
+ const fallback = readJson(path.join(home, '.claude', 'hooks', 'awm-hooks.json')) || {};
78
+
79
+ const agentId = process.env.AWM_AGENT_ID || env.AWM_AGENT_ID
80
+ || (entry ? deriveAgentFromDir(cwd) : (fallback.agentId || deriveAgentFromDir(cwd)));
81
+ const dbPath = env.AWM_DB_PATH || fallback.dbPath || null;
82
+ let secret = process.env.AWM_HOOK_SECRET || env.AWM_HOOK_SECRET || '';
83
+ if (!secret && dbPath) secret = readText(path.join(path.dirname(dbPath), '.awm-hook-secret'));
84
+ if (!secret && fallback.secretPath) secret = readText(fallback.secretPath);
85
+ const port = parseInt(process.env.AWM_HOOK_PORT || env.AWM_HOOK_PORT || fallback.hookPort || '8401', 10) || 8401;
86
+ const range = Math.max(1, parseInt(process.env.AWM_HOOK_PORT_RANGE || env.AWM_HOOK_PORT_RANGE || fallback.hookPortRange || '10', 10) || 10);
87
+ return { agentId: agentId, secret: secret, port: port, range: range, dbPath: dbPath, source: source };
88
+ }
89
+
90
+ function probe(port) {
91
+ return new Promise(function (resolve) {
92
+ const req = http.get({ host: '127.0.0.1', port: port, path: '/health', timeout: PROBE_TIMEOUT_MS }, function (res) {
93
+ let data = '';
94
+ res.on('data', function (c) { data += c; });
95
+ res.on('end', function () {
96
+ try {
97
+ const j = JSON.parse(data);
98
+ resolve(j && j.sidecar ? { port: port, agentId: j.agentId || null, version: j.version || '0.0.0', pid: j.pid || null } : null);
99
+ } catch (e) { resolve(null); }
100
+ });
101
+ });
102
+ req.on('error', function () { resolve(null); });
103
+ req.on('timeout', function () { req.destroy(); resolve(null); });
104
+ });
105
+ }
106
+
107
+ function versionKey(v) { return String(v || '0').split('.').map(function (n) { return parseInt(n, 10) || 0; }); }
108
+ function newer(a, b) {
109
+ const ka = versionKey(a), kb = versionKey(b);
110
+ for (let i = 0; i < Math.max(ka.length, kb.length); i++) {
111
+ const d = (ka[i] || 0) - (kb[i] || 0);
112
+ if (d) return d > 0;
113
+ }
114
+ return false;
115
+ }
116
+
117
+ /** Every live sidecar in the configured range, regardless of agent. */
118
+ async function listSidecars(cfg) {
119
+ const ports = [];
120
+ for (let i = 0; i < cfg.range; i++) ports.push(cfg.port + i);
121
+ return (await Promise.all(ports.map(probe))).filter(Boolean);
122
+ }
123
+
124
+ /**
125
+ * The sidecar for this session, or null. Resolves to
126
+ * { sidecar: {port, agentId, version, pid} | null, config, all: [...] }.
127
+ */
128
+ async function findSidecar(cwd) {
129
+ const cfg = resolveConfig(cwd);
130
+ const all = await listSidecars(cfg);
131
+ const mine = all.filter(function (s) { return s.agentId === cfg.agentId; });
132
+ mine.sort(function (a, b) { return newer(a.version, b.version) ? -1 : newer(b.version, a.version) ? 1 : a.port - b.port; });
133
+ return { sidecar: mine[0] || null, config: cfg, all: all };
134
+ }
135
+
136
+ /** Optional trace for troubleshooting: AWM_HOOK_DEBUG=1 appends to ~/.claude/hooks/awm-hooks.log. */
137
+ function debugLog(hook, fields) {
138
+ if (!process.env.AWM_HOOK_DEBUG) return;
139
+ try {
140
+ const line = Object.assign({ ts: new Date().toISOString(), hook: hook }, fields);
141
+ fs.appendFileSync(path.join(os.homedir(), '.claude', 'hooks', 'awm-hooks.log'), JSON.stringify(line) + '\n');
142
+ } catch (e) { /* never matters */ }
143
+ }
144
+
145
+ module.exports = { resolveConfig, findSidecar, listSidecars, deriveAgentFromDir, debugLog, PROBE_TIMEOUT_MS };
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ // awm-prime.cjs — installed by `awm setup` (hooks v0.14.6). UserPromptSubmit: prime the turn with relevant memories, or stay silent.
3
+ // Re-running `awm setup` rewrites this file; put local changes elsewhere.
4
+ // Fails open: on any problem it prints nothing and exits 0.
5
+
6
+ // The PRIME phase of the AWM-native harness (docs/patterns/awm-native-harness.md).
7
+ // Posts the prompt to POST /hooks/prime on this session's sidecar and injects the
8
+ // returned text as additionalContext. The sidecar decides what to inject, formats
9
+ // it, abstains when confidence is low and caps the token budget — see
10
+ // src/hooks/prime.ts — so this file stays thin.
11
+ //
12
+ // Turn it off without uninstalling: create ~/.claude/hooks/awm-prime.disabled
13
+ // (an empty file). Remove the file to turn it back on. `awm doctor` reports which.
14
+ 'use strict';
15
+ const fs = require('fs');
16
+ const os = require('os');
17
+ const path = require('path');
18
+ const http = require('http');
19
+
20
+ let finder = null;
21
+ try { finder = require('./awm-find-sidecar.cjs'); } catch (e) { process.exit(0); }
22
+
23
+ // A slow AWM must never delay a prompt. Warm recall with rerank measures 1.4–1.8 s
24
+ // on a 30k-memory store; 4 s leaves margin. Connection refused returns in ~100 ms.
25
+ const TIMEOUT_MS = 4000;
26
+ const MIN_PROMPT_LEN = 15; // skip "yes" / "ok" / "go on" turns
27
+ // Set-level confidence below which the sidecar injects nothing. 0.10 rather than
28
+ // the 0.25 once documented as "balanced": measured 2026-09-11, 0.25 silenced
29
+ // specific prompts with one clear winner (top score 0.26–0.38) while passing a
30
+ // vague one at 0.92. The gate is on the shape of the score distribution, not on
31
+ // relevance, so specific questions are exactly the ones a high threshold loses.
32
+ const MIN_CONFIDENCE = 0.10;
33
+ const DISABLE_FILE = path.join(os.homedir(), '.claude', 'hooks', 'awm-prime.disabled');
34
+
35
+ let raw = '';
36
+ process.stdin.on('data', function (d) { raw += d; });
37
+ process.stdin.on('end', function () {
38
+ let payload = {};
39
+ try { payload = JSON.parse(raw || '{}'); } catch (e) { process.exit(0); }
40
+ main(payload).catch(function () { process.exit(0); });
41
+ });
42
+ process.stdin.on('error', function () { process.exit(0); });
43
+
44
+ function extractPrompt(j) {
45
+ for (const key of ['prompt', 'message', 'user_prompt', 'text', 'input']) {
46
+ if (typeof j[key] === 'string' && j[key].trim()) return j[key];
47
+ }
48
+ return null;
49
+ }
50
+
51
+ async function main(payload) {
52
+ if (fs.existsSync(DISABLE_FILE)) process.exit(0);
53
+ const prompt = extractPrompt(payload);
54
+ if (!prompt || prompt.trim().length < MIN_PROMPT_LEN) process.exit(0);
55
+
56
+ const found = await finder.findSidecar(payload.cwd);
57
+ const sc = found.sidecar;
58
+ if (!sc) {
59
+ finder.debugLog('prime', { agent: found.config.agentId, sidecar: null, live: found.all.length });
60
+ process.exit(0);
61
+ }
62
+
63
+ const body = JSON.stringify({
64
+ hook_event_name: 'UserPromptSubmit',
65
+ session_id: payload.session_id,
66
+ cwd: payload.cwd,
67
+ prompt: prompt,
68
+ minConfidence: MIN_CONFIDENCE,
69
+ });
70
+ const req = http.request({
71
+ host: '127.0.0.1', port: sc.port, path: '/hooks/prime', method: 'POST',
72
+ headers: {
73
+ 'Content-Type': 'application/json',
74
+ 'Content-Length': Buffer.byteLength(body),
75
+ 'Authorization': 'Bearer ' + found.config.secret,
76
+ },
77
+ timeout: TIMEOUT_MS,
78
+ }, function (res) {
79
+ let data = '';
80
+ res.on('data', function (c) { data += c; });
81
+ res.on('end', function () {
82
+ let out = null;
83
+ try { out = JSON.parse(data); } catch (e) { out = null; }
84
+ const inject = out && typeof out.inject === 'string' ? out.inject : '';
85
+ finder.debugLog('prime', { agent: found.config.agentId, port: sc.port, status: res.statusCode, kept: out && out.kept, total: out && out.total, reason: out && out.reason });
86
+ if (res.statusCode === 200 && inject) {
87
+ process.stdout.write(JSON.stringify({
88
+ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: inject },
89
+ }));
90
+ }
91
+ process.exit(0);
92
+ });
93
+ });
94
+ req.on('error', function () { process.exit(0); });
95
+ req.on('timeout', function () { req.destroy(); process.exit(0); });
96
+ req.end(body);
97
+ }
@@ -0,0 +1,66 @@
1
+ {
2
+ "hooks": {
3
+ "Stop": [
4
+ {
5
+ "matcher": "",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "echo \"MEMORY: (1) Did you learn anything new? Call memory_write. (2) Are you about to work on a topic you might have prior knowledge about? Call memory_recall. (3) Switching tasks? Call memory_task_begin.\"",
10
+ "timeout": 5,
11
+ "async": true
12
+ }
13
+ ]
14
+ }
15
+ ],
16
+ "PreCompact": [
17
+ {
18
+ "matcher": "",
19
+ "hooks": [
20
+ {
21
+ "type": "command",
22
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/awm-checkpoint.cjs\"",
23
+ "timeout": 10
24
+ }
25
+ ]
26
+ }
27
+ ],
28
+ "SessionEnd": [
29
+ {
30
+ "matcher": "",
31
+ "hooks": [
32
+ {
33
+ "type": "command",
34
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/awm-checkpoint.cjs\"",
35
+ "timeout": 8
36
+ }
37
+ ]
38
+ }
39
+ ],
40
+ "UserPromptSubmit": [
41
+ {
42
+ "matcher": "",
43
+ "hooks": [
44
+ {
45
+ "type": "command",
46
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/awm-prime.cjs\"",
47
+ "timeout": 6
48
+ }
49
+ ]
50
+ }
51
+ ],
52
+ "PostToolUse": [
53
+ {
54
+ "matcher": "Bash",
55
+ "hooks": [
56
+ {
57
+ "type": "command",
58
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/awm-db-mutation-reminder.cjs\"",
59
+ "timeout": 5,
60
+ "async": true
61
+ }
62
+ ]
63
+ }
64
+ ]
65
+ }
66
+ }