@stackmemoryai/stackmemory 1.2.2 → 1.2.4

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 (63) hide show
  1. package/dist/src/cli/codex-sm.js +6 -8
  2. package/dist/src/cli/commands/config.js +0 -81
  3. package/dist/src/cli/commands/context-rehydrate.js +133 -47
  4. package/dist/src/cli/commands/db.js +35 -8
  5. package/dist/src/cli/commands/handoff.js +1 -1
  6. package/dist/src/cli/commands/linear.js +9 -0
  7. package/dist/src/cli/commands/ralph.js +2 -2
  8. package/dist/src/cli/commands/setup.js +2 -2
  9. package/dist/src/cli/commands/signup.js +3 -1
  10. package/dist/src/cli/commands/storage-tier.js +26 -8
  11. package/dist/src/cli/index.js +1 -57
  12. package/dist/src/core/config/feature-flags.js +0 -4
  13. package/dist/src/core/context/dual-stack-manager.js +10 -3
  14. package/dist/src/core/context/frame-database.js +32 -0
  15. package/dist/src/core/context/frame-handoff-manager.js +2 -2
  16. package/dist/src/core/context/{refactored-frame-manager.js → frame-manager.js} +3 -3
  17. package/dist/src/core/context/index.js +2 -2
  18. package/dist/src/core/database/sqlite-adapter.js +161 -1
  19. package/dist/src/core/digest/frame-digest-integration.js +1 -1
  20. package/dist/src/core/digest/index.js +1 -1
  21. package/dist/src/core/execution/parallel-executor.js +5 -1
  22. package/dist/src/core/projects/project-isolation.js +18 -4
  23. package/dist/src/core/security/index.js +2 -0
  24. package/dist/src/core/security/input-sanitizer.js +23 -0
  25. package/dist/src/core/utils/update-checker.js +10 -6
  26. package/dist/src/daemon/daemon-config.js +2 -1
  27. package/dist/src/daemon/services/auto-save-service.js +121 -0
  28. package/dist/src/daemon/services/maintenance-service.js +76 -1
  29. package/dist/src/features/sweep/prompt-builder.js +2 -2
  30. package/dist/src/integrations/linear/config.js +3 -1
  31. package/dist/src/integrations/linear/sync.js +18 -5
  32. package/dist/src/integrations/mcp/handlers/code-execution-handlers.js +33 -7
  33. package/dist/src/integrations/mcp/handlers/cord-handlers.js +397 -0
  34. package/dist/src/integrations/mcp/handlers/index.js +55 -1
  35. package/dist/src/integrations/mcp/handlers/task-handlers.js +55 -12
  36. package/dist/src/integrations/mcp/handlers/team-handlers.js +211 -0
  37. package/dist/src/integrations/mcp/handlers/trace-handlers.js +25 -9
  38. package/dist/src/integrations/mcp/index.js +2 -2
  39. package/dist/src/integrations/mcp/refactored-server.js +31 -10
  40. package/dist/src/integrations/mcp/tool-definitions.js +215 -1
  41. package/dist/src/integrations/ralph/context/context-budget-manager.js +10 -2
  42. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +54 -22
  43. package/dist/src/integrations/ralph/learning/pattern-learner.js +59 -24
  44. package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +81 -35
  45. package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +12 -4
  46. package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +32 -9
  47. package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +25 -8
  48. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +17 -5
  49. package/dist/src/integrations/ralph/visualization/ralph-debugger.js +73 -22
  50. package/dist/src/skills/claude-skills.js +0 -102
  51. package/dist/src/utils/hook-installer.js +8 -0
  52. package/package.json +5 -5
  53. package/scripts/install-claude-hooks-auto.js +8 -0
  54. package/templates/claude-hooks/cord-trace.js +225 -0
  55. package/dist/src/core/config/storage-config.js +0 -114
  56. package/dist/src/core/storage/chromadb-adapter.js +0 -379
  57. package/dist/src/integrations/claude-code/enhanced-pre-clear-hooks.js +0 -458
  58. package/dist/src/integrations/ralph/coordination/enhanced-coordination.js +0 -409
  59. package/dist/src/skills/repo-ingestion-skill.js +0 -631
  60. package/templates/claude-hooks/chromadb-wrapper +0 -21
  61. /package/dist/src/core/context/{enhanced-rehydration.js → rehydration.js} +0 -0
  62. /package/dist/src/core/digest/{enhanced-hybrid-digest.js → hybrid-digest.js} +0 -0
  63. /package/dist/src/core/session/{enhanced-handoff.js → handoff.js} +0 -0
