@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
@@ -23,26 +23,32 @@ import {
23
23
  } from "fs";
24
24
  import { homedir } from "os";
25
25
  import { compactPlan } from "../../orchestrators/multimodal/utils.js";
26
- import { filterPending } from "./pending-utils.js";
26
+ import {
27
+ parseMasterTasks,
28
+ getNextTask,
29
+ addTaskToFile,
30
+ updateTaskInFile
31
+ } from "../../core/tasks/md-task-parser.js";
27
32
  import { join, dirname } from "path";
28
33
  import { execSync } from "child_process";
29
34
  import { FrameManager } from "../../core/context/index.js";
30
35
  import { logger } from "../../core/monitoring/logger.js";
31
36
  import { isFeatureEnabled } from "../../core/config/feature-flags.js";
37
+ import { ContentCache } from "../../core/cache/index.js";
38
+ import { TraceEventStore } from "../../core/trace/trace-event-store.js";
32
39
  import { BrowserMCPIntegration } from "../../features/browser/browser-mcp.js";
33
40
  import { TraceDetector } from "../../core/trace/trace-detector.js";
34
41
  import { LLMContextRetrieval } from "../../core/retrieval/index.js";
35
42
  import { DiscoveryHandlers } from "./handlers/discovery-handlers.js";
36
- import { DiffMemHandlers } from "./handlers/diffmem-handlers.js";
37
- import { GreptileHandlers } from "./handlers/greptile-handlers.js";
38
- import { CordHandlers } from "./handlers/cord-handlers.js";
39
- import { TeamHandlers } from "./handlers/team-handlers.js";
40
- import { SQLiteAdapter } from "../../core/database/sqlite-adapter.js";
41
43
  import {
42
44
  generateChronologicalDigest
43
45
  } from "../../core/digest/chronological-digest.js";
44
46
  import { fuzzyEdit } from "../../utils/fuzzy-edit.js";
45
47
  import { v4 as uuidv4 } from "uuid";
