@stackmemoryai/stackmemory 1.7.0 → 1.8.1

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 (83) hide show
  1. package/dist/src/agents/core/agent-task-manager.js +3 -3
  2. package/dist/src/agents/verifiers/formatter-verifier.js +1 -1
  3. package/dist/src/agents/verifiers/llm-judge.js +2 -2
  4. package/dist/src/cli/commands/context-rehydrate.js +2 -2
  5. package/dist/src/cli/commands/log.js +1 -1
  6. package/dist/src/cli/commands/onboard.js +4 -4
  7. package/dist/src/cli/commands/search.js +3 -3
  8. package/dist/src/cli/commands/setup.js +1 -1
  9. package/dist/src/cli/commands/signup.js +1 -1
  10. package/dist/src/cli/commands/skills.js +2 -2
  11. package/dist/src/cli/commands/worktree.js +1 -1
  12. package/dist/src/core/config/config-manager.js +3 -1
  13. package/dist/src/core/config/types.js +7 -0
  14. package/dist/src/core/context/dual-stack-manager.js +2 -2
  15. package/dist/src/core/context/frame-database.js +38 -0
  16. package/dist/src/core/context/frame-enrichment.js +93 -0
  17. package/dist/src/core/context/frame-handoff-manager.js +1 -1
  18. package/dist/src/core/context/frame-manager.js +4 -4
  19. package/dist/src/core/context/rehydration.js +6 -6
  20. package/dist/src/core/database/sqlite-adapter.js +136 -15
  21. package/dist/src/core/digest/frame-digest-integration.js +1 -1
  22. package/dist/src/core/digest/hybrid-digest-generator.js +1 -1
  23. package/dist/src/core/digest/hybrid-digest.js +3 -3
  24. package/dist/src/core/errors/recovery.js +1 -1
  25. package/dist/src/core/extensions/loader.js +10 -10
  26. package/dist/src/core/extensions/plugin-system.js +1 -1
  27. package/dist/src/core/monitoring/logger.js +2 -2
  28. package/dist/src/core/monitoring/metrics.js +2 -2
  29. package/dist/src/core/monitoring/progress-tracker.js +2 -2
  30. package/dist/src/core/performance/lazy-context-loader.js +1 -1
  31. package/dist/src/core/performance/performance-benchmark.js +8 -8
  32. package/dist/src/core/performance/performance-profiler.js +2 -2
  33. package/dist/src/core/performance/streaming-jsonl-parser.js +1 -1
  34. package/dist/src/core/projects/project-isolation.js +1 -1
  35. package/dist/src/core/projects/project-manager.js +5 -5
  36. package/dist/src/core/session/clear-survival.js +3 -3
  37. package/dist/src/core/session/handoff-generator.js +1 -1
  38. package/dist/src/core/session/session-manager.js +4 -4
  39. package/dist/src/core/skills/skill-storage.js +2 -2
  40. package/dist/src/core/storage/remote-storage.js +2 -2
  41. package/dist/src/core/trace/cli-trace-wrapper.js +3 -3
  42. package/dist/src/core/trace/debug-trace.js +2 -2
  43. package/dist/src/core/trace/trace-detector.js +1 -1
  44. package/dist/src/core/worktree/worktree-manager.js +2 -2
  45. package/dist/src/features/analytics/api/analytics-api.js +1 -1
  46. package/dist/src/features/analytics/core/analytics-service.js +2 -2
  47. package/dist/src/features/analytics/queries/metrics-queries.js +1 -1
  48. package/dist/src/features/web/server/index.js +8 -8
  49. package/dist/src/integrations/anthropic/client.js +4 -4
  50. package/dist/src/integrations/claude-code/post-task-hooks.js +3 -3
  51. package/dist/src/integrations/claude-code/subagent-client.js +2 -2
  52. package/dist/src/integrations/diffmem/client.js +1 -1
  53. package/dist/src/integrations/linear/client.js +1 -1
  54. package/dist/src/integrations/linear/migration.js +1 -1
  55. package/dist/src/integrations/linear/oauth-server.js +2 -2
  56. package/dist/src/integrations/linear/sync.js +1 -1
  57. package/dist/src/integrations/linear/webhook-handler.js +2 -2
  58. package/dist/src/integrations/linear/webhook-server.js +2 -2
  59. package/dist/src/integrations/linear/webhook.js +2 -2
  60. package/dist/src/integrations/mcp/handlers/discovery-handlers.js +3 -3
  61. package/dist/src/integrations/mcp/handlers/linear-handlers.js +1 -1
  62. package/dist/src/integrations/mcp/handlers/skill-handlers.js +2 -2
  63. package/dist/src/integrations/mcp/handlers/task-handlers.js +2 -2
  64. package/dist/src/integrations/mcp/handlers/trace-handlers.js +1 -1
  65. package/dist/src/integrations/mcp/refactored-server.js +2 -2
  66. package/dist/src/integrations/mcp/remote-server.js +1 -1
  67. package/dist/src/integrations/mcp/server.js +4 -4
  68. package/dist/src/integrations/ralph/bridge/ralph-stackmemory-bridge.js +13 -13
  69. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +1 -1
  70. package/dist/src/integrations/ralph/learning/pattern-learner.js +3 -3
  71. package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +1 -1
  72. package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +2 -2
  73. package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +19 -19
  74. package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +5 -5
  75. package/dist/src/integrations/ralph/performance/performance-optimizer.js +1 -1
  76. package/dist/src/integrations/ralph/recovery/crash-recovery.js +1 -1
  77. package/dist/src/integrations/ralph/state/state-reconciler.js +1 -1
  78. package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +4 -4
  79. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +8 -8
  80. package/dist/src/integrations/ralph/visualization/ralph-debugger.js +3 -3
  81. package/dist/src/orchestrators/multimodal/harness.js +3 -3
  82. package/dist/src/servers/production/auth-middleware.js +2 -2
  83. package/package.json +1 -1