@@ -0,0 +1,121 @@
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 { existsSync } from "fs";
6
+ import { join } from "path";
7
+ import { execSync } from "child_process";
8
+ import { homedir } from "os";
9
+ class DaemonContextService {
10
+ config;
11
+ state;
12
+ intervalId;
13
+ isRunning = false;
14
+ onLog;
15
+ constructor(config, onLog) {
16
+ this.config = config;
17
+ this.onLog = onLog;
18
+ this.state = {
19
+ lastSaveTime: 0,
20
+ saveCount: 0,
21
+ errors: []
22
+ };
23
+ }
24
+ start() {
25
+ if (this.isRunning || !this.config.enabled) {
26
+ return;
27
+ }
28
+ this.isRunning = true;
29
+ const intervalMs = this.config.interval * 60 * 1e3;
30
+ this.onLog("INFO", "Context service started", {
31
+ interval: this.config.interval
32
+ });
33
+ this.saveContext();
34
+ this.intervalId = setInterval(() => {
35
+ this.saveContext();
36
+ }, intervalMs);
37
+ }
38
+ stop() {
39
+ if (this.intervalId) {
40
+ clearInterval(this.intervalId);
41
+ this.intervalId = void 0;
42
+ }
43
+ this.isRunning = false;
44
+ this.onLog("INFO", "Context service stopped");
45
+ }
46
+ getState() {
47
+ return { ...this.state };
48
+ }
49
+ updateConfig(config) {
50
+ const wasRunning = this.isRunning;
51
+ if (wasRunning) {
52
+ this.stop();
53
+ }
54
+ this.config = { ...this.config, ...config };
55
+ if (wasRunning && this.config.enabled) {
56
+ this.start();
57
+ }
58
+ }
59
+ forceSave() {
60
+ this.saveContext();
61
+ }
62
+ saveContext() {
63
+ if (!this.isRunning) return;
64
+ try {
65
+ const stackmemoryBin = this.getStackMemoryBin();
66
+ if (!stackmemoryBin) {
67
+ this.onLog("WARN", "StackMemory binary not found");
68
+ return;
69
+ }
70
+ const message = this.config.checkpointMessage || `Auto-checkpoint #${this.state.saveCount + 1}`;
71
+ const fullMessage = `${message} at ${(/* @__PURE__ */ new Date()).toISOString()}`;
72
+ execSync(`"${stackmemoryBin}" context add observation "${fullMessage}"`, {
73
+ timeout: 3e4,
74
+ encoding: "utf8",
75
+ stdio: "pipe"
76
+ });
77
+ this.state.saveCount++;
78
+ this.state.lastSaveTime = Date.now();
79
+ this.onLog("INFO", "Context saved", {
80
+ saveCount: this.state.saveCount
81
+ });
82
+ } catch (err) {
83
+ const errorMsg = err instanceof Error ? err.message : String(err);
84
+ if (!errorMsg.includes("EBUSY") && !errorMsg.includes("EAGAIN")) {
85
+ this.state.errors.push(errorMsg);
86
+ this.onLog("WARN", "Failed to save context", { error: errorMsg });
87
+ if (this.state.errors.length > 10) {
88
+ this.state.errors = this.state.errors.slice(-10);
89
+ }
90
+ }
91
+ }
92
+ }
93
+ getStackMemoryBin() {
94
+ const homeDir = homedir();
95
+ const locations = [
96
+ join(homeDir, ".stackmemory", "bin", "stackmemory"),
97
+ join(homeDir, ".local", "bin", "stackmemory"),
98
+ "/usr/local/bin/stackmemory",
99
+ "/opt/homebrew/bin/stackmemory"
100
+ ];
101
+ for (const loc of locations) {
102
+ if (existsSync(loc)) {
103
+ return loc;
104
+ }
105
+ }
106
+ try {
107
+ const result = execSync("which stackmemory", {
108
+ encoding: "utf8",
109
+ stdio: "pipe"
110
+ }).trim();
111
+ if (result && existsSync(result)) {
112
+ return result;
113
+ }
114
+ } catch {
115
+ }
116
+ return null;
117
+ }
118
+ }
119
+ export {
120
+ DaemonContextService
121
+ };
@@ -10,6 +10,7 @@ class DaemonMaintenanceService {
10
10
  state;
11
11
  embeddingProvider = null;
12
12
  intervalId;
13
+ gcIntervalId;
13
14
  isRunning = false;
14
15
  onLog;
15
16
  constructor(config, onLog) {
@@ -48,12 +49,26 @@ class DaemonMaintenanceService {
48
49
  );
49
50
  });
