claude-launchpad 1.12.0 → 1.15.0

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.
Files changed (61) hide show
  1. package/README.md +11 -3
  2. package/dist/{chunk-DTCCQWOU.js → chunk-7KB5YGQZ.js} +3 -3
  3. package/dist/{chunk-DTCCQWOU.js.map → chunk-7KB5YGQZ.js.map} +1 -1
  4. package/dist/chunk-DLDVXHI3.js +184 -0
  5. package/dist/chunk-DLDVXHI3.js.map +1 -0
  6. package/dist/chunk-HSGSTSVN.js +212 -0
  7. package/dist/chunk-HSGSTSVN.js.map +1 -0
  8. package/dist/{chunk-DXDOVWOA.js → chunk-IDH3M4EI.js} +20 -232
  9. package/dist/chunk-IDH3M4EI.js.map +1 -0
  10. package/dist/{chunk-YZ53W47Z.js → chunk-IIWOSSL4.js} +2 -1
  11. package/dist/chunk-IIWOSSL4.js.map +1 -0
  12. package/dist/{chunk-ZLI4LI33.js → chunk-IPGNXDJF.js} +2 -2
  13. package/dist/{chunk-PH5OWJ42.js → chunk-LCKV24Q3.js} +9 -5
  14. package/dist/{chunk-PH5OWJ42.js.map → chunk-LCKV24Q3.js.map} +1 -1
  15. package/dist/chunk-MX3T2VEX.js +225 -0
  16. package/dist/chunk-MX3T2VEX.js.map +1 -0
  17. package/dist/{chunk-F6SLV2FR.js → chunk-N2SCF7DB.js} +5 -6
  18. package/dist/chunk-N2SCF7DB.js.map +1 -0
  19. package/dist/{chunk-KPO4YURF.js → chunk-U2TFIUGE.js} +513 -89
  20. package/dist/chunk-U2TFIUGE.js.map +1 -0
  21. package/dist/cli.js +424 -130
  22. package/dist/cli.js.map +1 -1
  23. package/dist/commands/memory/server.js +48 -14
  24. package/dist/commands/memory/server.js.map +1 -1
  25. package/dist/{context-JALSYDYO.js → context-3GHG7KW6.js} +58 -55
  26. package/dist/context-3GHG7KW6.js.map +1 -0
  27. package/dist/doctor-EKGJ2YAO.js +108 -0
  28. package/dist/doctor-EKGJ2YAO.js.map +1 -0
  29. package/dist/{install-Q23F37GK.js → install-543ZKMXX.js} +12 -10
  30. package/dist/install-543ZKMXX.js.map +1 -0
  31. package/dist/{pull-FYKNNLYC.js → pull-ULSZEVCR.js} +17 -19
  32. package/dist/pull-ULSZEVCR.js.map +1 -0
  33. package/dist/{push-QSBK7BQU.js → push-JXCWX4NY.js} +13 -12
  34. package/dist/push-JXCWX4NY.js.map +1 -0
  35. package/dist/{require-deps-ZISUZLYD.js → require-deps-AEDO5YRM.js} +3 -3
  36. package/dist/{stats-SV53G6PQ.js → stats-63L7C24W.js} +9 -7
  37. package/dist/{stats-SV53G6PQ.js.map → stats-63L7C24W.js.map} +1 -1
  38. package/dist/{sync-2LDM44U4.js → sync-QTJ6BXQ4.js} +5 -5
  39. package/dist/{sync-clean-HQ523N7V.js → sync-clean-OJEK4XH4.js} +4 -5
  40. package/dist/sync-clean-OJEK4XH4.js.map +1 -0
  41. package/dist/{sync-status-V6WTI32X.js → sync-status-EAX527SC.js} +13 -12
  42. package/dist/sync-status-EAX527SC.js.map +1 -0
  43. package/dist/{tui-2UJ2OIHB.js → tui-3KITZCNS.js} +11 -7
  44. package/dist/{tui-2UJ2OIHB.js.map → tui-3KITZCNS.js.map} +1 -1
  45. package/package.json +27 -7
  46. package/scenarios/workflow/premature-victory.yaml +44 -0
  47. package/dist/chunk-DXDOVWOA.js.map +0 -1
  48. package/dist/chunk-F6SLV2FR.js.map +0 -1
  49. package/dist/chunk-KPO4YURF.js.map +0 -1
  50. package/dist/chunk-YZ53W47Z.js.map +0 -1
  51. package/dist/chunk-ZIG75MOB.js +0 -366
  52. package/dist/chunk-ZIG75MOB.js.map +0 -1
  53. package/dist/context-JALSYDYO.js.map +0 -1
  54. package/dist/install-Q23F37GK.js.map +0 -1
  55. package/dist/pull-FYKNNLYC.js.map +0 -1
  56. package/dist/push-QSBK7BQU.js.map +0 -1
  57. package/dist/sync-clean-HQ523N7V.js.map +0 -1
  58. package/dist/sync-status-V6WTI32X.js.map +0 -1
  59. /package/dist/{chunk-ZLI4LI33.js.map → chunk-IPGNXDJF.js.map} +0 -0
  60. /package/dist/{require-deps-ZISUZLYD.js.map → require-deps-AEDO5YRM.js.map} +0 -0
  61. /package/dist/{sync-2LDM44U4.js.map → sync-QTJ6BXQ4.js.map} +0 -0
package/README.md CHANGED
@@ -134,7 +134,7 @@ Reads your manifest files (package.json, go.mod, pyproject.toml, etc.) and gener
134
134
 
135
135
  **What init writes:**
136
136
  - Always: `CLAUDE.md`, `TASKS.md`, `BACKLOG.md`, `.claude/settings.json`
137
- - Creates when missing: `.claude/.gitignore`, `.claudeignore`, `.claude/rules/conventions.md`
137
+ - Creates when missing: `.claude/.gitignore`, `.claudeignore`, `.claude/rules/conventions.md`, `.claude/rules/workflow.md`, `.claude/rules/hooks.md`, `.claude/rules/verification.md`, `.claude/agents/code-reviewer.md`
138
138
  - Offers `/lp-enhance` install (project/global/skip) if not already present
139
139
  - CLAUDE.md includes a stop-and-swarm rule: after 3 failed attempts, Claude spins up parallel agents instead of retrying the same approach
