@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
@@ -0,0 +1,269 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ import Database from "better-sqlite3";
6
+ import {
7
+ existsSync,
8
+ readFileSync,
9
+ writeFileSync,
10
+ mkdirSync,
11
+ readdirSync
12
+ } from "fs";
13
+ import { join } from "path";
14
+ import { homedir } from "os";
15
+ import { logger } from "../monitoring/logger.js";
16
+ function sanitizeFtsQuery(query) {
17
+ const wantsPrefix = query.trimEnd().endsWith("*");
18
+ const cleaned = query.replace(/['"(){}[\]^~*\\,]/g, " ").replace(/\b(AND|OR|NOT|NEAR)\b/gi, "").trim();
19
+ const terms = cleaned.split(/\s+/).filter((t) => t.length > 0);
20
+ if (terms.length === 0) return '""';
21
+ const quoted = terms.map((t) => `"${t}"`);
22
+ if (wantsPrefix) {
23
+ quoted[quoted.length - 1] = quoted[quoted.length - 1] + "*";
24
+ }
25
+ return quoted.join(" ");
26
+ }
27
+ class CrossProjectSearch {
28
+ registryPath;
29
+ constructor(registryDir) {
30
+ const dir = registryDir || join(homedir(), ".stackmemory");
31
+ if (!existsSync(dir)) {
32
+ mkdirSync(dir, { recursive: true });
33
+ }
34
+ this.registryPath = join(dir, "projects.json");
35
+ }
36
+ // --- Project Registry CRUD ---
37
+ loadRegistry() {
38
+ if (!existsSync(this.registryPath)) {
39
+ return { projects: [] };
40
+ }
41
+ try {
42
+ const raw = readFileSync(this.registryPath, "utf-8");
43
+ return JSON.parse(raw);
44
+ } catch {
45
+ logger.warn("Failed to parse projects.json, returning empty registry");
46
+ return { projects: [] };
47
+ }
48
+ }
49
+ saveRegistry(registry) {
50
+ writeFileSync(this.registryPath, JSON.stringify(registry, null, 2));
51
+ }
52
+ registerProject(entry) {
53
+ const registry = this.loadRegistry();
54
+ const idx = registry.projects.findIndex(
55
+ (p) => p.path === entry.path || p.dbPath === entry.dbPath
56
+ );
57
+ if (idx >= 0) {
58
+ registry.projects[idx] = entry;
59
+ } else {
60
+ registry.projects.push(entry);
61
+ }
62
+ this.saveRegistry(registry);
63
+ }
64
+ unregisterProject(pathOrName) {
65
+ const registry = this.loadRegistry();
66
+ const before = registry.projects.length;
67
+ registry.projects = registry.projects.filter(
68
+ (p) => p.path !== pathOrName && p.name !== pathOrName
69
+ );
70
+ if (registry.projects.length < before) {
71
+ this.saveRegistry(registry);
72
+ return true;
73
+ }
74
+ return false;
75
+ }
76
+ listProjects() {
77
+ return this.loadRegistry().projects;
78
+ }
79
+ /**
80
+ * Auto-discover projects by scanning common directories for .stackmemory/context.db
81
+ */
82
+ discoverProjects(basePaths) {
83
+ const paths = basePaths || [
84
+ join(homedir(), "Dev"),
85
+ join(homedir(), "dev"),
86
+ join(homedir(), "Projects"),
87
+ join(homedir(), "projects"),
88
+ join(homedir(), "Work"),
89
+ join(homedir(), "work"),
90
+ join(homedir(), "code"),
91
+ join(homedir(), "Code")
92
+ ];
93
+ const homeDb = join(homedir(), ".stackmemory", "context.db");
94
+ const discovered = [];
95
+ if (existsSync(homeDb)) {
96
+ discovered.push({
97
+ name: "global",
98
+ path: homedir(),
99
+ dbPath: homeDb,
100
+ lastAccessed: Date.now()
101
+ });
102
+ }
103
+ for (const basePath of paths) {
104
+ if (!existsSync(basePath)) continue;
105
+ try {
106
+ this.scanForDatabases(basePath, 0, 3, discovered);
107
+ } catch {
108
+ }
109
+ }
110
+ const registry = this.loadRegistry();
111
+ for (const entry of discovered) {
112
+ const existing = registry.projects.find((p) => p.dbPath === entry.dbPath);
113
+ if (!existing) {
114
+ registry.projects.push(entry);
115
+ }
116
+ }
117
+ this.saveRegistry(registry);
118
+ return discovered;
119
+ }
120
+ scanForDatabases(dir, depth, maxDepth, results) {
121
+ if (depth > maxDepth) return;
122
+ const dbPath = join(dir, ".stackmemory", "context.db");
123
+ if (existsSync(dbPath)) {
124
+ const name = dir.split("/").pop() || dir;
125
+ results.push({
126
+ name,
127
+ path: dir,
128
+ dbPath,
129
+ lastAccessed: Date.now()
130
+ });
131
+ return;
132
+ }
133
+ try {
134
+ const entries = readdirSync(dir, { withFileTypes: true });
135
+ for (const entry of entries) {
136
+ if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules" && entry.name !== "dist" && entry.name !== "build") {
137
+ this.scanForDatabases(
138
+ join(dir, entry.name),
139
+ depth + 1,
140
+ maxDepth,
141
+ results
142
+ );
143
+ }
144
+ }
145
+ } catch {
146
+ }
147
+ }
148
+ // --- Cross-Project Search ---
149
+ /**
150
+ * Search across all registered project databases using FTS5/BM25.
151
+ * Opens read-only connections. Skips missing/locked databases gracefully.
152
+ */
153
+ async search(options) {
154
+ const { query, limit = 20, excludeProject } = options;
155
+ const registry = this.loadRegistry();
156
+ if (registry.projects.length === 0) {
157
+ return [];
158
+ }
159
+ const allResults = [];
160
+ const perDbLimit = Math.max(limit, 10);
161
+ for (const project of registry.projects) {
162
+ if (excludeProject && project.name === excludeProject) continue;
163
+ if (!existsSync(project.dbPath)) {
164
+ logger.debug(`Skipping missing database: ${project.dbPath}`);
165
+ continue;
166
+ }
167
+ try {
168
+ const results = this.searchSingleDb(project, query, perDbLimit);
169
+ allResults.push(...results);
170
+ } catch (error) {
171
+ logger.debug(
172
+ `Skipping database ${project.dbPath}: ${error instanceof Error ? error.message : String(error)}`
173
+ );
174
+ }
175
+ }
176
+ allResults.sort((a, b) => b.score - a.score);
177
+ return allResults.slice(0, limit);
178
+ }
179
+ /**
180
+ * Search a single project database (read-only connection).
181
+ */
182
+ searchSingleDb(project, query, limit) {
183
+ let db = null;
184
+ try {
185
+ db = new Database(project.dbPath, {
186
+ readonly: true,
187
+ fileMustExist: true
188
+ });
189
+ const hasFts = db.prepare(
190
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='frames_fts'"
191
+ ).get();
192
+ if (hasFts) {
193
+ return this.searchFts(db, project, query, limit);
194
+ } else {
195
+ return this.searchLike(db, project, query, limit);
196
+ }
197
+ } finally {
198
+ if (db) {
199
+ try {
200
+ db.close();
201
+ } catch {
202
+ }
203
+ }
204
+ }
205
+ }
206
+ searchFts(db, project, query, limit) {
207
+ const sanitized = sanitizeFtsQuery(query);
208
+ const sql = `
209
+ SELECT f.frame_id, f.name, f.type, f.state, f.digest_text, f.created_at,
210
+ -bm25(frames_fts, 10.0, 5.0, 2.0, 1.0) as score
211
+ FROM frames_fts fts
212
+ JOIN frames f ON f.rowid = fts.rowid
213
+ WHERE frames_fts MATCH ?
214
+ ORDER BY score DESC
215
+ LIMIT ?
216
+ `;
217
+ const rows = db.prepare(sql).all(sanitized, limit);
218
+ return rows.map((row) => ({
219
+ projectName: project.name,
220
+ projectPath: project.path,
221
+ frameId: row.frame_id,
222
+ name: row.name,
223
+ type: row.type,
224
+ state: row.state,
225
+ digestText: row.digest_text,
226
+ score: row.score,
227
+ createdAt: row.created_at
228
+ }));
229
+ }
230
+ searchLike(db, project, query, limit) {
231
+ const likeParam = `%${query}%`;
232
+ const sql = `
233
+ SELECT frame_id, name, type, state, digest_text, created_at,
234
+ CASE
235
+ WHEN name LIKE ? THEN 1.0
236
+ WHEN digest_text LIKE ? THEN 0.8
237
+ WHEN inputs LIKE ? THEN 0.6
238
+ ELSE 0.5
239
+ END as score
240
+ FROM frames
241
+ WHERE (name LIKE ? OR digest_text LIKE ? OR inputs LIKE ?)
242
+ ORDER BY score DESC
243
+ LIMIT ?
244
+ `;
245
+ const rows = db.prepare(sql).all(
246
+ likeParam,
247
+ likeParam,
248
+ likeParam,
249
+ likeParam,
250
+ likeParam,
251
+ likeParam,
252
+ limit
253
+ );
254
+ return rows.map((row) => ({
255
+ projectName: project.name,
256
+ projectPath: project.path,
257
+ frameId: row.frame_id,
258
+ name: row.name,
259
+ type: row.type,
260
+ state: row.state,
261
+ digestText: row.digest_text,
262
+ score: row.score,
263
+ createdAt: row.created_at
264
+ }));
265
+ }
266
+ }
267
+ export {
268
+ CrossProjectSearch
269
+ };
@@ -2,7 +2,9 @@ import { fileURLToPath as __fileURLToPath } from 'url';
2
2
  import { dirname as __pathDirname } from 'path';
3
3
  const __filename = __fileURLToPath(import.meta.url);
4
4
  const __dirname = __pathDirname(__filename);
5
- export * from "./types.js";
6
- export * from "./conflict-detector.js";
7
- export * from "./stack-diff.js";
8
- export * from "./resolution-engine.js";
5
+ import {
6
+ CrossProjectSearch
7
+ } from "./cross-project-search.js";
8
+ export {
9
+ CrossProjectSearch
10
+ };
@@ -1530,89 +1530,6 @@ class SQLiteAdapter extends FeatureAwareDatabaseAdapter {
1530
1530
  }
1531
1531
  return Buffer.from(JSON.stringify(data, null, 2));
1532
1532
  }
