@stackmemoryai/stackmemory 1.10.5 → 1.12.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 (92) hide show
  1. package/README.md +104 -23
  2. package/dist/src/cli/claude-sm.js +266 -84
  3. package/dist/src/cli/codex-sm.js +185 -33
  4. package/dist/src/cli/commands/bench.js +209 -2
  5. package/dist/src/cli/commands/cache.js +126 -0
  6. package/dist/src/cli/commands/daemon.js +41 -0
  7. package/dist/src/cli/commands/handoff.js +40 -9
  8. package/dist/src/cli/commands/onboard.js +70 -3
  9. package/dist/src/cli/commands/optimize.js +117 -0
  10. package/dist/src/cli/commands/orchestrate.js +230 -5
  11. package/dist/src/cli/commands/orchestrator.js +312 -24
  12. package/dist/src/cli/commands/pack.js +322 -0
  13. package/dist/src/cli/commands/search.js +40 -1
  14. package/dist/src/cli/commands/setup.js +177 -7
  15. package/dist/src/cli/commands/skills.js +10 -1
  16. package/dist/src/cli/commands/state.js +265 -0
  17. package/dist/src/cli/gemini-sm.js +19 -29
  18. package/dist/src/cli/index.js +90 -29
  19. package/dist/src/cli/opencode-sm.js +38 -21
  20. package/dist/src/cli/utils/determinism-watcher.js +66 -0
  21. package/dist/src/cli/utils/real-cli-bin.js +44 -0
  22. package/dist/src/core/cache/content-cache.js +238 -0
  23. package/dist/src/core/cache/index.js +11 -0
  24. package/dist/src/core/cache/token-estimator.js +16 -0
  25. package/dist/src/core/context/frame-database.js +38 -30
  26. package/dist/src/core/cross-search/cross-project-search.js +269 -0
  27. package/dist/src/core/{merge → cross-search}/index.js +6 -4
  28. package/dist/src/core/database/sqlite-adapter.js +0 -83
  29. package/dist/src/core/extensions/provider-adapter.js +5 -0
  30. package/dist/src/core/models/model-router.js +22 -2
  31. package/dist/src/core/monitoring/logger.js +2 -1
  32. package/dist/src/core/optimization/trace-optimizer.js +413 -0
  33. package/dist/src/core/provenance/confidence-scorer.js +128 -0
  34. package/dist/src/core/provenance/index.js +40 -0
  35. package/dist/src/core/provenance/provenance-store.js +194 -0
  36. package/dist/src/core/provenance/types.js +82 -0
  37. package/dist/src/core/session/project-handoff.js +64 -0
  38. package/dist/src/core/session/session-manager.js +28 -0
  39. package/dist/src/core/shared-state/canonical-store.js +564 -0
  40. package/dist/src/core/skill-packs/index.js +18 -0
  41. package/dist/src/core/skill-packs/parser.js +42 -0
  42. package/dist/src/core/skill-packs/registry.js +224 -0
  43. package/dist/src/core/skill-packs/types.js +66 -0
  44. package/dist/src/core/trace/trace-event-store.js +282 -0
  45. package/dist/src/core/trace/trace-event.js +4 -0
  46. package/dist/src/daemon/daemon-config.js +7 -0
  47. package/dist/src/daemon/services/github-service.js +126 -0
  48. package/dist/src/daemon/unified-daemon.js +30 -0
  49. package/dist/src/features/sweep/pty-wrapper.js +13 -5
  50. package/dist/src/hooks/schemas.js +2 -0
  51. package/dist/src/integrations/claude-code/subagent-client.js +89 -0
  52. package/dist/src/integrations/github/pr-state.js +158 -0
  53. package/dist/src/integrations/linear/client.js +4 -1
  54. package/dist/src/integrations/mcp/handlers/cross-search-handlers.js +188 -0
  55. package/dist/src/integrations/mcp/handlers/index.js +40 -59
  56. package/dist/src/integrations/mcp/server.js +425 -311
  57. package/dist/src/integrations/mcp/tool-alias-registry.js +370 -0
  58. package/dist/src/integrations/mcp/tool-definitions.js +98 -229
  59. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +3 -40
  60. package/dist/src/integrations/ralph/learning/pattern-learner.js +1 -20
  61. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -2
  62. package/dist/src/mcp/stackmemory-mcp-server.js +315 -0
  63. package/dist/src/orchestrators/multimodal/determinism.js +243 -0
  64. package/dist/src/orchestrators/multimodal/harness.js +147 -77
  65. package/dist/src/orchestrators/multimodal/providers.js +44 -3
  66. package/dist/src/utils/hook-installer.js +0 -8
  67. package/package.json +10 -1
  68. package/packs/coding/python-fastapi/instructions.md +60 -0
  69. package/packs/coding/python-fastapi/pack.yaml +28 -0
  70. package/packs/coding/typescript-react/instructions.md +47 -0
  71. package/packs/coding/typescript-react/pack.yaml +28 -0
  72. package/packs/core/commands/capture.md +32 -0
  73. package/packs/core/commands/learn.md +73 -0
  74. package/packs/core/commands/next.md +36 -0
  75. package/packs/core/commands/restart.md +58 -0
  76. package/packs/core/commands/restore.md +29 -0
  77. package/packs/core/commands/start.md +57 -0
  78. package/packs/core/commands/stop.md +65 -0
  79. package/packs/core/commands/summary.md +40 -0
  80. package/packs/core/manifest.json +24 -0
  81. package/packs/ops/decision-recovery/instructions.md +65 -0
  82. package/packs/ops/decision-recovery/pack.yaml +89 -0
  83. package/dist/src/cli/commands/team.js +0 -168
  84. package/dist/src/core/context/shared-context-layer.js +0 -620
  85. package/dist/src/core/context/stack-merge-resolver.js +0 -748
  86. package/dist/src/core/merge/conflict-detector.js +0 -430
  87. package/dist/src/core/merge/resolution-engine.js +0 -557
  88. package/dist/src/core/merge/stack-diff.js +0 -531
  89. package/dist/src/core/merge/unified-merge-resolver.js +0 -302
  90. package/dist/src/integrations/mcp/handlers/cord-handlers.js +0 -397
  91. package/dist/src/integrations/mcp/handlers/team-handlers.js +0 -211
  92. /package/dist/src/core/{merge → cache}/types.js +0 -0
