@stackmemoryai/stackmemory 1.2.1 → 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 (100) 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/skills.js +123 -1
  11. package/dist/src/cli/commands/storage-tier.js +26 -8
  12. package/dist/src/cli/index.js +1 -57
  13. package/dist/src/core/config/feature-flags.js +0 -4
  14. package/dist/src/core/context/dual-stack-manager.js +10 -3
  15. package/dist/src/core/context/frame-database.js +32 -0
  16. package/dist/src/core/context/frame-handoff-manager.js +2 -2
  17. package/dist/src/core/context/{refactored-frame-manager.js → frame-manager.js} +3 -3
  18. package/dist/src/core/context/index.js +2 -2
  19. package/dist/src/core/database/sqlite-adapter.js +161 -1
  20. package/dist/src/core/digest/frame-digest-integration.js +1 -1
  21. package/dist/src/core/digest/index.js +1 -1
  22. package/dist/src/core/execution/parallel-executor.js +5 -1
  23. package/dist/src/core/projects/project-isolation.js +18 -4
  24. package/dist/src/core/security/index.js +2 -0
  25. package/dist/src/core/security/input-sanitizer.js +23 -0
  26. package/dist/src/core/utils/update-checker.js +10 -6
  27. package/dist/src/daemon/daemon-config.js +2 -1
  28. package/dist/src/daemon/services/auto-save-service.js +121 -0
  29. package/dist/src/daemon/services/maintenance-service.js +76 -1
  30. package/dist/src/features/sweep/prompt-builder.js +2 -2
  31. package/dist/src/hooks/graphiti-hooks.js +149 -0
  32. package/dist/src/hooks/session-summary.js +30 -0
  33. package/dist/src/integrations/graphiti/linear-graphiti-bridge.js +115 -0
  34. package/dist/src/integrations/greptile/client.js +101 -0
  35. package/dist/src/integrations/greptile/config.js +14 -0
  36. package/dist/src/integrations/greptile/index.js +11 -0
  37. package/dist/src/integrations/greptile/types.js +4 -0
  38. package/dist/src/integrations/linear/config.js +3 -1
  39. package/dist/src/integrations/linear/sync.js +18 -5
  40. package/dist/src/integrations/linear/webhook.js +16 -0
  41. package/dist/src/integrations/mcp/handlers/code-execution-handlers.js +33 -7
  42. package/dist/src/integrations/mcp/handlers/cord-handlers.js +397 -0
  43. package/dist/src/integrations/mcp/handlers/greptile-handlers.js +456 -0
  44. package/dist/src/integrations/mcp/handlers/index.js +55 -1
  45. package/dist/src/integrations/mcp/handlers/task-handlers.js +55 -12
  46. package/dist/src/integrations/mcp/handlers/team-handlers.js +211 -0
  47. package/dist/src/integrations/mcp/handlers/trace-handlers.js +25 -9
  48. package/dist/src/integrations/mcp/index.js +2 -2
  49. package/dist/src/integrations/mcp/refactored-server.js +31 -10
  50. package/dist/src/integrations/mcp/server.js +27 -0
  51. package/dist/src/integrations/mcp/tool-definitions.js +215 -1
  52. package/dist/src/integrations/ralph/context/context-budget-manager.js +10 -2
  53. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +54 -22
  54. package/dist/src/integrations/ralph/learning/pattern-learner.js +59 -24
  55. package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +81 -35
  56. package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +12 -4
  57. package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +32 -9
  58. package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +25 -8
  59. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +17 -5
  60. package/dist/src/integrations/ralph/visualization/ralph-debugger.js +73 -22
  61. package/dist/src/skills/claude-skills.js +46 -103
  62. package/dist/src/skills/parallel-agent-skill.js +514 -0
  63. package/dist/src/utils/hook-installer.js +8 -0
  64. package/package.json +5 -5
  65. package/scripts/gepa/.before-optimize.md +140 -159
  66. package/scripts/gepa/config.json +7 -1
  67. package/scripts/gepa/evals/fixtures/api-endpoint.ts +31 -0
  68. package/scripts/gepa/evals/fixtures/brittle-integration.ts +38 -0
  69. package/scripts/gepa/evals/fixtures/fts5-triggers.sql +23 -0
  70. package/scripts/gepa/evals/fixtures/leaky-service.ts +70 -0
  71. package/scripts/gepa/evals/fixtures/mcp-dispatch-stub.ts +39 -0
  72. package/scripts/gepa/evals/fixtures/pr-diff.txt +24 -0
  73. package/scripts/gepa/evals/fixtures/unsafe-webhook.ts +34 -0
  74. package/scripts/gepa/evals/fixtures/unwrapped-db-op.ts +42 -0
  75. package/scripts/gepa/evals/stackmemory-tasks.jsonl +8 -0
  76. package/scripts/gepa/generations/gen-000/baseline.md +172 -159
  77. package/scripts/gepa/generations/gen-001/baseline.md +172 -159
  78. package/scripts/gepa/generations/gen-001/variant-a.md +156 -146
  79. package/scripts/gepa/generations/gen-001/variant-b.md +199 -170
  80. package/scripts/gepa/generations/gen-001/variant-c.md +127 -46
  81. package/scripts/gepa/generations/gen-001/variant-d.md +160 -107
  82. package/scripts/gepa/hooks/reflect.js +44 -5
  83. package/scripts/gepa/optimize.js +281 -39
  84. package/scripts/gepa/results/eval-1-baseline.json +187 -10
  85. package/scripts/gepa/results/eval-1-variant-a.json +188 -11
  86. package/scripts/gepa/results/eval-1-variant-b.json +188 -11
  87. package/scripts/gepa/results/eval-1-variant-c.json +168 -11
  88. package/scripts/gepa/results/eval-1-variant-d.json +169 -12
  89. package/scripts/gepa/state.json +18 -18
  90. package/scripts/install-claude-hooks-auto.js +8 -0
  91. package/templates/claude-hooks/cord-trace.js +225 -0
  92. package/dist/src/core/config/storage-config.js +0 -114
  93. package/dist/src/core/storage/chromadb-adapter.js +0 -379
  94. package/dist/src/integrations/claude-code/enhanced-pre-clear-hooks.js +0 -458
  95. package/dist/src/integrations/ralph/coordination/enhanced-coordination.js +0 -409
  96. package/dist/src/skills/repo-ingestion-skill.js +0 -631
  97. package/templates/claude-hooks/chromadb-wrapper +0 -21
  98. /package/dist/src/core/context/{enhanced-rehydration.js → rehydration.js} +0 -0
  99. /package/dist/src/core/digest/{enhanced-hybrid-digest.js → hybrid-digest.js} +0 -0
  100. /package/dist/src/core/session/{enhanced-handoff.js → handoff.js} +0 -0