48
+ import {
49
+ resolveToolAlias,
50
+ resolveParamAliases
51
+ } from "./tool-alias-registry.js";
46
52
  import {
47
53
  DEFAULT_PLANNER_MODEL,
48
54
  DEFAULT_IMPLEMENTER,
@@ -59,6 +65,28 @@ function _getEnv(key, defaultValue) {
59
65
  function _getOptionalEnv(key) {
60
66
  return process.env[key];
61
67
  }
68
+ const CACHEABLE_TOOLS = /* @__PURE__ */ new Set([
69
+ "get_context",
70
+ "get_hot_stack",
71
+ "get_active_tasks",
72
+ "get_task_metrics",
73
+ "get_traces",
74
+ "get_trace_statistics",
75
+ "smart_context",
76
+ "get_summary",
77
+ "sm_discover",
78
+ "sm_related_files",
79
+ "sm_session_summary",
80
+ "sm_search",
81
+ "sm_cross_search",
82
+ "sm_cross_discover",
83
+ "sm_cross_list",
84
+ "linear_get_tasks",
85
+ "linear_status",
86
+ "provenant_search",
87
+ "provenant_status",
88
+ "provenant_contradictions"
89
+ ]);
62
90
  class LocalStackMemoryMCP {
63
91
  server;
64
92
  db;
@@ -73,12 +101,13 @@ class LocalStackMemoryMCP {
73
101
  traceDetector;
74
102
  contextRetrieval;
75
103
  discoveryHandlers;
76
- diffMemHandlers;
77
- greptileHandlers;
78
104
  providerHandlers = null;
79
- cordHandlers = null;
80
- teamHandlers = null;
81
- pendingPlans = /* @__PURE__ */ new Map();
105
+ contentCache;
106
+ traceEventStore;
107
+ sessionId;
108
+ sessionTokensSaved = 0;
109
+ sessionCacheHits = 0;
110
+ sessionCacheMisses = 0;
82
111
  constructor() {
83
112
  this.projectRoot = this.findProjectRoot();
84
113
  this.projectId = this.getProjectId();
@@ -120,6 +149,9 @@ class LocalStackMemoryMCP {
120
149
  );
121
150
  CREATE INDEX IF NOT EXISTS idx_edit_telemetry_ts ON edit_telemetry(timestamp);
122
151
  `);
152
+ this.contentCache = new ContentCache(this.db);
153
+ this.traceEventStore = new TraceEventStore(this.db);
154
+ this.sessionId = uuidv4();
123
155
  this.frameManager = new FrameManager(this.db, this.projectId);
124
156
  this.initLinearIfEnabled();
125
157
  this.server = new Server(
@@ -149,10 +181,6 @@ class LocalStackMemoryMCP {
149
181
  db: this.db,
150
182
  projectRoot: this.projectRoot
151
183
  });
152
- this.diffMemHandlers = new DiffMemHandlers();
153
- this.greptileHandlers = new GreptileHandlers();
154
- this.initCordTeamHandlers();
155
- this.initProviderHandlers();
156
184
  this.setupHandlers();
157
185
  this.loadInitialContext();
158
186
  this.loadPendingPlans();
@@ -166,6 +194,105 @@ class LocalStackMemoryMCP {
166
194
  projectId: this.projectId
167
195
  });
168
196
  }
197
+ // ------------------------------------------------------------------
198
+ // Content-hash cache helpers
199
+ // ------------------------------------------------------------------
200
+ /**
201
+ * Build a deterministic cache key from tool name + sorted args.
202
+ * Returns null for non-cacheable tools or empty args.
203
+ */
204
+ buildCacheKey(tool, args) {
205
+ if (!CACHEABLE_TOOLS.has(tool)) return null;
206
+ const sorted = JSON.stringify(args, Object.keys(args).sort());
207
+ return `${tool}:${sorted}`;
208
+ }
209
+ handleCacheStats() {
210
+ const stats = this.contentCache.getStats();
211
+ return {
212
+ content: [
213
+ {
214
+ type: "text",
215
+ text: JSON.stringify({
216
+ session: {
217
+ cacheHits: this.sessionCacheHits,
218
+ cacheMisses: this.sessionCacheMisses,
219
+ tokensSaved: this.sessionTokensSaved,
220
+ hitRate: this.sessionCacheHits + this.sessionCacheMisses > 0 ? this.sessionCacheHits / (this.sessionCacheHits + this.sessionCacheMisses) : 0
221
+ },
222
+ lifetime: stats
223
+ })
224
+ }
225
+ ],
226
+ isError: false
227
+ };
228
+ }
229
+ handleCacheLookup(args) {
230
+ const content = String(args.content ?? "");
231
+ if (!content) {
232
+ return {
233
+ content: [
234
+ {
235
+ type: "text",
236
+ text: JSON.stringify({ error: "content is required" })
237
+ }
238
+ ],
239
+ isError: true
240
+ };
241
+ }
242
+ const result = this.contentCache.lookup(content, String(args.source ?? ""));
243
+ if (result.hit) {
244
+ this.sessionCacheHits++;
245
+ this.sessionTokensSaved += result.tokensSaved;
246
+ }
247
+ return {
248
+ content: [{ type: "text", text: JSON.stringify(result) }],
249
+ isError: false
250
+ };
251
+ }
252
+ // ------------------------------------------------------------------
253
+ // Trace event handlers
254
+ // ------------------------------------------------------------------
255
+ handleTraceEvents(args) {
256
+ const events = this.traceEventStore.query({
257
+ session_id: args.session_id,
258
+ operation: args.operation,
259
+ min_score: args.min_score,
260
+ has_feedback: args.has_feedback,
261
+ limit: args.limit ?? 50
262
+ });
263
+ return {
264
+ content: [{ type: "text", text: JSON.stringify(events) }],
265
+ isError: false
266
+ };
267
+ }
268
+ handleTraceEventStats(args) {
269
+ const stats = this.traceEventStore.getStats({
270
+ session_id: args.session_id
271
+ });
272
+ return {
273
+ content: [{ type: "text", text: JSON.stringify(stats) }],
274
+ isError: false
275
+ };
276
+ }
277
+ handleTraceEventAnnotate(args) {
278
+ const id = String(args.id ?? "");
279
+ if (!id) {
280
+ return {
281
+ content: [
282
+ { type: "text", text: JSON.stringify({ error: "id is required" }) }
283
+ ],
284
+ isError: true
285
+ };
286
+ }
287
+ const ok = this.traceEventStore.annotate(id, {
288
+ score: args.score,
289
+ feedback: args.feedback
290
+ });
291
+ return {
292
+ content: [{ type: "text", text: JSON.stringify({ ok, id }) }],
293
+ isError: false
294
+ };
295
+ }
169
296
  findProjectRoot() {
170
297
  let dir = process.cwd();
171
298
  while (dir !== "/") {
@@ -276,37 +403,6 @@ ${summary}...`, 0.8);
276
403
  this.contexts.set(ctx.id, ctx);
277
404
  });
278
405
  }
279
- async initCordTeamHandlers() {
280
- try {
281
- const dbPath = join(this.projectRoot, ".stackmemory", "context.db");
282
- const adapter = new SQLiteAdapter(this.projectId, {
283
- dbPath,
284
- walMode: true
285
- });
286
- await adapter.connect();
287
- this.cordHandlers = new CordHandlers({
288
- frameManager: this.frameManager,
289
- dbAdapter: adapter
290
- });
291
- this.teamHandlers = new TeamHandlers({
292
- frameManager: this.frameManager,
293
- dbAdapter: adapter
294
- });
295
- logger.info("Cord and Team handlers initialized");
296
- } catch (error) {
297
- logger.warn("Failed to initialize Cord/Team handlers", { error });
298
- }
299
- }
300
- async initProviderHandlers() {
301
- if (!isFeatureEnabled("multiProvider")) return;
302
- try {
303
- const { ProviderHandlers } = await import("./handlers/provider-handlers.js");
304
- this.providerHandlers = new ProviderHandlers();
305
- logger.info("Provider handlers initialized (multiProvider enabled)");
306
- } catch (error) {
307
- logger.warn("Failed to initialize provider handlers", { error });
308
- }
309
- }
310
406
  setupHandlers() {
311
407
  this.server.setRequestHandler(
312
408
  z.object({
@@ -335,7 +431,6 @@ ${summary}...`, 0.8);
335
431
  // Planning tools (only when ANTHROPIC_API_KEY is set)
336
432
  ...process.env.ANTHROPIC_API_KEY ? [
337
433
  {
338
- name: "plan_gate",
339
434
  description: "Phase 1: Generate a plan and return an approvalId for later execution",
340
435
  inputSchema: {
341
436
  type: "object",
@@ -353,14 +448,12 @@ ${summary}...`, 0.8);
353
448
  }
354
449
  },
355
450
  {
356
- name: "approve_plan",
357
451
  description: "Phase 2: Execute a previously generated plan by approvalId",
358
452
  inputSchema: {
359
453
  type: "object",
360
454
  properties: {
361
455
  approvalId: {
362
- type: "string",
363
- description: "Id from plan_gate"
456
+ type: "string"
364
457
  },
365
458
  implementer: {
366
459
  type: "string",
@@ -369,6 +462,11 @@ ${summary}...`, 0.8);
369
462
  description: "Which agent implements code"
370
463
  },
371
464
  maxIters: { type: "number", default: 2 },
465
+ verificationCommands: {
466
+ type: "array",
467
+ items: { type: "string" },
468
+ description: "Optional repro/test commands that must pass after implementation"
469
+ },
372
470
  recordFrame: { type: "boolean", default: true },
373
471
  execute: { type: "boolean", default: true }
374
472
  },
@@ -434,15 +532,13 @@ ${summary}...`, 0.8);
434
532
  type: "object",
435
533
  properties: {
436
534
  approvalId: {
437
- type: "string",
438
- description: "Approval id from plan_gate"
535
+ type: "string"
439
536
  }
440
537
  },
441
538
  required: ["approvalId"]
442
539
  }
443
540
  },
444
541
  {
445
- name: "plan_only",
446
542
  description: "Generate an implementation plan (Claude) and return JSON only",
447
543
  inputSchema: {
448
544
  type: "object",
@@ -460,7 +556,6 @@ ${summary}...`, 0.8);
460
556
  }
461
557
  },
462
558
  {
463
- name: "call_codex",
464
559
  description: "Invoke Codex via codex-sm with a prompt and args; dry-run by default",
465
560
  inputSchema: {
466
561
  type: "object",
@@ -484,7 +579,6 @@ ${summary}...`, 0.8);
484
579
  }
485
580
  },
486
581
  {
487
- name: "call_claude",
488
582
  description: "Invoke Claude with a prompt (Anthropic SDK)",
489
583
  inputSchema: {
490
584
  type: "object",
@@ -970,194 +1064,8 @@ ${summary}...`, 0.8);
970
1064
  required: ["query"]
971
1065
  }
972
1066
  },
973
- // DiffMem tools (only when DIFFMEM_ENDPOINT or DIFFMEM_ENABLED is set)
974
1067
  ...process.env.DIFFMEM_ENDPOINT || process.env.DIFFMEM_ENABLED === "true" ? this.diffMemHandlers.getToolDefinitions() : [],
975
- // Cord task orchestration tools
976
- {
977
- name: "cord_spawn",
978
- description: "Create a subtask with clean context (spawn). Child sees only its prompt and completed blocker results.",
979
- inputSchema: {
980
- type: "object",
981
- properties: {
982
- goal: {
983
- type: "string",
984
- description: "What this task should accomplish"
985
- },
986
- prompt: {
987
- type: "string",
988
- description: "Detailed instructions for the task"
989
- },
990
- blocked_by: {
991
- type: "array",
992
- items: { type: "string" },
993
- description: "Task IDs that must complete first"
994
- },
995
- parent_id: { type: "string", description: "Parent task ID" }
996
- },
997
- required: ["goal"]
998
- }
999
- },
1000
- {
1001
- name: "cord_fork",
1002
- description: "Create a subtask with full sibling context (fork). Child sees prompt, blocker results, AND completed sibling results.",
1003
- inputSchema: {
1004
- type: "object",
1005
- properties: {
1006
- goal: {
1007
- type: "string",
1008
- description: "What this task should accomplish"
1009
- },
1010
- prompt: {
1011
- type: "string",
1012
- description: "Detailed instructions for the task"
1013
- },
1014
- blocked_by: {
1015
- type: "array",
1016
- items: { type: "string" },
1017
- description: "Task IDs that must complete first"
1018
- },
1019
- parent_id: { type: "string", description: "Parent task ID" }
1020
- },
1021
- required: ["goal"]
1022
- }
1023
- },
1024
- {
1025
- name: "cord_complete",
1026
- description: "Mark a cord task as completed with a result. Automatically unblocks dependent tasks.",
1027
- inputSchema: {
1028
- type: "object",
1029
- properties: {
1030
- task_id: {
1031
- type: "string",
1032
- description: "Task ID to complete"
1033
- },
1034
- result: {
1035
- type: "string",
1036
- description: "The result/output of this task"
1037
- }
1038
- },
1039
- required: ["task_id", "result"]
1040
- }
1041
- },
1042
- {
1043
- name: "cord_ask",
1044
- description: "Create an ask task \u2014 a question that needs an answer before dependent tasks can proceed.",
1045
- inputSchema: {
1046
- type: "object",
1047
- properties: {
1048
- question: {
1049
- type: "string",
1050
- description: "The question to ask"
1051
- },
1052
- options: {
1053
- type: "array",
1054
- items: { type: "string" },
1055
- description: "Optional list of answer choices"
1056
- },
1057
- parent_id: { type: "string", description: "Parent task ID" }
1058
- },
1059
- required: ["question"]
1060
- }
1061
- },
1062
- {
1063
- name: "cord_tree",
1064
- description: "View the cord task tree with context scoping. Shows active, blocked, or completed tasks.",
1065
- inputSchema: {
1066
- type: "object",
1067
- properties: {
1068
- task_id: {
1069
- type: "string",
1070
- description: "Root task ID to show subtree (omit for full tree)"
1071
- },
1072
- include_results: {
1073
- type: "boolean",
1074
- default: false,
1075
- description: "Include task results in output"
1076
- }
1077
- }
1078
- }
1079
- },
1080
- // Team collaboration tools
1081
- {
1082
- name: "team_context_get",
1083
- description: "Get context from other agents working on the same project. Returns recent frames and shared anchors.",
1084
- inputSchema: {
1085
- type: "object",
1086
- properties: {
1087
- limit: {
1088
- type: "number",
1089
- default: 10,
1090
- description: "Max frames to return"
1091
- },
1092
- types: {
1093
- type: "array",
1094
- items: { type: "string" },
1095
- description: "Filter by frame types"
1096
- },
1097
- since: {
1098
- type: "number",
1099
- description: "Only frames created after this timestamp (epoch ms)"
1100
- }
1101
- }
1102
- }
1103
- },
1104
- {
1105
- name: "team_context_share",
1106
- description: "Share a piece of context with other agents. Creates a high-priority anchor visible to team_context_get.",
1107
- inputSchema: {
1108
- type: "object",
1109
- properties: {
1110
- content: {
1111
- type: "string",
1112
- description: "The context to share"
1113
- },
1114
- type: {
1115
- type: "string",
1116
- enum: [
1117
- "FACT",
1118
- "DECISION",
1119
- "CONSTRAINT",
1120
- "INTERFACE_CONTRACT",
1121
- "TODO",
1122
- "RISK"
1123
- ],
1124
- default: "FACT",
1125
- description: "Type of context"
1126
- },
1127
- priority: {
1128
- type: "number",
1129
- minimum: 1,
1130
- maximum: 10,
1131
- default: 8,
1132
- description: "Priority level (1-10)"
1133
- }
1134
- },
1135
- required: ["content"]
1136
- }
1137
- },
1138
- {
1139
- name: "team_search",
1140
- description: "Search across all agents' context in the project. Uses full-text search across all sessions.",
1141
- inputSchema: {
1142
- type: "object",
1143
- properties: {
1144
- query: { type: "string", description: "Search query" },
1145
- limit: {
1146
- type: "number",
1147
- default: 20,
1148
- description: "Maximum results to return"
1149
- },
1150
- include_events: {
1151
- type: "boolean",
1152
- default: false,
1153
- description: "Include events in results"
1154
- }
1155
- },
1156
- required: ["query"]
1157
- }
1158
- },
1159
1068
  // Greptile tools (only active when GREPTILE_API_KEY is set)
1160
- ...process.env.GREPTILE_API_KEY ? this.greptileHandlers.getToolDefinitions() : [],
1161
1069
  // Provider tools (only active when STACKMEMORY_MULTI_PROVIDER=true)
1162
1070
  ...isFeatureEnabled("multiProvider") ? [
1163
1071
  {
@@ -1274,6 +1182,143 @@ ${summary}...`, 0.8);
1274
1182
  },
1275
1183
  required: ["period"]
1276
1184
  }
1185
+ },
1186
+ // Cross-project search tools
1187
+ {
1188
+ name: "sm_cross_search",
1189
+ description: "Search frames across all registered project databases using FTS5/BM25. Returns results ranked by relevance with source project attribution.",
1190
+ inputSchema: {
1191
+ type: "object",
1192
+ properties: {
1193
+ query: {
1194
+ type: "string",
1195
+ description: "Search query (natural language or keywords)"
1196
+ },
1197
+ limit: {
1198
+ type: "number",
1199
+ default: 20,
1200
+ description: "Maximum results to return"
1201
+ },
1202
+ exclude_current: {
1203
+ type: "boolean",
1204
+ default: false,
1205
+ description: "Exclude the current project from results"
1206
+ }
1207
+ },
1208
+ required: ["query"]
1209
+ }
1210
+ },
1211
+ {
1212
+ name: "sm_cross_discover",
1213
+ description: "Auto-discover project databases by scanning common directories for .stackmemory/context.db files.",
1214
+ inputSchema: {
1215
+ type: "object",
1216
+ properties: {
1217
+ paths: {
1218
+ type: "array",
1219
+ items: { type: "string" },
1220
+ description: "Custom directory paths to scan"
1221
+ }
1222
+ }
1223
+ }
1224
+ },
1225
+ {
1226
+ name: "sm_cross_register",
1227
+ description: "Manually register a project database for cross-project search.",
1228
+ inputSchema: {
1229
+ type: "object",
1230
+ properties: {
1231
+ name: {
1232
+ type: "string",
1233
+ description: "Project display name"
1234
+ },
1235
+ path: {
1236
+ type: "string",
1237
+ description: "Project root directory path"
1238
+ },
1239
+ db_path: {
1240
+ type: "string",
1241
+ description: "Path to the SQLite context.db file"
1242
+ }
1243
+ },
1244
+ required: ["name", "path", "db_path"]
1245
+ }
1246
+ },
1247
+ {
1248
+ name: "sm_cross_list",
1249
+ description: "List all project databases registered for cross-project search.",
1250
+ inputSchema: {
1251
+ type: "object",
1252
+ properties: {}
1253
+ }
1254
+ },
1255
+ // Cache tools
1256
+ {
1257
+ name: "cache_stats",
1258
+ description: "Get content-hash cache statistics: session token savings, hit rate, and lifetime totals. Call this to see how many tokens have been saved by deduplication.",
1259
+ inputSchema: {
1260
+ type: "object",
1261
+ properties: {}
1262
+ }
1263
+ },
1264
+ {
1265
+ name: "cache_lookup",
1266
+ description: "Check if content is already cached. Returns hit/miss and token savings. Use for explicit deduplication before sending large content.",
1267
+ inputSchema: {
1268
+ type: "object",
1269
+ properties: {
1270
+ content: {
1271
+ type: "string",
1272
+ description: "The content to check against the cache."
1273
+ },
1274
+ source: {
1275
+ type: "string",
1276
+ description: 'Optional source label (e.g., "file:src/index.ts").'
1277
+ }
1278
+ },
1279
+ required: ["content"]
1280
+ }
1281
+ },
1282
+ // Trace event tools
1283
+ {
1284
+ name: "trace_events",
1285
+ description: "Query ASI-shaped trace events. Filter by session, operation, min score, or feedback presence. Returns events with provenance, cost, and token data.",
1286
+ inputSchema: {
1287
+ type: "object",
1288
+ properties: {
1289
+ session_id: { type: "string" },
1290
+ operation: { type: "string" },
1291
+ min_score: { type: "number" },
1292
+ has_feedback: { type: "boolean" },
1293
+ limit: { type: "number" }
1294
+ }
1295
+ }
1296
+ },
1297
+ {
1298
+ name: "trace_event_stats",
1299
+ description: "Get aggregate trace event statistics: total tokens, cost, operation counts, host distribution.",
1300
+ inputSchema: {
1301
+ type: "object",
1302
+ properties: {
1303
+ session_id: { type: "string" }
1304
+ }
1305
+ }
1306
+ },
1307
+ {
1308
+ name: "trace_event_annotate",
1309
+ description: "Add a numeric score and/or textual feedback to a trace event. Used by GEPA-class optimizers.",
1310
+ inputSchema: {
1311
+ type: "object",
1312
+ properties: {
1313
+ id: { type: "string", description: "Trace event ID" },
1314
+ score: { type: "number", description: "Numeric score (0-1)" },
1315
+ feedback: {
1316
+ type: "string",
1317
+ description: "Textual ASI feedback"
1318
+ }
1319
+ },
1320
+ required: ["id"]
1321
+ }
1277
1322
  }
