@stackmemoryai/stackmemory 1.10.5 → 1.14.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 (225) hide show
  1. package/LICENSE +131 -64
  2. package/README.md +107 -24
  3. package/bin/claude-sm +16 -1
  4. package/bin/claude-smd +16 -1
  5. package/bin/codex-smd +16 -1
  6. package/bin/gemini-sm +16 -1
  7. package/bin/hermes-sm +21 -0
  8. package/bin/hermes-smd +21 -0
  9. package/bin/opencode-sm +16 -1
  10. package/dist/src/cli/claude-sm.js +266 -84
  11. package/dist/src/cli/codex-sm.js +225 -33
  12. package/dist/src/cli/commands/bench.js +209 -2
  13. package/dist/src/cli/commands/brain.js +206 -0
  14. package/dist/src/cli/commands/cache.js +126 -0
  15. package/dist/src/cli/commands/company-os.js +184 -0
  16. package/dist/src/cli/commands/context.js +5 -0
  17. package/dist/src/cli/commands/daemon.js +41 -0
  18. package/dist/src/cli/commands/handoff.js +40 -9
  19. package/dist/src/cli/commands/onboard.js +70 -3
  20. package/dist/src/cli/commands/operator.js +127 -0
  21. package/dist/src/cli/commands/optimize.js +117 -0
  22. package/dist/src/cli/commands/orchestrate.js +232 -5
  23. package/dist/src/cli/commands/orchestrator.js +315 -26
  24. package/dist/src/cli/commands/pack.js +322 -0
  25. package/dist/src/cli/commands/patterns.js +254 -0
  26. package/dist/src/cli/commands/portal.js +161 -0
  27. package/dist/src/cli/commands/scaffold.js +92 -0
  28. package/dist/src/cli/commands/search.js +40 -1
  29. package/dist/src/cli/commands/setup.js +178 -11
  30. package/dist/src/cli/commands/skills.js +10 -1
  31. package/dist/src/cli/commands/state.js +265 -0
  32. package/dist/src/cli/commands/sync.js +253 -0
  33. package/dist/src/cli/commands/tasks.js +130 -1
  34. package/dist/src/cli/commands/vision.js +221 -0
  35. package/dist/src/cli/gemini-sm.js +19 -29
  36. package/dist/src/cli/hermes-sm.js +224 -0
  37. package/dist/src/cli/index.js +105 -39
  38. package/dist/src/cli/opencode-sm.js +38 -21
  39. package/dist/src/cli/utils/determinism-watcher.js +66 -0
  40. package/dist/src/cli/utils/real-cli-bin.js +116 -0
  41. package/dist/src/core/brain/brain-store.js +187 -0
  42. package/dist/src/core/brain/brain-sync.js +193 -0
  43. package/dist/src/core/brain/index.js +78 -0
  44. package/dist/src/core/brain/types.js +10 -0
  45. package/dist/src/core/cache/content-cache.js +238 -0
  46. package/dist/src/{integrations/diffmem → core/cache}/index.js +5 -5
  47. package/dist/src/core/cache/token-estimator.js +39 -0
  48. package/dist/src/core/config/feature-flags.js +2 -6
  49. package/dist/src/core/context/frame-database.js +79 -27
  50. package/dist/src/core/context/recursive-context-manager.js +1 -1
  51. package/dist/src/core/context/rehydration.js +2 -1
  52. package/dist/src/core/cross-search/cross-project-search.js +269 -0
  53. package/dist/src/core/cross-search/index.js +10 -0
  54. package/dist/src/core/database/sqlite-adapter.js +14 -84
  55. package/dist/src/core/extensions/provider-adapter.js +5 -0
  56. package/dist/src/core/models/model-router.js +54 -2
  57. package/dist/src/core/models/provider-pricing.js +58 -4
  58. package/dist/src/core/monitoring/logger.js +2 -1
  59. package/dist/src/core/optimization/trace-optimizer.js +413 -0
  60. package/dist/src/core/patterns/index.js +22 -0
  61. package/dist/src/core/patterns/pattern-applier.js +39 -0
  62. package/dist/src/core/patterns/pattern-observer.js +157 -0
  63. package/dist/src/core/patterns/pattern-store.js +259 -0
  64. package/dist/src/core/patterns/types.js +19 -0
  65. package/dist/src/core/provenance/confidence-scorer.js +128 -0
  66. package/dist/src/core/provenance/index.js +40 -0
  67. package/dist/src/core/provenance/provenance-store.js +194 -0
  68. package/dist/src/core/provenance/types.js +82 -0
  69. package/dist/src/core/retrieval/llm-context-retrieval.js +5 -4
  70. package/dist/src/core/retrieval/unified-context-assembler.js +11 -66
  71. package/dist/src/core/session/project-handoff.js +64 -0
  72. package/dist/src/core/session/session-manager.js +28 -0
  73. package/dist/src/core/shared-state/canonical-store.js +564 -0
  74. package/dist/src/core/skill-packs/index.js +18 -0
  75. package/dist/src/core/skill-packs/parser.js +42 -0
  76. package/dist/src/core/skill-packs/registry.js +224 -0
  77. package/dist/src/core/skill-packs/types.js +79 -0
  78. package/dist/src/core/storage/cloud-sync-manager.js +116 -0
  79. package/dist/src/core/storage/cloud-sync.js +574 -0
  80. package/dist/src/core/storage/two-tier-storage.js +5 -1
  81. package/dist/src/core/tasks/master-tasks-template.js +43 -0
  82. package/dist/src/core/tasks/md-task-parser.js +138 -0
  83. package/dist/src/core/trace/trace-event-store.js +282 -0
  84. package/dist/src/core/vision/index.js +27 -0
  85. package/dist/src/core/vision/signals.js +79 -0
  86. package/dist/src/core/vision/types.js +22 -0
  87. package/dist/src/core/vision/vision-file.js +146 -0
  88. package/dist/src/core/vision/vision-loop.js +220 -0
  89. package/dist/src/core/wiki/wiki-compiler.js +103 -1
  90. package/dist/src/daemon/daemon-config.js +52 -0
  91. package/dist/src/daemon/services/desire-path-service.js +566 -0
  92. package/dist/src/daemon/services/github-service.js +126 -0
  93. package/dist/src/daemon/services/research-stream-service.js +320 -0
  94. package/dist/src/daemon/services/telemetry-service.js +192 -0
  95. package/dist/src/daemon/unified-daemon.js +58 -1
  96. package/dist/src/features/browser/cli-browser-agent.js +417 -0
  97. package/dist/src/features/browser/stagehand-workflows.js +578 -0
  98. package/dist/src/features/operator/adapter-factory.js +62 -0
  99. package/dist/src/features/operator/browser-adapter.js +109 -0
  100. package/dist/src/features/operator/desktop-adapter.js +125 -0
  101. package/dist/src/features/operator/index.js +39 -0
  102. package/dist/src/features/operator/llm-decision.js +137 -0
  103. package/dist/src/features/operator/operator-logger.js +92 -0
  104. package/dist/src/features/operator/overnight-runner.js +327 -0
  105. package/dist/src/features/operator/screen-adapter.js +91 -0
  106. package/dist/src/features/operator/session-manager.js +127 -0
  107. package/dist/src/features/operator/state-machine.js +227 -0
  108. package/dist/src/features/operator/task-queue.js +81 -0
  109. package/dist/src/features/portal/index.js +26 -0
  110. package/dist/src/features/portal/server.js +240 -0
  111. package/dist/src/features/portal/types.js +14 -0
  112. package/dist/src/features/portal/ui.js +195 -0
  113. package/dist/src/features/sweep/pty-wrapper.js +13 -5
  114. package/dist/src/features/tasks/task-aware-context.js +2 -1
  115. package/dist/src/features/tui/simple-monitor.js +0 -23
  116. package/dist/src/features/tui/swarm-monitor.js +8 -66
  117. package/dist/src/features/web/client/hooks/use-socket.js +12 -0
  118. package/dist/src/{core/merge/index.js → features/web/client/lib/utils.js} +8 -4
  119. package/dist/src/features/web/client/next-env.d.js +4 -0
  120. package/dist/src/features/web/client/stores/session-store.js +12 -0
  121. package/dist/src/features/web/server/gcp-billing.js +76 -0
  122. package/dist/src/features/web/server/index.js +10 -0
  123. package/dist/src/features/web/server/spend-calculator.js +228 -0
  124. package/dist/src/hooks/schemas.js +6 -1
  125. package/dist/src/integrations/anthropic/client.js +3 -2
  126. package/dist/src/integrations/claude-code/agent-bridge.js +0 -3
  127. package/dist/src/integrations/claude-code/subagent-client.js +307 -11
  128. package/dist/src/integrations/claude-code/task-coordinator.js +2 -1
  129. package/dist/src/integrations/github/pr-state.js +158 -0
  130. package/dist/src/integrations/linear/client.js +4 -1
  131. package/dist/src/integrations/linear/webhook-retry.js +196 -0
  132. package/dist/src/integrations/linear/webhook-server.js +18 -22
  133. package/dist/src/integrations/mcp/handlers/cloud-sync-handlers.js +101 -0
  134. package/dist/src/integrations/mcp/handlers/index.js +40 -84
  135. package/dist/src/integrations/mcp/server.js +542 -641
  136. package/dist/src/integrations/mcp/tool-alias-registry.js +297 -0
  137. package/dist/src/integrations/mcp/tool-definitions.js +152 -682
  138. package/dist/src/mcp/stackmemory-mcp-server.js +571 -231
  139. package/dist/src/orchestrators/multimodal/determinism.js +244 -0
  140. package/dist/src/orchestrators/multimodal/harness.js +149 -78
  141. package/dist/src/orchestrators/multimodal/providers.js +44 -3
  142. package/dist/src/skills/recursive-agent-orchestrator.js +2 -4
  143. package/dist/src/utils/hook-installer.js +0 -8
  144. package/dist/src/utils/process-cleanup.js +1 -7
  145. package/docs/README.md +42 -0
  146. package/docs/guides/README_INSTALL.md +208 -0
  147. package/package.json +27 -9
  148. package/packs/coding/python-fastapi/instructions.md +60 -0
  149. package/packs/coding/python-fastapi/pack.yaml +28 -0
  150. package/packs/coding/typescript-react/instructions.md +47 -0
  151. package/packs/coding/typescript-react/pack.yaml +28 -0
  152. package/packs/core/commands/capture.md +32 -0
  153. package/packs/core/commands/learn.md +73 -0
  154. package/packs/core/commands/next.md +36 -0
  155. package/packs/core/commands/restart.md +58 -0
  156. package/packs/core/commands/restore.md +29 -0
  157. package/packs/core/commands/start.md +57 -0
  158. package/packs/core/commands/stop.md +65 -0
  159. package/packs/core/commands/summary.md +40 -0
  160. package/packs/core/manifest.json +24 -0
  161. package/packs/ops/decision-recovery/instructions.md +65 -0
  162. package/packs/ops/decision-recovery/pack.yaml +89 -0
  163. package/scripts/claude-code-wrapper.sh +11 -0
  164. package/scripts/claude-sm-setup.sh +12 -1
  165. package/scripts/codex-wrapper.sh +11 -0
  166. package/scripts/git-hooks/branch-context-manager.sh +11 -0
  167. package/scripts/git-hooks/post-checkout-stackmemory.sh +11 -0
  168. package/scripts/git-hooks/post-commit-stackmemory.sh +11 -0
  169. package/scripts/git-hooks/pre-commit-stackmemory.sh +11 -0
  170. package/scripts/hooks/cleanup-shell.sh +12 -1
  171. package/scripts/hooks/task-complete.sh +12 -1
  172. package/scripts/install-code-execution-hooks.sh +12 -1
  173. package/scripts/install-sweep-hook.sh +12 -0
  174. package/scripts/install.sh +11 -0
  175. package/scripts/opencode-wrapper.sh +11 -0
  176. package/scripts/portal/cloud-init.yaml +69 -0
  177. package/scripts/portal/setup.sh +69 -0
  178. package/scripts/portal/stackmemory-portal.service +34 -0
  179. package/scripts/setup-claude-integration.sh +12 -1
  180. package/scripts/smoke-init-db.sh +23 -0
  181. package/scripts/stackmemory-daemon.sh +11 -0
  182. package/scripts/verify-dist.cjs +11 -4
  183. package/dist/src/cli/commands/ralph.js +0 -1053
  184. package/dist/src/cli/commands/team.js +0 -168
  185. package/dist/src/core/context/shared-context-layer.js +0 -620
  186. package/dist/src/core/context/stack-merge-resolver.js +0 -748
  187. package/dist/src/core/merge/conflict-detector.js +0 -430
  188. package/dist/src/core/merge/resolution-engine.js +0 -557
  189. package/dist/src/core/merge/stack-diff.js +0 -531
  190. package/dist/src/core/merge/unified-merge-resolver.js +0 -302
  191. package/dist/src/hooks/diffmem-hooks.js +0 -376
  192. package/dist/src/integrations/diffmem/client.js +0 -208
  193. package/dist/src/integrations/diffmem/config.js +0 -14
  194. package/dist/src/integrations/greptile/client.js +0 -101
  195. package/dist/src/integrations/greptile/config.js +0 -14
  196. package/dist/src/integrations/greptile/index.js +0 -11
  197. package/dist/src/integrations/mcp/handlers/cord-handlers.js +0 -397
  198. package/dist/src/integrations/mcp/handlers/diffmem-handlers.js +0 -455
  199. package/dist/src/integrations/mcp/handlers/greptile-handlers.js +0 -456
  200. package/dist/src/integrations/mcp/handlers/provider-handlers.js +0 -227
  201. package/dist/src/integrations/mcp/handlers/team-handlers.js +0 -211
  202. package/dist/src/integrations/ralph/bridge/ralph-stackmemory-bridge.js +0 -863
  203. package/dist/src/integrations/ralph/context/context-budget-manager.js +0 -308
  204. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +0 -391
  205. package/dist/src/integrations/ralph/index.js +0 -17
  206. package/dist/src/integrations/ralph/learning/pattern-learner.js +0 -435
  207. package/dist/src/integrations/ralph/lifecycle/iteration-lifecycle.js +0 -448
  208. package/dist/src/integrations/ralph/loopmax.js +0 -488
  209. package/dist/src/integrations/ralph/monitoring/swarm-dashboard.js +0 -293
  210. package/dist/src/integrations/ralph/monitoring/swarm-registry.js +0 -107
  211. package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +0 -508
  212. package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +0 -407
  213. package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +0 -495
  214. package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +0 -387
  215. package/dist/src/integrations/ralph/performance/performance-optimizer.js +0 -357
  216. package/dist/src/integrations/ralph/recovery/crash-recovery.js +0 -461
  217. package/dist/src/integrations/ralph/state/state-reconciler.js +0 -420
  218. package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +0 -444
  219. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -1007
  220. package/dist/src/integrations/ralph/visualization/ralph-debugger.js +0 -635
  221. package/scripts/ralph-loop-implementation.js +0 -404
  222. /package/dist/src/core/{merge → cache}/types.js +0 -0
  223. /package/dist/src/{integrations/diffmem/types.js → core/storage/cloud-sync-types.js} +0 -0
  224. /package/dist/src/{integrations/greptile/types.js → core/trace/trace-event.js} +0 -0
  225. /package/dist/src/{integrations/ralph → features/operator}/types.js +0 -0