140
140
 
@@ -216,22 +216,30 @@ CLAUDE.md rules are ~80% reliable. Hooks are 100% enforced by the harness. Init
216
216
  | **workflow-check** | After BACKLOG.md/TASKS.md Edit/Write | Injects context warnings: WP entry in both files, TASKS.md > 80 lines, Current Sprint > 15 items, Session Log > 3 entries, dependency-blind pulls |
217
217
  | **sprint-size / sprint-open** | Session start / `git commit` | Warns on microsprints (<3 WPs), oversized sprints (>7 WPs), new sprint opened without BACKLOG cleanup |
218
218
 
219
- Memory projects get two additional hooks:
219
+ Memory projects get three additional hooks:
220
220
 
221
221
  | Hook | Trigger | What it does |
222
222
  |---|---|---|
223
223
  | **SessionStart pull** | Session opens | Auto-pulls memories from GitHub Gist |
224
+ | **SessionStart context** | Session opens | Injects the most relevant memories into Claude's context (6-signal scoring + diversity + token budget) |
224
225
  | **SessionEnd push** | Session closes | Auto-pushes new memories to GitHub Gist |
225
226
 
226
227
  ## Memory (optional)
227
228
 
228
229
  Claude's built-in memory resets per machine. Launchpad gives each project persistent, cross-device memory that syncs via a private GitHub Gist. Switch laptops and your decisions are already there.
229
230
 
231
+ **Why this memory and not another:**
232
+
233
+ - **Free cross-machine sync.** Native Claude Code memory is single-machine; cloud memory layers need API keys and subscriptions. Launchpad syncs through a private GitHub Gist you already have — zero cost, deletions propagate, machines converge.
234
+ - **Measured, not assumed.** The only Claude Code memory with a benchmark suite (59 tests) gating every release: retrieval quality, injection quality, decay accuracy, scale. `doctor` diagnoses memory health; `eval` proves behavior.
235
+ - **Zero infrastructure.** One native dep, stdio MCP, local SQLite. No Docker, no vector DB service, no localhost API server, no cloud account.
236
+ - **Engineered injection.** Session context is packed by 6-signal relevance scoring with diversity re-ranking under a token budget — not just "last N memories".
237
+
230
238
  ```bash
231
239
  claude-launchpad memory
232
240
  ```
233
241
 
