@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.
- package/dist/src/agents/core/agent-task-manager.js +3 -3
- package/dist/src/agents/verifiers/formatter-verifier.js +1 -1
- package/dist/src/agents/verifiers/llm-judge.js +2 -2
- package/dist/src/cli/commands/context-rehydrate.js +2 -2
- package/dist/src/cli/commands/log.js +1 -1
- package/dist/src/cli/commands/onboard.js +4 -4
- package/dist/src/cli/commands/search.js +3 -3
- package/dist/src/cli/commands/setup.js +1 -1
- package/dist/src/cli/commands/signup.js +1 -1
- package/dist/src/cli/commands/skills.js +2 -2
- package/dist/src/cli/commands/worktree.js +1 -1
- package/dist/src/core/config/config-manager.js +3 -1
- package/dist/src/core/config/types.js +7 -0
- package/dist/src/core/context/dual-stack-manager.js +2 -2
- package/dist/src/core/context/frame-database.js +38 -0
- package/dist/src/core/context/frame-enrichment.js +93 -0
- package/dist/src/core/context/frame-handoff-manager.js +1 -1
- package/dist/src/core/context/frame-manager.js +4 -4
- package/dist/src/core/context/rehydration.js +6 -6
- package/dist/src/core/database/sqlite-adapter.js +136 -15
- package/dist/src/core/digest/frame-digest-integration.js +1 -1
- package/dist/src/core/digest/hybrid-digest-generator.js +1 -1
- package/dist/src/core/digest/hybrid-digest.js +3 -3
- package/dist/src/core/errors/recovery.js +1 -1
- package/dist/src/core/extensions/loader.js +10 -10
- package/dist/src/core/extensions/plugin-system.js +1 -1
- package/dist/src/core/monitoring/logger.js +2 -2
- package/dist/src/core/monitoring/metrics.js +2 -2
- package/dist/src/core/monitoring/progress-tracker.js +2 -2
- package/dist/src/core/performance/lazy-context-loader.js +1 -1
- package/dist/src/core/performance/performance-benchmark.js +8 -8
- package/dist/src/core/performance/performance-profiler.js +2 -2
- package/dist/src/core/performance/streaming-jsonl-parser.js +1 -1
- package/dist/src/core/projects/project-isolation.js +1 -1
- package/dist/src/core/projects/project-manager.js +5 -5
- package/dist/src/core/session/clear-survival.js +3 -3
- package/dist/src/core/session/handoff-generator.js +1 -1
- package/dist/src/core/session/session-manager.js +4 -4
- package/dist/src/core/skills/skill-storage.js +2 -2
- package/dist/src/core/storage/remote-storage.js +2 -2
- package/dist/src/core/trace/cli-trace-wrapper.js +3 -3
- package/dist/src/core/trace/debug-trace.js +2 -2
- package/dist/src/core/trace/trace-detector.js +1 -1
- package/dist/src/core/worktree/worktree-manager.js +2 -2
- package/dist/src/features/analytics/api/analytics-api.js +1 -1
- package/dist/src/features/analytics/core/analytics-service.js +2 -2
- package/dist/src/features/analytics/queries/metrics-queries.js +1 -1
- package/dist/src/features/web/server/index.js +8 -8
- package/dist/src/integrations/anthropic/client.js +4 -4
- package/dist/src/integrations/claude-code/post-task-hooks.js +3 -3
- package/dist/src/integrations/claude-code/subagent-client.js +2 -2
- package/dist/src/integrations/diffmem/client.js +1 -1
- package/dist/src/integrations/linear/client.js +1 -1
- package/dist/src/integrations/linear/migration.js +1 -1
- package/dist/src/integrations/linear/oauth-server.js +2 -2
- package/dist/src/integrations/linear/sync.js +1 -1
- package/dist/src/integrations/linear/webhook-handler.js +2 -2
- package/dist/src/integrations/linear/webhook-server.js +2 -2
- package/dist/src/integrations/linear/webhook.js +2 -2
- package/dist/src/integrations/mcp/handlers/discovery-handlers.js +3 -3
- package/dist/src/integrations/mcp/handlers/linear-handlers.js +1 -1
- package/dist/src/integrations/mcp/handlers/skill-handlers.js +2 -2
- package/dist/src/integrations/mcp/handlers/task-handlers.js +2 -2
- package/dist/src/integrations/mcp/handlers/trace-handlers.js +1 -1
- package/dist/src/integrations/mcp/refactored-server.js +2 -2
- package/dist/src/integrations/mcp/remote-server.js +1 -1
- package/dist/src/integrations/mcp/server.js +4 -4
- package/dist/src/integrations/ralph/bridge/ralph-stackmemory-bridge.js +13 -13
- package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +1 -1
- package/dist/src/integrations/ralph/learning/pattern-learner.js +3 -3
- package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +1 -1
- package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +2 -2
- package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +19 -19
- package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +5 -5
- package/dist/src/integrations/ralph/performance/performance-optimizer.js +1 -1
- package/dist/src/integrations/ralph/recovery/crash-recovery.js +1 -1
- package/dist/src/integrations/ralph/state/state-reconciler.js +1 -1
- package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +4 -4
- package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +8 -8
- package/dist/src/integrations/ralph/visualization/ralph-debugger.js +3 -3
- package/dist/src/orchestrators/multimodal/harness.js +3 -3
- package/dist/src/servers/production/auth-middleware.js +2 -2
- 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,
|
|
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
|
|
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
|
|
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 {
|
|
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(
|
|
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,
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
61
|
+
function _getOptionalEnv(key) {
|
|
62
62
|
return process.env[key];
|
|
63
63
|
}
|
|
64
64
|
async function initializeSkillContext() {
|
|
@@ -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,
|
|
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
|
|
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((
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
828
|
+
async getDirectoryFiles(_dir) {
|
|
829
829
|
return [];
|
|
830
830
|
}
|
|
831
|
-
async getRecentlyModifiedFiles(
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
472
|
-
*
|
|
471
|
+
* Compute structural salience for a frame (range 0.3-1.0).
|
|
472
|
+
* Based on anchors, events, digest, and children.
|
|
473
473
|
*/
|
|
474
|
-
|
|
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 (
|
|
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
|
-
|
|
498
|
-
|
|
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
|