50
51
  }, intervalMs);
52
+ if (this.config.gcEnabled !== false) {
53
+ const gcMs = (this.config.gcIntervalSeconds ?? 60) * 1e3;
54
+ this.gcIntervalId = setInterval(() => {
55
+ this.runGCCycle().catch((err) => {
56
+ this.addError(
57
+ `GC cycle: ${err instanceof Error ? err.message : String(err)}`
58
+ );
59
+ });
60
+ }, gcMs);
61
+ }
51
62
  }
52
63
  stop() {
53
64
  if (this.intervalId) {
54
65
  clearInterval(this.intervalId);
55
66
  this.intervalId = void 0;
56
67
  }
68
+ if (this.gcIntervalId) {
69
+ clearInterval(this.gcIntervalId);
70
+ this.gcIntervalId = void 0;
71
+ }
57
72
  this.isRunning = false;
58
73
  this.onLog("INFO", "Maintenance service stopped");
59
74
  }
@@ -93,6 +108,7 @@ class DaemonMaintenanceService {
93
108
  await this.backfillEmbeddings(db);
94
109
  await this.maybeVacuum(db);
95
110
  await this.generateMissingDigests(db);
111
+ await this.recomputeScores(db);
96
112
  await this.runGC(db);
97
113
  await db.disconnect();
98
114
  this.state.lastRunTime = Date.now();
@@ -280,14 +296,73 @@ class DaemonMaintenanceService {
280
296
  );
281
297
  }
282
298
  }
