@stackmemoryai/stackmemory 1.2.2 → 1.2.6

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 (77) hide show
  1. package/README.md +10 -6
  2. package/dist/src/cli/claude-sm.js +33 -4
  3. package/dist/src/cli/codex-sm-danger.js +4 -1
  4. package/dist/src/cli/codex-sm.js +6 -8
  5. package/dist/src/cli/commands/config.js +0 -81
  6. package/dist/src/cli/commands/context-rehydrate.js +133 -47
  7. package/dist/src/cli/commands/db.js +35 -8
  8. package/dist/src/cli/commands/handoff.js +1 -1
  9. package/dist/src/cli/commands/linear.js +9 -0
  10. package/dist/src/cli/commands/ralph.js +2 -2
  11. package/dist/src/cli/commands/setup.js +2 -2
  12. package/dist/src/cli/commands/signup.js +3 -1
  13. package/dist/src/cli/commands/skills.js +108 -1
  14. package/dist/src/cli/commands/storage-tier.js +26 -8
  15. package/dist/src/cli/index.js +1 -57
  16. package/dist/src/core/config/feature-flags.js +0 -4
  17. package/dist/src/core/context/dual-stack-manager.js +10 -3
  18. package/dist/src/core/context/frame-database.js +32 -0
  19. package/dist/src/core/context/frame-handoff-manager.js +2 -2
  20. package/dist/src/core/context/{refactored-frame-manager.js → frame-manager.js} +3 -3
  21. package/dist/src/core/context/index.js +2 -2
  22. package/dist/src/core/database/sqlite-adapter.js +161 -1
  23. package/dist/src/core/digest/frame-digest-integration.js +1 -1
  24. package/dist/src/core/digest/index.js +1 -1
  25. package/dist/src/core/execution/parallel-executor.js +5 -1
  26. package/dist/src/core/projects/project-isolation.js +18 -4
  27. package/dist/src/core/security/index.js +2 -0
  28. package/dist/src/core/security/input-sanitizer.js +23 -0
  29. package/dist/src/core/utils/update-checker.js +10 -6
  30. package/dist/src/daemon/daemon-config.js +2 -1
  31. package/dist/src/daemon/services/auto-save-service.js +121 -0
  32. package/dist/src/daemon/services/maintenance-service.js +76 -1
  33. package/dist/src/features/sweep/prompt-builder.js +2 -2
  34. package/dist/src/hooks/daemon.js +0 -8
  35. package/dist/src/integrations/linear/config.js +3 -1
  36. package/dist/src/integrations/linear/sync.js +18 -5
  37. package/dist/src/integrations/linear/webhook.js +0 -16
  38. package/dist/src/integrations/mcp/handlers/code-execution-handlers.js +33 -7
  39. package/dist/src/integrations/mcp/handlers/cord-handlers.js +397 -0
  40. package/dist/src/integrations/mcp/handlers/index.js +55 -9
  41. package/dist/src/integrations/mcp/handlers/task-handlers.js +55 -12
  42. package/dist/src/integrations/mcp/handlers/team-handlers.js +211 -0
  43. package/dist/src/integrations/mcp/handlers/trace-handlers.js +28 -9
  44. package/dist/src/integrations/mcp/index.js +2 -2
  45. package/dist/src/integrations/mcp/refactored-server.js +31 -10
  46. package/dist/src/integrations/mcp/server.js +0 -130
  47. package/dist/src/integrations/mcp/tool-definitions.js +196 -67
  48. package/dist/src/integrations/ralph/context/context-budget-manager.js +10 -2
  49. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +54 -22
  50. package/dist/src/integrations/ralph/learning/pattern-learner.js +59 -24
  51. package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +81 -35
  52. package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +12 -4
  53. package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +32 -9
  54. package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +25 -8
  55. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +17 -5
  56. package/dist/src/integrations/ralph/visualization/ralph-debugger.js +73 -22
  57. package/dist/src/skills/claude-skills.js +37 -103
  58. package/dist/src/skills/theory-skill.js +191 -0
  59. package/dist/src/utils/hook-installer.js +16 -0
  60. package/package.json +6 -6
  61. package/scripts/install-claude-hooks-auto.js +8 -0
  62. package/templates/claude-hooks/cord-trace.js +225 -0
  63. package/templates/claude-hooks/theory-capture.js +100 -0
  64. package/dist/src/core/config/storage-config.js +0 -114
  65. package/dist/src/core/storage/chromadb-adapter.js +0 -379
  66. package/dist/src/hooks/graphiti-hooks.js +0 -253
  67. package/dist/src/integrations/claude-code/enhanced-pre-clear-hooks.js +0 -458
  68. package/dist/src/integrations/graphiti/client.js +0 -115
  69. package/dist/src/integrations/graphiti/config.js +0 -17
  70. package/dist/src/integrations/graphiti/linear-graphiti-bridge.js +0 -115
  71. package/dist/src/integrations/graphiti/types.js +0 -4
  72. package/dist/src/integrations/ralph/coordination/enhanced-coordination.js +0 -409
  73. package/dist/src/skills/repo-ingestion-skill.js +0 -631
  74. package/templates/claude-hooks/chromadb-wrapper +0 -21
  75. /package/dist/src/core/context/{enhanced-rehydration.js → rehydration.js} +0 -0
  76. /package/dist/src/core/digest/{enhanced-hybrid-digest.js → hybrid-digest.js} +0 -0
  77. /package/dist/src/core/session/{enhanced-handoff.js → handoff.js} +0 -0