1278
1323
  ]
1279
1324
  };
@@ -1288,7 +1333,18 @@ ${summary}...`, 0.8);
1288
1333
  })
1289
1334
  }),
1290
1335
  async (request) => {
1291
- const { name, arguments: args } = request.params;
1336
+ const { name: rawName, arguments: rawArgs } = request.params;
1337
+ const aliasResolution = resolveToolAlias(rawName);
1338
+ const name = aliasResolution.canonicalName;
1339
+ const paramResolution = resolveParamAliases(name, rawArgs);
1340
+ const args = paramResolution.resolvedParams;
1341
+ if (aliasResolution.wasAlias || Object.keys(paramResolution.renames).length > 0) {
1342
+ logger.debug("Tool alias resolved", {
1343
+ originalTool: rawName,
1344
+ canonicalTool: name,
1345
+ paramRenames: paramResolution.renames
1346
+ });
1347
+ }
1292
1348
  const callId = uuidv4();
1293
1349
  const startTime = Date.now();
1294
1350
  const currentFrameId = this.frameManager.getCurrentFrameId();
@@ -1296,7 +1352,8 @@ ${summary}...`, 0.8);
1296
1352
  this.frameManager.addEvent("tool_call", {
1297
1353
  tool_name: name,
1298
1354
  arguments: args,
1299
- timestamp: startTime
1355
+ timestamp: startTime,
1356
+ ...aliasResolution.wasAlias ? { alias_from: rawName } : {}
1300
1357
  });