299
+ /**
300
+ * Dedicated lightweight GC cycle (runs more frequently than full maintenance).
301
+ * Queries active run_ids for protection and delegates to adapter.runGC().
302
+ */
303
+ async runGCCycle() {
304
+ try {
305
+ const db = await this.getDatabase();
306
+ if (!db) return;
307
+ const rawDb = db.getRawDatabase?.();
308
+ let protectedRunIds = [];
309
+ if (rawDb) {
310
+ const rows = rawDb.prepare(
311
+ "SELECT DISTINCT run_id FROM frames WHERE state = 'active' LIMIT 10"
312
+ ).all();
313
+ protectedRunIds = rows.map((r) => r.run_id);
314
+ }
315
+ const result = await db.runGC({
316
+ retentionDays: this.config.gcRetentionDays ?? 90,
317
+ batchSize: this.config.gcBatchSize ?? 100,
318
+ dryRun: false,
319
+ protectedRunIds
320
+ });
321
+ this.state.framesGarbageCollected += result.framesDeleted;
322
+ this.state.lastGcRun = Date.now();
323
+ if (result.framesDeleted > 0) {
324
+ this.onLog(
325
+ "INFO",
326
+ `GC cycle deleted ${result.framesDeleted} expired frames`
327
+ );
328
+ }
329
+ await db.disconnect();
330
+ } catch (err) {
331
+ this.addError(
332
+ `GC cycle: ${err instanceof Error ? err.message : String(err)}`
333
+ );
334
+ }
335
+ }
336
+ async recomputeScores(db) {
337
+ try {
338
+ if (typeof db.recomputeImportanceScores !== "function") return;
339
+ const updated = db.recomputeImportanceScores(100);
340
+ if (updated > 0) {
341
+ this.onLog("INFO", `Recomputed ${updated} importance scores`);
342
+ }
343
+ } catch (err) {
344
+ this.addError(
345
+ `Score recompute: ${err instanceof Error ? err.message : String(err)}`
346
+ );
347
+ }
348
+ }
283
349
  async runGC(db) {
284
350
  try {
285
351
  if (this.config.gcEnabled === false) return;
286
352
  if (typeof db.runGC !== "function") return;
353
+ const rawDb = db.getRawDatabase?.();
354
+ let protectedRunIds = [];
355
+ if (rawDb) {
356
+ const rows = rawDb.prepare(
357
+ "SELECT DISTINCT run_id FROM frames WHERE state = 'active' LIMIT 10"
358
+ ).all();
359
+ protectedRunIds = rows.map((r) => r.run_id);
360
+ }
287
361
  const result = await db.runGC({
288
362
  retentionDays: this.config.gcRetentionDays ?? 90,
289
363
  batchSize: this.config.gcBatchSize ?? 100,
290
- dryRun: false
364
+ dryRun: false,
365
+ protectedRunIds
291
366
  });
292
367
  this.state.framesGarbageCollected += result.framesDeleted;
293
368
  this.state.lastGcRun = Date.now();
@@ -49,7 +49,7 @@ function trimContentAroundCursor(lines, cursorLine, cursorCol, maxTokens) {
49
49
  const windowSize = Math.floor(targetChars / avgLineLength);
50
50
  const halfWindow = Math.floor(windowSize / 2);
51
51
  let start = Math.max(0, cursorLine - halfWindow);
52
- let end = Math.min(lines.length, start + windowSize);
52
+ const end = Math.min(lines.length, start + windowSize);
53
53
  if (end === lines.length) {
54
54
  start = Math.max(0, end - windowSize);
55
55
  }
@@ -60,7 +60,7 @@ function trimContentAroundCursor(lines, cursorLine, cursorCol, maxTokens) {
60
60
  };
61
61
  }
62
62
  function parseCompletion(completionText, originalLines, windowStart, windowEnd) {
63
- let text = completionText.replace(/<\|file_sep\|>$/, "").replace(/<\/s>$/, "").trimEnd();
63
+ const text = completionText.replace(/<\|file_sep\|>$/, "").replace(/<\/s>$/, "").trimEnd();
64
64
  if (!text || text.trim().length === 0) {
65
65
  return null;
66
66
  }
@@ -62,7 +62,9 @@ class LinearConfigManager {
62
62
  * Get default configuration with project isolation
63
63
  */
64
64
  getDefaultConfig() {
65
- const projectId = this.isolationManager.getProjectIdentification(this.projectRoot);
65
+ const projectId = this.isolationManager.getProjectIdentification(
66
+ this.projectRoot
67
+ );
66
68
  return {
67
69
  enabled: true,
68
70
  interval: 5,
@@ -5,7 +5,6 @@ const __dirname = __pathDirname(__filename);
5
5
  import { readFileSync, writeFileSync, existsSync } from "fs";
6
6
  import { join } from "path";
7
7
  import { logger } from "../../core/monitoring/logger.js";
8
- import { IntegrationError, ErrorCode } from "../../core/errors/index.js";
9
8
  import { LinearClient } from "./client.js";
10
9
  class LinearSyncEngine {
11
10
  taskStore;
@@ -15,6 +14,7 @@ class LinearSyncEngine {
15
14
  mappings = /* @__PURE__ */ new Map();
16
15
  projectRoot;
17
16
  mappingsPath;
17
+ _isConfigured = true;
18
18
  constructor(taskStore, authManager, config, projectRoot) {
19
19
  this.taskStore = taskStore;
20
20
  this.authManager = authManager;
@@ -33,10 +33,9 @@ class LinearSyncEngine {
33
33
  } else {
34
34
  const tokens = this.authManager.loadTokens();
35
35
  if (!tokens) {
36
- throw new IntegrationError(
37
- 'Linear API key or authentication tokens not found. Set LINEAR_API_KEY environment variable or run "stackmemory linear setup" first.',
38
- ErrorCode.LINEAR_SYNC_FAILED
39
- );
36
+ logger.info("Linear API key not configured \u2014 skipping Linear sync");
37
+ this._isConfigured = false;
38
+ return;
40
39
  }
41
40
  this.linearClient = new LinearClient({
42
41
  apiKey: tokens.accessToken,
@@ -49,6 +48,12 @@ class LinearSyncEngine {
49
48
  }
50
49
  this.loadMappings();
51
50
  }
51
+ /**
52
+ * Check if Linear credentials are available
53
+ */
54
+ get isConfigured() {
55
+ return this._isConfigured;
56
+ }
52
57
  /**
53
58
  * Update sync configuration
54
59
  */
@@ -59,6 +64,14 @@ class LinearSyncEngine {
59
64
  * Perform bi-directional sync
60
65
  */
61
66
  async sync() {
67
+ if (!this._isConfigured) {
68
+ return {
69
+ success: true,
70
+ synced: { toLinear: 0, fromLinear: 0, updated: 0 },
71
+ conflicts: [],
72
+ errors: []
73
+ };
74
+ }
62
75
  if (!this.config.enabled) {
63
76
  return {
64
77
  success: false,
@@ -27,7 +27,12 @@ class CodeExecutionHandler {
27
27
  * Execute code in a controlled environment
28
28
  */
29
29
  async executeCode(params) {
30
- const { language, code, workingDirectory, timeout = EXECUTION_TIMEOUT } = params;
30
+ const {
31
+ language,
32
+ code,
33
+ workingDirectory,
34
+ timeout = EXECUTION_TIMEOUT
35
+ } = params;
31
36
  if (!this.allowedLanguages.includes(language.toLowerCase())) {
32
37
  return {
33
38
  success: false,
@@ -43,7 +48,12 @@ class CodeExecutionHandler {
43
48
  );
44
49
  try {
45
50
  await fs.writeFile(tempFile, code, "utf-8");
46
- const result = await this.runCode(language, tempFile, workingDirectory || this.sandboxDir, timeout);
51
+ const result = await this.runCode(
52
+ language,
53
+ tempFile,
54
+ workingDirectory || this.sandboxDir,
55
+ timeout
56
+ );
47
57
  await fs.unlink(tempFile).catch(() => {
48
58
  });
49
59
  return result;
@@ -98,8 +108,15 @@ class CodeExecutionHandler {
98
108
  clearTimeout(timeoutId);
99
109
  let outputFile;
100
110
  if (truncated) {
101
- outputFile = join(this.sandboxDir, `output_${randomBytes(8).toString("hex")}.txt`);
102
- await fs.writeFile(outputFile, stdout + "\n---STDERR---\n" + stderr, "utf-8").catch(() => {
111
+ outputFile = join(
112
+ this.sandboxDir,
113
+ `output_${randomBytes(8).toString("hex")}.txt`
114
+ );
115
+ await fs.writeFile(
116
+ outputFile,
117
+ stdout + "\n---STDERR---\n" + stderr,
118
+ "utf-8"
119
+ ).catch(() => {
103
120
  });
104
121
  }
105
122
  resolve({
@@ -175,12 +192,21 @@ class CodeExecutionHandler {
175
192
  const warnings = [];
176
193
  const dangerousPatterns = [
177
194
  { pattern: /import\s+os/i, message: "Importing os module detected" },
178
- { pattern: /import\s+subprocess/i, message: "Subprocess module detected" },
195
+ {
196
+ pattern: /import\s+subprocess/i,
197
+ message: "Subprocess module detected"
198
+ },
179
199
  { pattern: /exec\s*\(/i, message: "exec() function detected" },
180
200
  { pattern: /eval\s*\(/i, message: "eval() function detected" },
181
201
  { pattern: /__import__/i, message: "__import__ detected" },
182
- { pattern: /open\s*\([^)]*['"]\//i, message: "Absolute path file access detected" },
183
- { pattern: /require\s*\([^)]*child_process/i, message: "child_process module detected" },
202
+ {
203
+ pattern: /open\s*\([^)]*['"]\//i,
204
+ message: "Absolute path file access detected"
205
+ },
206
+ {
207
+ pattern: /require\s*\([^)]*child_process/i,
208
+ message: "child_process module detected"
209
+ },
184
210
  { pattern: /require\s*\([^)]*fs/i, message: "fs module access detected" }
185
211
  ];
186
212
  for (const { pattern, message } of dangerousPatterns) {