@stackmemoryai/stackmemory 1.6.2 → 1.8.0
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/conductor-traces.js +314 -0
- 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/orchestrate.js +328 -27
- package/dist/src/cli/commands/orchestrator.js +50 -3
- 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
|
@@ -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
|
|
@@ -313,7 +313,7 @@ class HybridDigestGenerator {
|
|
|
313
313
|
* Determine priority based on frame characteristics
|
|
314
314
|
*/
|
|
315
315
|
determinePriority(input) {
|
|
316
|
-
const {
|
|
316
|
+
const { _frame, anchors, events } = input;
|
|
317
317
|
const decisionCount = anchors.filter((a) => a.type === "DECISION").length;
|
|
318
318
|
const errorCount = events.filter(
|
|
319
319
|
(e) => e.payload?.error || e.payload?.success === false
|
|
@@ -119,7 +119,7 @@ class EnhancedHybridDigestGenerator extends HybridDigestGenerator {
|
|
|
119
119
|
if (!this.llmProvider) {
|
|
120
120
|
throw new Error("No LLM provider configured");
|
|
121
121
|
}
|
|
122
|
-
const
|
|
122
|
+
const _prompt = this.buildEnhancedPrompt(input, deterministic);
|
|
123
123
|
const response = await this.llmProvider.generateSummary(
|
|
124
124
|
input,
|
|
125
125
|
deterministic,
|
|
@@ -162,7 +162,7 @@ class EnhancedHybridDigestGenerator extends HybridDigestGenerator {
|
|
|
162
162
|
/**
|
|
163
163
|
* Extract key decisions from AI response
|
|
164
164
|
*/
|
|
165
|
-
extractKeyDecisions(
|
|
165
|
+
extractKeyDecisions(_response) {
|
|
166
166
|
return [];
|
|
167
167
|
}
|
|
168
168
|
/**
|
|
@@ -178,7 +178,7 @@ class EnhancedHybridDigestGenerator extends HybridDigestGenerator {
|
|
|
178
178
|
/**
|
|
179
179
|
* Extract next steps from AI response
|
|
180
180
|
*/
|
|
181
|
-
extractNextSteps(
|
|
181
|
+
extractNextSteps(_response) {
|
|
182
182
|
return [];
|
|
183
183
|
}
|
|
184
184
|
/**
|
|
@@ -155,7 +155,7 @@ class ExtensionLoader {
|
|
|
155
155
|
/**
|
|
156
156
|
* Load extension from source based on type
|
|
157
157
|
*/
|
|
158
|
-
async loadFromSource(type, uri,
|
|
158
|
+
async loadFromSource(type, uri, _options) {
|
|
159
159
|
switch (type) {
|
|
160
160
|
case "url":
|
|
161
161
|
return this.loadFromUrl(uri);
|
|
@@ -277,7 +277,7 @@ class ExtensionLoader {
|
|
|
277
277
|
*/
|
|
278
278
|
async loadFromNpm(packageName) {
|
|
279
279
|
try {
|
|
280
|
-
const { name,
|
|
280
|
+
const { name, _version } = this.parseNpmPackage(packageName);
|
|
281
281
|
let module;
|
|
282
282
|
try {
|
|
283
283
|
module = await import(name);
|
|
@@ -295,7 +295,7 @@ class ExtensionLoader {
|
|
|
295
295
|
const entryPoint = packageJson.main || "index.js";
|
|
296
296
|
const entryPath = path.join(nodeModulesPath, entryPoint);
|
|
297
297
|
module = await import(pathToFileURL(entryPath).href);
|
|
298
|
-
} catch
|
|
298
|
+
} catch {
|
|
299
299
|
return {
|
|
300
300
|
success: false,
|
|
301
301
|
error: `Package not found: ${name}. Try running: npm install ${name}`
|
|
@@ -321,7 +321,7 @@ class ExtensionLoader {
|
|
|
321
321
|
/**
|
|
322
322
|
* Evaluate extension code (for URL sources)
|
|
323
323
|
*/
|
|
324
|
-
async evaluateExtensionCode(code,
|
|
324
|
+
async evaluateExtensionCode(code, _sourceUrl) {
|
|
325
325
|
const dataUrl = `data:text/javascript;base64,${Buffer.from(code).toString("base64")}`;
|
|
326
326
|
try {
|
|
327
327
|
const module = await import(dataUrl);
|
|
@@ -473,12 +473,12 @@ class ExtensionLoader {
|
|
|
473
473
|
permissions,
|
|
474
474
|
// State management (always available, scoped to extension)
|
|
475
475
|
state: {
|
|
476
|
-
get: async (
|
|
476
|
+
get: async (_key) => {
|
|
477
477
|
return void 0;
|
|
478
478
|
},
|
|
479
|
-
set: async (
|
|
479
|
+
set: async (_key, _value) => {
|
|
480
480
|
},
|
|
481
|
-
delete: async (
|
|
481
|
+
delete: async (_key) => {
|
|
482
482
|
}
|
|
483
483
|
},
|
|
484
484
|
// Sandboxed fetch (always available)
|
|
@@ -511,7 +511,7 @@ class ExtensionLoader {
|
|
|
511
511
|
};
|
|
512
512
|
if (permSet.has("frames:read") || permSet.has("frames:write")) {
|
|
513
513
|
context.frames = {
|
|
514
|
-
get: async (
|
|
514
|
+
get: async (_frameId) => {
|
|
515
515
|
return void 0;
|
|
516
516
|
},
|
|
517
517
|
list: async () => {
|
|
@@ -519,10 +519,10 @@ class ExtensionLoader {
|
|
|
519
519
|
}
|
|
520
520
|
};
|
|
521
521
|
if (permSet.has("frames:write")) {
|
|
522
|
-
context.frames.create = async (
|
|
522
|
+
context.frames.create = async (_options) => {
|
|
523
523
|
throw new Error("Frame creation not implemented");
|
|
524
524
|
};
|
|
525
|
-
context.frames.update = async (
|
|
525
|
+
context.frames.update = async (_frameId, _data) => {
|
|
526
526
|
throw new Error("Frame update not implemented");
|
|
527
527
|
};
|
|
528
528
|
}
|
|
@@ -362,7 +362,7 @@ function createLinearPlugin() {
|
|
|
362
362
|
context.logger.info(`Sync complete: ${frames.length} frames`);
|
|
363
363
|
return frames;
|
|
364
364
|
},
|
|
365
|
-
watch(
|
|
365
|
+
watch(_callback) {
|
|
366
366
|
context?.logger.info("Watch mode enabled");
|
|
367
367
|
return () => {
|
|
368
368
|
context?.logger.info("Watch mode disabled");
|
|
@@ -112,7 +112,7 @@ class Logger {
|
|
|
112
112
|
if (!fs.existsSync(logDir)) {
|
|
113
113
|
fs.mkdirSync(logDir, { recursive: true });
|
|
114
114
|
}
|
|
115
|
-
} catch
|
|
115
|
+
} catch {
|
|
116
116
|
this.logFile = void 0;
|
|
117
117
|
if (!this.fileLoggingDisabledNotified) {
|
|
118
118
|
this.fileLoggingDisabledNotified = true;
|
|
@@ -131,7 +131,7 @@ class Logger {
|
|
|
131
131
|
if (this.logFile) {
|
|
132
132
|
try {
|
|
133
133
|
fs.appendFileSync(this.logFile, logLine);
|
|
134
|
-
} catch
|
|
134
|
+
} catch {
|
|
135
135
|
this.logFile = void 0;
|
|
136
136
|
if (!this.fileLoggingDisabledNotified) {
|
|
137
137
|
this.fileLoggingDisabledNotified = true;
|
|
@@ -6,7 +6,7 @@ import { EventEmitter } from "events";
|
|
|
6
6
|
import * as fs from "fs";
|
|
7
7
|
import * as path from "path";
|
|
8
8
|
import { logger } from "./logger.js";
|
|
9
|
-
function
|
|
9
|
+
function _getEnv(key, defaultValue) {
|
|
10
10
|
const value = process.env[key];
|
|
11
11
|
if (value === void 0) {
|
|
12
12
|
if (defaultValue !== void 0) return defaultValue;
|
|
@@ -14,7 +14,7 @@ function getEnv(key, defaultValue) {
|
|
|
14
14
|
}
|
|
15
15
|
return value;
|
|
16
16
|
}
|
|
17
|
-
function
|
|
17
|
+
function _getOptionalEnv(key) {
|
|
18
18
|
return process.env[key];
|
|
19
19
|
}
|
|
20
20
|
class MetricsCollector extends EventEmitter {
|
|
@@ -4,7 +4,7 @@ const __filename = __fileURLToPath(import.meta.url);
|
|
|
4
4
|
const __dirname = __pathDirname(__filename);
|
|
5
5
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
6
6
|
import { join } from "path";
|
|
7
|
-
function
|
|
7
|
+
function _getEnv(key, defaultValue) {
|
|
8
8
|
const value = process.env[key];
|
|
9
9
|
if (value === void 0) {
|
|
10
10
|
if (defaultValue !== void 0) return defaultValue;
|
|
@@ -12,7 +12,7 @@ function getEnv(key, defaultValue) {
|
|
|
12
12
|
}
|
|
13
13
|
return value;
|
|
14
14
|
}
|
|
15
|
-
function
|
|
15
|
+
function _getOptionalEnv(key) {
|
|
16
16
|
return process.env[key];
|
|
17
17
|
}
|
|
18
18
|
class ProgressTracker {
|
|
@@ -89,14 +89,14 @@ class PerformanceBenchmark {
|
|
|
89
89
|
}
|
|
90
90
|
const populateDuration = performance.now() - populateStart;
|
|
91
91
|
const accessStart = performance.now();
|
|
92
|
-
let
|
|
93
|
-
let
|
|
92
|
+
let _hits = 0;
|
|
93
|
+
let _misses = 0;
|
|
94
94
|
for (let i = 0; i < accessPatterns; i++) {
|
|
95
95
|
const index = Math.floor(Math.random() * itemCount * 1.2);
|
|
96
96
|
const key = `item-${index}`;
|
|
97
97
|
const result2 = cache.get(key);
|
|
98
|
-
if (result2)
|
|
99
|
-
else
|
|
98
|
+
if (result2) _hits++;
|
|
99
|
+
else _misses++;
|
|
100
100
|
}
|
|
101
101
|
const accessDuration = performance.now() - accessStart;
|
|
102
102
|
const stats = cache.getStats();
|
|
@@ -134,7 +134,7 @@ class PerformanceBenchmark {
|
|
|
134
134
|
let frames = [];
|
|
135
135
|
try {
|
|
136
136
|
frames = db.prepare("SELECT id FROM frames ORDER BY updated_at DESC LIMIT ?").all(frameCount);
|
|
137
|
-
} catch
|
|
137
|
+
} catch {
|
|
138
138
|
logger.warn("Frames table not found, using mock data for benchmark");
|
|
139
139
|
frames = Array.from({ length: Math.min(frameCount, 10) }, (_, i) => ({
|
|
140
140
|
id: `frame-${i}`
|
|
@@ -205,9 +205,9 @@ class PerformanceBenchmark {
|
|
|
205
205
|
const suiteStart = performance.now();
|
|
206
206
|
logger.info("Starting Performance Benchmark Suite");
|
|
207
207
|
const tasksFile = join(projectRoot, ".stackmemory", "tasks.jsonl");
|
|
208
|
-
const
|
|
209
|
-
const
|
|
210
|
-
const
|
|
208
|
+
const _jsonlResult = await this.benchmarkJSONLParsing(tasksFile);
|
|
209
|
+
const _cacheResult = await this.benchmarkContextCache();
|
|
210
|
+
const _lazyResult = await this.benchmarkLazyLoading(db, projectId);
|
|
211
211
|
const totalDuration = performance.now() - suiteStart;
|
|
212
212
|
const averageImprovement = this.results.filter((r) => r.improvement !== void 0).reduce((sum, r) => sum + (r.improvement || 0), 0) / this.results.filter((r) => r.improvement !== void 0).length;
|
|
213
213
|
const suite = {
|
|
@@ -4,7 +4,7 @@ const __filename = __fileURLToPath(import.meta.url);
|
|
|
4
4
|
const __dirname = __pathDirname(__filename);
|
|
5
5
|
import { logger } from "../monitoring/logger.js";
|
|
6
6
|
import { getQueryStatistics } from "../trace/db-trace-wrapper.js";
|
|
7
|
-
function
|
|
7
|
+
function _getEnv(key, defaultValue) {
|
|
8
8
|
const value = process.env[key];
|
|
9
9
|
if (value === void 0) {
|
|
10
10
|
if (defaultValue !== void 0) return defaultValue;
|
|
@@ -12,7 +12,7 @@ function getEnv(key, defaultValue) {
|
|
|
12
12
|
}
|
|
13
13
|
return value;
|
|
14
14
|
}
|
|
15
|
-
function
|
|
15
|
+
function _getOptionalEnv(key) {
|
|
16
16
|
return process.env[key];
|
|
17
17
|
}
|
|
18
18
|
class PerformanceProfiler {
|