@@ -697,6 +697,104 @@ Searched ${result.data.timeRange.from} to ${result.data.timeRange.to}`
697
697
  process.exit(1);
698
698
  }
699
699
  });
700
+ const agentCmd = skillsCmd.command("agent").description("Spawn parallel Claude agents in isolated workspaces");
701
+ agentCmd.command("research <question>").description("Explore codebase and save findings as a frame").option("--timeout <ms>", "Agent timeout in milliseconds", "300000").action(async (question, options) => {
702
+ const spinner = ora("Spawning research agent...").start();
703
+ try {
704
+ const { context } = await initializeSkillContext();
705
+ const { ParallelAgentSkill } = await import("../../skills/parallel-agent-skill.js");
706
+ const agentSkill = new ParallelAgentSkill(context);
707
+ const result = await agentSkill.research(question, {
708
+ timeout: parseInt(options.timeout)
709
+ });
710
+ spinner.stop();
711
+ if (result.success) {
712
+ console.log(chalk.green("\u2713"), result.message);
713
+ if (result.data?.findings) {
714
+ console.log(chalk.cyan("\nFindings:\n"));
715
+ console.log(result.data.findings);
716
+ }
717
+ } else {
718
+ console.log(chalk.red("\u2717"), result.message);
719
+ }
720
+ await context.database.disconnect();
721
+ } catch (error) {
722
+ spinner.stop();
723
+ console.error(chalk.red("Error:"), error.message);
724
+ process.exit(1);
725
+ }
726
+ });
727
+ agentCmd.command("maintain <task>").description("Low-stakes fix, produces a .patch file").option("--timeout <ms>", "Agent timeout in milliseconds", "300000").action(async (task, options) => {
728
+ const spinner = ora("Spawning maintenance agent...").start();
729
+ try {
730
+ const { context } = await initializeSkillContext();
731
+ const { ParallelAgentSkill } = await import("../../skills/parallel-agent-skill.js");
732
+ const agentSkill = new ParallelAgentSkill(context);
733
+ const result = await agentSkill.maintain(task, {
734
+ timeout: parseInt(options.timeout)
735
+ });
736
+ spinner.stop();
737
+ if (result.success) {
738
+ console.log(chalk.green("\u2713"), result.message);
739
+ if (result.data) {
740
+ console.log(chalk.cyan("\nPatch Details:"));
741
+ console.log(` Files changed: ${result.data.filesChanged}`);
742
+ console.log(` Additions: +${result.data.additions}`);
743
+ console.log(` Deletions: -${result.data.deletions}`);
744
+ console.log(chalk.yellow(`
745
+ ${result.action}`));
746
+ }
747
+ } else {
748
+ console.log(chalk.red("\u2717"), result.message);
749
+ }
750
+ await context.database.disconnect();
751
+ } catch (error) {
752
+ spinner.stop();
753
+ console.error(chalk.red("Error:"), error.message);
754
+ process.exit(1);
755
+ }
756
+ });
757
+ agentCmd.command("spec-run <specPath>").description("Implement a spec on a branch, validate with lint+test+build").option("--timeout <ms>", "Agent timeout in milliseconds", "300000").action(async (specPath, options) => {
758
+ const spinner = ora("Spawning spec agent...").start();
759
+ try {
760
+ const { context } = await initializeSkillContext();
761
+ const { ParallelAgentSkill } = await import("../../skills/parallel-agent-skill.js");
762
+ const agentSkill = new ParallelAgentSkill(context);
763
+ const result = await agentSkill.specRun(specPath, {
764
+ timeout: parseInt(options.timeout)
765
+ });
766
+ spinner.stop();
767
+ if (result.success) {
768
+ console.log(chalk.green("\u2713"), result.message);
769
+ } else {
770
+ console.log(chalk.red("\u2717"), result.message);
771
+ }
772
+ if (result.data) {
773
+ console.log(chalk.cyan("\nResults:"));
774
+ console.log(` Branch: ${result.data.branch}`);
775
+ console.log(` Workspace: ${result.data.workDir}`);
776
+ const v = result.data.validation;
777
+ if (v) {
778
+ console.log(
779
+ ` Lint: ${v.lint ? chalk.green("pass") : chalk.red("fail")} Test: ${v.test ? chalk.green("pass") : chalk.red("fail")} Build: ${v.build ? chalk.green("pass") : chalk.red("fail")}`
780
+ );
781
+ }
782
+ if (result.data.diffStat) {
783
+ console.log(chalk.gray(`
784
+ ${result.data.diffStat}`));
785
+ }
786
+ if (result.action) {
787
+ console.log(chalk.yellow(`
788
+ ${result.action}`));
789
+ }
790
+ }
791
+ await context.database.disconnect();
792
+ } catch (error) {
793
+ spinner.stop();
794
+ console.error(chalk.red("Error:"), error.message);
795
+ process.exit(1);
796
+ }
797
+ });
700
798
  skillsCmd.command("help [skill]").description("Show help for a specific skill").action(async (skill) => {
701
799
  if (skill) {
702
800
  switch (skill) {
@@ -725,6 +823,29 @@ Options:
725
823
  --security Focus on security vulnerabilities
726
824
  --performance Focus on performance issues
727
825
  --verbose Show detailed output
826
+ `);
827
+ break;
828
+ case "agent":
829
+ console.log(`
830
+ agent \u2014 Parallel Agent Skill (Willison Patterns)
831
+
832
+ Spawn isolated Claude agents in disposable /tmp workspaces.
833
+
834
+ Subcommands:
835
+ research <question> Explore codebase, save findings as a frame
836
+ maintain <task> Low-stakes fix, produces a .patch file
837
+ spec-run <specPath> Implement spec on branch, validate
838
+
839
+ Options:
840
+ --timeout <ms> Agent timeout (default: 300000)
841
+
842
+ Examples:
843
+ stackmemory skills agent research "How does FTS5 search work?"
844
+ stackmemory skills agent maintain "Fix deprecation warning in webhook.ts"
845
+ stackmemory skills agent spec-run docs/specs/my-feature.md
846
+
847
+ Apply patches: git apply .stackmemory/patches/<file>.patch
848
+ Review specs: cd /tmp/sm-spec-* && git log --oneline
728
849
  `);