1533
- /**
1534
- * Get recent frames from other run_ids within the same project.
1535
- * Used by team tools to read cross-agent context.
1536
- */
1537
- async getRecentFramesExcludingRun(projectId, excludeRunId, opts) {
1538
- if (!this.db)
1539
- throw new DatabaseError(
1540
- "Database not connected",
1541
- ErrorCode.DB_CONNECTION_FAILED
1542
- );
1543
- const limit = opts?.limit ?? 10;
1544
- const params = [projectId, excludeRunId];
1545
- let whereExtra = "";
1546
- if (opts?.since) {
1547
- whereExtra += " AND created_at > ?";
1548
- params.push(Math.floor(opts.since / 1e3));
1549
- }
1550
- if (opts?.types && opts.types.length > 0) {
1551
- const placeholders = opts.types.map(() => "?").join(",");
1552
- whereExtra += ` AND type IN (${placeholders})`;
1553
- params.push(...opts.types);
1554
- }
1555
- params.push(limit);
1556
- const rows = this.db.prepare(
1557
- `SELECT * FROM frames
1558
- WHERE project_id = ? AND run_id != ?${whereExtra}
1559
- ORDER BY created_at DESC
1560
- LIMIT ?`
1561
- ).all(...params);
1562
- const frameIds = rows.map((r) => r.frame_id);
1563
- const anchorsByFrame = /* @__PURE__ */ new Map();
1564
- if (frameIds.length > 0) {
1565
- const placeholders = frameIds.map(() => "?").join(",");
1566
- const anchorRows = this.db.prepare(
1567
- `SELECT * FROM anchors WHERE frame_id IN (${placeholders})
1568
- ORDER BY priority DESC, created_at ASC`
1569
- ).all(...frameIds);
1570
- for (const row of anchorRows) {
1571
- const list = anchorsByFrame.get(row.frame_id) || [];
1572
- list.push({ ...row, metadata: JSON.parse(row.metadata || "{}") });
1573
- anchorsByFrame.set(row.frame_id, list);
1574
- }
1575
- }
1576
- return rows.map((row) => ({
1577
- ...row,
1578
- inputs: JSON.parse(row.inputs || "{}"),
1579
- outputs: JSON.parse(row.outputs || "{}"),
1580
- digest_json: JSON.parse(row.digest_json || "{}"),
1581
- anchors: anchorsByFrame.get(row.frame_id) || []
1582
- }));
1583
- }
1584
- /**
1585
- * Get anchors explicitly shared for team visibility.
1586
- * Finds anchors where metadata contains `"shared":true`.
1587
- */
1588
- async getSharedAnchors(projectId, opts) {
1589
- if (!this.db)
1590
- throw new DatabaseError(
1591
- "Database not connected",
1592
- ErrorCode.DB_CONNECTION_FAILED
1593
- );
1594
- const limit = opts?.limit ?? 20;
1595
- const params = [projectId];
1596
- let whereExtra = "";
1597
- if (opts?.since) {
1598
- whereExtra += " AND a.created_at > ?";
1599
- params.push(Math.floor(opts.since / 1e3));
1600
- }
1601
- params.push(limit);
1602
- const rows = this.db.prepare(
1603
- `SELECT a.*, f.name as frame_name, f.run_id
1604
- FROM anchors a
1605
- JOIN frames f ON a.frame_id = f.frame_id
1606
- WHERE f.project_id = ?
1607
- AND a.metadata LIKE '%"shared":true%'${whereExtra}
1608
- ORDER BY a.priority DESC, a.created_at DESC
1609
- LIMIT ?`
1610
- ).all(...params);
1611
- return rows.map((row) => ({
1612
- ...row,
1613
- metadata: JSON.parse(row.metadata || "{}")
1614
- }));
1615
- }
1616
1533
  async importData(data, format, options) {
1617
1534
  if (!this.db)
1618
1535
  throw new DatabaseError(
@@ -387,6 +387,11 @@ function createProvider(id, config) {
387
387
  apiKey: config.apiKey,
388
388
  baseUrl: config.baseUrl || "https://openrouter.ai/api"
389
389
  });
390
+ case "moonshot":
391
+ return new GPTAdapter({
392
+ apiKey: config.apiKey,
393
+ baseUrl: config.baseUrl || "https://api.moonshot.ai/v1"
394
+ });
390
395
  default:
391
396
  throw new Error(`No adapter for provider: ${id}`);
392
397
  }