@@ -29,20 +29,24 @@ import { execSync } from "child_process";
29
29
  import { FrameManager } from "../../core/context/index.js";
30
30
  import { logger } from "../../core/monitoring/logger.js";
31
31
  import { isFeatureEnabled } from "../../core/config/feature-flags.js";
32
+ import { ContentCache } from "../../core/cache/index.js";
33
+ import { TraceEventStore } from "../../core/trace/trace-event-store.js";
32
34
  import { BrowserMCPIntegration } from "../../features/browser/browser-mcp.js";
33
35
  import { TraceDetector } from "../../core/trace/trace-detector.js";
34
36
  import { LLMContextRetrieval } from "../../core/retrieval/index.js";
35
37
  import { DiscoveryHandlers } from "./handlers/discovery-handlers.js";
36
38
  import { DiffMemHandlers } from "./handlers/diffmem-handlers.js";
37
39
  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";
40
+ import { CrossSearchHandlers } from "./handlers/cross-search-handlers.js";
41
41
  import {
42
42
  generateChronologicalDigest
43
43
  } from "../../core/digest/chronological-digest.js";
44
44
  import { fuzzyEdit } from "../../utils/fuzzy-edit.js";
45
45
  import { v4 as uuidv4 } from "uuid";
46
+ import {
47
+ resolveToolAlias,
48
+ resolveParamAliases
49
+ } from "./tool-alias-registry.js";
46
50
  import {
47
51
  DEFAULT_PLANNER_MODEL,
48
52
  DEFAULT_IMPLEMENTER,
@@ -59,6 +63,35 @@ function _getEnv(key, defaultValue) {
59
63
  function _getOptionalEnv(key) {
60
64
  return process.env[key];
61
65
  }
66
+ const CACHEABLE_TOOLS = /* @__PURE__ */ new Set([
67
+ "get_context",
68
+ "get_hot_stack",
69
+ "get_active_tasks",
70
+ "get_task_metrics",
71
+ "get_traces",
72
+ "get_trace_statistics",
73
+ "smart_context",
74
+ "get_summary",
75
+ "sm_discover",
76
+ "sm_related_files",
77
+ "sm_session_summary",
78
+ "sm_search",
79
+ "sm_cross_search",
80
+ "sm_cross_discover",
81
+ "sm_cross_list",
82
+ "diffmem_get_user_context",
83
+ "diffmem_search",
84
+ "diffmem_status",
85
+ "greptile_list_prs",
86
+ "greptile_pr_details",
87
+ "greptile_pr_comments",
88
+ "greptile_status",
89
+ "linear_get_tasks",
90
+ "linear_status",
91
+ "provenant_search",
92
+ "provenant_status",
93
+ "provenant_contradictions"
94
+ ]);
62
95
  class LocalStackMemoryMCP {
63
96
  server;
64
97
  db;
@@ -76,9 +109,14 @@ class LocalStackMemoryMCP {
76
109
  diffMemHandlers;
77
110
  greptileHandlers;
78
111
  providerHandlers = null;
79
- cordHandlers = null;
80
- teamHandlers = null;
112
+ crossSearchHandlers;
81
113
  pendingPlans = /* @__PURE__ */ new Map();
114
+ contentCache;
115
+ traceEventStore;
116
+ sessionId;
117
+ sessionTokensSaved = 0;
118
+ sessionCacheHits = 0;
119
+ sessionCacheMisses = 0;
82
120
  constructor() {
83
121
  this.projectRoot = this.findProjectRoot();
84
122
  this.projectId = this.getProjectId();
@@ -120,6 +158,9 @@ class LocalStackMemoryMCP {
120
158
  );
121
159
  CREATE INDEX IF NOT EXISTS idx_edit_telemetry_ts ON edit_telemetry(timestamp);
122
160
  `);
161
+ this.contentCache = new ContentCache(this.db);
162
+ this.traceEventStore = new TraceEventStore(this.db);
163
+ this.sessionId = uuidv4();
123
164
  this.frameManager = new FrameManager(this.db, this.projectId);
124
165
  this.initLinearIfEnabled();
125
166
  this.server = new Server(
@@ -151,7 +192,7 @@ class LocalStackMemoryMCP {
151
192
  });
152
193
  this.diffMemHandlers = new DiffMemHandlers();
153
194
  this.greptileHandlers = new GreptileHandlers();
154
- this.initCordTeamHandlers();
195
+ this.crossSearchHandlers = new CrossSearchHandlers({});
155
196
  this.initProviderHandlers();
156
197
  this.setupHandlers();
157
198
  this.loadInitialContext();
@@ -166,6 +207,105 @@ class LocalStackMemoryMCP {
166
207
  projectId: this.projectId
167
208
  });
168
209
  }
210
+ // ------------------------------------------------------------------
211
+ // Content-hash cache helpers
212
+ // ------------------------------------------------------------------
213
+ /**
214
+ * Build a deterministic cache key from tool name + sorted args.
215
+ * Returns null for non-cacheable tools or empty args.
216
+ */
217
+ buildCacheKey(tool, args) {
218
+ if (!CACHEABLE_TOOLS.has(tool)) return null;
219
+ const sorted = JSON.stringify(args, Object.keys(args).sort());
220
+ return `${tool}:${sorted}`;
221
+ }
222
+ handleCacheStats() {
223
+ const stats = this.contentCache.getStats();
224
+ return {
225
+ content: [
226
+ {
227
+ type: "text",
228
+ text: JSON.stringify({
229
+ session: {
230
+ cacheHits: this.sessionCacheHits,
231
+ cacheMisses: this.sessionCacheMisses,
232
+ tokensSaved: this.sessionTokensSaved,
233
+ hitRate: this.sessionCacheHits + this.sessionCacheMisses > 0 ? this.sessionCacheHits / (this.sessionCacheHits + this.sessionCacheMisses) : 0
234
+ },
235
+ lifetime: stats
236
+ })
237
+ }
238
+ ],
239
+ isError: false
240
+ };
241
+ }
242
+ handleCacheLookup(args) {
243
+ const content = String(args.content ?? "");
244
+ if (!content) {
245
+ return {
246
+ content: [
247
+ {
248
+ type: "text",
249
+ text: JSON.stringify({ error: "content is required" })
250
+ }
251
+ ],
252
+ isError: true
253
+ };
254
+ }
255
+ const result = this.contentCache.lookup(content, String(args.source ?? ""));
256
+ if (result.hit) {
257
+ this.sessionCacheHits++;
258
+ this.sessionTokensSaved += result.tokensSaved;
259
+ }
260
+ return {
261
+ content: [{ type: "text", text: JSON.stringify(result) }],
262
+ isError: false
263
+ };
264
+ }
265
+ // ------------------------------------------------------------------
266
+ // Trace event handlers
267
+ // ------------------------------------------------------------------
268
+ handleTraceEvents(args) {
269
+ const events = this.traceEventStore.query({
270
+ session_id: args.session_id,
271
+ operation: args.operation,
272
+ min_score: args.min_score,
273
+ has_feedback: args.has_feedback,
274
+ limit: args.limit ?? 50
275
+ });
276
+ return {
277
+ content: [{ type: "text", text: JSON.stringify(events) }],
278
+ isError: false
279
+ };
280
+ }
281
+ handleTraceEventStats(args) {
282
+ const stats = this.traceEventStore.getStats({
283
+ session_id: args.session_id
284
+ });
285
+ return {
286
+ content: [{ type: "text", text: JSON.stringify(stats) }],
287
+ isError: false
288
+ };
289
+ }
290
+ handleTraceEventAnnotate(args) {
291
+ const id = String(args.id ?? "");
292
+ if (!id) {
293
+ return {
294
+ content: [
295
+ { type: "text", text: JSON.stringify({ error: "id is required" }) }
296
+ ],
297
+ isError: true
298
+ };
299
+ }
300
+ const ok = this.traceEventStore.annotate(id, {
301
+ score: args.score,
302
+ feedback: args.feedback
303
+ });
304
+ return {
305
+ content: [{ type: "text", text: JSON.stringify({ ok, id }) }],
306
+ isError: false
307
+ };
308
+ }
169
309
  findProjectRoot() {
170
310
  let dir = process.cwd();
171
311
  while (dir !== "/") {
@@ -276,27 +416,6 @@ ${summary}...`, 0.8);
276
416
  this.contexts.set(ctx.id, ctx);
277
417
  });
278
418
  }
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
419
  async initProviderHandlers() {
301
420
  if (!isFeatureEnabled("multiProvider")) return;
302
421
  try {
@@ -369,6 +488,11 @@ ${summary}...`, 0.8);
369
488
  description: "Which agent implements code"
370
489
  },
371
490
  maxIters: { type: "number", default: 2 },
491
+ verificationCommands: {
492
+ type: "array",
493
+ items: { type: "string" },
494
+ description: "Optional repro/test commands that must pass after implementation"
495
+ },
372
496
  recordFrame: { type: "boolean", default: true },
373
497
  execute: { type: "boolean", default: true }
374
498
  },
@@ -972,190 +1096,6 @@ ${summary}...`, 0.8);
972
1096
  },
973
1097
  // DiffMem tools (only when DIFFMEM_ENDPOINT or DIFFMEM_ENABLED is set)
974
1098
  ...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
1099
  // Greptile tools (only active when GREPTILE_API_KEY is set)
1160
1100
  ...process.env.GREPTILE_API_KEY ? this.greptileHandlers.getToolDefinitions() : [],
1161
1101
  // Provider tools (only active when STACKMEMORY_MULTI_PROVIDER=true)
@@ -1274,6 +1214,143 @@ ${summary}...`, 0.8);
1274
1214
  },
1275
1215
  required: ["period"]
1276
1216
  }
1217
+ },
1218
+ // Cross-project search tools
1219
+ {
1220
+ name: "sm_cross_search",
1221
+ description: "Search frames across all registered project databases using FTS5/BM25. Returns results ranked by relevance with source project attribution.",
1222
+ inputSchema: {
1223
+ type: "object",
1224
+ properties: {
1225
+ query: {
1226
+ type: "string",
1227
+ description: "Search query (natural language or keywords)"
1228
+ },
1229
+ limit: {
1230
+ type: "number",
1231
+ default: 20,
1232
+ description: "Maximum results to return"
1233
+ },
1234
+ exclude_current: {
1235
+ type: "boolean",
1236
+ default: false,
1237
+ description: "Exclude the current project from results"
1238
+ }
1239
+ },
1240
+ required: ["query"]
1241
+ }
1242
+ },
1243
+ {
1244
+ name: "sm_cross_discover",
1245
+ description: "Auto-discover project databases by scanning common directories for .stackmemory/context.db files.",
1246
+ inputSchema: {
1247
+ type: "object",
1248
+ properties: {
1249
+ paths: {
1250
+ type: "array",
1251
+ items: { type: "string" },
1252
+ description: "Custom directory paths to scan"
1253
+ }
1254
+ }
1255
+ }
1256
+ },
1257
+ {
1258
+ name: "sm_cross_register",
1259
+ description: "Manually register a project database for cross-project search.",
1260
+ inputSchema: {
1261
+ type: "object",
1262
+ properties: {
1263
+ name: {
1264
+ type: "string",
1265
+ description: "Project display name"
1266
+ },
1267
+ path: {
1268
+ type: "string",
1269
+ description: "Project root directory path"
1270
+ },
1271
+ db_path: {
1272
+ type: "string",
1273
+ description: "Path to the SQLite context.db file"
1274
+ }
1275
+ },
1276
+ required: ["name", "path", "db_path"]
1277
+ }
1278
+ },
1279
+ {
1280
+ name: "sm_cross_list",
1281
+ description: "List all project databases registered for cross-project search.",
1282
+ inputSchema: {
1283
+ type: "object",
1284
+ properties: {}
1285
+ }
1286
+ },
1287
+ // Cache tools
1288
+ {
1289
+ name: "cache_stats",
1290
+ 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.",
1291
+ inputSchema: {
1292
+ type: "object",
1293
+ properties: {}
1294
+ }
1295
+ },
1296
+ {
1297
+ name: "cache_lookup",
1298
+ description: "Check if content is already cached. Returns hit/miss and token savings. Use for explicit deduplication before sending large content.",
1299
+ inputSchema: {
1300
+ type: "object",
1301
+ properties: {
1302
+ content: {
1303
+ type: "string",
1304
+ description: "The content to check against the cache."
1305
+ },
1306
+ source: {
1307
+ type: "string",
1308
+ description: 'Optional source label (e.g., "file:src/index.ts").'
1309
+ }
1310
+ },
1311
+ required: ["content"]
1312
+ }
1313
+ },
1314
+ // Trace event tools
1315
+ {
1316
+ name: "trace_events",
1317
+ description: "Query ASI-shaped trace events. Filter by session, operation, min score, or feedback presence. Returns events with provenance, cost, and token data.",
1318
+ inputSchema: {
1319
+ type: "object",
1320
+ properties: {
1321
+ session_id: { type: "string" },
1322
+ operation: { type: "string" },
1323
+ min_score: { type: "number" },
1324
+ has_feedback: { type: "boolean" },
1325
+ limit: { type: "number" }
1326
+ }
1327
+ }
1328
+ },
1329
+ {
1330
+ name: "trace_event_stats",
1331
+ description: "Get aggregate trace event statistics: total tokens, cost, operation counts, host distribution.",
1332
+ inputSchema: {
1333
+ type: "object",
1334
+ properties: {
1335
+ session_id: { type: "string" }
1336
+ }
1337
+ }
1338
+ },
1339
+ {
1340
+ name: "trace_event_annotate",
1341
+ description: "Add a numeric score and/or textual feedback to a trace event. Used by GEPA-class optimizers.",
1342
+ inputSchema: {
1343
+ type: "object",
1344
+ properties: {
1345
+ id: { type: "string", description: "Trace event ID" },
1346
+ score: { type: "number", description: "Numeric score (0-1)" },
1347
+ feedback: {
1348
+ type: "string",
1349
+ description: "Textual ASI feedback"
1350
+ }
1351
+ },
1352
+ required: ["id"]
1353
+ }
1277
1354
  }
1278
1355
  ]
1279
1356
  };
@@ -1288,7 +1365,18 @@ ${summary}...`, 0.8);
1288
1365
  })