@@ -27,7 +27,10 @@ class RalphDebugger {
27
27
  await sessionManager.initialize();
28
28
  const session = await sessionManager.getOrCreateSession({});
29
29
  if (session.database) {
30
- this.frameManager = new FrameManager(session.database, session.projectId);
30
+ this.frameManager = new FrameManager(
31
+ session.database,
32
+ session.projectId
33
+ );
31
34
  }
32
35
  logger.info("Debugger initialized successfully");
33
36
  } catch (error) {
@@ -111,7 +114,10 @@ class RalphDebugger {
111
114
  contextSize: iter.contextSize,
112
115
  phase: iter.phase
113
116
  })),
114
- totalDuration: session.performance.iterationTimes.reduce((sum, time) => sum + time, 0)
117
+ totalDuration: session.performance.iterationTimes.reduce(
118
+ (sum, time) => sum + time,
119
+ 0
120
+ )
115
121
  };
116
122
  const html = await this.generateTimelineHTML(timeline);
117
123
  const timelinePath = path.join(".ralph-debug", `timeline-${loopId}.html`);
@@ -165,7 +171,10 @@ class RalphDebugger {
165
171
  diagram.metrics = {
166
172
  totalNodes: diagram.nodes.length,
167
173
  totalEdges: diagram.edges.length,
168
- avgContextSize: session.performance.contextSizes.length > 0 ? session.performance.contextSizes.reduce((sum, size) => sum + size, 0) / session.performance.contextSizes.length : 0,
174
+ avgContextSize: session.performance.contextSizes.length > 0 ? session.performance.contextSizes.reduce(
175
+ (sum, size) => sum + size,
176
+ 0
177
+ ) / session.performance.contextSizes.length : 0,
169
178
  maxContextSize: Math.max(...session.performance.contextSizes)
170
179
  };
171
180
  return diagram;
@@ -230,15 +239,24 @@ class RalphDebugger {
230
239
  async updatePerformanceMetrics(session) {
231
240
  const currentMemory = process.memoryUsage().heapUsed;
232
241
  session.performance.memoryUsage.push(currentMemory);
233
- session.performance.peakMemory = Math.max(session.performance.peakMemory, currentMemory);
242
+ session.performance.peakMemory = Math.max(
243
+ session.performance.peakMemory,
244
+ currentMemory
245
+ );
234
246
  const contextSize = await this.calculateContextSize(session.ralphDir);
235
247
  session.performance.contextSizes.push(contextSize);
236
248
  if (session.performance.iterationTimes.length > 0) {
237
- session.performance.averageIterationTime = session.performance.iterationTimes.reduce((sum, time) => sum + time, 0) / session.performance.iterationTimes.length;
249
+ session.performance.averageIterationTime = session.performance.iterationTimes.reduce(
250
+ (sum, time) => sum + time,
251
+ 0
252
+ ) / session.performance.iterationTimes.length;
238
253
  }
239
254
  if (session.performance.contextSizes.length > 0) {
240
255
  const avgContextSize = session.performance.contextSizes.reduce((sum, size) => sum + size, 0) / session.performance.contextSizes.length;
241
- session.performance.contextEfficiency = Math.max(0, 1 - avgContextSize / 1e4);
256
+ session.performance.contextEfficiency = Math.max(
257
+ 0,
258
+ 1 - avgContextSize / 1e4
259
+ );
242
260
  }
243
261
  }
244
262
  /**
@@ -246,8 +264,13 @@ class RalphDebugger {
246
264
  */
247
265
  async generateSummary(session) {
248
266
  const totalIterations = session.iterations.length;
249
- const successfulIterations = session.iterations.filter((i) => i.success).length;
250
- const totalDuration = session.performance.iterationTimes.reduce((sum, time) => sum + time, 0);
267
+ const successfulIterations = session.iterations.filter(
268
+ (i) => i.success
269
+ ).length;
270
+ const totalDuration = session.performance.iterationTimes.reduce(
271
+ (sum, time) => sum + time,
272
+ 0
273
+ );
251
274
  return {
252
275
  loopId: session.loopId,
253
276
  totalIterations,
@@ -271,18 +294,26 @@ class RalphDebugger {
271
294
  const avgDuration = durations.reduce((sum, d) => sum + d, 0) / durations.length;
272
295
  if (durations.some((d) => d > avgDuration * 2)) {
273
296
  patterns.push("Variable iteration times detected");
274
- insights.push("Some iterations took significantly longer than average - investigate bottlenecks");
297
+ insights.push(
298
+ "Some iterations took significantly longer than average - investigate bottlenecks"
299
+ );
275
300
  }
276
- const consecutiveFailures = this.findConsecutiveFailures(session.iterations);
301
+ const consecutiveFailures = this.findConsecutiveFailures(
302
+ session.iterations
303
+ );
277
304
  if (consecutiveFailures.length > 2) {
278
305
  patterns.push("Multiple consecutive failures detected");
279
- insights.push("Consider adjusting approach or criteria after consecutive failures");
306
+ insights.push(
307
+ "Consider adjusting approach or criteria after consecutive failures"
308
+ );
280
309
  }
281
310
  if (session.performance.contextSizes.length > 1) {
282
311
  const contextGrowth = session.performance.contextSizes[session.performance.contextSizes.length - 1] - session.performance.contextSizes[0];
283
312
  if (contextGrowth > 1e3) {
284
313
  patterns.push("Significant context growth");
285
- insights.push("Context is growing rapidly - consider context pruning strategies");
314
+ insights.push(
315
+ "Context is growing rapidly - consider context pruning strategies"
316
+ );
286
317
  }
287
318
  }
288
319
  return { patterns, insights };
@@ -292,9 +323,14 @@ class RalphDebugger {
292
323
  */
293
324
  async analyzeContextFlow(session) {
294
325
  return {
295
- avgContextSize: session.performance.contextSizes.length > 0 ? session.performance.contextSizes.reduce((sum, size) => sum + size, 0) / session.performance.contextSizes.length : 0,
326
+ avgContextSize: session.performance.contextSizes.length > 0 ? session.performance.contextSizes.reduce(
327
+ (sum, size) => sum + size,
328
+ 0
329
+ ) / session.performance.contextSizes.length : 0,
296
330
  maxContextSize: Math.max(...session.performance.contextSizes),
297
- contextGrowthRate: this.calculateGrowthRate(session.performance.contextSizes),
331
+ contextGrowthRate: this.calculateGrowthRate(
332
+ session.performance.contextSizes
333
+ ),
298
334
  efficiency: session.performance.contextEfficiency
299
335
  };
300
336
  }
@@ -303,7 +339,9 @@ class RalphDebugger {
303
339
  */
304
340
  async analyzePerformance(session) {
305
341
  return {
306
- memoryEfficiency: this.calculateMemoryEfficiency(session.performance.memoryUsage),
342
+ memoryEfficiency: this.calculateMemoryEfficiency(
343
+ session.performance.memoryUsage
344
+ ),
307
345
  iterationEfficiency: session.performance.averageIterationTime,
308
346
  resourceUtilization: {
309
347
  cpu: "N/A",
@@ -318,7 +356,10 @@ class RalphDebugger {
318
356
  */
319
357
  async generateVisualization(session) {
320
358
  const htmlContent = await this.generateVisualizationHTML(session);
321
- const vizPath = path.join(".ralph-debug", `visualization-${session.loopId}.html`);
359
+ const vizPath = path.join(
360
+ ".ralph-debug",
361
+ `visualization-${session.loopId}.html`
362
+ );
322
363
  await fs.mkdir(path.dirname(vizPath), { recursive: true });
323
364
  await fs.writeFile(vizPath, htmlContent);
324
365
  return {
@@ -343,17 +384,25 @@ class RalphDebugger {
343
384
  async generateRecommendations(session) {
344
385
  const recommendations = [];
345
386
  if (session.performance.averageIterationTime > 3e4) {
346
- recommendations.push("Consider breaking down complex tasks into smaller iterations");
387
+ recommendations.push(
388
+ "Consider breaking down complex tasks into smaller iterations"
389
+ );
347
390
  }
348
391
  if (session.performance.contextEfficiency < 0.7) {
349
- recommendations.push("Optimize context management - consider using context budgeting");
392
+ recommendations.push(
393
+ "Optimize context management - consider using context budgeting"
394
+ );
350
395
  }
351
396
  const successRate = session.iterations.filter((i) => i.success).length / Math.max(1, session.iterations.length);
352
397
  if (successRate < 0.5) {
353
- recommendations.push("Low success rate detected - review task criteria and approach");
398
+ recommendations.push(
399
+ "Low success rate detected - review task criteria and approach"
400
+ );
354
401
  }
355
402
  if (session.performance.peakMemory > 500 * 1024 * 1024) {
356
- recommendations.push("High memory usage detected - investigate memory leaks");
403
+ recommendations.push(
404
+ "High memory usage detected - investigate memory leaks"
405
+ );
357
406
  }
358
407
  return recommendations;
359
408
  }
@@ -450,14 +499,16 @@ class RalphDebugger {
450
499
  <body>
451
500
  <h1>${timeline.title}</h1>
452
501
  <div class="timeline">
453
- ${timeline.iterations.map((iter) => `
502
+ ${timeline.iterations.map(
503
+ (iter) => `
454
504
  <div class="iteration ${iter.success ? "success" : "failure"}">
455
505
  <h3>Iteration ${iter.iteration}</h3>
456
506
  <p>Duration: ${iter.duration}ms</p>
457
507
  <p>Changes: ${iter.changes} | Errors: ${iter.errors}</p>
458
508
  <p>Context Size: ${iter.contextSize}</p>
459
509
  </div>
460
- `).join("")}
510
+ `
511
+ ).join("")}
461
512
  </div>
462
513
  </body>
463
514
  </html>
@@ -3,9 +3,6 @@ import { dirname as __pathDirname } from 'path';
3
3
  const __filename = __fileURLToPath(import.meta.url);
4
4
  const __dirname = __pathDirname(__filename);
5
5
  import { logger } from "../core/monitoring/logger.js";
6
- import {
7
- RepoIngestionSkill
8
- } from "./repo-ingestion-skill.js";
9
6
  import {
10
7
  RecursiveAgentOrchestrator
11
8
  } from "./recursive-agent-orchestrator.js";
@@ -617,22 +614,6 @@ class ClaudeSkillsManager {
617
614
  this.dashboardLauncher = new module.DashboardLauncherSkill();
618
615
  logger.info("Dashboard launcher initialized (manual launch required)");
619
616
  });
620
- const chromaConfig = {
621
- apiKey: process.env["CHROMADB_API_KEY"] || "",
622
- tenant: process.env["CHROMADB_TENANT"] || "",
623
- database: process.env["CHROMADB_DATABASE"] || "stackmemory",
624
- collectionName: process.env["CHROMADB_COLLECTION"] || "stackmemory_repos"
625
- };
626
- if (chromaConfig.apiKey && chromaConfig.tenant) {
627
- this.repoIngestionSkill = new RepoIngestionSkill(
628
- chromaConfig,
629
- context.userId,
630
- process.env["CHROMADB_TEAM_ID"]
631
- );
632
- this.repoIngestionSkill.initialize().catch((error) => {
633
- logger.warn("Repo ingestion skill initialization failed:", error);
634
- });
635
- }
636
617
  import("../features/tasks/linear-task-manager.js").then((module) => {
637
618
  const taskStore = new module.LinearTaskManager();
638
619
  const frameManager = context.frameManager;
@@ -662,7 +643,6 @@ class ClaudeSkillsManager {
662
643
  checkpointSkill;
663
644
  archaeologistSkill;
664
645
  dashboardLauncher;
665
- repoIngestionSkill = null;
666
646
  rlmOrchestrator = null;
667
647
  apiSkill;
668
648
  specGeneratorSkill;
@@ -738,64 +718,6 @@ class ClaudeSkillsManager {
738
718
  };
739
719
  }
740
720
  return this.rlmOrchestrator.execute(args[0], options);
741
- case "repo":
742
- case "ingest":
743
- if (!this.repoIngestionSkill) {
744
- return {
745
- success: false,
746
- message: "Repo ingestion skill not initialized. Please configure ChromaDB."
747
- };
748
- }
749
- const repoCommand = args[0];
750
- switch (repoCommand) {
751
- case "ingest":
752
- const repoPath = args[1] || process.cwd();
753
- const repoName = args[2] || path.basename(repoPath);
754
- return await this.repoIngestionSkill.ingestRepository(
755
- repoPath,
756
- repoName,
757
- options
758
- );
759
- case "update":
760
- const updatePath = args[1] || process.cwd();
761
- const updateName = args[2] || path.basename(updatePath);
762
- return await this.repoIngestionSkill.updateRepository(
763
- updatePath,
764
- updateName,
765
- options
766
- );
767
- case "search":
768
- const query = args[1];
769
- if (!query) {
770
- return {
771
- success: false,
772
- message: "Search query required"
773
- };
774
- }
775
- const results = await this.repoIngestionSkill.searchCode(query, {
776
- repoName: options?.repoName,
777
- language: options?.language,
778
- limit: options?.limit,
779
- includeContext: options?.includeContext
780
- });
781
- return {
782
- success: true,
783
- message: `Found ${results.length} results`,
784
- data: results
785
- };
786
- case "stats":
787
- const stats = await this.repoIngestionSkill.getRepoStats(args[1]);
788
- return {
789
- success: true,
790
- message: "Repository statistics",
791
- data: stats
792
- };
793
- default:
794
- return {
795
- success: false,
796
- message: `Unknown repo command: ${repoCommand}. Use: ingest, update, search, or stats`
797
- };
798
- }
799
721
  case "dashboard":
800
722
  const dashboardCmd = args[0];
801
723
  if (!this.dashboardLauncher) {
@@ -967,6 +889,25 @@ class ClaudeSkillsManager {
967
889
  };
968
890
  }
969
891
  }
892
+ case "theory": {
893
+ const { TheorySkill } = await import("./theory-skill.js");
894
+ const theorySkill = new TheorySkill(this.context);
895
+ switch (args[0]) {
896
+ case "show":
897
+ return theorySkill.show();
898
+ case "update":
899
+ return theorySkill.update(args.slice(1).join(" "));
900
+ case "init":
901
+ return theorySkill.init(args.slice(1).join(" "));
902
+ case "status":
903
+ return theorySkill.status();
904
+ default:
905
+ return {
906
+ success: false,
907
+ message: "Usage: theory show|init|update|status"
908
+ };
909
+ }
910
+ }
970
911
  case "linear-run": {
971
912
  if (!this.linearTaskRunner) {
972
913
  return {
@@ -1009,11 +950,9 @@ class ClaudeSkillsManager {
1009
950
  "dashboard",
1010
951
  "api",
1011
952
  "spec",
1012
- "agent"
953
+ "agent",
954
+ "theory"
1013
955
  ];
1014
- if (this.repoIngestionSkill) {
1015
- skills.push("repo");
1016
- }
1017
956
  if (this.rlmOrchestrator) {
1018
957
  skills.push("rlm", "lint");
1019
958
  }
@@ -1097,27 +1036,6 @@ Launch the StackMemory web dashboard for real-time monitoring
1097
1036
  - launch: Start the web dashboard and open in browser (default)
1098
1037
  - stop: Stop the dashboard server
1099
1038
  Auto-launches on new sessions when configured
1100
- `;
1101
- case "repo":
1102
- return `
1103
- /repo ingest [path] [name] [--incremental] [--include-tests] [--include-docs]
1104
- /repo update [path] [name] [--force-update]
1105
- /repo search "query" [--repo-name name] [--language lang] [--limit n]
1106
- /repo stats [repo-name]
1107
-
1108
- Ingest and search code repositories in ChromaDB:
1109
- - ingest: Index a new repository (defaults to current directory)
1110
- - update: Update an existing repository with changes
1111
- - search: Semantic search across ingested code
1112
- - stats: View statistics about ingested repositories
1113
-
1114
- Options:
1115
- - --incremental: Only process changed files
1116
- - --include-tests: Include test files in indexing
1117
- - --include-docs: Include documentation files
1118
- - --force-update: Force re-indexing of all files
1119
- - --language: Filter search by programming language
1120
- - --limit: Maximum search results (default: 20)
1121
1039
  `;
1122
1040
  case "recursive":
1123
1041
  return `
@@ -1190,6 +1108,22 @@ Options:
1190
1108
  Each agent runs in a disposable /tmp workspace (git clone --depth=1).
1191
1109
  Patches: git apply .stackmemory/patches/<file>.patch
1192
1110
  Spec branches: cd /tmp/sm-spec-* && git log --oneline
1111
+ `;
1112
+ case "theory":
1113
+ return `
1114
+ /theory show
1115
+ /theory init "<problem statement>"
1116
+ /theory update "<full content>"
1117
+ /theory status
1118
+
1119
+ Maintain a living THEORY.MD at repo root \u2014 a narrative operating theory:
1120
+ show \u2014 Display current THEORY.MD content
1121
+ init \u2014 Create THEORY.MD with scaffold sections
1122
+ update \u2014 Overwrite THEORY.MD (validates length, warns on anti-patterns)
1123
+ status \u2014 Show metadata: exists, line count, sections, last modified
1124
+
1125
+ Sections: Problem, Operating Theory, Strategy, Key Discoveries, Open Questions
1126
+ Based on Theorist by @blader (MIT).
1193
1127
  `;
1194
1128
  case "linear-run":
1195
1129
  return `
@@ -0,0 +1,191 @@
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 * as fs from "fs";
6
+ import * as path from "path";
7
+ import { execSync } from "child_process";
8
+ import { logger } from "../core/monitoring/logger.js";
9
+ const THEORY_FILE = "THEORY.MD";
10
+ const MIN_CONTENT_LENGTH = 100;
11
+ const MAX_LINE_WARNING = 200;
12
+ const TEMPLATE_SECTIONS = [
13
+ "## Problem",
14
+ "## Operating Theory",
15
+ "## Strategy",
16
+ "## Key Discoveries",
17
+ "## Open Questions"
18
+ ];
19
+ function getGitRoot() {
20
+ try {
21
+ return execSync("git rev-parse --show-toplevel", {
22
+ encoding: "utf-8",
23
+ timeout: 5e3
24
+ }).trim();
25
+ } catch {
26
+ return void 0;
27
+ }
28
+ }
29
+ function generateTemplate(problemStatement) {
30
+ return `# THEORY.MD
31
+
32
+ ## Problem
33
+
34
+ ${problemStatement}
35
+
36
+ ## Operating Theory
37
+
38
+ _What is your current mental model of how this system works or should work?_
39
+
40
+ ## Strategy
41
+
42
+ _What approach are you taking and why?_
43
+
44
+ ## Key Discoveries
45
+
46
+ _What have you learned that changed your thinking?_
47
+
48
+ ## Open Questions
49
+
50
+ _What don't you know yet? What assumptions need validation?_
51
+ `;
52
+ }
53
+ class TheorySkill {
54
+ constructor(context) {
55
+ this.context = context;
56
+ this.rootDir = getGitRoot() || process.cwd();
57
+ }
58
+ rootDir;
59
+ get theoryPath() {
60
+ return path.join(this.rootDir, THEORY_FILE);
61
+ }
62
+ show() {
63
+ if (!fs.existsSync(this.theoryPath)) {
64
+ return {
65
+ success: false,
66
+ message: `No ${THEORY_FILE} found. Run \`theory init "<problem>"\` to create one.`
67
+ };
68
+ }
69
+ const content = fs.readFileSync(this.theoryPath, "utf-8");
70
+ return {
71
+ success: true,
72
+ message: content,
73
+ data: { path: this.theoryPath, length: content.length }
74
+ };
75
+ }
76
+ init(problemStatement) {
77
+ if (!problemStatement || problemStatement.trim().length === 0) {
78
+ return {
79
+ success: false,
80
+ message: "A problem statement is required to initialize THEORY.MD."
81
+ };
82
+ }
83
+ if (fs.existsSync(this.theoryPath)) {
84
+ return {
85
+ success: false,
86
+ message: `${THEORY_FILE} already exists at ${this.theoryPath}. Use \`theory update\` to modify it.`
87
+ };
88
+ }
89
+ const content = generateTemplate(problemStatement.trim());
90
+ fs.writeFileSync(this.theoryPath, content, "utf-8");
91
+ logger.info("Created THEORY.MD", { path: this.theoryPath });
92
+ return {
93
+ success: true,
94
+ message: `Created ${THEORY_FILE} at ${this.theoryPath}`,
95
+ data: { path: this.theoryPath, sections: TEMPLATE_SECTIONS }
96
+ };
97
+ }
98
+ update(content) {
99
+ if (!content || content.trim().length === 0) {
100
+ return { success: false, message: "Content is required for update." };
101
+ }
102
+ if (content.trim().length < MIN_CONTENT_LENGTH) {
103
+ return {
104
+ success: false,
105
+ message: `Content too short (${content.trim().length} chars). THEORY.MD should be at least ${MIN_CONTENT_LENGTH} characters to be meaningful.`
106
+ };
107
+ }
108
+ const warnings = [];
109
+ if (/\[[ x]\]/i.test(content)) {
110
+ warnings.push(
111
+ "Contains checkboxes \u2014 THEORY.MD is narrative, not a checklist."
112
+ );
113
+ }
114
+ if (/\d{4}-\d{2}-\d{2}/.test(content)) {
115
+ warnings.push(
116
+ "Contains dates \u2014 THEORY.MD captures current thinking, not a changelog."
117
+ );
118
+ }
119
+ const lineCount = content.split("\n").length;
120
+ if (lineCount > MAX_LINE_WARNING) {
121
+ warnings.push(
122
+ `${lineCount} lines is long. Consider distilling to keep THEORY.MD focused.`
123
+ );
124
+ }
125
+ fs.writeFileSync(this.theoryPath, content, "utf-8");
126
+ if (this.context.frameManager) {
127
+ try {
128
+ const frameId = this.context.frameManager.createFrame(
129
+ "write",
130
+ "theory-update",
131
+ { source: THEORY_FILE, length: content.length }
132
+ );
133
+ this.context.frameManager.addEvent(
134
+ "artifact",
135
+ {
136
+ type: "theory-update",
137
+ path: this.theoryPath,
138
+ length: content.length,
139
+ lineCount
140
+ },
141
+ frameId
142
+ );
143
+ this.context.frameManager.closeFrame(frameId, {
144
+ theory_updated: true
145
+ });
146
+ } catch (err) {
147
+ logger.warn("Failed to record theory update frame", { error: err });
148
+ }
149
+ }
150
+ logger.info("Updated THEORY.MD", {
151
+ path: this.theoryPath,
152
+ length: content.length
153
+ });
154
+ const message = warnings.length > 0 ? `Updated ${THEORY_FILE}. Warnings:
155
+ ${warnings.map((w) => ` - ${w}`).join("\n")}` : `Updated ${THEORY_FILE} (${lineCount} lines)`;
156
+ return {
157
+ success: true,
158
+ message,
159
+ data: { path: this.theoryPath, lineCount, warnings }
160
+ };
161
+ }
162
+ status() {
163
+ if (!fs.existsSync(this.theoryPath)) {
164
+ return {
165
+ success: true,
166
+ message: `No ${THEORY_FILE} found.`,
167
+ data: { exists: false }
168
+ };
169
+ }
170
+ const content = fs.readFileSync(this.theoryPath, "utf-8");
171
+ const lines = content.split("\n");
172
+ const stat = fs.statSync(this.theoryPath);
173
+ const sections = TEMPLATE_SECTIONS.filter((s) => content.includes(s));
174
+ return {
175
+ success: true,
176
+ message: `${THEORY_FILE}: ${lines.length} lines, ${sections.length}/${TEMPLATE_SECTIONS.length} sections`,
177
+ data: {
178
+ exists: true,
179
+ path: this.theoryPath,
180
+ lineCount: lines.length,
181
+ charCount: content.length,
182
+ sections,
183
+ totalSections: TEMPLATE_SECTIONS.length,
184
+ lastModified: stat.mtime.toISOString()
185
+ }
186
+ };
187
+ }
188
+ }
189
+ export {
190
+ TheorySkill
191
+ };
@@ -31,6 +31,22 @@ const CANONICAL_HOOKS = [
31
31
  timeout: 2,
32
32
  commandPrefix: "node",
33
33
  required: true
34
+ },
35
+ {
36
+ scriptName: "cord-trace.js",
37
+ eventType: "PostToolUse",
38
+ matcher: "mcp__.*__cord_(spawn|fork|complete|ask|tree)",
39
+ timeout: 2,
40
+ commandPrefix: "node",
41
+ required: true
42
+ },
43
+ {
44
+ scriptName: "theory-capture.js",
45
+ eventType: "PostToolUse",
46
+ matcher: "Edit|Write|MultiEdit",
47
+ timeout: 2,
48
+ commandPrefix: "node",
49
+ required: false
34
50
  }
35
51
  ];
36
52
  const DEAD_HOOKS = ["sms-response-handler.js"];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stackmemoryai/stackmemory",
3
- "version": "1.2.2",
4
- "description": "Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.",
3
+ "version": "1.2.6",
4
+ "description": "Project-scoped memory for AI coding tools. Durable context across sessions with 32 MCP tools, FTS5 search, Claude/Codex/OpenCode wrappers, Linear sync, automatic hooks, and log analysis.",
5
5
  "engines": {
6
6
  "node": ">=20.0.0",
7
7
  "npm": ">=10.0.0"
@@ -87,8 +87,8 @@
87
87
  "postinstall": "node scripts/install-claude-hooks-auto.js || true",
88
88
  "init": "node dist/scripts/initialize.js",
89
89
  "build": "rm -rf dist && node esbuild.config.js",
90
- "lint": "eslint src/**/*.ts scripts/**/*.ts",
91
- "lint:fix": "eslint src/**/*.ts scripts/**/*.ts --fix --max-warnings=-1",
90
+ "lint": "eslint 'src/**/*.ts' 'scripts/**/*.ts'",
91
+ "lint:fix": "eslint 'src/**/*.ts' 'scripts/**/*.ts' --fix --max-warnings=-1",
92
92
  "lint:fast": "oxlint src scripts",
93
93
  "format": "prettier --write src/**/*.ts scripts/**/*.ts",
94
94
  "test": "vitest",
@@ -121,6 +121,7 @@
121
121
  "daemon:status": "node dist/cli/index.js daemon status",
122
122
  "sync:start": "node scripts/background-sync-manager.js",
123
123
  "sync:setup": "./scripts/setup-background-sync.sh",
124
+ "eval:cord": "npx tsx scripts/evals/cord-vs-flat-eval.ts",
124
125
  "prepare": "echo 'Prepare step completed'",
125
126
  "verify:dist": "node scripts/verify-dist.cjs",
126
127
  "test:smoke-db": "bash scripts/smoke-init-db.sh",
@@ -197,7 +198,6 @@
197
198
  "@xenova/transformers": "^2.17.2",
198
199
  "blessed": "^0.1.81",
199
200
  "blessed-contrib": "^4.11.0",
200
- "chokidar": "^5.0.0",
201
- "chromadb": "^3.2.2"
201
+ "chokidar": "^5.0.0"
202
202
  }
203
203
  }
@@ -81,6 +81,14 @@ try {
81
81
  commandPrefix: 'node',
82
82
  required: true,
83
83
  },
84
+ {
85
+ scriptName: 'cord-trace.js',
86
+ eventType: 'PostToolUse',
87
+ matcher: 'mcp__.*__cord_(spawn|fork|complete|ask|tree)',
88
+ timeout: 2,
89
+ commandPrefix: 'node',
90
+ required: true,
91
+ },
84
92
  ];
85
93
 
86
94
  const DEAD_HOOKS = ['sms-response-handler.js'];