1301
1358
  }
1302
1359
  const toolCall = {
@@ -1305,6 +1362,28 @@ ${summary}...`, 0.8);
1305
1362
  arguments: args,
1306
1363
  timestamp: startTime
1307
1364
  };
1365
+ const cacheKey = this.buildCacheKey(name, args);
1366
+ if (cacheKey) {
1367
+ const cached = this.contentCache.lookupByKey(
1368
+ cacheKey,
1369
+ `tool:${name}`
1370
+ );
1371
+ if (cached.hit && cached.entry) {
1372
+ this.sessionCacheHits++;
1373
+ this.sessionTokensSaved += cached.tokensSaved;
1374
+ const total = this.sessionCacheHits + this.sessionCacheMisses;
1375
+ const rate = (this.sessionCacheHits / total * 100).toFixed(0);
1376
+ console.error(
1377
+ `[cache] HIT ${name} \u2014 saved ~${cached.tokensSaved} tokens (session: ${this.sessionTokensSaved.toLocaleString()} total, ${rate}% hit rate)`
1378
+ );
1379
+ const cachedResult = JSON.parse(cached.entry.content);
1380
+ toolCall.result = cachedResult;
1381
+ toolCall.duration = Date.now() - startTime;
1382
+ this.traceDetector.addToolCall(toolCall);
1383
+ return cachedResult;
1384
+ }
1385
+ this.sessionCacheMisses++;
1386
+ }
1308
1387
  let result;
1309
1388
  let error;
1310
1389
  try {
@@ -1372,20 +1451,10 @@ ${summary}...`, 0.8);
1372
1451
  case "compress_old_traces":
1373
1452
  result = await this.handleCompressOldTraces(args);
1374
1453
  break;
1375
- case "plan_only":
1376
- result = await this.handlePlanOnly(args);
1377
1454
  break;
1378
- case "call_codex":
1379
- result = await this.handleCallCodex(args);
1380
1455
  break;
1381
- case "call_claude":
1382
- result = await this.handleCallClaude(args);
1383
1456
  break;
1384
- case "plan_gate":
1385
- result = await this.handlePlanGate(args);
1386
1457
  break;