1289
1366
  }),
1290
1367
  async (request) => {
1291
- const { name, arguments: args } = request.params;
1368
+ const { name: rawName, arguments: rawArgs } = request.params;
1369
+ const aliasResolution = resolveToolAlias(rawName);
1370
+ const name = aliasResolution.canonicalName;
1371
+ const paramResolution = resolveParamAliases(name, rawArgs);
1372
+ const args = paramResolution.resolvedParams;
1373
+ if (aliasResolution.wasAlias || Object.keys(paramResolution.renames).length > 0) {
1374
+ logger.debug("Tool alias resolved", {
1375
+ originalTool: rawName,
1376
+ canonicalTool: name,
1377
+ paramRenames: paramResolution.renames
1378
+ });
1379
+ }
1292
1380
  const callId = uuidv4();
1293
1381
  const startTime = Date.now();
1294
1382
  const currentFrameId = this.frameManager.getCurrentFrameId();
@@ -1296,7 +1384,8 @@ ${summary}...`, 0.8);
1296
1384
  this.frameManager.addEvent("tool_call", {
1297
1385
  tool_name: name,
1298
1386
  arguments: args,
1299
- timestamp: startTime
1387
+ timestamp: startTime,
1388
+ ...aliasResolution.wasAlias ? { alias_from: rawName } : {}
1300
1389
  });
1301
1390
  }
1302
1391
  const toolCall = {
@@ -1305,6 +1394,28 @@ ${summary}...`, 0.8);
1305
1394
  arguments: args,