729
850
  break;
730
851
  default:
@@ -752,8 +873,9 @@ Options:
752
873
  console.log(
753
874
  " spec - Generate iterative spec docs (one-pager, dev-spec, prompt-plan, agents)"
754
875
  );
876
+ console.log(" linear-run - Execute Linear tasks via RLM orchestrator");
755
877
  console.log(
756
- " linear-run - Execute Linear tasks via RLM orchestrator\n"
878
+ " agent - Spawn parallel agents (research, maintain, spec-run)\n"
757
879
  );
758
880
  console.log(
759
881
  chalk.yellow(
@@ -13,7 +13,9 @@ import chalk from "chalk";
13
13
  import * as os from "os";
14
14
  import * as path from "path";
15
15
  function createStorageTierCommand() {
16
- const cmd = new Command("storage").description("Manage two-tier storage system");
16
+ const cmd = new Command("storage").description(
17
+ "Manage two-tier storage system"
18
+ );
17
19
  cmd.command("status").description("Show storage tier status and statistics").option("--json", "Output as JSON").action(async (options) => {
18
20
  try {
19
21
  const storage = await initializeStorage();
@@ -56,10 +58,18 @@ function createStorageTierCommand() {
56
58
  console.log(table.toString());
57
59
  console.log(`
58
60
  \u{1F4C8} Summary:`);
59
- console.log(` Local Usage: ${chalk.cyan(stats.localUsageMB.toFixed(1))} MB`);
60
- console.log(` Compression Ratio: ${chalk.cyan(stats.compressionRatio.toFixed(2))}x`);
61
- console.log(` Pending Migrations: ${chalk.yellow(stats.migrationsPending)}`);
62
- console.log(` Last Migration: ${stats.lastMigration ? chalk.green(stats.lastMigration.toISOString()) : chalk.gray("Never")}`);
61
+ console.log(
62
+ ` Local Usage: ${chalk.cyan(stats.localUsageMB.toFixed(1))} MB`
63
+ );
64
+ console.log(
65
+ ` Compression Ratio: ${chalk.cyan(stats.compressionRatio.toFixed(2))}x`
66
+ );
67
+ console.log(
68
+ ` Pending Migrations: ${chalk.yellow(stats.migrationsPending)}`
69
+ );
70
+ console.log(
71
+ ` Last Migration: ${stats.lastMigration ? chalk.green(stats.lastMigration.toISOString()) : chalk.gray("Never")}`
72
+ );
63
73
  await storage.shutdown();
64
74
  } catch (error) {
65
75
  logger.error("Failed to get storage status", { error });
@@ -75,7 +85,9 @@ function createStorageTierCommand() {
75
85
  console.log("Would migrate based on current triggers");
76
86
  } else {
77
87
  console.log("\u{1F504} Starting migration process...");
78
- console.log("Migration triggered. Use `storage status` to monitor progress.");
88
+ console.log(
89
+ "Migration triggered. Use `storage status` to monitor progress."
90
+ );
79
91
  }
80
92
  await storage.shutdown();
81
93
  } catch (error) {
@@ -87,7 +99,9 @@ function createStorageTierCommand() {
87
99
  cmd.command("cleanup").description("Clean up old data and optimize storage").option("--force", "Force cleanup without confirmation").option("--tier <tier>", "Clean specific tier only").action(async (options) => {
88
100
  try {
89
101
  if (!options.force) {
90
- console.log("\u26A0\uFE0F This will permanently delete old data. Use --force to confirm.");
102
+ console.log(
103
+ "\u26A0\uFE0F This will permanently delete old data. Use --force to confirm."
104
+ );
91
105
  process.exit(1);
92
106
  }
93
107
  const storage = await initializeStorage();
@@ -172,7 +186,11 @@ function getStorageConfig() {
172
186
  },
173
187
  migration: {
174
188
  ...defaultTwoTierConfig.migration,
175
- offlineQueuePath: path.join(homeDir, ".stackmemory", "offline-queue.json")
189
+ offlineQueuePath: path.join(
190
+ homeDir,
191
+ ".stackmemory",
192
+ "offline-queue.json"
193
+ )
176
194
  }
177
195
  };
178
196
  return config;
@@ -61,8 +61,6 @@ import { filterPending } from "../integrations/mcp/pending-utils.js";
61
61
  import { ProjectManager } from "../core/projects/project-manager.js";
62
62
  import { join } from "path";
63
63
  import { existsSync, mkdirSync } from "fs";
64
- import inquirer from "inquirer";
65
- import { enableChromaDB } from "../core/config/storage-config.js";
66
64
  import { createRequire } from "module";
67
65
  import { fileURLToPath } from "url";
68
66
  import * as pathModule from "path";
@@ -104,10 +102,7 @@ program.name("stackmemory").description(
104
102
  ).version(VERSION);
105
103
  program.command("init").description(
106
104
  "Initialize StackMemory in current project (zero-config by default)"
107
- ).option("-i, --interactive", "Interactive mode with configuration prompts").option(
108
- "--chromadb",
109
- "Enable ChromaDB for semantic search (prompts for API key)"
110
- ).option("--daemon", "Start the background daemon after initialization").action(async (options) => {
105
+ ).option("-i, --interactive", "Interactive mode with configuration prompts").option("--daemon", "Start the background daemon after initialization").action(async (options) => {
111
106
  try {
112
107
  const projectRoot = process.cwd();
113
108
  const dbDir = join(projectRoot, ".stackmemory");
@@ -120,28 +115,6 @@ program.command("init").description(
120
115
  if (!existsSync(dbDir)) {
121
116
  mkdirSync(dbDir, { recursive: true });
122
117
  }
123
- if (options.chromadb) {
124
- await promptAndEnableChromaDB();
125
- } else if (options.interactive && process.stdin.isTTY) {
126
- console.log(chalk.cyan("\nStorage Configuration"));
127
- console.log(
128
- chalk.gray("SQLite (default) is fast and requires no setup.")
129
- );
130
- console.log(
131
- chalk.gray("ChromaDB adds semantic search but requires an API key.\n")
132
- );
133
- const { enableChroma } = await inquirer.prompt([
134
- {
135
- type: "confirm",
136
- name: "enableChroma",
137
- message: "Enable ChromaDB for semantic search?",
138
- default: false
139
- }
140
- ]);
141
- if (enableChroma) {
142
- await promptAndEnableChromaDB();
143
- }
144
- }
145
118
  const dbPath = join(dbDir, "context.db");
146
119
  if (!isTestEnv()) {
147
120
  const db = await openDatabase(dbPath);
@@ -195,35 +168,6 @@ program.command("init").description(
195
168
  process.exit(1);
196
169
  }
197
170
  });
198
- async function promptAndEnableChromaDB() {
199
- const answers = await inquirer.prompt([
200
- {
201
- type: "password",
202
- name: "apiKey",
203
- message: "Enter your ChromaDB API key:",
204
- validate: (input) => {
205
- if (!input || input.trim().length === 0) {
206
- return "API key is required for ChromaDB";
207
- }
208
- return true;
209
- }
210
- },
211
- {
212
- type: "input",
213
- name: "apiUrl",
214
- message: "ChromaDB API URL (press Enter for default):",
215
- default: "https://api.trychroma.com"
216
- }
217
- ]);
218
- enableChromaDB({
219
- apiKey: answers.apiKey,
220
- apiUrl: answers.apiUrl
221
- });
222
- console.log(chalk.green("[OK] ChromaDB enabled for semantic search."));
223
- console.log(
224
- chalk.gray("API key saved to ~/.stackmemory/storage-config.json")
225
- );
226
- }
227
171
  program.command("status").description("Show current StackMemory status").option("--all", "Show all active frames across sessions").option("--project", "Show all active frames in current project").option("--session <id>", "Show frames for specific session").action(async (options) => {
228
172
  return trace.command("stackmemory-status", options, async () => {
229
173
  try {
@@ -11,8 +11,6 @@ function isFeatureEnabled(feature) {
11
11
  switch (feature) {
12
12
  case "linear":
13
13
  return process.env["STACKMEMORY_LINEAR"] !== "false" && (!!process.env["LINEAR_API_KEY"] || !!process.env["LINEAR_OAUTH_TOKEN"]);
14
- case "chromadb":
15
- return process.env["STACKMEMORY_CHROMADB"] === "true";
16
14
  case "aiSummaries":
17
15
  return process.env["STACKMEMORY_AI"] !== "false" && (!!process.env["ANTHROPIC_API_KEY"] || !!process.env["OPENAI_API_KEY"]);
18
16
  case "skills":
@@ -29,7 +27,6 @@ function getFeatureFlags() {
29
27
  return {
30
28
  core: true,
31
29
  linear: isFeatureEnabled("linear"),
32
- chromadb: isFeatureEnabled("chromadb"),
33
30
  aiSummaries: isFeatureEnabled("aiSummaries"),
34
31
  skills: isFeatureEnabled("skills"),
35
32
  ralph: isFeatureEnabled("ralph"),
@@ -46,7 +43,6 @@ function logFeatureStatus() {
46
43
  console.log(
47
44
  ` Linear: ${flags.linear ? "enabled" : "disabled (no API key)"}`
48
45
  );
49
- console.log(` ChromaDB: ${flags.chromadb ? "enabled" : "disabled"}`);
50
46
  console.log(
51
47
  ` AI Summaries: ${flags.aiSummaries ? "enabled" : "disabled (no API key)"}`
52
48
  );
@@ -99,9 +99,16 @@ class DualStackManager {
99
99
  )
100
100
  `;
101
101
  if (this.adapter.isConnected()) {
102
- await this.adapter.execute?.(createStackContextsTable) || this.executeSchemaQuery(createStackContextsTable);
103
- await this.adapter.execute?.(createHandoffRequestsTable) || this.executeSchemaQuery(createHandoffRequestsTable);
104
- await this.adapter.execute?.(createStackSyncLogTable) || this.executeSchemaQuery(createStackSyncLogTable);
102
+ const adapterAny = this.adapter;
103
+ if (!await adapterAny.execute?.(createStackContextsTable)) {
104
+ this.executeSchemaQuery(createStackContextsTable);
105
+ }
106
+ if (!await adapterAny.execute?.(createHandoffRequestsTable)) {
107
+ this.executeSchemaQuery(createHandoffRequestsTable);
108
+ }
109
+ if (!await adapterAny.execute?.(createStackSyncLogTable)) {
110
+ this.executeSchemaQuery(createStackSyncLogTable);
111
+ }
105
112
  }
106
113
  await this.adapter.commitTransaction();
107
114
  logger.info("Dual stack schema initialized successfully");
@@ -56,6 +56,7 @@ class FrameDatabase {
56
56
  created_at INTEGER NOT NULL DEFAULT (unixepoch()),
57
57
  closed_at INTEGER,
58
58
  retention_policy TEXT DEFAULT 'default',
59
+ importance_score REAL DEFAULT 0.5,
59
60
  FOREIGN KEY (parent_frame_id) REFERENCES frames(frame_id)
60
61
  );
61
62
  `);
@@ -90,6 +91,12 @@ class FrameDatabase {
90
91
  );
91
92
  } catch {
92
93
  }
94
+ try {
95
+ this.db.exec(
96
+ "ALTER TABLE frames ADD COLUMN importance_score REAL DEFAULT 0.5"
97
+ );
98
+ } catch {
99
+ }
93
100
  this.db.exec(`
94
101
  CREATE INDEX IF NOT EXISTS idx_frames_run ON frames(run_id);
95
102
  CREATE INDEX IF NOT EXISTS idx_frames_project ON frames(project_id);
@@ -99,6 +106,7 @@ class FrameDatabase {
99
106
  CREATE INDEX IF NOT EXISTS idx_frames_project_state ON frames(project_id, state);
100
107
  CREATE INDEX IF NOT EXISTS idx_frames_project_created ON frames(project_id, created_at DESC);
101
108
  CREATE INDEX IF NOT EXISTS idx_frames_retention_created ON frames(retention_policy, created_at);
109
+ CREATE INDEX IF NOT EXISTS idx_frames_gc_score ON frames(state, retention_policy, importance_score ASC, created_at ASC);
102
110
  CREATE INDEX IF NOT EXISTS idx_events_frame ON events(frame_id);
103
111
  CREATE INDEX IF NOT EXISTS idx_events_seq ON events(frame_id, seq);
104
112
  CREATE INDEX IF NOT EXISTS idx_anchors_frame ON anchors(frame_id);
@@ -142,6 +150,30 @@ class FrameDatabase {
142
150
  -- Set initial schema version if not exists
143
151
  INSERT OR IGNORE INTO schema_version (version) VALUES (1);
144
152
  `);
153
+ this.db.exec(`
154
+ CREATE TABLE IF NOT EXISTS cord_tasks (
155
+ task_id TEXT PRIMARY KEY,
156
+ parent_id TEXT,
157
+ project_id TEXT NOT NULL,
158
+ run_id TEXT NOT NULL,
159
+ goal TEXT NOT NULL,
160
+ prompt TEXT NOT NULL DEFAULT '',
161
+ result TEXT,
162
+ status TEXT NOT NULL DEFAULT 'pending'
163
+ CHECK (status IN ('pending','active','completed','blocked','asked')),
164
+ context_mode TEXT NOT NULL DEFAULT 'spawn'
165
+ CHECK (context_mode IN ('spawn','fork','ask')),
166
+ blocked_by TEXT NOT NULL DEFAULT '[]',
167
+ depth INTEGER NOT NULL DEFAULT 0,
168
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
169
+ completed_at INTEGER,
170
+ FOREIGN KEY (parent_id) REFERENCES cord_tasks(task_id)
171
+ );
172
+ CREATE INDEX IF NOT EXISTS idx_cord_tasks_project ON cord_tasks(project_id);
173
+ CREATE INDEX IF NOT EXISTS idx_cord_tasks_parent ON cord_tasks(parent_id);
174
+ CREATE INDEX IF NOT EXISTS idx_cord_tasks_status ON cord_tasks(status);
175
+ CREATE INDEX IF NOT EXISTS idx_cord_tasks_project_status ON cord_tasks(project_id, status);
176
+ `);
145
177
  logger.info("Frame database schema initialized");
146
178
  } catch (error) {
147
179
  throw new DatabaseError(
@@ -583,12 +583,12 @@ class FrameHandoffManager {
583
583
  ErrorCode.RESOURCE_NOT_FOUND
584
584
  );
585
585
  }
586
- const self = this;
586
+ const activeHandoffs = this.activeHandoffs;
587
587
  return {
588
588
  async *[Symbol.asyncIterator]() {
589
589
  let lastStatus = progress.status;
590
590
  while (lastStatus !== "completed" && lastStatus !== "failed" && lastStatus !== "cancelled") {
591
- const currentProgress = self.activeHandoffs.get(requestId);
591
+ const currentProgress = activeHandoffs.get(requestId);
592
592
  if (currentProgress && currentProgress.status !== lastStatus) {
593
593
  lastStatus = currentProgress.status;
594
594
  yield currentProgress;
@@ -18,7 +18,7 @@ import { FrameDatabase } from "./frame-database.js";
18
18
  import { FrameStack } from "./frame-stack.js";
19
19
  import { FrameDigestGenerator } from "./frame-digest.js";
20
20
  import { FrameRecovery } from "./frame-recovery.js";
21
- class RefactoredFrameManager {
21
+ class FrameManager {
22
22
  frameDb;
23
23
  frameStack;
24
24
  digestGenerator;
@@ -53,7 +53,7 @@ class RefactoredFrameManager {
53
53
  this.frameRecovery = new FrameRecovery(db);
54
54
  this.frameRecovery.setCurrentRunId(this.currentRunId);
55
55
  this.frameDb.initSchema();
56
- logger.info("RefactoredFrameManager initialized", {
56
+ logger.info("FrameManager initialized", {
57
57
  projectId: this.projectId,
58
58
  runId: this.currentRunId,
59
59
  sessionId: this.sessionId
@@ -756,5 +756,5 @@ class RefactoredFrameManager {
756
756
  }
757
757
  }
758
758
  export {
759
- RefactoredFrameManager
759
+ FrameManager
760
760
  };
@@ -2,7 +2,7 @@ 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
- import { RefactoredFrameManager } from "./refactored-frame-manager.js";
5
+ import { FrameManager } from "./frame-manager.js";
6
6
  import { FrameDatabase } from "./frame-database.js";
7
7
  import { FrameStack } from "./frame-stack.js";
8
8
  import { FrameDigestGenerator } from "./frame-digest.js";
@@ -16,7 +16,7 @@ import {
16
16
  export {
17
17
  FrameDatabase,
18
18
  FrameDigestGenerator,
19
- RefactoredFrameManager as FrameManager,
19
+ FrameManager,
20
20
  FrameRecovery,
21
21
  FrameStack,
22
22
  frameLifecycleHooks,
@@ -237,6 +237,10 @@ class SQLiteAdapter extends FeatureAwareDatabaseAdapter {
237
237
  "FTS5 index populated from existing frames (migration v1\u2192v2)"
238
238
  );
239
239
  }
240
+ if (version < 3) {
241
+ this.db.prepare("INSERT OR REPLACE INTO schema_version (version) VALUES (?)").run(3);
242
+ logger.info("Schema migration v2\u2192v3: GC importance_score support");
243
+ }
240
244
  }
241
245
  /**
242
246
  * Initialize sqlite-vec for vector search
@@ -305,6 +309,7 @@ class SQLiteAdapter extends FeatureAwareDatabaseAdapter {
305
309
  const retentionDays = options.retentionDays ?? 90;
306
310
  const batchSize = options.batchSize ?? 100;
307
311
  const dryRun = options.dryRun ?? false;
312
+ const protectedRunIds = options.protectedRunIds ?? [];
308
313
  const nowSec = Math.floor(Date.now() / 1e3);
309
314
  const defaultCutoff = nowSec - retentionDays * 86400;
310
315
  const ttl30dCutoff = nowSec - 30 * 86400;
@@ -317,8 +322,17 @@ class SQLiteAdapter extends FeatureAwareDatabaseAdapter {
317
322
  OR (retention_policy = 'ttl_7d' AND created_at < ?)
318
323
  )
319
324
  AND retention_policy != 'keep_forever'
325
+ AND state = 'closed'
326
+ AND run_id NOT IN (SELECT value FROM json_each(?))
327
+ ORDER BY importance_score ASC, created_at ASC
320
328
  LIMIT ?`
321
- ).all(defaultCutoff, ttl30dCutoff, ttl7dCutoff, batchSize);
329
+ ).all(
330
+ defaultCutoff,
331
+ ttl30dCutoff,
332
+ ttl7dCutoff,
333
+ JSON.stringify(protectedRunIds),
334
+ batchSize
335
+ );
322
336
  const frameIds = candidates.map((r) => r.frame_id);
323
337
  if (frameIds.length === 0) {
324
338
  return {
@@ -388,6 +402,69 @@ class SQLiteAdapter extends FeatureAwareDatabaseAdapter {
388
402
  ftsEntriesDeleted: frameIds.length
389
403
  };
390
404
  }
405
+ /**
406
+ * Compute importance score for a single frame.
407
+ * Score range: [0.0, 1.0] — higher means more important, less likely to be GC'd.
408
+ */
409
+ computeImportanceScore(frameId) {
410
+ if (!this.db)
411
+ throw new DatabaseError(
412
+ "Database not connected",
413
+ ErrorCode.DB_CONNECTION_FAILED
414
+ );
415
+ const frame = this.db.prepare(
416
+ "SELECT frame_id, digest_text, created_at FROM frames WHERE frame_id = ?"
417
+ ).get(frameId);
418
+ if (!frame) return 0.3;
419
+ let score = 0.3;
420
+ const decisionCount = this.db.prepare(
421
+ "SELECT COUNT(*) as count FROM anchors WHERE frame_id = ? AND type = 'DECISION'"
422
+ ).get(frameId).count;
423
+ if (decisionCount > 0) score += 0.15;
424
+ const eventCount = this.db.prepare("SELECT COUNT(*) as count FROM events WHERE frame_id = ?").get(frameId).count;
425
+ if (eventCount > 3) score += 0.1;
426
+ if (frame.digest_text) score += 0.15;
427
+ const childCount = this.db.prepare(
428
+ "SELECT COUNT(*) as count FROM frames WHERE parent_frame_id = ?"
429
+ ).get(frameId).count;
430
+ if (childCount > 0) score += 0.1;
431
+ const nowSec = Math.floor(Date.now() / 1e3);
432
+ if (nowSec - frame.created_at < 86400) score += 0.1;
433
+ return Math.round(Math.min(score, 1) * 100) / 100;
434
+ }
435
+ /**
436
+ * Recompute importance scores for frames still at default score (0.5).
437
+ * Processes oldest frames first in batches.
438
+ * Returns count of frames updated.
439
+ */
440
+ recomputeImportanceScores(batchSize = 100) {
441
+ if (!this.db)
442
+ throw new DatabaseError(
443
+ "Database not connected",
444
+ ErrorCode.DB_CONNECTION_FAILED
445
+ );
446
+ const frames = this.db.prepare(
447
+ "SELECT frame_id FROM frames WHERE importance_score = 0.5 ORDER BY created_at ASC LIMIT ?"
448
+ ).all(batchSize);
449
+ const updateStmt = this.db.prepare(
450
+ "UPDATE frames SET importance_score = ? WHERE frame_id = ?"
451
+ );
452
+ let updated = 0;
453
+ for (const { frame_id } of frames) {
454
+ const score = this.computeImportanceScore(frame_id);
455
+ if (score !== 0.5) {
456
+ updateStmt.run(score, frame_id);
457
+ updated++;
458
+ }
459
+ }
460
+ if (updated > 0) {
461
+ logger.info("Recomputed importance scores", {
462
+ checked: frames.length,
463
+ updated
464
+ });
465
+ }
466
+ return updated;
467
+ }
391
468
  async migrateSchema(targetVersion) {
392
469
  if (!this.db)
393
470
  throw new DatabaseError(
@@ -1252,6 +1329,89 @@ class SQLiteAdapter extends FeatureAwareDatabaseAdapter {
1252
1329
  }
1253
1330
  return Buffer.from(JSON.stringify(data, null, 2));
1254
1331
  }
1332
+ /**
1333
+ * Get recent frames from other run_ids within the same project.
1334
+ * Used by team tools to read cross-agent context.
1335
+ */
1336
+ async getRecentFramesExcludingRun(projectId, excludeRunId, opts) {
1337
+ if (!this.db)
1338
+ throw new DatabaseError(
1339
+ "Database not connected",
1340
+ ErrorCode.DB_CONNECTION_FAILED
1341
+ );
1342
+ const limit = opts?.limit ?? 10;
1343
+ const params = [projectId, excludeRunId];
1344
+ let whereExtra = "";
1345
+ if (opts?.since) {
1346
+ whereExtra += " AND created_at > ?";
1347
+ params.push(Math.floor(opts.since / 1e3));
1348
+ }
1349
+ if (opts?.types && opts.types.length > 0) {
1350
+ const placeholders = opts.types.map(() => "?").join(",");
1351
+ whereExtra += ` AND type IN (${placeholders})`;
1352
+ params.push(...opts.types);
1353
+ }
1354
+ params.push(limit);
1355
+ const rows = this.db.prepare(
1356
+ `SELECT * FROM frames
1357
+ WHERE project_id = ? AND run_id != ?${whereExtra}
1358
+ ORDER BY created_at DESC
1359
+ LIMIT ?`
1360
+ ).all(...params);
1361
+ const frameIds = rows.map((r) => r.frame_id);
1362
+ const anchorsByFrame = /* @__PURE__ */ new Map();
1363
+ if (frameIds.length > 0) {
1364
+ const placeholders = frameIds.map(() => "?").join(",");
1365
+ const anchorRows = this.db.prepare(
1366
+ `SELECT * FROM anchors WHERE frame_id IN (${placeholders})
1367
+ ORDER BY priority DESC, created_at ASC`
1368
+ ).all(...frameIds);
1369
+ for (const row of anchorRows) {
1370
+ const list = anchorsByFrame.get(row.frame_id) || [];
1371
+ list.push({ ...row, metadata: JSON.parse(row.metadata || "{}") });
1372
+ anchorsByFrame.set(row.frame_id, list);
1373
+ }
1374
+ }
1375
+ return rows.map((row) => ({
1376
+ ...row,
1377
+ inputs: JSON.parse(row.inputs || "{}"),
1378
+ outputs: JSON.parse(row.outputs || "{}"),
1379
+ digest_json: JSON.parse(row.digest_json || "{}"),
1380
+ anchors: anchorsByFrame.get(row.frame_id) || []
1381
+ }));
1382
+ }
1383
+ /**
1384
+ * Get anchors explicitly shared for team visibility.
1385
+ * Finds anchors where metadata contains `"shared":true`.
1386
+ */
1387
+ async getSharedAnchors(projectId, opts) {
1388
+ if (!this.db)
1389
+ throw new DatabaseError(
1390
+ "Database not connected",
1391
+ ErrorCode.DB_CONNECTION_FAILED
1392
+ );
1393
+ const limit = opts?.limit ?? 20;
1394
+ const params = [projectId];
1395
+ let whereExtra = "";
1396
+ if (opts?.since) {
1397
+ whereExtra += " AND a.created_at > ?";
1398
+ params.push(Math.floor(opts.since / 1e3));
1399
+ }
1400
+ params.push(limit);
1401
+ const rows = this.db.prepare(
1402
+ `SELECT a.*, f.name as frame_name, f.run_id
1403
+ FROM anchors a
1404
+ JOIN frames f ON a.frame_id = f.frame_id
1405
+ WHERE f.project_id = ?
1406
+ AND a.metadata LIKE '%"shared":true%'${whereExtra}
1407
+ ORDER BY a.priority DESC, a.created_at DESC
1408
+ LIMIT ?`
1409
+ ).all(...params);
1410
+ return rows.map((row) => ({
1411
+ ...row,
1412
+ metadata: JSON.parse(row.metadata || "{}")
1413
+ }));
1414
+ }
1255
1415
  async importData(data, format, options) {
1256
1416
  if (!this.db)
1257
1417
  throw new DatabaseError(
@@ -2,7 +2,7 @@ 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
- import { EnhancedHybridDigestGenerator } from "./enhanced-hybrid-digest.js";
5
+ import { EnhancedHybridDigestGenerator } from "./hybrid-digest.js";
6
6
  import { logger } from "../monitoring/logger.js";
7
7
  class FrameDigestIntegration {
8
8
  frameManager;
@@ -4,5 +4,5 @@ const __filename = __fileURLToPath(import.meta.url);
4
4
  const __dirname = __pathDirname(__filename);
5
5
  export * from "./types.js";
6
6
  export * from "./hybrid-digest-generator.js";
7
- export * from "./enhanced-hybrid-digest.js";
7
+ export * from "./hybrid-digest.js";
8
8
  export * from "./frame-digest-integration.js";