1387
- case "approve_plan":
1388
- result = await this.handleApprovePlan(args);
1389
1458
  break;
1390
1459
  case "pending_list":
1391
1460
  result = await this.handlePendingList();
@@ -1415,141 +1484,17 @@ ${summary}...`, 0.8);
1415
1484
  case "sm_search":
1416
1485
  result = await this.handleSmSearch(args);
1417
1486
  break;
1418
- // DiffMem handlers
1419
- case "diffmem_get_user_context":
1420
- result = await this.diffMemHandlers.handleGetUserContext(args);
1421
- break;
1422
- case "diffmem_store_learning":
1423
- result = await this.diffMemHandlers.handleStoreLearning(args);
1424
- break;
1425
- case "diffmem_search":
1426
- result = await this.diffMemHandlers.handleSearch(args);
1427
- break;
1428
- case "diffmem_status":
1429
- result = await this.diffMemHandlers.handleStatus();
1430
- break;
1431
- // Greptile handlers
1432
- case "greptile_pr_comments":
1433
- result = await this.greptileHandlers.handleListPRComments(args);
1434
- break;
1435
- case "greptile_pr_details":
1436
- result = await this.greptileHandlers.handleGetMergeRequest(args);
1437
- break;
1438
- case "greptile_list_prs":
1439
- result = await this.greptileHandlers.handleListPullRequests(args);
1440
- break;
1441
- case "greptile_trigger_review":
1442
- result = await this.greptileHandlers.handleTriggerCodeReview(args);
1443
- break;
1444
- case "greptile_search_patterns":
1445
- result = await this.greptileHandlers.handleSearchPatterns(args);
1446
- break;
1447
- case "greptile_create_pattern":
1448
- result = await this.greptileHandlers.handleCreatePattern(args);
1449
- break;
1450
- case "greptile_status":
1451
- result = await this.greptileHandlers.handleStatus();
1452
- break;
1453
- case "sm_edit":
1454
- result = await this.handleSmEdit(args);
1455
- break;
1456
- // Cord task orchestration
1457
- case "cord_spawn":
1458
- if (!this.cordHandlers) {
1459
- result = {
1460
- content: [
1461
- { type: "text", text: "Cord handlers not initialized." }
1462
- ],
1463
- is_error: true
1464
- };
1465
- } else {
1466
- result = await this.cordHandlers.handleCordSpawn(args);
1467
- }
1468
- break;
1469
- case "cord_fork":
1470
- if (!this.cordHandlers) {
1471
- result = {
1472
- content: [
1473
- { type: "text", text: "Cord handlers not initialized." }
1474
- ],
1475
- is_error: true
1476
- };
1477
- } else {
1478
- result = await this.cordHandlers.handleCordFork(args);
1479
- }
1480
- break;
1481
- case "cord_complete":
1482
- if (!this.cordHandlers) {
1483
- result = {
1484
- content: [
1485
- { type: "text", text: "Cord handlers not initialized." }
1486
- ],
1487
- is_error: true
1488
- };
1489
- } else {
1490
- result = await this.cordHandlers.handleCordComplete(args);
1491
- }
1492
- break;
1493
- case "cord_ask":
1494
- if (!this.cordHandlers) {
1495
- result = {
1496
- content: [
1497
- { type: "text", text: "Cord handlers not initialized." }
1498
- ],
1499
- is_error: true
1500
- };
1501
- } else {
1502
- result = await this.cordHandlers.handleCordAsk(args);
1503
- }
1487
+ result = await this.diffMemHandlers.handleGetUserContext(args);
1504
1488
  break;
1505
- case "cord_tree":
1506
- if (!this.cordHandlers) {
1507
- result = {
1508
- content: [
1509
- { type: "text", text: "Cord handlers not initialized." }
1510
- ],
1511
- is_error: true
1512
- };
1513
- } else {
1514
- result = await this.cordHandlers.handleCordTree(args);
1515
- }
1489
+ result = await this.diffMemHandlers.handleStoreLearning(args);
1516
1490
  break;
1517
- // Team collaboration
1518
- case "team_context_get":
1519
- if (!this.teamHandlers) {
1520
- result = {
1521
- content: [
1522
- { type: "text", text: "Team handlers not initialized." }
1523
- ],
1524
- is_error: true
1525
- };
1526
- } else {
1527
- result = await this.teamHandlers.handleTeamContextGet(args);
1528
- }
1491
+ result = await this.diffMemHandlers.handleSearch(args);
1529
1492
  break;
1530
- case "team_context_share":
1531
- if (!this.teamHandlers) {
1532
- result = {
1533
- content: [
1534
- { type: "text", text: "Team handlers not initialized." }
1535
- ],
1536
- is_error: true
1537
- };
1538
- } else {
1539
- result = await this.teamHandlers.handleTeamContextShare(args);
1540
- }
1493
+ result = await this.diffMemHandlers.handleStatus();
1541
1494
  break;
1542
- case "team_search":
1543
- if (!this.teamHandlers) {
1544
- result = {
1545
- content: [
1546
- { type: "text", text: "Team handlers not initialized." }
1547
- ],
1548
- is_error: true
1549
- };
1550
- } else {
1551
- result = await this.teamHandlers.handleTeamSearch(args);
1552
- }
1495
+ // Greptile handlers
1496
+ case "sm_edit":
1497
+ result = await this.handleSmEdit(args);
1553
1498
  break;
1554
1499
  // Provider tools
1555
1500
  case "delegate_to_model":
@@ -1609,6 +1554,44 @@ ${summary}...`, 0.8);
1609
1554
  case "sm_desire_paths":
1610
1555
  result = this.handleDesirePaths(args);
1611
1556
  break;
1557
+ case "sm_cross_search":
1558
+ result = await this.crossSearchHandlers.handleCrossSearch(args);
1559
+ break;
1560
+ case "sm_cross_discover":
1561
+ result = await this.crossSearchHandlers.handleCrossDiscover(args);
1562
+ break;
1563
+ case "sm_cross_register":
1564
+ result = await this.crossSearchHandlers.handleCrossRegister(args);
1565
+ break;
1566
+ case "sm_cross_list":
1567
+ result = await this.crossSearchHandlers.handleCrossList();
1568
+ break;
1569
+ // Cache tools
1570
+ case "cache_stats":
1571
+ result = this.handleCacheStats();
1572
+ break;
1573
+ case "cache_lookup":
1574
+ result = this.handleCacheLookup(args);
1575
+ break;
1576
+ // Trace event tools
1577
+ case "trace_events":
1578
+ result = this.handleTraceEvents(args);
1579
+ break;
1580
+ case "trace_event_stats":
1581
+ result = this.handleTraceEventStats(args);
1582
+ break;
1583
+ case "trace_event_annotate":
1584
+ result = this.handleTraceEventAnnotate(args);
1585
+ break;
1586
+ case "get_next_master_task":
1587
+ result = this.handleGetNextMasterTask(args);
1588
+ break;
1589
+ case "update_master_task":
1590
+ result = this.handleUpdateMasterTask(args);
1591
+ break;
1592
+ case "create_master_task":
1593
+ result = this.handleCreateMasterTask(args);
1594
+ break;
1612
1595
  default:
1613
1596
  throw new Error(`Unknown tool: ${name}`);
1614
1597
  }
@@ -1619,6 +1602,22 @@ ${summary}...`, 0.8);
1619
1602
  throw err;
1620
1603
  } finally {
1621
1604
  const endTime = Date.now();
1605
+ if (!error && result && cacheKey) {
1606
+ try {
1607
+ const serialized = JSON.stringify(result);
1608
+ const entry = this.contentCache.putByKey(
1609
+ cacheKey,
1610
+ serialized,
1611
+ `tool:${name}`
1612
+ );
1613
+ if (entry.hitCount === 0) {
1614
+ console.error(
1615
+ `[cache] STORE ${name} \u2014 ~${entry.tokenCount} tokens cached for future dedup`
1616
+ );
1617
+ }
1618
+ } catch {
1619
+ }
1620
+ }
1622
1621
  if (currentFrameId && name !== "close_frame") {
1623
1622
  try {
1624
1623
  this.frameManager.addEvent("tool_result", {
@@ -1641,11 +1640,50 @@ ${summary}...`, 0.8);
1641
1640
  toolCall.filesAffected = Array.isArray(files) ? files : [files];
1642
1641
  }
1643
1642
  this.traceDetector.addToolCall(toolCall);
1643
+ try {
1644
+ const traceEvent = {
1645
+ timestamp: new Date(startTime).toISOString(),
1646
+ session_id: this.sessionId,
1647
+ trace_id: callId,
1648
+ tenant_id: "local",
1649
+ actor: {
1650
+ host: process.env["STACKMEMORY_HOST"] || "claude-code",
1651
+ agent: "stackmemory-mcp",
1652
+ user: process.env["USER"] || "anonymous"
1653
+ },
1654
+ operation: name,
1655
+ inputs: args,
1656
+ outputs: error ? { error: error.message } : result ?? {},
1657
+ tokens_in: 0,
1658
+ tokens_out: 0,
1659
+ cost_usd: 0,
1660
+ duration_ms: endTime - startTime,
1661
+ error: error?.message,
1662
+ provenance: {
1663
+ sources: [{ type: "tool", id: name }],
1664
+ derivation: ["mcp-call"],
1665
+ confidence: 1
1666
+ }
1667
+ };
1668
+ this.traceEventStore.record(traceEvent);
1669
+ } catch {
1670
+ }
1644
1671
  }
1645
1672
  return result;
1646
1673
  }
1647
1674
  );
1648
1675
  }
1676
+ getVerificationCommands(args) {
1677
+ const commands = args.verificationCommands ?? args.verifyCommands;
1678
+ if (Array.isArray(commands)) {
1679
+ return commands.map((command) => String(command).trim()).filter(Boolean);
1680
+ }
1681
+ const single = args.verificationCommand ?? args.verifyCommand;
1682
+ if (typeof single === "string" && single.trim()) {
1683
+ return [single.trim()];
1684
+ }
1685
+ return [];
1686
+ }
1649
1687
  // Handle plan_and_code tool by invoking the mm harness
1650
1688
  async handlePlanAndCode(args) {
1651
1689
  const { runSpike } = await import("../../orchestrators/multimodal/harness.js");
@@ -1660,6 +1698,7 @@ ${summary}...`, 0.8);
1660
1698
  const record = Boolean(args.record);
1661
1699
  const recordFrame = Boolean(args.recordFrame);
1662
1700
  const compact = Boolean(args.compact);
1701
+ const verificationCommands = this.getVerificationCommands(args);
1663
1702
  const task = String(args.task || "Plan and implement change");
1664
1703
  const result = await runSpike(
1665
1704
  {
@@ -1673,6 +1712,7 @@ ${summary}...`, 0.8);
1673
1712
  maxIters: isFinite(maxIters) ? Math.max(1, maxIters) : 2,
1674
1713
  dryRun: !execute,
1675
1714
  auditDir: void 0,
1715
+ verificationCommands,
1676
1716
  recordFrame
1677
1717
  }
1678
1718
  );
@@ -1696,60 +1736,6 @@ ${summary}...`, 0.8);
1696
1736
  isError: false
1697
1737
  };
1698
1738
  }
1699
- async handlePlanOnly(args) {
1700
- const { runPlanOnly } = await import("../../orchestrators/multimodal/harness.js");
1701
- const task = String(args.task || "Plan change");
1702
- const plannerModel = args.plannerModel || process.env["STACKMEMORY_MM_PLANNER_MODEL"] || DEFAULT_PLANNER_MODEL;
1703
- const plan = await runPlanOnly(
1704
- { task, repoPath: this.projectRoot },
1705
- { plannerModel }
1706
- );
1707
- return {
1708
- content: [
1709
- {
1710
- type: "text",
1711
- text: JSON.stringify({ ok: true, plan })
1712
- }
1713
- ],
1714
- isError: false
1715
- };
1716
- }
1717
- async handleCallCodex(args) {
1718
- const { callCodexCLI } = await import("../../orchestrators/multimodal/providers.js");
1719
- const prompt = String(args.prompt || "");
1720
- const extraArgs = Array.isArray(args.args) ? args.args : [];
1721
- const execute = Boolean(args.execute);
1722
- const resp = callCodexCLI(prompt, extraArgs, !execute);
1723
- return {
1724
- content: [
1725
- {
1726
- type: "text",
1727
- text: JSON.stringify({
1728
- ok: resp.ok,
1729
- command: resp.command,
1730
- output: resp.output
1731
- })
1732
- }
1733
- ],
1734
- isError: false
1735
- };
1736
- }
1737
- async handleCallClaude(args) {
1738
- const { callClaude } = await import("../../orchestrators/multimodal/providers.js");
1739
- const prompt = String(args.prompt || "");
1740
- const model = args.model || process.env["STACKMEMORY_MM_PLANNER_MODEL"] || DEFAULT_PLANNER_MODEL;
1741
- const system = args.system || "You are a precise assistant. Return plain text unless asked for JSON.";
1742
- const text = await callClaude(prompt, { model, system });
1743
- return {
1744
- content: [
1745
- {
1746
- type: "text",
1747
- text: JSON.stringify({ ok: true, text })
1748
- }
1749
- ],
1750
- isError: false
1751
- };
1752
- }
1753
1739
  // Pending plan persistence (best-effort)
1754
1740
  getPendingStoreDir() {
1755
1741
  return join(this.projectRoot, ".stackmemory", "build");
@@ -1773,7 +1759,6 @@ ${summary}...`, 0.8);
1773
1759
  }