1306
1395
  timestamp: startTime
1307
1396
  };
1397
+ const cacheKey = this.buildCacheKey(name, args);
1398
+ if (cacheKey) {
1399
+ const cached = this.contentCache.lookupByKey(
1400
+ cacheKey,
1401
+ `tool:${name}`
1402
+ );
1403
+ if (cached.hit && cached.entry) {
1404
+ this.sessionCacheHits++;
1405
+ this.sessionTokensSaved += cached.tokensSaved;
1406
+ const total = this.sessionCacheHits + this.sessionCacheMisses;
1407
+ const rate = (this.sessionCacheHits / total * 100).toFixed(0);
1408
+ console.error(
1409
+ `[cache] HIT ${name} \u2014 saved ~${cached.tokensSaved} tokens (session: ${this.sessionTokensSaved.toLocaleString()} total, ${rate}% hit rate)`
1410
+ );
1411
+ const cachedResult = JSON.parse(cached.entry.content);
1412
+ toolCall.result = cachedResult;
1413
+ toolCall.duration = Date.now() - startTime;
1414
+ this.traceDetector.addToolCall(toolCall);
1415
+ return cachedResult;
1416
+ }
1417
+ this.sessionCacheMisses++;
1418
+ }
1308
1419
  let result;
1309
1420
  let error;