@@ -35,7 +35,10 @@ const MODEL_TOKEN_LIMITS = {
35
35
  // Cerebras
36
36
  "llama-4-scout-17b-16e-instruct": 131072,
37
37
  // DeepInfra
38
- "THUDM/glm-4-9b-chat": 128e3
38
+ "THUDM/glm-4-9b-chat": 128e3,
39
+ // Moonshot (Kimi)
40
+ "kimi-k2.6": 256e3,
41
+ "kimi-k2.5": 256e3
39
42
  };
40
43
  const DEFAULT_MODEL_TOKEN_LIMIT = 2e5;
41
44
  function getModelTokenLimit(model) {
@@ -91,6 +94,12 @@ const DEFAULT_CONFIG = {
91
94
  baseUrl: "https://openrouter.ai/api",
92
95
  apiKeyEnv: "OPENROUTER_API_KEY"
93
96
  },
97
+ moonshot: {
98
+ provider: "moonshot",
99
+ model: "kimi-k2.6",
100
+ baseUrl: "https://api.moonshot.ai/v1",
101
+ apiKeyEnv: "MOONSHOT_API_KEY"
102
+ },
94
103
  "anthropic-batch": {
95
104
  provider: "anthropic-batch",
96
105
  model: "claude-sonnet-4-5-20250929",
@@ -235,8 +244,19 @@ const OPTIMAL_ROUTING = {
235
244
  apiKeyEnv: "ANTHROPIC_API_KEY"
236
245
  }
237
246
  };
238
- const FALLBACK_CHAIN = ["deepinfra", "cerebras", "anthropic"];
247
+ const FALLBACK_CHAIN = [
248
+ "moonshot",
249
+ "deepinfra",
250
+ "cerebras",
251
+ "anthropic"
252
+ ];
239
253
  const CHEAP_PROVIDERS = [
254
+ {
255
+ provider: "moonshot",
256
+ model: "kimi-k2.6",
257
+ apiKeyEnv: "MOONSHOT_API_KEY",
258
+ baseUrl: "https://api.moonshot.ai/v1"
259
+ },
240
260
  {
241
261
  provider: "openrouter",
242
262
  model: "meta-llama/llama-4-scout",
@@ -76,6 +76,7 @@ class Logger {
76
76
  fileLoggingDisabledNotified = false;
77
77
  constructor() {
78
78
  const envLevel = process.env["STACKMEMORY_LOG_LEVEL"]?.toUpperCase();
79
+ const jsonCliMode = !envLevel && process.argv.includes("--json");
79
80
  switch (envLevel) {
80
81
  case "ERROR":
81
82
  this.logLevel = 0 /* ERROR */;
@@ -87,7 +88,7 @@ class Logger {
87
88
  this.logLevel = 3 /* DEBUG */;
88
89
  break;
89
90
  default:
90
- this.logLevel = 2 /* INFO */;
91
+ this.logLevel = jsonCliMode ? 0 /* ERROR */ : 2 /* INFO */;
91
92
  }
92
93
  if (this.logLevel === 3 /* DEBUG */ || process.env["STACKMEMORY_LOG_FILE"]) {
93
94
  this.logFile = process.env["STACKMEMORY_LOG_FILE"] || path.join(