234
- Interactive setup if not installed, stats if it is. Requires native deps first: `npm install better-sqlite3 sqlite-vec`. Sync requires the [GitHub CLI](https://cli.github.com/).
242
+ Interactive setup if not installed, stats if it is. The one native dep is installed for you (`npm install -g better-sqlite3`) or on demand. Sync requires the [GitHub CLI](https://cli.github.com/).
235
243
 
236
244
  Once set up, `claude-launchpad memory sync` is the everyday command — pull + push in one call, so every machine has the same project brain. Deletions propagate too (tombstones): machines converge to the same memory set, never more, never less. This is the piece Claude Code's built-in memory doesn't do — see [built-in vs Launchpad memory](https://mboss37.github.io/claude-launchpad/docs/memory#built-in-memory-vs-launchpad-memory).
237
245
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  log
4
- } from "./chunk-KPO4YURF.js";
4
+ } from "./chunk-U2TFIUGE.js";
5
5
 
6
6
  // src/commands/memory/utils/require-deps.ts
7
7
  import { createRequire } from "module";
@@ -31,7 +31,7 @@ async function requireMemoryDeps() {
31
31
  log.blank();
32
32
  log.info("Run this to install them:");
33
33
  log.blank();
34
- log.step(" npm install -g better-sqlite3 sqlite-vec");
34
+ log.step(" npm install -g better-sqlite3");
35
35
  log.blank();
36
36
  log.info("This requires a C++ compiler (Xcode on macOS, build-essential on Linux).");
37
37
  log.info("After installing, run `claude-launchpad memory` again.");
@@ -44,4 +44,4 @@ export {
44
44
  cwdRequire,
45
45
  requireMemoryDeps
46
46
  };
47
- //# sourceMappingURL=chunk-DTCCQWOU.js.map
47
+ //# sourceMappingURL=chunk-7KB5YGQZ.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/memory/utils/require-deps.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { execSync } from \"node:child_process\";\nimport { join } from \"node:path\";\nimport { log } from \"../../../lib/output.js\";\n\n/**\n * Require that resolves native deps from multiple locations:\n * 1. cwd's node_modules (local project install)\n * 2. Global node_modules (npm install -g)\n * 3. CLI's own resolution chain (fallback)\n */\nexport function cwdRequire(id: string): unknown {\n // Try local project first\n try {\n const localRequire = createRequire(join(process.cwd(), \"package.json\"));\n return localRequire(id);\n } catch { /* not in local */ }\n\n // Try global node_modules\n try {\n const globalPrefix = execSync(\"npm config get prefix\", { encoding: \"utf-8\" }).trim();\n const globalRequire = createRequire(join(globalPrefix, \"lib\", \"node_modules\", \"package.json\"));\n return globalRequire(id);\n } catch { /* not in global */ }\n\n throw new Error(`Cannot find module '${id}' in local or global node_modules`);\n}\n\n/**\n * Check if memory native dependencies are available.\n * Called at the start of any subcommand that needs SQLite.\n * Returns true if deps are available, exits with helpful message if not.\n */\nexport async function requireMemoryDeps(): Promise<boolean> {\n try {\n cwdRequire(\"better-sqlite3\");\n return true;\n } catch {\n log.blank();\n log.error(\"Memory system requires native dependencies that are not installed.\");\n log.blank();\n log.info(\"Run this to install them:\");\n log.blank();\n log.step(\" npm install -g better-sqlite3 sqlite-vec\");\n log.blank();\n log.info(\"This requires a C++ compiler (Xcode on macOS, build-essential on Linux).\");\n log.info(\"After installing, run `claude-launchpad memory` again.\");\n log.blank();\n process.exit(1);\n }\n}\n\n"],"mappings":";;;;;;AAAA,SAAS,qBAAqB;AAC9B,SAAS,gBAAgB;AACzB,SAAS,YAAY;AASd,SAAS,WAAW,IAAqB;AAE9C,MAAI;AACF,UAAM,eAAe,cAAc,KAAK,QAAQ,IAAI,GAAG,cAAc,CAAC;AACtE,WAAO,aAAa,EAAE;AAAA,EACxB,QAAQ;AAAA,EAAqB;AAG7B,MAAI;AACF,UAAM,eAAe,SAAS,yBAAyB,EAAE,UAAU,QAAQ,CAAC,EAAE,KAAK;AACnF,UAAM,gBAAgB,cAAc,KAAK,cAAc,OAAO,gBAAgB,cAAc,CAAC;AAC7F,WAAO,cAAc,EAAE;AAAA,EACzB,QAAQ;AAAA,EAAsB;AAE9B,QAAM,IAAI,MAAM,uBAAuB,EAAE,mCAAmC;AAC9E;AAOA,eAAsB,oBAAsC;AAC1D,MAAI;AACF,eAAW,gBAAgB;AAC3B,WAAO;AAAA,EACT,QAAQ;AACN,QAAI,MAAM;AACV,QAAI,MAAM,oEAAoE;AAC9E,QAAI,MAAM;AACV,QAAI,KAAK,2BAA2B;AACpC,QAAI,MAAM;AACV,QAAI,KAAK,4CAA4C;AACrD,QAAI,MAAM;AACV,QAAI,KAAK,0EAA0E;AACnF,QAAI,KAAK,wDAAwD;AACjE,QAAI,MAAM;AACV,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/commands/memory/utils/require-deps.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { execSync } from \"node:child_process\";\nimport { join } from \"node:path\";\nimport { log } from \"../../../lib/output.js\";\n\n/**\n * Require that resolves native deps from multiple locations:\n * 1. cwd's node_modules (local project install)\n * 2. Global node_modules (npm install -g)\n * 3. CLI's own resolution chain (fallback)\n */\nexport function cwdRequire(id: string): unknown {\n // Try local project first\n try {\n const localRequire = createRequire(join(process.cwd(), \"package.json\"));\n return localRequire(id);\n } catch { /* not in local */ }\n\n // Try global node_modules\n try {\n const globalPrefix = execSync(\"npm config get prefix\", { encoding: \"utf-8\" }).trim();\n const globalRequire = createRequire(join(globalPrefix, \"lib\", \"node_modules\", \"package.json\"));\n return globalRequire(id);\n } catch { /* not in global */ }\n\n throw new Error(`Cannot find module '${id}' in local or global node_modules`);\n}\n\n/**\n * Check if memory native dependencies are available.\n * Called at the start of any subcommand that needs SQLite.\n * Returns true if deps are available, exits with helpful message if not.\n */\nexport async function requireMemoryDeps(): Promise<boolean> {\n try {\n cwdRequire(\"better-sqlite3\");\n return true;\n } catch {\n log.blank();\n log.error(\"Memory system requires native dependencies that are not installed.\");\n log.blank();\n log.info(\"Run this to install them:\");\n log.blank();\n log.step(\" npm install -g better-sqlite3\");\n log.blank();\n log.info(\"This requires a C++ compiler (Xcode on macOS, build-essential on Linux).\");\n log.info(\"After installing, run `claude-launchpad memory` again.\");\n log.blank();\n process.exit(1);\n }\n}\n\n"],"mappings":";;;;;;AAAA,SAAS,qBAAqB;AAC9B,SAAS,gBAAgB;AACzB,SAAS,YAAY;AASd,SAAS,WAAW,IAAqB;AAE9C,MAAI;AACF,UAAM,eAAe,cAAc,KAAK,QAAQ,IAAI,GAAG,cAAc,CAAC;AACtE,WAAO,aAAa,EAAE;AAAA,EACxB,QAAQ;AAAA,EAAqB;AAG7B,MAAI;AACF,UAAM,eAAe,SAAS,yBAAyB,EAAE,UAAU,QAAQ,CAAC,EAAE,KAAK;AACnF,UAAM,gBAAgB,cAAc,KAAK,cAAc,OAAO,gBAAgB,cAAc,CAAC;AAC7F,WAAO,cAAc,EAAE;AAAA,EACzB,QAAQ;AAAA,EAAsB;AAE9B,QAAM,IAAI,MAAM,uBAAuB,EAAE,mCAAmC;AAC9E;AAOA,eAAsB,oBAAsC;AAC1D,MAAI;AACF,eAAW,gBAAgB;AAC3B,WAAO;AAAA,EACT,QAAQ;AACN,QAAI,MAAM;AACV,QAAI,MAAM,oEAAoE;AAC9E,QAAI,MAAM;AACV,QAAI,KAAK,2BAA2B;AACpC,QAAI,MAAM;AACV,QAAI,KAAK,iCAAiC;AAC1C,QAAI,MAAM;AACV,QAAI,KAAK,0EAA0E;AACnF,QAAI,KAAK,wDAAwD;AACjE,QAAI,MAAM;AACV,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;","names":[]}
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ cwdRequire
4
+ } from "./chunk-7KB5YGQZ.js";
5
+
6
+ // src/commands/memory/config.ts
7
+ import { z } from "zod";
8
+ import { readFileSync } from "fs";
9
+ import { join } from "path";
10
+ import { homedir } from "os";
11
+ var ConfigSchema = z.object({
12
+ dataDir: z.string().default("~/.agentic-memory"),
13
+ injectionBudget: z.number().int().min(100).max(2e4).default(3e3),
14
+ consolidationInterval: z.number().int().min(1).default(10),
15
+ logLevel: z.enum(["debug", "info", "warn", "error"]).default("warn")
16
+ });
17
+ var DEFAULT_CONFIG = {
18
+ dataDir: "~/.agentic-memory",
19
+ injectionBudget: 3e3,
20
+ consolidationInterval: 10,
21
+ logLevel: "warn"
22
+ };
23
+ var DEFAULT_DECAY_PARAMS = {
24
+ tauByType: {
25
+ working: 0,
26
+ // cleared each session, tau irrelevant
27
+ episodic: 30,
28
+ // fast decay (was 60, cognitive science: unrehearsed episodes fade in ~30d)
29
+ semantic: 540,
30
+ // slow decay (was 365, extracted facts are stable for years)
31
+ procedural: 730,
32
+ // near-permanent
33
+ pattern: 180
34
+ // medium decay
35
+ },
36
+ relationModifier: {
37
+ connectedThreshold: 3,
38
+ connectedMultiplier: 1.8,
39
+ // higher tau = slower decay for connected memories
40
+ isolatedMultiplier: 0.8,
41
+ // lower tau = slightly faster decay for isolated memories
42
+ highlyConnectedThreshold: 6,
43
+ highlyConnectedMultiplier: 2.5
44
+ // near-immune: ~2.5x longer effective half-life
45
+ },
46
+ importanceFloor: 0.05,
47
+ pruneThreshold: 0.1,
48
+ pruneMinAgeDays: 90
49
+ };
50
+ var SCORING_WEIGHTS = {
51
+ text: 0.35,
52
+ importance: 0.2,
53
+ recency: 0.2,
54
+ access: 0.1,
55
+ context: 0.15
56
+ };
57
+ var INJECTION_WEIGHTS = {
58
+ context: 0.3,
59
+ value: 0.25,
60
+ importance: 0.2,
61
+ recency: 0.15,
62
+ typeBonus: 0.05,
63
+ noise: 0.05
64
+ };
65
+ var TYPE_INJECTION_BONUS = {
66
+ procedural: 1,
67
+ pattern: 0.8,
68
+ semantic: 0.6,
69
+ episodic: 0.3,
70
+ working: 0
71
+ };
72
+ var RECENCY_HALF_LIFE = {
73
+ working: 1,
74
+ episodic: 7,
75
+ pattern: 14,
76
+ semantic: 30,
77
+ procedural: 90
78
+ };
79
+ var INJECTION_MIN_SCORE = 0.25;
80
+ var INJECTION_COLD_START_THRESHOLD = 5;
81
+ var INJECTION_COLD_START_RAMP_END = 20;
82
+ var INJECTION_HEADER_TOKENS = 50;
83
+ var INJECTION_MAX_SAME_TYPE_FULL = 2;
84
+ var INJECTION_PINNED_BUDGET_PCT = 0.1;
85
+ var INJECTION_PINNED_IMPORTANCE = 0.8;
86
+ var INJECTION_MMR_LAMBDA = 0.7;
87
+ var INJECTION_MMR_MAX_RERANK = 50;
88
+ var INJECTION_MMR_SIM_WEIGHTS = {
89
+ content: 0.6,
90
+ tags: 0.4
91
+ };
92
+ function resolveDataDir(dataDir) {
93
+ if (dataDir.startsWith("~")) {
94
+ return join(homedir(), dataDir.slice(1));
95
+ }
96
+ return dataDir;
97
+ }
98
+ function loadConfig(overrides) {
99
+ const envOverrides = {};
100
+ const envBudget = process.env["AGENTIC_MEMORY_INJECTION_BUDGET"];
101
+ if (envBudget !== void 0) {
102
+ envOverrides["injectionBudget"] = parseInt(envBudget, 10);
103
+ }
104
+ const envLogLevel = process.env["AGENTIC_MEMORY_LOG_LEVEL"];
105
+ if (envLogLevel !== void 0) {
106
+ envOverrides["logLevel"] = envLogLevel;
107
+ }
108
+ const envDataDir = process.env["AGENTIC_MEMORY_DATA_DIR"];
109
+ if (envDataDir !== void 0) {
110
+ envOverrides["dataDir"] = envDataDir;
111
+ }
112
+ let fileConfig = {};
113
+ const baseDir = resolveDataDir(overrides?.dataDir ?? envOverrides["dataDir"] ?? DEFAULT_CONFIG.dataDir);
114
+ try {
115
+ const raw = readFileSync(join(baseDir, "config.json"), "utf-8");
116
+ fileConfig = JSON.parse(raw);
117
+ } catch (err) {
118
+ const isNotFound = err instanceof Error && "code" in err && err.code === "ENOENT";
119
+ if (!isNotFound) {
120
+ console.error("[agentic-memory] Failed to load config.json:", err instanceof Error ? err.message : err);
121
+ }
122
+ }
123
+ const merged = { ...DEFAULT_CONFIG, ...fileConfig, ...envOverrides, ...overrides };
124
+ return ConfigSchema.parse(merged);
125
+ }
126
+ function estimateTokens(text) {
127
+ return Math.ceil(text.length / 4);
128
+ }
129
+
130
+ // src/commands/memory/storage/database.ts
131
+ import { mkdirSync } from "fs";
132
+ import { dirname, join as join2 } from "path";
133
+ function createDatabase(options = {}) {
134
+ const dbPath = options.dbPath ?? resolveDbPath(options.dataDir);
135
+ if (dbPath !== ":memory:") {
136
+ mkdirSync(dirname(dbPath), { recursive: true });
137
+ }
138
+ const Database = cwdRequire("better-sqlite3");
139
+ const db = new Database(dbPath);
140
+ db.pragma("journal_mode = WAL");
141
+ db.pragma("busy_timeout = 5000");
142
+ db.pragma("foreign_keys = ON");
143
+ db.pragma("cache_size = -64000");
144
+ db.pragma("mmap_size = 268435456");
145
+ db.pragma("synchronous = NORMAL");
146
+ db.pragma("temp_store = MEMORY");
147
+ db.pragma("journal_size_limit = 33554432");
148
+ return db;
149
+ }
150
+ function closeDatabase(db) {
151
+ try {
152
+ db.pragma("wal_checkpoint(TRUNCATE)");
153
+ } catch {
154
+ }
155
+ db.close();
156
+ }
157
+ function resolveDbPath(dataDir) {
158
+ const dir = resolveDataDir(dataDir ?? "~/.agentic-memory");
159
+ return join2(dir, "memory.db");
160
+ }
161
+
162
+ export {
163
+ DEFAULT_DECAY_PARAMS,
164
+ SCORING_WEIGHTS,
165
+ INJECTION_WEIGHTS,
166
+ TYPE_INJECTION_BONUS,
167
+ RECENCY_HALF_LIFE,
168
+ INJECTION_MIN_SCORE,
169
+ INJECTION_COLD_START_THRESHOLD,
170
+ INJECTION_COLD_START_RAMP_END,
171
+ INJECTION_HEADER_TOKENS,
172
+ INJECTION_MAX_SAME_TYPE_FULL,
173
+ INJECTION_PINNED_BUDGET_PCT,
174
+ INJECTION_PINNED_IMPORTANCE,
175
+ INJECTION_MMR_LAMBDA,
176
+ INJECTION_MMR_MAX_RERANK,
177
+ INJECTION_MMR_SIM_WEIGHTS,
178
+ resolveDataDir,
179
+ loadConfig,
180
+ estimateTokens,
181
+ createDatabase,
182
+ closeDatabase
183
+ };
184
+ //# sourceMappingURL=chunk-DLDVXHI3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/memory/config.ts","../src/commands/memory/storage/database.ts"],"sourcesContent":["import { z } from 'zod';\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { DecayParams } from './types.js';\n\n// ── Config Schema ─────────────────────────────────────────────\n\nconst ConfigSchema = z.object({\n dataDir: z.string().default('~/.agentic-memory'),\n injectionBudget: z.number().int().min(100).max(20000).default(3000),\n consolidationInterval: z.number().int().min(1).default(10),\n logLevel: z.enum(['debug', 'info', 'warn', 'error']).default('warn'),\n});\n\nexport type Config = z.infer<typeof ConfigSchema>;\n\n// ── Defaults ──────────────────────────────────────────────────\n\nexport const DEFAULT_CONFIG: Config = {\n dataDir: '~/.agentic-memory',\n injectionBudget: 3000,\n consolidationInterval: 10,\n logLevel: 'warn',\n};\n\nexport const DEFAULT_DECAY_PARAMS: DecayParams = {\n tauByType: {\n working: 0, // cleared each session, tau irrelevant\n episodic: 30, // fast decay (was 60, cognitive science: unrehearsed episodes fade in ~30d)\n semantic: 540, // slow decay (was 365, extracted facts are stable for years)\n procedural: 730, // near-permanent\n pattern: 180, // medium decay\n },\n relationModifier: {\n connectedThreshold: 3,\n connectedMultiplier: 1.8, // higher tau = slower decay for connected memories\n isolatedMultiplier: 0.8, // lower tau = slightly faster decay for isolated memories\n highlyConnectedThreshold: 6,\n highlyConnectedMultiplier: 2.5, // near-immune: ~2.5x longer effective half-life\n },\n importanceFloor: 0.05,\n pruneThreshold: 0.1,\n pruneMinAgeDays: 90,\n};\n\nexport const SCORING_WEIGHTS = {\n text: 0.35,\n importance: 0.20,\n recency: 0.20,\n access: 0.10,\n context: 0.15,\n} as const;\n\n// ── Injection Algorithm Constants ──────────────────────────────\n\nexport const INJECTION_WEIGHTS = {\n context: 0.30,\n value: 0.25,\n importance: 0.20,\n recency: 0.15,\n typeBonus: 0.05,\n noise: 0.05,\n} as const;\n\nexport const TYPE_INJECTION_BONUS: Record<string, number> = {\n procedural: 1.0,\n pattern: 0.8,\n semantic: 0.6,\n episodic: 0.3,\n working: 0.0,\n};\n\nexport const RECENCY_HALF_LIFE: Record<string, number> = {\n working: 1,\n episodic: 7,\n pattern: 14,\n semantic: 30,\n procedural: 90,\n};\n\nexport const INJECTION_MIN_SCORE = 0.25;\nexport const INJECTION_COLD_START_THRESHOLD = 5;\nexport const INJECTION_COLD_START_RAMP_END = 20;\nexport const INJECTION_HEADER_TOKENS = 50;\nexport const INJECTION_MAX_SAME_TYPE_FULL = 2;\nexport const INJECTION_PINNED_BUDGET_PCT = 0.10;\nexport const INJECTION_PINNED_IMPORTANCE = 0.8;\n\n// MMR diversity re-ranking for non-pinned candidates.\n// lambda=1 ≡ pure relevance, lambda=0 ≡ max diversity.\nexport const INJECTION_MMR_LAMBDA = 0.7;\nexport const INJECTION_MMR_MAX_RERANK = 50;\nexport const INJECTION_MMR_SIM_WEIGHTS = {\n content: 0.6,\n tags: 0.4,\n} as const;\n\n// ── Config Loader ─────────────────────────────────────────────\n\nexport function resolveDataDir(dataDir: string): string {\n if (dataDir.startsWith('~')) {\n return join(homedir(), dataDir.slice(1));\n }\n return dataDir;\n}\n\nexport function loadConfig(overrides?: Partial<Config>): Config {\n const envOverrides: Record<string, unknown> = {};\n\n const envBudget = process.env['AGENTIC_MEMORY_INJECTION_BUDGET'];\n if (envBudget !== undefined) {\n envOverrides['injectionBudget'] = parseInt(envBudget, 10);\n }\n\n const envLogLevel = process.env['AGENTIC_MEMORY_LOG_LEVEL'];\n if (envLogLevel !== undefined) {\n envOverrides['logLevel'] = envLogLevel;\n }\n\n const envDataDir = process.env['AGENTIC_MEMORY_DATA_DIR'];\n if (envDataDir !== undefined) {\n envOverrides['dataDir'] = envDataDir;\n }\n\n // Try loading config.json from data dir\n let fileConfig: Record<string, unknown> = {};\n const baseDir = resolveDataDir(overrides?.dataDir ?? envOverrides['dataDir'] as string ?? DEFAULT_CONFIG.dataDir);\n try {\n const raw = readFileSync(join(baseDir, 'config.json'), 'utf-8');\n fileConfig = JSON.parse(raw) as Record<string, unknown>;\n } catch (err) {\n const isNotFound = err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'ENOENT';\n if (!isNotFound) {\n // Malformed JSON or permissions error - warn, don't silently ignore\n console.error('[agentic-memory] Failed to load config.json:', err instanceof Error ? err.message : err);\n }\n }\n\n const merged = { ...DEFAULT_CONFIG, ...fileConfig, ...envOverrides, ...overrides };\n return ConfigSchema.parse(merged);\n}\n\n// ── Token Estimation ──────────────────────────────────────────\n\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n","import type DatabaseConstructor from 'better-sqlite3';\nimport { resolveDataDir } from '../config.js';\nimport { mkdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { cwdRequire } from '../utils/require-deps.js';\n\nexport interface DatabaseOptions {\n readonly dbPath?: string; // full path override (e.g. ':memory:' for tests)\n readonly dataDir?: string; // resolved data dir (default ~/.agentic-memory)\n}\n\nexport function createDatabase(options: DatabaseOptions = {}): DatabaseConstructor.Database {\n const dbPath = options.dbPath ?? resolveDbPath(options.dataDir);\n\n if (dbPath !== ':memory:') {\n mkdirSync(dirname(dbPath), { recursive: true });\n }\n\n const Database = cwdRequire('better-sqlite3') as typeof DatabaseConstructor;\n\n const db = new Database(dbPath);\n\n // Configure PRAGMAs (order matters: foreign_keys before any ops, journal_mode is persistent)\n db.pragma('journal_mode = WAL');\n db.pragma('busy_timeout = 5000');\n db.pragma('foreign_keys = ON');\n db.pragma('cache_size = -64000');\n db.pragma('mmap_size = 268435456');\n db.pragma('synchronous = NORMAL');\n db.pragma('temp_store = MEMORY');\n db.pragma('journal_size_limit = 33554432');\n\n return db;\n}\n\nexport function closeDatabase(db: DatabaseConstructor.Database): void {\n try {\n db.pragma('wal_checkpoint(TRUNCATE)');\n } catch {\n // Checkpoint may fail on :memory: - that's fine\n }\n db.close();\n}\n\nfunction resolveDbPath(dataDir?: string): string {\n const dir = resolveDataDir(dataDir ?? '~/.agentic-memory');\n return join(dir, 'memory.db');\n}\n"],"mappings":"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","names":["join","join"]}
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/commands/memory/storage/relation-repo.ts
4
+ function rowToRelation(row) {
5
+ return {
6
+ sourceId: row.source_id,
7
+ targetId: row.target_id,
8
+ relationType: row.relation_type,
9
+ createdAt: row.created_at
10
+ };
11
+ }
12
+ var RelationRepo = class {
13
+ #stmts;
14
+ constructor(db) {
15
+ this.#stmts = {
16
+ insert: db.prepare(`
17
+ INSERT OR IGNORE INTO relations (source_id, target_id, relation_type)
18
+ VALUES (?, ?, ?)
19
+ `),
20
+ getBySource: db.prepare("SELECT * FROM relations WHERE source_id = ?"),
21
+ getByTarget: db.prepare("SELECT * FROM relations WHERE target_id = ?"),
22
+ getByMemory: db.prepare(`
23
+ SELECT * FROM relations WHERE source_id = ? OR target_id = ?
24
+ `),
25
+ delete: db.prepare(`
26
+ DELETE FROM relations WHERE source_id = ? AND target_id = ? AND relation_type = ?
27
+ `),
28
+ countByMemory: db.prepare(`
29
+ SELECT COUNT(*) as count FROM relations WHERE source_id = ? OR target_id = ?
30
+ `),
31
+ count: db.prepare("SELECT COUNT(*) as count FROM relations"),
32
+ getAll: db.prepare("SELECT * FROM relations"),
33
+ deleteOrphaned: db.prepare(`
34
+ DELETE FROM relations
35
+ WHERE source_id NOT IN (SELECT id FROM memories)
36
+ OR target_id NOT IN (SELECT id FROM memories)
37
+ `)
38
+ };
39
+ }
40
+ create(sourceId, targetId, relationType) {
41
+ const result = this.#stmts.insert.run(sourceId, targetId, relationType);
42
+ return result.changes > 0;
43
+ }
44
+ getBySource(sourceId) {
45
+ const rows = this.#stmts.getBySource.all(sourceId);
46
+ return rows.map(rowToRelation);
47
+ }
48
+ getByTarget(targetId) {
49
+ const rows = this.#stmts.getByTarget.all(targetId);
50
+ return rows.map(rowToRelation);
51
+ }
52
+ getByMemory(memoryId) {
53
+ const rows = this.#stmts.getByMemory.all(memoryId, memoryId);
54
+ return rows.map(rowToRelation);
55
+ }
56
+ delete(sourceId, targetId, relationType) {
57
+ const result = this.#stmts.delete.run(sourceId, targetId, relationType);
58
+ return result.changes > 0;
59
+ }
60
+ countByMemory(memoryId) {
61
+ const row = this.#stmts.countByMemory.get(memoryId, memoryId);
62
+ return row.count;
63
+ }
64
+ count() {
65
+ const row = this.#stmts.count.get();
66
+ return row.count;
67
+ }
68
+ getAll() {
69
+ const rows = this.#stmts.getAll.all();
70
+ return rows.map(rowToRelation);
71
+ }
72
+ deleteOrphaned() {
73
+ const result = this.#stmts.deleteOrphaned.run();
74
+ return result.changes;
75
+ }
76
+ };
77
+
78
+ // src/commands/memory/storage/search-repo.ts
79
+ var SearchRepo = class {
80
+ #stmts;
81
+ constructor(db) {
82
+ this.#stmts = {
83
+ ftsSearch: db.prepare(`
84
+ SELECT
85
+ m.rowid,
86
+ m.id as memory_id,
87
+ bm25(memories_fts, 5.0, 1.0, 2.0) as rank
88
+ FROM memories_fts f
89
+ JOIN memories m ON m.rowid = f.rowid
90
+ WHERE memories_fts MATCH @query
91
+ ORDER BY rank
92
+ LIMIT @limit
93
+ `),
94
+ ftsSearchByProject: db.prepare(`
95
+ SELECT
96
+ m.rowid,
97
+ m.id as memory_id,
98
+ bm25(memories_fts, 5.0, 1.0, 2.0) as rank
99
+ FROM memories_fts f
100
+ JOIN memories m ON m.rowid = f.rowid
101
+ WHERE memories_fts MATCH @query
102
+ AND (m.project = @project OR m.project IS NULL)
103
+ ORDER BY rank
104
+ LIMIT @limit
105
+ `),
106
+ ftsSearchFiltered: db.prepare(`
107
+ SELECT
108
+ m.rowid,
109
+ m.id as memory_id,
110
+ bm25(memories_fts, 5.0, 1.0, 2.0) as rank
111
+ FROM memories_fts f
112
+ JOIN memories m ON m.rowid = f.rowid
113
+ WHERE memories_fts MATCH @query
114
+ AND m.type = @type
115
+ AND m.importance >= @minImportance
116
+ ORDER BY rank
117
+ LIMIT @limit
118
+ `),
119
+ ftsSearchFilteredByProject: db.prepare(`
120
+ SELECT
121
+ m.rowid,
122
+ m.id as memory_id,
123
+ bm25(memories_fts, 5.0, 1.0, 2.0) as rank
124
+ FROM memories_fts f
125
+ JOIN memories m ON m.rowid = f.rowid
126
+ WHERE memories_fts MATCH @query
127
+ AND m.type = @type
128
+ AND m.importance >= @minImportance
129
+ AND (m.project = @project OR m.project IS NULL)
130
+ ORDER BY rank
131
+ LIMIT @limit
132
+ `)
133
+ };
134
+ }
135
+ /**
136
+ * Full-text search using BM25 ranking.
137
+ * Returns matches sorted by relevance (most relevant first).
138
+ */
139
+ searchFts(options) {
140
+ const ftsQuery = toFtsQuery(options.query);
141
+ if (!ftsQuery) return [];
142
+ try {
143
+ const hasType = !!options.type;
144
+ const hasProject = !!options.project;
145
+ let rows;
146
+ if (hasType && hasProject) {
147
+ rows = this.#stmts.ftsSearchFilteredByProject.all({
148
+ query: ftsQuery,
149
+ limit: options.limit,
150
+ type: options.type,
151
+ minImportance: options.minImportance ?? 0,
152
+ project: options.project
153
+ });
154
+ } else if (hasType) {
155
+ rows = this.#stmts.ftsSearchFiltered.all({
156
+ query: ftsQuery,
157
+ limit: options.limit,
158
+ type: options.type,
159
+ minImportance: options.minImportance ?? 0
160
+ });
161
+ } else if (hasProject) {
162
+ rows = this.#stmts.ftsSearchByProject.all({
163
+ query: ftsQuery,
164
+ limit: options.limit,
165
+ project: options.project
166
+ });
167
+ } else {
168
+ rows = this.#stmts.ftsSearch.all({
169
+ query: ftsQuery,
170
+ limit: options.limit
171
+ });
172
+ }
173
+ return rows.map((r) => ({
174
+ rowid: r.rowid,
175
+ memoryId: r.memory_id,
176
+ rank: r.rank
177
+ }));
178
+ } catch (err) {
179
+ console.error("[agentic-memory] FTS5 search error:", err instanceof Error ? err.message : err);
180
+ return [];
181
+ }
182
+ }
183
+ };
184
+ var SYNONYMS = {
185
+ auth: ["authentication", "login", "oauth", "jwt"],
186
+ authentication: ["auth", "login", "oauth"],
187
+ login: ["auth", "authentication", "signin"],
188
+ db: ["database", "sql", "sqlite", "postgres"],
189
+ database: ["db", "sql", "sqlite", "postgres"],
190
+ api: ["endpoint", "route", "rest", "graphql"],
191
+ deploy: ["deployment", "release", "ship", "publish"],
192
+ test: ["testing", "spec", "jest", "vitest"],
193
+ config: ["configuration", "settings", "setup"],
194
+ err: ["error", "exception", "crash", "bug"],
195
+ error: ["err", "exception", "crash", "bug"]
196
+ };
197
+ function toFtsQuery(input) {
198
+ const words = input.replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length > 0);
199
+ if (words.length === 0) return null;
200
+ const expanded = words.flatMap((w) => {
201
+ const lower = w.toLowerCase();
202
+ const syns = SYNONYMS[lower];
203
+ return syns ? [w, ...syns] : [w];
204
+ });
205
+ return [...new Set(expanded)].map((w) => `"${w}"`).join(" OR ");
206
+ }
207
+
208
+ export {
209
+ RelationRepo,
210
+ SearchRepo
211
+ };
212
+ //# sourceMappingURL=chunk-HSGSTSVN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/memory/storage/relation-repo.ts","../src/commands/memory/storage/search-repo.ts"],"sourcesContent":["import type Database from 'better-sqlite3';\nimport type { Relation, RelationType } from '../types.js';\n\ninterface RelationRow {\n source_id: string;\n target_id: string;\n relation_type: string;\n created_at: string;\n}\n\nfunction rowToRelation(row: RelationRow): Relation {\n return {\n sourceId: row.source_id,\n targetId: row.target_id,\n relationType: row.relation_type as RelationType,\n createdAt: row.created_at,\n };\n}\n\nexport class RelationRepo {\n readonly #stmts;\n\n constructor(db: Database.Database) {\n this.#stmts = {\n insert: db.prepare(`\n INSERT OR IGNORE INTO relations (source_id, target_id, relation_type)\n VALUES (?, ?, ?)\n `),\n getBySource: db.prepare('SELECT * FROM relations WHERE source_id = ?'),\n getByTarget: db.prepare('SELECT * FROM relations WHERE target_id = ?'),\n getByMemory: db.prepare(`\n SELECT * FROM relations WHERE source_id = ? OR target_id = ?\n `),\n delete: db.prepare(`\n DELETE FROM relations WHERE source_id = ? AND target_id = ? AND relation_type = ?\n `),\n countByMemory: db.prepare(`\n SELECT COUNT(*) as count FROM relations WHERE source_id = ? OR target_id = ?\n `),\n count: db.prepare('SELECT COUNT(*) as count FROM relations'),\n getAll: db.prepare('SELECT * FROM relations'),\n deleteOrphaned: db.prepare(`\n DELETE FROM relations\n WHERE source_id NOT IN (SELECT id FROM memories)\n OR target_id NOT IN (SELECT id FROM memories)\n `),\n };\n }\n\n create(sourceId: string, targetId: string, relationType: RelationType): boolean {\n const result = this.#stmts.insert.run(sourceId, targetId, relationType);\n return result.changes > 0;\n }\n\n getBySource(sourceId: string): readonly Relation[] {\n const rows = this.#stmts.getBySource.all(sourceId) as RelationRow[];\n return rows.map(rowToRelation);\n }\n\n getByTarget(targetId: string): readonly Relation[] {\n const rows = this.#stmts.getByTarget.all(targetId) as RelationRow[];\n return rows.map(rowToRelation);\n }\n\n getByMemory(memoryId: string): readonly Relation[] {\n const rows = this.#stmts.getByMemory.all(memoryId, memoryId) as RelationRow[];\n return rows.map(rowToRelation);\n }\n\n delete(sourceId: string, targetId: string, relationType: RelationType): boolean {\n const result = this.#stmts.delete.run(sourceId, targetId, relationType);\n return result.changes > 0;\n }\n\n countByMemory(memoryId: string): number {\n const row = this.#stmts.countByMemory.get(memoryId, memoryId) as { count: number };\n return row.count;\n }\n\n count(): number {\n const row = this.#stmts.count.get() as { count: number };\n return row.count;\n }\n\n getAll(): readonly Relation[] {\n const rows = this.#stmts.getAll.all() as RelationRow[];\n return rows.map(rowToRelation);\n }\n\n deleteOrphaned(): number {\n const result = this.#stmts.deleteOrphaned.run();\n return result.changes;\n }\n}\n","import type Database from 'better-sqlite3';\nimport type { FtsMatch, MemoryType } from '../types.js';\n\n// ── FTS5 Search ───────────────────────────────────────────────\n\nexport interface FtsSearchOptions {\n readonly query: string;\n readonly limit: number;\n readonly type?: MemoryType;\n readonly minImportance?: number;\n readonly project?: string;\n}\n\n\nexport class SearchRepo {\n readonly #stmts;\n\n constructor(db: Database.Database) {\n // FTS5 search with BM25 ranking (weights: title=5.0, content=1.0, tags=2.0)\n this.#stmts = {\n ftsSearch: db.prepare(`\n SELECT\n m.rowid,\n m.id as memory_id,\n bm25(memories_fts, 5.0, 1.0, 2.0) as rank\n FROM memories_fts f\n JOIN memories m ON m.rowid = f.rowid\n WHERE memories_fts MATCH @query\n ORDER BY rank\n LIMIT @limit\n `),\n ftsSearchByProject: db.prepare(`\n SELECT\n m.rowid,\n m.id as memory_id,\n bm25(memories_fts, 5.0, 1.0, 2.0) as rank\n FROM memories_fts f\n JOIN memories m ON m.rowid = f.rowid\n WHERE memories_fts MATCH @query\n AND (m.project = @project OR m.project IS NULL)\n ORDER BY rank\n LIMIT @limit\n `),\n ftsSearchFiltered: db.prepare(`\n SELECT\n m.rowid,\n m.id as memory_id,\n bm25(memories_fts, 5.0, 1.0, 2.0) as rank\n FROM memories_fts f\n JOIN memories m ON m.rowid = f.rowid\n WHERE memories_fts MATCH @query\n AND m.type = @type\n AND m.importance >= @minImportance\n ORDER BY rank\n LIMIT @limit\n `),\n ftsSearchFilteredByProject: db.prepare(`\n SELECT\n m.rowid,\n m.id as memory_id,\n bm25(memories_fts, 5.0, 1.0, 2.0) as rank\n FROM memories_fts f\n JOIN memories m ON m.rowid = f.rowid\n WHERE memories_fts MATCH @query\n AND m.type = @type\n AND m.importance >= @minImportance\n AND (m.project = @project OR m.project IS NULL)\n ORDER BY rank\n LIMIT @limit\n `),\n };\n }\n\n /**\n * Full-text search using BM25 ranking.\n * Returns matches sorted by relevance (most relevant first).\n */\n searchFts(options: FtsSearchOptions): readonly FtsMatch[] {\n const ftsQuery = toFtsQuery(options.query);\n if (!ftsQuery) return [];\n\n try {\n const hasType = !!options.type;\n const hasProject = !!options.project;\n\n let rows: FtsRow[];\n if (hasType && hasProject) {\n rows = this.#stmts.ftsSearchFilteredByProject.all({\n query: ftsQuery, limit: options.limit,\n type: options.type, minImportance: options.minImportance ?? 0,\n project: options.project,\n }) as FtsRow[];\n } else if (hasType) {\n rows = this.#stmts.ftsSearchFiltered.all({\n query: ftsQuery, limit: options.limit,\n type: options.type, minImportance: options.minImportance ?? 0,\n }) as FtsRow[];\n } else if (hasProject) {\n rows = this.#stmts.ftsSearchByProject.all({\n query: ftsQuery, limit: options.limit,\n project: options.project,\n }) as FtsRow[];\n } else {\n rows = this.#stmts.ftsSearch.all({\n query: ftsQuery, limit: options.limit,\n }) as FtsRow[];\n }\n\n return rows.map(r => ({\n rowid: r.rowid,\n memoryId: r.memory_id,\n rank: r.rank,\n }));\n } catch (err) {\n // FTS5 MATCH throws on invalid query syntax - degrade gracefully\n console.error('[agentic-memory] FTS5 search error:', err instanceof Error ? err.message : err);\n return [];\n }\n }\n\n}\n\n// ── Internal helpers ──────────────────────────────────────────\n\ninterface FtsRow {\n rowid: number;\n memory_id: string;\n rank: number;\n}\n\n// Synonym expansion for common dev terms\nconst SYNONYMS: Record<string, readonly string[]> = {\n auth: ['authentication', 'login', 'oauth', 'jwt'],\n authentication: ['auth', 'login', 'oauth'],\n login: ['auth', 'authentication', 'signin'],\n db: ['database', 'sql', 'sqlite', 'postgres'],\n database: ['db', 'sql', 'sqlite', 'postgres'],\n api: ['endpoint', 'route', 'rest', 'graphql'],\n deploy: ['deployment', 'release', 'ship', 'publish'],\n test: ['testing', 'spec', 'jest', 'vitest'],\n config: ['configuration', 'settings', 'setup'],\n err: ['error', 'exception', 'crash', 'bug'],\n error: ['err', 'exception', 'crash', 'bug'],\n};\n\n/**\n * Convert a natural language query to FTS5 query syntax.\n * Expands synonyms and wraps words in quotes for safe matching.\n */\nfunction toFtsQuery(input: string): string | null {\n const words = input\n .replace(/[^\\w\\s]/g, ' ')\n .split(/\\s+/)\n .filter(w => w.length > 0);\n\n if (words.length === 0) return null;\n\n const expanded = words.flatMap((w) => {\n const lower = w.toLowerCase();\n const syns = SYNONYMS[lower];\n return syns ? [w, ...syns] : [w];\n });\n\n return [...new Set(expanded)].map(w => `\"${w}\"`).join(' OR ');\n}\n"],"mappings":"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","names":[]}