1310
1421
  try {
@@ -1453,104 +1564,6 @@ ${summary}...`, 0.8);
1453
1564
  case "sm_edit":
1454
1565
  result = await this.handleSmEdit(args);
1455
1566
  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
- }
1504
- 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
- }
1516
- 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
- }
1529
- 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
- }
1541
- 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
- }
1553
- break;
1554
1567
  // Provider tools
1555
1568
  case "delegate_to_model":
1556
1569
  if (!this.providerHandlers) {
@@ -1609,6 +1622,35 @@ ${summary}...`, 0.8);
1609
1622
  case "sm_desire_paths":
1610
1623
  result = this.handleDesirePaths(args);
1611
1624
  break;
1625
+ case "sm_cross_search":
1626
+ result = await this.crossSearchHandlers.handleCrossSearch(args);
1627
+ break;
1628
+ case "sm_cross_discover":
1629
+ result = await this.crossSearchHandlers.handleCrossDiscover(args);
1630
+ break;
1631
+ case "sm_cross_register":
1632
+ result = await this.crossSearchHandlers.handleCrossRegister(args);
1633
+ break;
1634
+ case "sm_cross_list":
1635
+ result = await this.crossSearchHandlers.handleCrossList();
1636
+ break;
1637
+ // Cache tools
1638
+ case "cache_stats":
1639
+ result = this.handleCacheStats();
1640
+ break;
1641
+ case "cache_lookup":
1642
+ result = this.handleCacheLookup(args);
1643
+ break;
1644
+ // Trace event tools
1645
+ case "trace_events":
1646
+ result = this.handleTraceEvents(args);
1647
+ break;
1648
+ case "trace_event_stats":
1649
+ result = this.handleTraceEventStats(args);
1650
+ break;
1651
+ case "trace_event_annotate":
1652
+ result = this.handleTraceEventAnnotate(args);
1653
+ break;
1612
1654
  default:
1613
1655
  throw new Error(`Unknown tool: ${name}`);
1614
1656
  }
@@ -1619,6 +1661,22 @@ ${summary}...`, 0.8);
1619
1661
  throw err;
1620
1662
  } finally {
1621
1663
  const endTime = Date.now();
1664
+ if (!error && result && cacheKey) {
1665
+ try {
1666
+ const serialized = JSON.stringify(result);
1667
+ const entry = this.contentCache.putByKey(
1668
+ cacheKey,
1669
+ serialized,
1670
+ `tool:${name}`
1671
+ );
1672
+ if (entry.hitCount === 0) {
1673
+ console.error(
1674
+ `[cache] STORE ${name} \u2014 ~${entry.tokenCount} tokens cached for future dedup`
1675
+ );
1676
+ }
1677
+ } catch {
1678
+ }
1679
+ }
1622
1680
  if (currentFrameId && name !== "close_frame") {
1623
1681
  try {
1624
1682
  this.frameManager.addEvent("tool_result", {
@@ -1641,11 +1699,50 @@ ${summary}...`, 0.8);
1641
1699
  toolCall.filesAffected = Array.isArray(files) ? files : [files];
1642
1700
  }
1643
1701
  this.traceDetector.addToolCall(toolCall);
1702
+ try {
1703
+ const traceEvent = {
1704
+ timestamp: new Date(startTime).toISOString(),
1705
+ session_id: this.sessionId,
1706
+ trace_id: callId,
1707
+ tenant_id: "local",
1708
+ actor: {
1709
+ host: process.env["STACKMEMORY_HOST"] || "claude-code",
1710
+ agent: "stackmemory-mcp",
1711
+ user: process.env["USER"] || "anonymous"
1712
+ },
1713
+ operation: name,
1714
+ inputs: args,
1715
+ outputs: error ? { error: error.message } : result ?? {},
1716
+ tokens_in: 0,
1717
+ tokens_out: 0,
1718
+ cost_usd: 0,
1719
+ duration_ms: endTime - startTime,
1720
+ error: error?.message,
1721
+ provenance: {
1722
+ sources: [{ type: "tool", id: name }],
1723
+ derivation: ["mcp-call"],
1724
+ confidence: 1
1725
+ }
1726
+ };
1727
+ this.traceEventStore.record(traceEvent);
1728
+ } catch {
1729
+ }
1644
1730
  }