1774
1760
  const data = JSON.parse(readFileSync(sourceFile, "utf-8"));
1775
1761
  if (data && typeof data === "object") {
1776
- this.pendingPlans = new Map(Object.entries(data));
1777
1762
  if (sourceFile !== file) this.savePendingPlans();
1778
1763
  }
1779
1764
  } catch {
@@ -1784,212 +1769,11 @@ ${summary}...`, 0.8);
1784
1769
  const dir = this.getPendingStoreDir();
1785
1770
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
1786
1771
  const file = this.getPendingStorePath();
1787
- const obj = Object.fromEntries(this.pendingPlans);
1788
1772
  writeFileSync(file, JSON.stringify(obj, null, 2));
1789
1773
  } catch {
1790
1774
  }
1791
1775
  }
1792
- async handlePlanGate(args) {
1793
- const { runPlanOnly } = await import("../../orchestrators/multimodal/harness.js");
1794
- const task = String(args.task || "Plan change");
1795
- const plannerModel = args.plannerModel || process.env["STACKMEMORY_MM_PLANNER_MODEL"] || DEFAULT_PLANNER_MODEL;
1796
- const plan = await runPlanOnly(
1797
- { task, repoPath: this.projectRoot },
1798
- { plannerModel }
1799
- );
1800
- const approvalId = `appr_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1801
- this.pendingPlans.set(approvalId, { task, plan, createdAt: Date.now() });
1802
- this.savePendingPlans();
1803
- const compact = Boolean(args.compact);
1804
- const planOut = compact ? compactPlan(plan) : plan;
1805
- return {
1806
- content: [
1807
- {
1808
- type: "text",
1809
- text: JSON.stringify({ ok: true, approvalId, plan: planOut })
1810
- }
1811
- ],
1812
- isError: false
1813
- };
1814
- }
1815
- async handleApprovePlan(args) {
1816
- const { runSpike } = await import("../../orchestrators/multimodal/harness.js");
1817
- const approvalId = String(args.approvalId || "");
1818
- const pending = this.pendingPlans.get(approvalId);
1819
- if (!pending) {
1820
- return {
1821
- content: [
1822
- {
1823
- type: "text",
1824
- text: JSON.stringify({ ok: false, error: "Invalid approvalId" })
1825
- }
1826
- ],
1827
- isError: false
1828
- };
1829
- }
1830
- const implementer = args.implementer || process.env["STACKMEMORY_MM_IMPLEMENTER"] || DEFAULT_IMPLEMENTER;
1831
- const maxIters = Number(
1832
- args.maxIters ?? process.env["STACKMEMORY_MM_MAX_ITERS"] ?? DEFAULT_MAX_ITERS
1833
- );
1834
- const recordFrame = args.recordFrame !== false;
1835
- const execute = args.execute !== false;
1836
- const result = await runSpike(
1837
- { task: pending.task, repoPath: this.projectRoot },
1838
- {
1839
- plannerModel: process.env["STACKMEMORY_MM_PLANNER_MODEL"] || DEFAULT_PLANNER_MODEL,
1840
- reviewerModel: process.env["STACKMEMORY_MM_REVIEWER_MODEL"] || process.env["STACKMEMORY_MM_PLANNER_MODEL"] || DEFAULT_PLANNER_MODEL,
1841
- implementer: implementer === "claude" ? "claude" : "codex",
1842
- maxIters: isFinite(maxIters) ? Math.max(1, maxIters) : 2,
1843
- dryRun: !execute,
1844
- recordFrame
1845
- }
1846
- );
1847
- this.pendingPlans.delete(approvalId);
1848
- this.savePendingPlans();
1849
- const compact = Boolean(args.compact);
1850
- const payload = compact ? { ...result, plan: compactPlan(result.plan) } : result;
1851
- return {
1852
- content: [
1853
- {
1854
- type: "text",
1855
- text: JSON.stringify({ ok: true, approvalId, result: payload })
1856
- }
1857
- ],
1858
- isError: false
1859
- };
1860
- }
1861
- async handlePendingList(args) {
1862
- const schema = z.object({
1863
- taskContains: z.string().optional(),
1864
- olderThanMs: z.number().optional(),
1865
- newerThanMs: z.number().optional(),
1866
- sort: z.enum(["asc", "desc"]).optional(),
1867
- limit: z.number().int().positive().optional()
1868
- }).optional();
1869
- const parsed = schema.safeParse(args);
1870
- if (args && !parsed.success) {
1871
- return {
1872
- content: [
1873
- {
1874
- type: "text",
1875
- text: JSON.stringify({
1876
- ok: false,
1877
- error: "Invalid arguments",
1878
- details: parsed.error.issues
1879
- })
1880
- }
1881
- ],
1882
- isError: false
1883
- };
1884
- }
1885
- const a = parsed.success && parsed.data ? parsed.data : {};
1886
- const now = Date.now();
1887
- let items = Array.from(this.pendingPlans.entries()).map(
1888
- ([approvalId, data]) => ({
1889
- approvalId,
1890
- task: data?.task,
1891
- createdAt: Number(data?.createdAt || 0) || null
1892
- })
1893
- );
1894
- items = filterPending(items, a, now);
1895
- return {
1896
- content: [
1897
- { type: "text", text: JSON.stringify({ ok: true, pending: items }) }
1898
- ],
1899
- isError: false
1900
- };
1901
- }
1902
- async handlePendingClear(args) {
1903
- const removed = [];
1904
- const now = Date.now();
1905
- const all = Boolean(args?.all);
1906
- const approvalId = args?.approvalId ? String(args.approvalId) : void 0;
1907
- const olderThanMs = Number.isFinite(Number(args?.olderThanMs)) ? Number(args.olderThanMs) : void 0;
1908
- if (all) {
1909
- for (const id of this.pendingPlans.keys()) removed.push(id);
1910
- this.pendingPlans.clear();
1911
- this.savePendingPlans();
1912
- return {
1913
- content: [
1914
- { type: "text", text: JSON.stringify({ ok: true, removed }) }
1915
- ],
1916
- isError: false
1917
- };
1918
- }
1919
- if (approvalId) {
1920
- if (this.pendingPlans.has(approvalId)) {
1921
- this.pendingPlans.delete(approvalId);
1922
- removed.push(approvalId);
1923
- this.savePendingPlans();
1924
- }
1925
- return {
1926
- content: [
1927
- { type: "text", text: JSON.stringify({ ok: true, removed }) }
1928
- ],
1929
- isError: false
1930
- };
1931
- }
1932
- if (olderThanMs !== void 0 && olderThanMs >= 0) {
1933
- for (const [id, data] of this.pendingPlans.entries()) {
1934
- const ts = Number(data?.createdAt || 0);
1935
- if (ts && now - ts > olderThanMs) {
1936
- this.pendingPlans.delete(id);
1937
- removed.push(id);
1938
- }
1939
- }
1940
- this.savePendingPlans();
1941
- return {
1942
- content: [
1943
- { type: "text", text: JSON.stringify({ ok: true, removed }) }
1944
- ],
1945
- isError: false
1946
- };
1947
- }
1948
- return {
1949
- content: [
1950
- {
1951
- type: "text",
1952
- text: JSON.stringify({
1953
- ok: false,
1954
- error: "Specify approvalId, all=true, or olderThanMs"
1955
- })
1956
- }
1957
- ],
1958
- isError: false
1959
- };
1960
- }
1961
- async handlePendingShow(args) {
1962
- const approvalId = String(args?.approvalId || "");
1963
- const data = this.pendingPlans.get(approvalId);
1964
- if (!data) {
1965
- return {
1966
- content: [
1967
- {
1968
- type: "text",
1969
- text: JSON.stringify({ ok: false, error: "Invalid approvalId" })
1970
- }
1971
- ],
1972
- isError: false
1973
- };
1974
- }
1975
- const compact = Boolean(args.compact);
1976
- const planOut = compact ? compactPlan(data.plan) : data.plan;
1977
- return {
1978
- content: [
1979
- {
1980
- type: "text",
1981
- text: JSON.stringify({
1982
- ok: true,
1983
- approvalId,
1984
- task: data.task,
1985
- plan: planOut,
1986
- createdAt: data.createdAt || null
1987
- })
1988
- }
1989
- ],
1990
- isError: false
1991
- };
1992
- }
1776
+ // [REMOVED in v1.14] Conductor/multimodal handlers + pending plan handlers
1993
1777
  async handleGetContext(args) {
1994
1778
  const { query: query2 = "", limit = 10 } = args;
1995
1779
  const contexts = Array.from(this.contexts.values()).sort((a, b) => b.importance - a.importance).slice(0, limit);
@@ -3341,6 +3125,123 @@ ${catLines.join("\n")}`
3341
3125
  const transport = new StdioServerTransport();
3342
3126
  await this.server.connect(transport);
3343
3127
  console.error("StackMemory MCP Server started");
3128
+ const printCacheSummary = () => {
3129
+ if (this.sessionCacheHits > 0 || this.sessionCacheMisses > 0) {
3130
+ const total = this.sessionCacheHits + this.sessionCacheMisses;
3131
+ const rate = (this.sessionCacheHits / total * 100).toFixed(1);
3132
+ console.error(
3133
+ `
3134
+ \u{1F4CA} Cache: ${this.sessionCacheHits}/${total} hits (${rate}%), ~${this.sessionTokensSaved.toLocaleString()} tokens saved`
3135
+ );
3136
+ }
3137
+ };
3138
+ process.on("SIGINT", printCacheSummary);
3139
+ process.on("SIGTERM", printCacheSummary);
3140
+ process.on("exit", printCacheSummary);
3141
+ }
3142
+ // ── Master Task Handlers ───────────────────────────────────
3143
+ resolveMasterTasksPath() {
3144
+ const smPath = join(
3145
+ this.projectRoot,
3146
+ ".stackmemory",
3147
+ "tasks",
3148
+ "master-tasks.md"
3149
+ );
3150
+ if (existsSync(smPath)) return smPath;
3151
+ const rootPath = join(this.projectRoot, "master-tasks.md");
3152
+ if (existsSync(rootPath)) return rootPath;
3153
+ return null;
3154
+ }
3155
+ handleGetNextMasterTask(args) {
3156
+ const mdPath = this.resolveMasterTasksPath();
3157
+ if (!mdPath) {
3158
+ return {
3159
+ content: [
3160
+ {
3161
+ type: "text",
3162
+ text: 'No master-tasks.md found. Run "stackmemory tasks init" to create one.'
3163
+ }
3164
+ ]
3165
+ };
3166
+ }
3167
+ let tasks = parseMasterTasks(readFileSync(mdPath, "utf-8"));
3168
+ if (args.owner) {
3169
+ tasks = tasks.filter((t) => t.owner === String(args.owner));
3170
+ }
3171
+ const next = getNextTask(tasks);
3172
+ if (!next) {
3173
+ return {
3174
+ content: [{ type: "text", text: "No actionable tasks found." }]
3175
+ };
3176
+ }
3177
+ return {
3178
+ content: [{ type: "text", text: JSON.stringify(next, null, 2) }]
3179
+ };
3180
+ }
3181
+ handleUpdateMasterTask(args) {
3182
+ const mdPath = this.resolveMasterTasksPath();
3183
+ if (!mdPath) {
3184
+ return {
3185
+ content: [{ type: "text", text: "No master-tasks.md found." }]
3186
+ };
3187
+ }
3188
+ const taskId = String(args.task_id || "").toUpperCase();
3189
+ if (!taskId) {
3190
+ return {
3191
+ content: [{ type: "text", text: "task_id is required." }],
3192
+ isError: true
3193
+ };
3194
+ }
3195
+ const updates = {};
3196
+ if (args.status) updates.status = String(args.status);
3197
+ if (args.priority) updates.priority = String(args.priority);
3198
+ if (args.owner) updates.owner = String(args.owner);
3199
+ if (args.branch_pr) updates.branchPr = String(args.branch_pr);
3200
+ if (args.notes) updates.notes = String(args.notes);
3201
+ if (args.sync) updates.sync = String(args.sync);
3202
+ try {
3203
+ updateTaskInFile(mdPath, taskId, updates);
3204
+ return {
3205
+ content: [{ type: "text", text: `Updated ${taskId}` }]
3206
+ };
3207
+ } catch (err) {
3208
+ return {
3209
+ content: [{ type: "text", text: err.message }],
3210
+ isError: true
3211
+ };
3212
+ }
3213
+ }
3214
+ handleCreateMasterTask(args) {
3215
+ const mdPath = this.resolveMasterTasksPath();
3216
+ if (!mdPath) {
3217
+ return {
3218
+ content: [
3219
+ {
3220
+ type: "text",
3221
+ text: 'No master-tasks.md found. Run "stackmemory tasks init" to create one.'
3222
+ }
3223
+ ]
3224
+ };
3225
+ }
3226
+ const task = String(args.task || "");
3227
+ if (!task) {
3228
+ return {
3229
+ content: [{ type: "text", text: "task description is required." }],
3230
+ isError: true
3231
+ };
3232
+ }
3233
+ const id = addTaskToFile(mdPath, {
3234
+ priority: String(args.priority || "P1"),
3235
+ status: "todo",
3236
+ owner: String(args.owner || "@me"),
3237
+ sync: String(args.sync || "local"),
3238
+ task,
3239
+ branchPr: "",
3240
+ notes: String(args.notes || "")
3241
+ });
3242
+ return {
3243
+ content: [{ type: "text", text: `Created ${id}: ${task}` }]
3244
+ };
3344
3245
  }
3345
3246
  }
3346
3247
  var server_default = LocalStackMemoryMCP;