@@ -2,10 +2,10 @@ import { fileURLToPath as __fileURLToPath } from 'url';
2
2
  import { dirname as __pathDirname } from 'path';
3
3
  const __filename = __fileURLToPath(import.meta.url);
4
4
  const __dirname = __pathDirname(__filename);
5
- import { DEFAULT_DIFFMEM_CONFIG } from "./config.js";
6
- import { DiffMemClient, DiffMemClientError } from "./client.js";
5
+ import { ContentCache } from "./content-cache.js";
6
+ import { estimateTokens, hashContent } from "./token-estimator.js";
7
7
  export {
8
- DEFAULT_DIFFMEM_CONFIG,
9
- DiffMemClient,
10
- DiffMemClientError
8
+ ContentCache,
9
+ estimateTokens,
10
+ hashContent
11
11
  };
@@ -0,0 +1,39 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ import { createHash } from "crypto";
6
+ import { createRequire } from "module";
7
+ let encoder = null;
8
+ let initAttempted = false;
9
+ function getEncoder() {
10
+ if (initAttempted) return encoder;
11
+ initAttempted = true;
12
+ try {
13
+ const require2 = createRequire(import.meta.url);
14
+ const tiktoken = require2("js-tiktoken");
15
+ encoder = tiktoken.getEncoding("cl100k_base");
16
+ } catch {
17
+ encoder = null;
18
+ }
19
+ return encoder;
20
+ }
21
+ function estimateTokens(content) {
22
+ if (!content) return 0;
23
+ const enc = getEncoder();
24
+ if (enc) {
25
+ return enc.encode(content).length;
26
+ }
27
+ return Math.ceil(content.length / 4);
28
+ }
29
+ function isTiktokenActive() {
30
+ return getEncoder() !== null;
31
+ }
32
+ function hashContent(content) {
33
+ return createHash("sha256").update(content).digest("hex");
34
+ }
35
+ export {
36
+ estimateTokens,
37
+ hashContent,
38
+ isTiktokenActive
39
+ };
@@ -15,8 +15,7 @@ function isFeatureEnabled(feature) {
15
15
  return process.env["STACKMEMORY_AI"] !== "false" && (!!process.env["ANTHROPIC_API_KEY"] || !!process.env["OPENAI_API_KEY"]);
16
16
  case "skills":
17
17
  return process.env["STACKMEMORY_SKILLS"] === "true" || process.env["STACKMEMORY_SKILLS"] === "1";
18
- case "ralph":
19
- return process.env["STACKMEMORY_RALPH"] !== "false";
18
+ // For npm package users, must be explicitly enabled
20
19
  case "multiProvider":
21
20
  return process.env["STACKMEMORY_MULTI_PROVIDER"] === "true" || process.env["STACKMEMORY_MULTI_PROVIDER"] === "1";
22
21
  default:
@@ -29,7 +28,6 @@ function getFeatureFlags() {
29
28
  linear: isFeatureEnabled("linear"),
30
29
  aiSummaries: isFeatureEnabled("aiSummaries"),
31
30
  skills: isFeatureEnabled("skills"),
32
- ralph: isFeatureEnabled("ralph"),
33
31
  multiProvider: isFeatureEnabled("multiProvider")
34
32
  };
35
33
  }