1645
1731
  return result;
1646
1732
  }
1647
1733
  );
1648
1734
  }
1735
+ getVerificationCommands(args) {
1736
+ const commands = args.verificationCommands ?? args.verifyCommands;
1737
+ if (Array.isArray(commands)) {
1738
+ return commands.map((command) => String(command).trim()).filter(Boolean);
1739
+ }
1740
+ const single = args.verificationCommand ?? args.verifyCommand;
1741
+ if (typeof single === "string" && single.trim()) {
1742
+ return [single.trim()];
1743
+ }
1744
+ return [];
1745
+ }
1649
1746
  // Handle plan_and_code tool by invoking the mm harness
1650
1747
  async handlePlanAndCode(args) {
1651
1748
  const { runSpike } = await import("../../orchestrators/multimodal/harness.js");
@@ -1660,6 +1757,7 @@ ${summary}...`, 0.8);
1660
1757
  const record = Boolean(args.record);
1661
1758
  const recordFrame = Boolean(args.recordFrame);
1662
1759
  const compact = Boolean(args.compact);
1760
+ const verificationCommands = this.getVerificationCommands(args);
1663
1761
  const task = String(args.task || "Plan and implement change");
1664
1762
  const result = await runSpike(
1665
1763
  {
@@ -1673,6 +1771,7 @@ ${summary}...`, 0.8);
1673
1771
  maxIters: isFinite(maxIters) ? Math.max(1, maxIters) : 2,