@@ -161,7 +161,7 @@ class AgentTaskManager {
161
161
  /**
162
162
  * Run a single verifier
163
163
  */
164
- async runVerifier(verifierId, action, context) {
164
+ async runVerifier(verifierId, _action, _context) {
165
165
  const mockResults = {
166
166
  formatter: () => ({
167
167
  verifierId: "formatter",
@@ -317,7 +317,7 @@ class AgentTaskManager {
317
317
  * Generate feedback from verification results
318
318
  */
319
319
  generateFeedback(results) {
320
- const failed = results.filter((r) => !r.passed);
320
+ const _failed = results.filter((r) => !r.passed);
321
321
  const warnings = results.filter(
322
322
  (r) => !r.passed && r.severity === "warning"
323
323
  );
@@ -363,7 +363,7 @@ ${warningMessages}`;
363
363
  taskId: session.taskId,
364
364
  turnCount: session.turnCount
365
365
  });
366
- const task = this.taskStore.getTask(session.taskId);
366
+ const _task = this.taskStore.getTask(session.taskId);
367
367
  const isComplete = this.assessTaskCompletion(session);
368
368
  if (isComplete) {
369
369
  await this.completeSession(session);
@@ -95,7 +95,7 @@ class FormatterVerifier extends BaseVerifier {
95
95
  async runFormatter(formatter, context) {
96
96
  const command = `${formatter.checkCommand} "${context.filePath}"`;
97
97
  try {
98
- const { stdout, stderr } = await execAsync(command, {
98
+ const { _stdout, _stderr } = await execAsync(command, {
99
99
  cwd: process.cwd(),
100
100
  timeout: this.config.timeout
101
101
  });
@@ -28,7 +28,7 @@ class LLMJudge extends BaseVerifier {
28
28
  ...config
29
29
  });
30
30
  }
31
- shouldActivate(context) {
31
+ shouldActivate(_context) {
32
32
  return true;
33
33
  }
34
34
  async verify(input, context) {
@@ -185,7 +185,7 @@ class LLMJudge extends BaseVerifier {
185
185
  }
186
186
  );
187
187
  }
188
- generateJudgementFeedback(criteria, overallScore, context) {
188
+ generateJudgementFeedback(criteria, overallScore, _context) {
189
189
  if (overallScore >= this.VETO_THRESHOLD) {
190
190
  return `Semantic validation PASSED (${(overallScore * 100).toFixed(1)}% confidence). Changes adequately address the requirements.`;
191
191
  }
@@ -289,7 +289,7 @@ async function listCheckpoints() {
289
289
  console.log(
290
290
  "\u{1F4A1} Verify: stackmemory context rehydrate --verify -c <checkpoint-id>"
291
291
  );
292
- } catch (err) {
292
+ } catch {
293
293
  console.log("\u{1F4ED} No rehydration checkpoints directory found");
294
294
  console.log(
295
295
  "\u{1F4A1} Create first checkpoint with: stackmemory context rehydrate --create"
@@ -382,7 +382,7 @@ async function captureStackTraces() {
382
382
  try {
383
383
  const traces = [];
384
384
  const fs = await import("fs/promises");
385
- const { execSync } = await import("child_process");
385
+ const { _execSync } = await import("child_process");
386
386
  const stackMemoryLogs = [
387
387
  ".stackmemory/error.log",
388
388
  ".stackmemory/compaction.log",
@@ -10,7 +10,7 @@ function createLogCommand() {
10
10
  const log = new Command("log").alias("history").description("View recent activity log").option("-n, --lines <n>", "Number of entries to show", "20").option("-t, --type <type>", "Filter by type (task, frame, event, sync)").option("-f, --follow", "Follow log in real-time").action(async (options) => {
11
11
  const projectRoot = process.cwd();
12
12
  const dbPath = join(projectRoot, ".stackmemory", "context.db");
13
- const tasksPath = join(projectRoot, ".stackmemory", "tasks.jsonl");
13
+ const _tasksPath = join(projectRoot, ".stackmemory", "tasks.jsonl");
14
14
  if (!existsSync(dbPath)) {
15
15
  console.log(
16
16
  '\u274C StackMemory not initialized. Run "stackmemory init" first.'
@@ -99,7 +99,7 @@ async function runOnboarding() {
99
99
  console.log(chalk.gray(`Opening signup page: ${signupUrl}`));
100
100
  const mod = await import("open");
101
101
  await mod.default(signupUrl);
102
- } catch (e) {
102
+ } catch {
103
103
  console.log(
104
104
  chalk.yellow(
105
105
  "Could not open browser automatically. Please sign up and obtain your DATABASE_URL."
@@ -379,7 +379,7 @@ DATABASE_URL=${config.databaseUrl}
379
379
  " Tip: export DATABASE_URL from this file in your shell profile."
380
380
  )
381
381
  );
382
- } catch (e) {
382
+ } catch {
383
383
  console.log(chalk.yellow(" \u26A0 Could not write hosted DB env file"));
384
384
  }
385
385
  }
@@ -410,7 +410,7 @@ exec stackmemory "$@"
410
410
  execFileSync("ln", ["-s", sourcePath, binPath]);
411
411
  console.log(chalk.green(" \u2713 Created claude-sm command"));
412
412
  }
413
- } catch (error) {
413
+ } catch {
414
414
  console.log(
415
415
  chalk.yellow(" \u26A0 Could not create claude-sm symlink (may need sudo)")
416
416
  );
@@ -506,7 +506,7 @@ fi
506
506
  execFileSync("ln", ["-s", codexSourcePath, codexBinPath]);
507
507
  console.log(chalk.green(" \u2713 Created codex-sm command"));
508
508
  }
509
- } catch (error) {
509
+ } catch {
510
510
  console.log(
511
511
  chalk.yellow(" \u26A0 Could not create codex-sm symlink (may need sudo)")
512
512
  );
@@ -88,7 +88,7 @@ function createSearchCommand() {
88
88
  console.log("");
89
89
  totalResults += tasks.length;
90
90
  }
91
- } catch (error) {
91
+ } catch {
92
92
  }
93
93
  }
94
94
  if (searchContext) {
@@ -123,7 +123,7 @@ function createSearchCommand() {
123
123
  console.log("");
124
124
  totalResults += contexts.length;
125
125
  }
126
- } catch (error) {
126
+ } catch {
127
127
  }
128
128
  }
129
129
  if (searchContext) {
@@ -154,7 +154,7 @@ function createSearchCommand() {
154
154
  console.log("");
155
155
  totalResults += events.length;
156
156
  }
157
- } catch (error) {
157
+ } catch {
158
158
  }
159
159
  }
160
160
  db.close();
@@ -759,7 +759,7 @@ function createSetupRemoteCommand() {
759
759
  const serviceDir = platform === "darwin" ? join(home, "Library", "LaunchAgents") : join(home, ".config", "systemd", "user");
760
760
  const serviceFile = platform === "darwin" ? join(serviceDir, `${serviceName}.plist`) : join(serviceDir, `${serviceName}.service`);
761
761
  const logDir = join(home, ".stackmemory", "logs");
762
- const pidFile = join(home, ".stackmemory", "remote-mcp.pid");
762
+ const _pidFile = join(home, ".stackmemory", "remote-mcp.pid");
763
763
  if (options.status) {
764
764
  console.log(chalk.cyan("\nRemote MCP Server Status\n"));
765
765
  if (platform === "darwin") {
@@ -15,7 +15,7 @@ function registerSignupCommand(program) {
15
15
  try {
16
16
  await open(signupUrl);
17
17
  console.log(chalk.green("\u2713 Opened: ") + chalk.cyan(signupUrl));
18
- } catch (error) {
18
+ } catch {
19
19
  console.log(chalk.yellow("Could not open browser automatically."));
20
20
  console.log(chalk.gray("Please visit: ") + chalk.cyan(signupUrl));
21
21
  }
@@ -46,7 +46,7 @@ function getVersion() {
46
46
  _version = "0.0.0";
47
47
  return _version;
48
48
  }
49
- function getEnv(key, defaultValue) {
49
+ function _getEnv(key, defaultValue) {
50
50
  const value = process.env[key];
51
51
  if (value === void 0) {
52
52
  if (defaultValue !== void 0) return defaultValue;
@@ -58,7 +58,7 @@ function getEnv(key, defaultValue) {
58
58
  }
59
59
  return value;
60
60
  }
61
- function getOptionalEnv(key) {
61
+ function _getOptionalEnv(key) {
62
62
  return process.env[key];
63
63
  }
64
64
  async function initializeSkillContext() {
@@ -75,7 +75,7 @@ Detected ${worktrees.length} worktree(s):`));
75
75
  } else {
76
76
  contextStatus = "\u25CB Not initialized";
77
77
  }
78
- } catch (error) {
78
+ } catch {
79
79
  contextStatus = "\u2717 Error";
80
80
  }
81
81
  table.push([
@@ -7,7 +7,8 @@ import * as path from "path";
7
7
  import * as yaml from "js-yaml";
8
8
  import {
9
9
  DEFAULT_CONFIG,
10
- PRESET_PROFILES
10
+ PRESET_PROFILES,
11
+ DEFAULT_ENRICHMENT
11
12
  } from "./types.js";
12
13
  class ConfigManager {
13
14
  static instance = null;
@@ -75,6 +76,7 @@ class ConfigManager {
75
76
  }
76
77
  },
77
78
  performance: { ...DEFAULT_CONFIG.performance, ...loaded.performance },
79
+ enrichment: { ...DEFAULT_ENRICHMENT, ...loaded.enrichment },
78
80
  profiles: { ...PRESET_PROFILES, ...loaded.profiles }
79
81
  };
80
82
  if (config.profile && config.profiles?.[config.profile]) {
@@ -103,6 +103,11 @@ const PRESET_PROFILES = {
103
103
  }
104
104
  }
105
105
  };
106
+ const DEFAULT_ENRICHMENT = {
107
+ enabled: false,
108
+ lookbackDepth: 3,
109
+ extractEntities: true
110
+ };
106
111
  const DEFAULT_CONFIG = {
107
112
  version: "1.0",
108
113
  scoring: {
@@ -133,10 +138,12 @@ const DEFAULT_CONFIG = {
133
138
  retrieval_timeout_ms: 500,
134
139
  batch_upload_size: 100
135
140
  },
141
+ enrichment: DEFAULT_ENRICHMENT,
136
142
  profiles: PRESET_PROFILES
137
143
  };
138
144
  export {
139
145
  DEFAULT_CONFIG,
146
+ DEFAULT_ENRICHMENT,
140
147
  DEFAULT_TOOL_SCORES,
141
148
  DEFAULT_WEIGHTS,
142
149
  PRESET_PROFILES
@@ -19,7 +19,7 @@ class DualStackManager {
19
19
  activeContext;
20
20
  handoffRequests = /* @__PURE__ */ new Map();
21
21
  permissionManager;
22
- constructor(adapter, projectId, userId, defaultTeamId) {
22
+ constructor(adapter, projectId, userId, _defaultTeamId) {
23
23
  this.adapter = adapter;
24
24
  this.permissionManager = new PermissionManager();
25
25
  const rawDb = adapter instanceof SQLiteAdapter ? adapter.getRawDatabase() : null;
@@ -796,7 +796,7 @@ class DualStackManager {
796
796
  )
797
797
  );
798
798
  stacks.push(context);
799
- } catch (permissionError) {
799
+ } catch {
800
800
  logger.debug("User lacks access to stack", {
801
801
  stackId: context.stackId,
802
802
  userId: this.activeContext.ownerId
@@ -150,6 +150,44 @@ class FrameDatabase {
150
150
  -- Set initial schema version if not exists
151
151
  INSERT OR IGNORE INTO schema_version (version) VALUES (1);
152
152
  `);
153
+ try {
154
+ this.db.exec(
155
+ "ALTER TABLE frames ADD COLUMN access_count INTEGER DEFAULT 0"
156
+ );
157
+ } catch {
158
+ }
159
+ try {
160
+ this.db.exec("ALTER TABLE frames ADD COLUMN last_accessed INTEGER");
161
+ } catch {
162
+ }
163
+ this.db.exec(`
164
+ CREATE TABLE IF NOT EXISTS frame_access_log (
165
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
166
+ frame_id TEXT NOT NULL,
167
+ accessed_at INTEGER NOT NULL DEFAULT (unixepoch()),
168
+ FOREIGN KEY (frame_id) REFERENCES frames(frame_id) ON DELETE CASCADE
169
+ );
170
+ CREATE INDEX IF NOT EXISTS idx_frame_access_log_frame
171
+ ON frame_access_log(frame_id);
172
+ `);
173
+ this.db.exec(`
174
+ CREATE TABLE IF NOT EXISTS entity_states (
175
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
176
+ project_id TEXT NOT NULL,
177
+ entity_name TEXT NOT NULL,
178
+ relation TEXT NOT NULL,
179
+ value TEXT NOT NULL,
180
+ context TEXT,
181
+ source_frame_id TEXT,
182
+ valid_from INTEGER NOT NULL DEFAULT (unixepoch()),
183
+ superseded_at INTEGER,
184
+ FOREIGN KEY (source_frame_id) REFERENCES frames(frame_id)
185
+ );
186
+ CREATE INDEX IF NOT EXISTS idx_entity_name
187
+ ON entity_states(project_id, entity_name, relation);
188
+ CREATE INDEX IF NOT EXISTS idx_entity_temporal
189
+ ON entity_states(entity_name, valid_from DESC);
190
+ `);
153
191
  this.db.exec(`
154
192
  CREATE TABLE IF NOT EXISTS cord_tasks (
155
193
  task_id TEXT PRIMARY KEY,
@@ -0,0 +1,93 @@
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 { logger } from "../monitoring/logger.js";
6
+ import { frameLifecycleHooks } from "./frame-lifecycle-hooks.js";
7
+ const ENRICHMENT_PROMPT = `You are a context enrichment engine. Given a frame digest and its parent context, do two things:
8
+ 1. Rewrite the digest to be fully self-contained \u2014 resolve all pronouns ("it", "that", "the project") using parent context.
9
+ 2. Extract entity-relation-value triples from the content.
10
+
11
+ Respond in JSON only:
12
+ {"enrichedDigest":"...","entities":[{"name":"...","relation":"...","value":"...","context":"..."}]}`;
13
+ function buildEnrichmentInput(frame, parents) {
14
+ const parentCtx = parents.map((p) => `[${p.name}]: ${p.digest_text ?? ""}`).join("\n");
15
+ return `Parent context:
16
+ ${parentCtx}
17
+
18
+ Current frame "${frame.name}":
19
+ ${frame.digest_text ?? ""}`;
20
+ }
21
+ async function enrichFrame(frame, parents, apiKey) {
22
+ try {
23
+ const Anthropic = (await import("@anthropic-ai/sdk")).default;
24
+ const client = new Anthropic({ apiKey });
25
+ const input = buildEnrichmentInput(frame, parents);
26
+ const response = await client.messages.create({
27
+ model: "claude-3-5-haiku-latest",
28
+ max_tokens: 1024,
29
+ system: ENRICHMENT_PROMPT,
30
+ messages: [{ role: "user", content: input }]
31
+ });
32
+ const text = response.content[0].type === "text" ? response.content[0].text : "";
33
+ return JSON.parse(text);
34
+ } catch (err) {
35
+ logger.warn("Frame enrichment LLM call failed", {
36
+ error: err instanceof Error ? err.message : String(err),
37
+ frameId: frame.frame_id
38
+ });
39
+ return void 0;
40
+ }
41
+ }
42
+ let unregister = null;
43
+ function registerEnrichmentHook(config, deps) {
44
+ if (unregister) unregister();
45
+ if (!config.enabled) return () => {
46
+ };
47
+ const apiKey = process.env.ANTHROPIC_API_KEY;
48
+ if (!apiKey) {
49
+ logger.warn("Enrichment enabled but ANTHROPIC_API_KEY not set");
50
+ return () => {
51
+ };
52
+ }
53
+ unregister = frameLifecycleHooks.onFrameClosed(
54
+ "frame-enrichment",
55
+ async (data) => {
56
+ if (!data.frame.digest_text) return;
57
+ const parents = await deps.getParentFrames(
58
+ data.frame.frame_id,
59
+ config.lookbackDepth
60
+ );
61
+ const result = await enrichFrame(data.frame, parents, apiKey);
62
+ if (!result) return;
63
+ await deps.updateDigest(data.frame.frame_id, result.enrichedDigest);
64
+ if (config.extractEntities) {
65
+ for (const entity of result.entities) {
66
+ try {
67
+ deps.recordEntity(
68
+ data.frame.project_id,
69
+ entity.name,
70
+ entity.relation,
71
+ entity.value,
72
+ entity.context,
73
+ data.frame.frame_id
74
+ );
75
+ } catch {
76
+ }
77
+ }
78
+ }
79
+ },
80
+ -10
81
+ // low priority — runs after other hooks
82
+ );
83
+ return () => {
84
+ if (unregister) {
85
+ unregister();
86
+ unregister = null;
87
+ }
88
+ };
89
+ }
90
+ export {
91
+ enrichFrame,
92
+ registerEnrichmentHook
93
+ };
@@ -489,7 +489,7 @@ class FrameHandoffManager {
489
489
  */
490
490
  async getHandoffMetrics(timeRange) {
491
491
  const handoffs = Array.from(this.activeHandoffs.values());
492
- const filteredHandoffs = timeRange ? handoffs.filter((h) => {
492
+ const filteredHandoffs = timeRange ? handoffs.filter((_h) => {
493
493
  return true;
494
494
  }) : handoffs;
495
495
  const completedHandoffs = filteredHandoffs.filter(
@@ -12,7 +12,7 @@ import {
12
12
  } from "../errors/index.js";
13
13
  import { FrameQueryMode } from "../session/index.js";
14
14
  import { frameLifecycleHooks } from "./frame-lifecycle-hooks.js";
15
- const MAX_FRAME_DEPTH = 100;
15
+ const _MAX_FRAME_DEPTH = 100;
16
16
  const DEFAULT_MAX_DEPTH = 100;
17
17
  import { FrameDatabase } from "./frame-database.js";
18
18
  import { FrameStack } from "./frame-stack.js";
@@ -167,7 +167,7 @@ class FrameManager {
167
167
  outputs: {},
168
168
  digest_json: {}
169
169
  };
170
- const createdFrame = this.frameDb.insertFrame(frame);
170
+ const _createdFrame = this.frameDb.insertFrame(frame);
171
171
  this.frameStack.pushFrame(frameId);
172
172
  logger.info("Created frame", {
173
173
  frameId,
@@ -275,7 +275,7 @@ class FrameManager {
275
275
  event_type: eventType,
276
276
  payload
277
277
  };
278
- const createdEvent = this.frameDb.insertEvent(event);
278
+ const _createdEvent = this.frameDb.insertEvent(event);
279
279
  logger.debug("Added event", {
280
280
  eventId,
281
281
  frameId: targetFrameId,
@@ -316,7 +316,7 @@ class FrameManager {
316
316
  priority,
317
317
  metadata
318
318
  };
319
- const createdAnchor = this.frameDb.insertAnchor(anchor);
319
+ const _createdAnchor = this.frameDb.insertAnchor(anchor);
320
320
  logger.debug("Added anchor", {
321
321
  anchorId,
322
322
  frameId: targetFrameId,
@@ -622,7 +622,7 @@ class EnhancedRehydrationManager {
622
622
  ];
623
623
  const files = await this.getDirectoryFiles(workingDir);
624
624
  for (const file of files) {
625
- const ext = path.extname(file);
625
+ const _ext = path.extname(file);
626
626
  const basename = path.basename(file);
627
627
  if (configPatterns.some(
628
628
  (pattern) => pattern.includes("*") ? basename.includes(pattern.replace("*", "")) : basename === pattern
@@ -825,10 +825,10 @@ Stack preview: ${trace.stack_frames.slice(0, 3).join("\n")}`,
825
825
  logger.info("Rich context injected successfully");
826
826
  }
827
827
  // Helper methods
828
- async getDirectoryFiles(dir) {
828
+ async getDirectoryFiles(_dir) {
829
829
  return [];
830
830
  }
831
- async getRecentlyModifiedFiles(dir) {
831
+ async getRecentlyModifiedFiles(_dir) {
832
832
  return [];
833
833
  }
834
834
  inferContextTags(filePath) {
@@ -917,7 +917,7 @@ Stack preview: ${trace.stack_frames.slice(0, 3).join("\n")}`,
917
917
  resolution_attempted: data.resolutionAttempts || [],
918
918
  resolution_status: data.resolved ? "resolved" : "pending"
919
919
  };
920
- } catch (error) {
920
+ } catch {
921
921
  return null;
922
922
  }
923
923
  }
@@ -944,7 +944,7 @@ Stack preview: ${trace.stack_frames.slice(0, 3).join("\n")}`,
944
944
  }
945
945
  return [...new Set(workflows)];
946
946
  }
947
- inferCurrentFocus(snapshots, context) {
947
+ inferCurrentFocus(snapshots, _context) {
948
948
  if (snapshots.some((s) => s.contextTags.includes("migration"))) {
949
949
  return "Data migration and transformation";
950
950
  }
@@ -953,7 +953,7 @@ Stack preview: ${trace.stack_frames.slice(0, 3).join("\n")}`,
953
953
  }
954
954
  return "Development";
955
955
  }
956
- createRecoveryAnchors(snapshots, context) {
956
+ createRecoveryAnchors(snapshots, _context) {
957
957
  const anchors = [];
958
958
  for (const snapshot of snapshots.slice(0, 3)) {
959
959
  anchors.push(
@@ -468,19 +468,11 @@ class SQLiteAdapter extends FeatureAwareDatabaseAdapter {
468
468
  };
469
469
  }
470
470
  /**
471
- * Compute importance score for a single frame.
472
- * Score range: [0.0, 1.0] — higher means more important, less likely to be GC'd.
471
+ * Compute structural salience for a frame (range 0.3-1.0).
472
+ * Based on anchors, events, digest, and children.
473
473
  */
474
- computeImportanceScore(frameId) {
475
- if (!this.db)
476
- throw new DatabaseError(
477
- "Database not connected",
478
- ErrorCode.DB_CONNECTION_FAILED
479
- );
480
- const frame = this.db.prepare(
481
- "SELECT frame_id, digest_text, created_at FROM frames WHERE frame_id = ?"
482
- ).get(frameId);
483
- if (!frame) return 0.3;
474
+ computeSalience(frameId, digestText) {
475
+ if (!this.db) return 0.3;
484
476
  let score = 0.3;
485
477
  const decisionCount = this.db.prepare(
486
478
  "SELECT COUNT(*) as count FROM anchors WHERE frame_id = ? AND type = 'DECISION'"
@@ -488,14 +480,61 @@ class SQLiteAdapter extends FeatureAwareDatabaseAdapter {
488
480
  if (decisionCount > 0) score += 0.15;
489
481
  const eventCount = this.db.prepare("SELECT COUNT(*) as count FROM events WHERE frame_id = ?").get(frameId).count;
490
482
  if (eventCount > 3) score += 0.1;
491
- if (frame.digest_text) score += 0.15;
483
+ if (digestText) score += 0.15;
492
484
  const childCount = this.db.prepare(
493
485
  "SELECT COUNT(*) as count FROM frames WHERE parent_frame_id = ?"
494
486
  ).get(frameId).count;
495
487
  if (childCount > 0) score += 0.1;
488
+ return Math.min(score, 1);
489
+ }
490
+ /**
491
+ * Compute importance score using Ebbinghaus retention decay + reinforcement.
492
+ *
493
+ * R(frame, t) = I_salience * e^(-lambda * dt) + sigma * sum(1 / (t - t_access_i))
494
+ *
495
+ * Score range: [0.05, 1.0] — never fully zero.
496
+ */
497
+ computeImportanceScore(frameId) {
498
+ if (!this.db)
499
+ throw new DatabaseError(
500
+ "Database not connected",
501
+ ErrorCode.DB_CONNECTION_FAILED
502
+ );
503
+ const frame = this.db.prepare(
504
+ "SELECT frame_id, digest_text, created_at FROM frames WHERE frame_id = ?"
505
+ ).get(frameId);
506
+ if (!frame) return 0.3;
507
+ const salience = this.computeSalience(frameId, frame.digest_text);
496
508
  const nowSec = Math.floor(Date.now() / 1e3);
497
- if (nowSec - frame.created_at < 86400) score += 0.1;
498
- return Math.round(Math.min(score, 1) * 100) / 100;
509
+ const dtDays = Math.max(0, (nowSec - frame.created_at) / 86400);
510
+ const lambda = 0.05;
511
+ const decayTerm = salience * Math.exp(-lambda * dtDays);
512
+ const sigma = 0.1;
513
+ const accesses = this.db.prepare("SELECT accessed_at FROM frame_access_log WHERE frame_id = ?").all(frameId);
514
+ let reinforcement = 0;
515
+ for (const a of accesses) {
516
+ const gap = nowSec - a.accessed_at;
517
+ if (gap > 0) reinforcement += 1 / gap;
518
+ }
519
+ const raw = decayTerm + sigma * reinforcement;
520
+ return Math.round(Math.min(Math.max(raw, 0.05), 1) * 100) / 100;
521
+ }
522
+ /**
523
+ * Record a frame access for retention decay scoring.
524
+ * Inserts into frame_access_log and bumps access_count/last_accessed.
525
+ */
526
+ recordFrameAccess(frameId) {
527
+ if (!this.db) return;
528
+ const nowSec = Math.floor(Date.now() / 1e3);
529
+ try {
530
+ this.db.prepare(
531
+ "INSERT INTO frame_access_log (frame_id, accessed_at) VALUES (?, ?)"
532
+ ).run(frameId, nowSec);
533
+ this.db.prepare(
534
+ "UPDATE frames SET access_count = COALESCE(access_count, 0) + 1, last_accessed = ? WHERE frame_id = ?"
535
+ ).run(nowSec, frameId);
536
+ } catch {
537
+ }
499
538
  }
500
539
  /**
501
540
  * Compress a frame by stripping data according to strategy.
@@ -1611,6 +1650,88 @@ class SQLiteAdapter extends FeatureAwareDatabaseAdapter {
1611
1650
  }
1612
1651
  });
1613
1652
  }
1653
+ // --- Entity State Methods ---
1654
+ recordEntityState(projectId, entityName, relation, value, context, sourceFrameId) {
1655
+ if (!this.db)
1656
+ throw new DatabaseError(
1657
+ "Database not connected",
1658
+ ErrorCode.DB_CONNECTION_FAILED
1659
+ );
1660
+ const nowSec = Math.floor(Date.now() / 1e3);
1661
+ this.db.prepare(
1662
+ `UPDATE entity_states
1663
+ SET superseded_at = ?
1664
+ WHERE entity_name = ? AND relation = ? AND superseded_at IS NULL`
1665
+ ).run(nowSec, entityName, relation);
1666
+ const result = this.db.prepare(
1667
+ `INSERT INTO entity_states
1668
+ (project_id, entity_name, relation, value, context, source_frame_id, valid_from)
1669
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
1670
+ ).run(
1671
+ projectId,
1672
+ entityName,
1673
+ relation,
1674
+ value,
1675
+ context ?? null,
1676
+ sourceFrameId ?? null,
1677
+ nowSec
1678
+ );
1679
+ return Number(result.lastInsertRowid);
1680
+ }
1681
+ getEntityState(entityName, relation, asOf) {
1682
+ if (!this.db)
1683
+ throw new DatabaseError(
1684
+ "Database not connected",
1685
+ ErrorCode.DB_CONNECTION_FAILED
1686
+ );
1687
+ if (asOf != null) {
1688
+ const relFilter2 = relation ? " AND relation = ?" : "";
1689
+ const sql2 = `SELECT * FROM entity_states
1690
+ WHERE entity_name = ?${relFilter2}
1691
+ AND valid_from <= ?
1692
+ AND (superseded_at IS NULL OR superseded_at > ?)
1693
+ ORDER BY valid_from DESC`;
1694
+ const params2 = [entityName];
1695
+ if (relation) params2.push(relation);
1696
+ params2.push(asOf, asOf);
1697
+ return this.db.prepare(sql2).all(...params2);
1698
+ }
1699
+ const relFilter = relation ? " AND relation = ?" : "";
1700
+ const sql = `SELECT * FROM entity_states
1701
+ WHERE entity_name = ?${relFilter} AND superseded_at IS NULL
1702
+ ORDER BY valid_from DESC, id DESC`;
1703
+ const params = [entityName];
1704
+ if (relation) params.push(relation);
1705
+ return this.db.prepare(sql).all(...params);
1706
+ }
1707
+ getEntityHistory(entityName, relation) {
1708
+ if (!this.db)
1709
+ throw new DatabaseError(
1710
+ "Database not connected",
1711
+ ErrorCode.DB_CONNECTION_FAILED
1712
+ );
1713
+ const relFilter = relation ? " AND relation = ?" : "";
1714
+ const sql = `SELECT * FROM entity_states
1715
+ WHERE entity_name = ?${relFilter}
1716
+ ORDER BY valid_from DESC, id DESC`;
1717
+ const params = [entityName];
1718
+ if (relation) params.push(relation);
1719
+ return this.db.prepare(sql).all(...params);
1720
+ }
1721
+ getEntityDiff(entityName, since) {
1722
+ if (!this.db)
1723
+ throw new DatabaseError(
1724
+ "Database not connected",
1725
+ ErrorCode.DB_CONNECTION_FAILED
1726
+ );
1727
+ const sinceFilter = since != null ? " AND valid_from > ?" : "";
1728
+ const sql = `SELECT * FROM entity_states
1729
+ WHERE entity_name = ?${sinceFilter}
1730
+ ORDER BY valid_from DESC, id DESC`;
1731
+ const params = [entityName];
1732
+ if (since != null) params.push(since);
1733
+ return this.db.prepare(sql).all(...params);
1734
+ }
1614
1735
  }
1615
1736
  export {
1616
1737
  SQLiteAdapter