@@ -49,9 +47,7 @@ function logFeatureStatus() {
49
47
  console.log(
50
48
  ` Skills: ${flags.skills ? "enabled" : "disabled (set STACKMEMORY_SKILLS=true)"}`
51
49
  );
52
- console.log(
53
- ` Ralph: ${flags.ralph ? "enabled" : "disabled (set STACKMEMORY_RALPH=true)"}`
54
- );
50
+ console.log();
55
51
  console.log(
56
52
  ` MultiProvider: ${flags.multiProvider ? "enabled" : "disabled (set STACKMEMORY_MULTI_PROVIDER=true)"}`
57
53
  );
@@ -97,6 +97,26 @@ class FrameDatabase {
97
97
  );
98
98
  } catch {
99
99
  }
100
+ const provenanceCols = [
101
+ { table: "frames", col: "prov_source", def: "TEXT DEFAULT ''" },
102
+ { table: "frames", col: "prov_derivation", def: "TEXT DEFAULT '[]'" },
103
+ { table: "frames", col: "prov_confidence", def: "REAL DEFAULT 1.0" },
104
+ { table: "frames", col: "prov_superseded_by", def: "TEXT" },
105
+ {
106
+ table: "frames",
107
+ col: "prov_program_version",
108
+ def: "TEXT DEFAULT ''"
109
+ },
110
+ { table: "anchors", col: "prov_source", def: "TEXT DEFAULT ''" },
111
+ { table: "anchors", col: "prov_confidence", def: "REAL DEFAULT 1.0" },
112
+ { table: "anchors", col: "prov_superseded_by", def: "TEXT" }
113
+ ];
114
+ for (const { table, col, def } of provenanceCols) {
115
+ try {
116
+ this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${col} ${def}`);
117
+ } catch {
118
+ }
119
+ }
100
120
  this.db.exec(`
101
121
  CREATE INDEX IF NOT EXISTS idx_frames_run ON frames(run_id);
102
122
  CREATE INDEX IF NOT EXISTS idx_frames_project ON frames(project_id);
@@ -189,28 +209,48 @@ class FrameDatabase {
189
209
  ON entity_states(entity_name, valid_from DESC);
190
210
  `);
191
211
  this.db.exec(`
192
- CREATE TABLE IF NOT EXISTS cord_tasks (
193
- task_id TEXT PRIMARY KEY,
194
- parent_id TEXT,
195
- project_id TEXT NOT NULL,
196
- run_id TEXT NOT NULL,
197
- goal TEXT NOT NULL,
198
- prompt TEXT NOT NULL DEFAULT '',
199
- result TEXT,
200
- status TEXT NOT NULL DEFAULT 'pending'
201
- CHECK (status IN ('pending','active','completed','blocked','asked')),
202
- context_mode TEXT NOT NULL DEFAULT 'spawn'
203
- CHECK (context_mode IN ('spawn','fork','ask')),
204
- blocked_by TEXT NOT NULL DEFAULT '[]',
205
- depth INTEGER NOT NULL DEFAULT 0,
206
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
207
- completed_at INTEGER,
208
- FOREIGN KEY (parent_id) REFERENCES cord_tasks(task_id)
212
+ CREATE TABLE IF NOT EXISTS cloud_sync_state (
213
+ table_name TEXT NOT NULL,
214
+ row_id TEXT NOT NULL,
215
+ last_pushed_at INTEGER,
216
+ last_pushed_version INTEGER,
217
+ last_pulled_at INTEGER,
218
+ last_pulled_version INTEGER,
219
+ sync_status TEXT NOT NULL DEFAULT 'pending',
220
+ push_error TEXT,
221
+ push_attempts INTEGER DEFAULT 0,
222
+ PRIMARY KEY (table_name, row_id)
209
223
  );
210
- CREATE INDEX IF NOT EXISTS idx_cord_tasks_project ON cord_tasks(project_id);
211
- CREATE INDEX IF NOT EXISTS idx_cord_tasks_parent ON cord_tasks(parent_id);
212
- CREATE INDEX IF NOT EXISTS idx_cord_tasks_status ON cord_tasks(status);
213
- CREATE INDEX IF NOT EXISTS idx_cord_tasks_project_status ON cord_tasks(project_id, status);
224
+ CREATE INDEX IF NOT EXISTS idx_cloud_sync_status ON cloud_sync_state(sync_status);
225
+ CREATE INDEX IF NOT EXISTS idx_cloud_sync_pushed ON cloud_sync_state(last_pushed_at);
226
+
227
+ CREATE TABLE IF NOT EXISTS cloud_sync_cursors (
228
+ direction TEXT PRIMARY KEY,
229
+ cursor_value TEXT NOT NULL,
230
+ updated_at INTEGER NOT NULL
231
+ );
232
+
233
+ -- Learned behavioral patterns (observe \u2192 learn \u2192 apply)
234
+ CREATE TABLE IF NOT EXISTS patterns (
235
+ id TEXT PRIMARY KEY,
236
+ domain TEXT NOT NULL,
237
+ trigger TEXT NOT NULL,
238
+ action TEXT NOT NULL,
239
+ evidence TEXT DEFAULT '[]',
240
+ confidence REAL DEFAULT 0.3,
241
+ observation_count INTEGER DEFAULT 0,
242
+ scope TEXT DEFAULT 'project',
243
+ project_id TEXT,
244
+ status TEXT DEFAULT 'pending',
245
+ source TEXT DEFAULT 'observed',
246
+ created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
247
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
248
+ last_matched_at INTEGER,
249
+ superseded_by TEXT
250
+ );
251
+ CREATE INDEX IF NOT EXISTS idx_patterns_project ON patterns(project_id, status);
252
+ CREATE INDEX IF NOT EXISTS idx_patterns_domain ON patterns(domain, confidence DESC);
253
+ CREATE INDEX IF NOT EXISTS idx_patterns_status ON patterns(status, confidence DESC);
214
254
  `);
215
255
  logger.info("Frame database schema initialized");
216
256
  } catch (error) {
@@ -228,8 +268,8 @@ class FrameDatabase {
228
268
  insertFrame(frame) {
229
269
  try {
230
270
  const stmt = this.db.prepare(`
231
- INSERT INTO frames (frame_id, run_id, project_id, parent_frame_id, depth, type, name, state, inputs, outputs, digest_json)
232
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
271
+ INSERT INTO frames (frame_id, run_id, project_id, parent_frame_id, depth, type, name, state, inputs, outputs, digest_json, prov_source, prov_derivation, prov_confidence, prov_program_version)
272
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
233
273
  `);
234
274
  const result = stmt.run(
235
275
  frame.frame_id,
@@ -242,7 +282,15 @@ class FrameDatabase {
242
282
  frame.state,
243
283
  JSON.stringify(frame.inputs),
244
284
  JSON.stringify(frame.outputs),
245
- JSON.stringify(frame.digest_json)
285
+ JSON.stringify(frame.digest_json),
286
+ frame.type,
287
+ // prov_source: frame type as source
288
+ JSON.stringify([`frame:${frame.type}`]),
289
+ // prov_derivation
290
+ 1,
291
+ // prov_confidence: default full confidence
292
+ process.env["npm_package_version"] || ""
293
+ // prov_program_version
246
294
  );
247
295
  if (result.changes === 0) {
248
296
  throw new DatabaseError(
@@ -473,8 +521,8 @@ class FrameDatabase {
473
521
  insertAnchor(anchor) {
474
522
  try {
475
523
  const stmt = this.db.prepare(`
476
- INSERT INTO anchors (anchor_id, frame_id, project_id, type, text, priority, metadata)
477
- VALUES (?, ?, ?, ?, ?, ?, ?)
524
+ INSERT INTO anchors (anchor_id, frame_id, project_id, type, text, priority, metadata, prov_source, prov_confidence)
525
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
478
526
  `);
479
527
  const result = stmt.run(
480
528
  anchor.anchor_id,
@@ -483,7 +531,11 @@ class FrameDatabase {
483
531
  anchor.type,
484
532
  anchor.text,
485
533
  anchor.priority,
486
- JSON.stringify(anchor.metadata)
534
+ JSON.stringify(anchor.metadata),
535
+ anchor.type,
536
+ // prov_source: anchor type (DECISION, EVENT, etc.)
537
+ 1
538
+ // prov_confidence: default full confidence
487
539
  );
488
540
  if (result.changes === 0) {
489
541
  throw new DatabaseError(
@@ -3,6 +3,7 @@ import { dirname as __pathDirname } from 'path';
3
3
  const __filename = __fileURLToPath(import.meta.url);
4
4
  const __dirname = __pathDirname(__filename);
5
5
  import { logger } from "../monitoring/logger.js";
6
+ import { estimateTokens } from "../cache/token-estimator.js";
6
7
  import { ValidationError, ErrorCode } from "../errors/index.js";
7
8
  import * as fs from "fs";
8
9
  import * as path from "path";
@@ -381,7 +382,6 @@ class RecursiveContextManager {
381
382
  fitChunksToTokenBudget(chunks, _maxTokens) {
382
383
  const selected = [];
383
384
  let totalTokens = 0;
384
- const estimateTokens = (text) => Math.ceil(text.length / 4);
385
385
  for (const chunk of chunks) {
386
386
  const chunkTokens = estimateTokens(chunk.content);
387
387
  if (totalTokens + chunkTokens <= maxTokens) {
@@ -5,6 +5,7 @@ const __dirname = __pathDirname(__filename);
5
5
  import * as fs from "fs/promises";
6
6
  import * as path from "path";
7
7
  import { logger } from "../monitoring/logger.js";
8
+ import { estimateTokens } from "../cache/token-estimator.js";
8
9
  import {
9
10
  getModelTokenLimit
10
11
  } from "../models/model-router.js";
@@ -44,7 +45,7 @@ class CompactionHandler {
44
45
  * Track token usage from a message
45
46
  */
46
47
  trackTokens(content) {
47
- const estimatedTokens = Math.ceil(content.length / 4);
48
+ const estimatedTokens = estimateTokens(content);
48
49
  this.tokenAccumulator += estimatedTokens;
49
50
  this.metrics.estimatedTokens += estimatedTokens;
50
51
  if (this.isApproachingCompaction()) {
@@ -0,0 +1,269 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ import Database from "better-sqlite3";
6
+ import {
7
+ existsSync,
8
+ readFileSync,
9
+ writeFileSync,
10
+ mkdirSync,
11
+ readdirSync
12
+ } from "fs";
13
+ import { join } from "path";
14
+ import { homedir } from "os";
15
+ import { logger } from "../monitoring/logger.js";
16
+ function sanitizeFtsQuery(query) {
17
+ const wantsPrefix = query.trimEnd().endsWith("*");
18
+ const cleaned = query.replace(/['"(){}[\]^~*\\,]/g, " ").replace(/\b(AND|OR|NOT|NEAR)\b/gi, "").trim();
19
+ const terms = cleaned.split(/\s+/).filter((t) => t.length > 0);
20
+ if (terms.length === 0) return '""';
21
+ const quoted = terms.map((t) => `"${t}"`);
22
+ if (wantsPrefix) {
23
+ quoted[quoted.length - 1] = quoted[quoted.length - 1] + "*";
24
+ }
25
+ return quoted.join(" ");
26
+ }
27
+ class CrossProjectSearch {
28
+ registryPath;
29
+ constructor(registryDir) {
30
+ const dir = registryDir || join(homedir(), ".stackmemory");
31
+ if (!existsSync(dir)) {
32
+ mkdirSync(dir, { recursive: true });
33
+ }
34
+ this.registryPath = join(dir, "projects.json");
35
+ }
36
+ // --- Project Registry CRUD ---
37
+ loadRegistry() {
38
+ if (!existsSync(this.registryPath)) {
39
+ return { projects: [] };
40
+ }
41
+ try {
42
+ const raw = readFileSync(this.registryPath, "utf-8");
43
+ return JSON.parse(raw);
44
+ } catch {
45
+ logger.warn("Failed to parse projects.json, returning empty registry");
46
+ return { projects: [] };
47
+ }
48
+ }
49
+ saveRegistry(registry) {
50
+ writeFileSync(this.registryPath, JSON.stringify(registry, null, 2));
51
+ }
52
+ registerProject(entry) {
53
+ const registry = this.loadRegistry();
54
+ const idx = registry.projects.findIndex(
55
+ (p) => p.path === entry.path || p.dbPath === entry.dbPath
56
+ );
57
+ if (idx >= 0) {
58
+ registry.projects[idx] = entry;
59
+ } else {
60
+ registry.projects.push(entry);
61
+ }
62
+ this.saveRegistry(registry);
63
+ }
64
+ unregisterProject(pathOrName) {
65
+ const registry = this.loadRegistry();
66
+ const before = registry.projects.length;
67
+ registry.projects = registry.projects.filter(
68
+ (p) => p.path !== pathOrName && p.name !== pathOrName
69
+ );
70
+ if (registry.projects.length < before) {
71
+ this.saveRegistry(registry);
72
+ return true;
73
+ }
74
+ return false;
75
+ }
76
+ listProjects() {
77
+ return this.loadRegistry().projects;
78
+ }
79
+ /**
80
+ * Auto-discover projects by scanning common directories for .stackmemory/context.db
81
+ */
82
+ discoverProjects(basePaths) {
83
+ const paths = basePaths || [
84
+ join(homedir(), "Dev"),
85
+ join(homedir(), "dev"),
86
+ join(homedir(), "Projects"),
87
+ join(homedir(), "projects"),
88
+ join(homedir(), "Work"),
89
+ join(homedir(), "work"),
90
+ join(homedir(), "code"),
91
+ join(homedir(), "Code")
92
+ ];
93
+ const homeDb = join(homedir(), ".stackmemory", "context.db");
94
+ const discovered = [];
95
+ if (existsSync(homeDb)) {
96
+ discovered.push({
97
+ name: "global",
98
+ path: homedir(),
99
+ dbPath: homeDb,
100
+ lastAccessed: Date.now()
101
+ });
102
+ }
103
+ for (const basePath of paths) {
104
+ if (!existsSync(basePath)) continue;
105
+ try {
106
+ this.scanForDatabases(basePath, 0, 3, discovered);
107
+ } catch {
108
+ }
109
+ }
110
+ const registry = this.loadRegistry();
111
+ for (const entry of discovered) {
112
+ const existing = registry.projects.find((p) => p.dbPath === entry.dbPath);
113
+ if (!existing) {
114
+ registry.projects.push(entry);
115
+ }
116
+ }
117
+ this.saveRegistry(registry);
118
+ return discovered;
119
+ }
120
+ scanForDatabases(dir, depth, maxDepth, results) {
121
+ if (depth > maxDepth) return;
122
+ const dbPath = join(dir, ".stackmemory", "context.db");
123
+ if (existsSync(dbPath)) {
124
+ const name = dir.split("/").pop() || dir;
125
+ results.push({
126
+ name,
127
+ path: dir,
128
+ dbPath,
129
+ lastAccessed: Date.now()
130
+ });
131
+ return;
132
+ }
133
+ try {
134
+ const entries = readdirSync(dir, { withFileTypes: true });
135
+ for (const entry of entries) {
136
+ if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules" && entry.name !== "dist" && entry.name !== "build") {
137
+ this.scanForDatabases(
138
+ join(dir, entry.name),
139
+ depth + 1,
140
+ maxDepth,
141
+ results
142
+ );
143
+ }
144
+ }
145
+ } catch {
146
+ }
147
+ }
148
+ // --- Cross-Project Search ---
149
+ /**
150
+ * Search across all registered project databases using FTS5/BM25.
151
+ * Opens read-only connections. Skips missing/locked databases gracefully.
152
+ */
153
+ async search(options) {
154
+ const { query, limit = 20, excludeProject } = options;
155
+ const registry = this.loadRegistry();
156
+ if (registry.projects.length === 0) {
157
+ return [];
158
+ }
159
+ const allResults = [];
160
+ const perDbLimit = Math.max(limit, 10);
161
+ for (const project of registry.projects) {
162
+ if (excludeProject && project.name === excludeProject) continue;
163
+ if (!existsSync(project.dbPath)) {
164
+ logger.debug(`Skipping missing database: ${project.dbPath}`);
165
+ continue;
166
+ }
167
+ try {
168
+ const results = this.searchSingleDb(project, query, perDbLimit);
169
+ allResults.push(...results);
170
+ } catch (error) {
171
+ logger.debug(
172
+ `Skipping database ${project.dbPath}: ${error instanceof Error ? error.message : String(error)}`
173
+ );
174
+ }
175
+ }
176
+ allResults.sort((a, b) => b.score - a.score);
177
+ return allResults.slice(0, limit);
178
+ }
179
+ /**
180
+ * Search a single project database (read-only connection).
181
+ */
182
+ searchSingleDb(project, query, limit) {
183
+ let db = null;
184
+ try {
185
+ db = new Database(project.dbPath, {
186
+ readonly: true,
187
+ fileMustExist: true
188
+ });
189
+ const hasFts = db.prepare(
190
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='frames_fts'"
191
+ ).get();
192
+ if (hasFts) {
193
+ return this.searchFts(db, project, query, limit);
194
+ } else {
195
+ return this.searchLike(db, project, query, limit);
196
+ }
197
+ } finally {
198
+ if (db) {
199
+ try {
200
+ db.close();
201
+ } catch {
202
+ }
203
+ }
204
+ }
205
+ }
206
+ searchFts(db, project, query, limit) {
207
+ const sanitized = sanitizeFtsQuery(query);
208
+ const sql = `
209
+ SELECT f.frame_id, f.name, f.type, f.state, f.digest_text, f.created_at,
210
+ -bm25(frames_fts, 10.0, 5.0, 2.0, 1.0) as score
211
+ FROM frames_fts fts
212
+ JOIN frames f ON f.rowid = fts.rowid
213
+ WHERE frames_fts MATCH ?
214
+ ORDER BY score DESC
215
+ LIMIT ?
216
+ `;
217
+ const rows = db.prepare(sql).all(sanitized, limit);
218
+ return rows.map((row) => ({
219
+ projectName: project.name,
220
+ projectPath: project.path,
221
+ frameId: row.frame_id,
222
+ name: row.name,
223
+ type: row.type,
224
+ state: row.state,
225
+ digestText: row.digest_text,
226
+ score: row.score,
227
+ createdAt: row.created_at
228
+ }));
229
+ }
230
+ searchLike(db, project, query, limit) {
231
+ const likeParam = `%${query}%`;
232
+ const sql = `
233
+ SELECT frame_id, name, type, state, digest_text, created_at,
234
+ CASE
235
+ WHEN name LIKE ? THEN 1.0
236
+ WHEN digest_text LIKE ? THEN 0.8
237
+ WHEN inputs LIKE ? THEN 0.6
238
+ ELSE 0.5
239
+ END as score
240
+ FROM frames
241
+ WHERE (name LIKE ? OR digest_text LIKE ? OR inputs LIKE ?)
242
+ ORDER BY score DESC
243
+ LIMIT ?
244
+ `;
245
+ const rows = db.prepare(sql).all(
246
+ likeParam,
247
+ likeParam,
248
+ likeParam,
249
+ likeParam,
250
+ likeParam,
251
+ likeParam,
252
+ limit
253
+ );
254
+ return rows.map((row) => ({
255
+ projectName: project.name,
256
+ projectPath: project.path,
257
+ frameId: row.frame_id,
258
+ name: row.name,
259
+ type: row.type,
260
+ state: row.state,
261
+ digestText: row.digest_text,
262
+ score: row.score,
263
+ createdAt: row.created_at
264
+ }));
265
+ }
266
+ }
267
+ export {
268
+ CrossProjectSearch
269
+ };
@@ -0,0 +1,10 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ import {
6
+ CrossProjectSearch
7
+ } from "./cross-project-search.js";
8
+ export {
9
+ CrossProjectSearch
10
+ };
@@ -44,7 +44,20 @@ class SQLiteAdapter extends FeatureAwareDatabaseAdapter {
44
44
  const config = this.config;
45
45
  const dir = path.dirname(this.dbPath);
46
46
  await fs.mkdir(dir, { recursive: true });
47
- this.db = new Database(this.dbPath);
47
+ try {
48
+ this.db = new Database(this.dbPath);
49
+ } catch (err) {
50
+ const msg = err instanceof Error ? err.message : String(err);
51
+ if (msg.includes("NODE_MODULE_VERSION") || msg.includes("was compiled against a different Node.js version")) {
52
+ const nodeVersion = process.version;
53
+ throw new Error(
54
+ `better-sqlite3 was compiled for a different Node.js version than the one currently running (${nodeVersion}).
55
+ Fix: cd ${process.cwd()} && npm rebuild better-sqlite3
56
+ If that fails: npm install`
57
+ );
58
+ }
59
+ throw err;
60
+ }
48
61
  this.db.pragma("foreign_keys = ON");
49
62
  if (config.walMode !== false) {
50
63
  this.db.pragma("journal_mode = WAL");
@@ -1530,89 +1543,6 @@ class SQLiteAdapter extends FeatureAwareDatabaseAdapter {
1530
1543
  }
1531
1544
  return Buffer.from(JSON.stringify(data, null, 2));
1532
1545
  }
1533
- /**
1534
- * Get recent frames from other run_ids within the same project.
1535
- * Used by team tools to read cross-agent context.
1536
- */
1537
- async getRecentFramesExcludingRun(projectId, excludeRunId, opts) {
1538
- if (!this.db)
1539
- throw new DatabaseError(
1540
- "Database not connected",
1541
- ErrorCode.DB_CONNECTION_FAILED
1542
- );
1543
- const limit = opts?.limit ?? 10;
1544
- const params = [projectId, excludeRunId];
1545
- let whereExtra = "";
1546
- if (opts?.since) {
1547
- whereExtra += " AND created_at > ?";
1548
- params.push(Math.floor(opts.since / 1e3));
1549
- }
1550
- if (opts?.types && opts.types.length > 0) {
1551
- const placeholders = opts.types.map(() => "?").join(",");
1552
- whereExtra += ` AND type IN (${placeholders})`;
1553
- params.push(...opts.types);
1554
- }
1555
- params.push(limit);
1556
- const rows = this.db.prepare(
1557
- `SELECT * FROM frames
1558
- WHERE project_id = ? AND run_id != ?${whereExtra}
1559
- ORDER BY created_at DESC
1560
- LIMIT ?`
1561
- ).all(...params);
1562
- const frameIds = rows.map((r) => r.frame_id);
1563
- const anchorsByFrame = /* @__PURE__ */ new Map();
1564
- if (frameIds.length > 0) {
1565
- const placeholders = frameIds.map(() => "?").join(",");
1566
- const anchorRows = this.db.prepare(
1567
- `SELECT * FROM anchors WHERE frame_id IN (${placeholders})
1568
- ORDER BY priority DESC, created_at ASC`
1569
- ).all(...frameIds);
1570
- for (const row of anchorRows) {
1571
- const list = anchorsByFrame.get(row.frame_id) || [];
1572
- list.push({ ...row, metadata: JSON.parse(row.metadata || "{}") });
1573
- anchorsByFrame.set(row.frame_id, list);
1574
- }
1575
- }
1576
- return rows.map((row) => ({
1577
- ...row,
1578
- inputs: JSON.parse(row.inputs || "{}"),
1579
- outputs: JSON.parse(row.outputs || "{}"),
1580
- digest_json: JSON.parse(row.digest_json || "{}"),
1581
- anchors: anchorsByFrame.get(row.frame_id) || []
1582
- }));
1583
- }
1584
- /**
1585
- * Get anchors explicitly shared for team visibility.
1586
- * Finds anchors where metadata contains `"shared":true`.
1587
- */
1588
- async getSharedAnchors(projectId, opts) {
1589
- if (!this.db)
1590
- throw new DatabaseError(
1591
- "Database not connected",
1592
- ErrorCode.DB_CONNECTION_FAILED
1593
- );
1594
- const limit = opts?.limit ?? 20;
1595
- const params = [projectId];
1596
- let whereExtra = "";
1597
- if (opts?.since) {
1598
- whereExtra += " AND a.created_at > ?";
1599
- params.push(Math.floor(opts.since / 1e3));
1600
- }
1601
- params.push(limit);
1602
- const rows = this.db.prepare(
1603
- `SELECT a.*, f.name as frame_name, f.run_id
1604
- FROM anchors a
1605
- JOIN frames f ON a.frame_id = f.frame_id
1606
- WHERE f.project_id = ?
1607
- AND a.metadata LIKE '%"shared":true%'${whereExtra}
1608
- ORDER BY a.priority DESC, a.created_at DESC
1609
- LIMIT ?`
1610
- ).all(...params);
1611
- return rows.map((row) => ({
1612
- ...row,
1613
- metadata: JSON.parse(row.metadata || "{}")
1614
- }));
1615
- }
1616
1546
  async importData(data, format, options) {
1617
1547
  if (!this.db)
1618
1548
  throw new DatabaseError(
@@ -387,6 +387,11 @@ function createProvider(id, config) {
387
387
  apiKey: config.apiKey,
388
388
  baseUrl: config.baseUrl || "https://openrouter.ai/api"
389
389
  });
390
+ case "moonshot":
391
+ return new GPTAdapter({
392
+ apiKey: config.apiKey,
393
+ baseUrl: config.baseUrl || "https://api.moonshot.ai/v1"
394
+ });
390
395
  default:
391
396
  throw new Error(`No adapter for provider: ${id}`);
392
397
  }