1674
1772
  dryRun: !execute,
1675
1773
  auditDir: void 0,
1774
+ verificationCommands,
1676
1775
  recordFrame
1677
1776
  }
1678
1777
  );
@@ -1833,6 +1932,7 @@ ${summary}...`, 0.8);
1833
1932
  );
1834
1933
  const recordFrame = args.recordFrame !== false;
1835
1934
  const execute = args.execute !== false;
1935
+ const verificationCommands = this.getVerificationCommands(args);
1836
1936
  const result = await runSpike(
1837
1937
  { task: pending.task, repoPath: this.projectRoot },
1838
1938
  {
@@ -1841,6 +1941,7 @@ ${summary}...`, 0.8);
1841
1941
  implementer: implementer === "claude" ? "claude" : "codex",
1842
1942
  maxIters: isFinite(maxIters) ? Math.max(1, maxIters) : 2,
1843
1943
  dryRun: !execute,
1944
+ verificationCommands,
1844
1945
  recordFrame
1845
1946
  }
1846
1947
  );
@@ -3341,6 +3442,19 @@ ${catLines.join("\n")}`
3341
3442
  const transport = new StdioServerTransport();
3342
3443
  await this.server.connect(transport);
3343
3444
  console.error("StackMemory MCP Server started");
3445
+ const printCacheSummary = () => {
3446
+ if (this.sessionCacheHits > 0 || this.sessionCacheMisses > 0) {
3447
+ const total = this.sessionCacheHits + this.sessionCacheMisses;
3448
+ const rate = (this.sessionCacheHits / total * 100).toFixed(1);
3449
+ console.error(
3450
+ `
3451
+ \u{1F4CA} Cache: ${this.sessionCacheHits}/${total} hits (${rate}%), ~${this.sessionTokensSaved.toLocaleString()} tokens saved`
3452
+ );
3453
+ }
3454
+ };
3455
+ process.on("SIGINT", printCacheSummary);
3456
+ process.on("SIGTERM", printCacheSummary);
3457
+ process.on("exit", printCacheSummary);
3344
3458
  }
3345
3459
  }
3346
3460
  var server_default = LocalStackMemoryMCP;