@stackmemoryai/stackmemory 0.3.17 → 0.3.19
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/cli/claude-sm.js +51 -5
- package/dist/cli/claude-sm.js.map +2 -2
- package/dist/cli/codex-sm.js +52 -19
- package/dist/cli/codex-sm.js.map +2 -2
- package/dist/cli/commands/db.js +143 -0
- package/dist/cli/commands/db.js.map +7 -0
- package/dist/cli/commands/login.js +50 -0
- package/dist/cli/commands/login.js.map +7 -0
- package/dist/cli/commands/migrate.js +178 -0
- package/dist/cli/commands/migrate.js.map +7 -0
- package/dist/cli/commands/onboard.js +158 -2
- package/dist/cli/commands/onboard.js.map +2 -2
- package/dist/cli/commands/skills.js +15 -2
- package/dist/cli/commands/skills.js.map +2 -2
- package/dist/cli/index.js +118 -834
- package/dist/cli/index.js.map +3 -3
- package/dist/core/context/dual-stack-manager.js +1 -1
- package/dist/core/context/dual-stack-manager.js.map +1 -1
- package/dist/core/context/frame-database.js +1 -0
- package/dist/core/context/frame-database.js.map +2 -2
- package/dist/core/context/frame-manager.js +59 -2
- package/dist/core/context/frame-manager.js.map +2 -2
- package/dist/core/database/database-adapter.js +6 -1
- package/dist/core/database/database-adapter.js.map +2 -2
- package/dist/core/database/sqlite-adapter.js +60 -2
- package/dist/core/database/sqlite-adapter.js.map +2 -2
- package/dist/integrations/claude-code/subagent-client.js +106 -3
- package/dist/integrations/claude-code/subagent-client.js.map +2 -2
- package/dist/servers/railway/config.js +51 -0
- package/dist/servers/railway/config.js.map +7 -0
- package/dist/servers/railway/index-enhanced.js +156 -0
- package/dist/servers/railway/index-enhanced.js.map +7 -0
- package/dist/servers/railway/index.js +843 -82
- package/dist/servers/railway/index.js.map +3 -3
- package/dist/servers/railway/minimal.js +48 -3
- package/dist/servers/railway/minimal.js.map +2 -2
- package/dist/servers/railway/storage-test.js +455 -0
- package/dist/servers/railway/storage-test.js.map +7 -0
- package/dist/skills/claude-skills.js +13 -12
- package/dist/skills/claude-skills.js.map +2 -2
- package/dist/skills/recursive-agent-orchestrator.js +27 -18
- package/dist/skills/recursive-agent-orchestrator.js.map +2 -2
- package/dist/skills/unified-rlm-orchestrator.js.map +2 -2
- package/package.json +13 -21
- package/scripts/README-TESTING.md +186 -0
- package/scripts/analyze-cli-security.js +288 -0
- package/scripts/archive/add-phase-tasks-to-linear.js +163 -0
- package/scripts/archive/analyze-linear-duplicates.js +214 -0
- package/scripts/archive/analyze-remaining-duplicates.js +230 -0
- package/scripts/archive/analyze-sta-duplicates.js +292 -0
- package/scripts/archive/analyze-sta-graphql.js +399 -0
- package/scripts/archive/cancel-duplicate-tasks.ts +246 -0
- package/scripts/archive/check-all-duplicates.ts +419 -0
- package/scripts/archive/clean-duplicate-tasks.js +114 -0
- package/scripts/archive/cleanup-duplicate-tasks.ts +286 -0
- package/scripts/archive/create-phase-tasks.js +387 -0
- package/scripts/archive/delete-linear-duplicates.js +182 -0
- package/scripts/archive/delete-remaining-duplicates.js +158 -0
- package/scripts/archive/delete-sta-duplicates.js +201 -0
- package/scripts/archive/delete-sta-oauth.js +201 -0
- package/scripts/archive/export-sta-tasks.js +62 -0
- package/scripts/archive/install-auto-sync.js +266 -0
- package/scripts/archive/install-chromadb-hooks.sh +133 -0
- package/scripts/archive/install-enhanced-clear-hooks.sh +431 -0
- package/scripts/archive/install-post-task-hooks.sh +289 -0
- package/scripts/archive/install-stackmemory-hooks.sh +420 -0
- package/scripts/archive/merge-linear-duplicates-safe.ts +362 -0
- package/scripts/archive/merge-linear-duplicates.ts +180 -0
- package/scripts/archive/remove-sta-tasks.js +70 -0
- package/scripts/archive/setup-background-sync.sh +168 -0
- package/scripts/archive/setup-claude-auto-triggers.sh +181 -0
- package/scripts/archive/setup-claude-autostart.sh +305 -0
- package/scripts/archive/setup-git-hooks.sh +25 -0
- package/scripts/archive/setup-linear-oauth.sh +46 -0
- package/scripts/archive/setup-mcp.sh +113 -0
- package/scripts/archive/setup-railway-deployment.sh +81 -0
- package/scripts/auto-handoff.sh +262 -0
- package/scripts/background-sync-manager.js +416 -0
- package/scripts/benchmark-performance.ts +57 -0
- package/scripts/check-redis.ts +48 -0
- package/scripts/chromadb-auto-loader.sh +128 -0
- package/scripts/chromadb-context-loader.js +479 -0
- package/scripts/claude-chromadb-hook.js +460 -0
- package/scripts/claude-code-wrapper.sh +66 -0
- package/scripts/claude-linear-skill.js +455 -0
- package/scripts/claude-pre-commit.sh +302 -0
- package/scripts/claude-sm-autostart.js +532 -0
- package/scripts/claude-sm-setup.sh +367 -0
- package/scripts/claude-with-chromadb.sh +69 -0
- package/scripts/claude-worktree-manager.sh +323 -0
- package/scripts/claude-worktree-monitor.sh +371 -0
- package/scripts/claude-worktree-setup.sh +327 -0
- package/scripts/clean-linear-backlog.js +273 -0
- package/scripts/cleanup-old-sessions.sh +57 -0
- package/scripts/codex-wrapper.sh +88 -0
- package/scripts/create-sandbox.sh +269 -0
- package/scripts/debug-linear-update.js +174 -0
- package/scripts/delete-linear-tasks.js +167 -0
- package/scripts/deploy.sh +89 -0
- package/scripts/deployment/railway.sh +352 -0
- package/scripts/deployment/test-deployment.js +194 -0
- package/scripts/detect-and-rehydrate.js +162 -0
- package/scripts/detect-and-rehydrate.mjs +165 -0
- package/scripts/development/create-demo-tasks.js +143 -0
- package/scripts/development/debug-frame-test.js +16 -0
- package/scripts/development/demo-auto-sync.js +128 -0
- package/scripts/development/fix-all-imports.js +213 -0
- package/scripts/development/fix-imports.js +229 -0
- package/scripts/development/fix-lint-loop.cjs +103 -0
- package/scripts/development/fix-project-id.ts +161 -0
- package/scripts/development/fix-strict-mode-issues.ts +291 -0
- package/scripts/development/reorganize-structure.sh +228 -0
- package/scripts/development/test-persistence-direct.js +148 -0
- package/scripts/development/test-persistence.js +114 -0
- package/scripts/development/test-tasks.js +93 -0
- package/scripts/development/update-imports.js +212 -0
- package/scripts/fetch-linear-status.js +125 -0
- package/scripts/git-hooks/README.md +310 -0
- package/scripts/git-hooks/branch-context-manager.sh +342 -0
- package/scripts/git-hooks/post-checkout-stackmemory.sh +63 -0
- package/scripts/git-hooks/post-commit-stackmemory.sh +305 -0
- package/scripts/git-hooks/pre-commit-stackmemory.sh +275 -0
- package/scripts/hooks/cleanup-shell.sh +130 -0
- package/scripts/hooks/task-complete.sh +114 -0
- package/scripts/initialize.ts +129 -0
- package/scripts/install-claude-hooks-auto.js +104 -0
- package/scripts/install-claude-hooks.sh +133 -0
- package/scripts/install-global.sh +296 -0
- package/scripts/install.sh +235 -0
- package/scripts/linear-auto-sync.js +262 -0
- package/scripts/linear-auto-sync.sh +161 -0
- package/scripts/linear-sync-daemon.js +150 -0
- package/scripts/linear-task-review.js +237 -0
- package/scripts/list-linear-tasks.ts +178 -0
- package/scripts/mcp-proxy.js +66 -0
- package/scripts/opencode-wrapper.sh +85 -0
- package/scripts/publish-local.js +74 -0
- package/scripts/query-chromadb.ts +201 -0
- package/scripts/railway-env-setup.sh +39 -0
- package/scripts/reconcile-local-tasks.js +170 -0
- package/scripts/recreate-frames-db.js +89 -0
- package/scripts/setup/claude-integration.js +138 -0
- package/scripts/setup/configure-alias.js +125 -0
- package/scripts/setup/configure-codex-alias.js +161 -0
- package/scripts/setup/configure-opencode-alias.js +175 -0
- package/scripts/setup-claude-integration.js +204 -0
- package/scripts/setup-claude-integration.sh +183 -0
- package/scripts/setup-railway-deployment.sh +37 -0
- package/scripts/setup.sh +31 -0
- package/scripts/show-linear-summary.ts +172 -0
- package/scripts/stackmemory-auto-handoff.sh +231 -0
- package/scripts/stackmemory-daemon.sh +40 -0
- package/scripts/start-linear-sync-daemon.sh +141 -0
- package/scripts/start-temporal-paradox.sh +214 -0
- package/scripts/status.ts +159 -0
- package/scripts/sync-and-clean-tasks.js +258 -0
- package/scripts/sync-frames-from-railway.js +228 -0
- package/scripts/sync-linear-graphql.js +303 -0
- package/scripts/sync-linear-tasks.js +186 -0
- package/scripts/test-auto-triggers.sh +57 -0
- package/scripts/test-browser-mcp.js +74 -0
- package/scripts/test-chromadb-full.js +115 -0
- package/scripts/test-chromadb-hooks.sh +28 -0
- package/scripts/test-chromadb-sync.ts +245 -0
- package/scripts/test-cli-security.js +293 -0
- package/scripts/test-hooks-persistence.sh +220 -0
- package/scripts/test-installation-scenarios.sh +359 -0
- package/scripts/test-installation.sh +224 -0
- package/scripts/test-mcp.js +163 -0
- package/scripts/test-pre-publish-quick.sh +75 -0
- package/scripts/test-quality-gates.sh +263 -0
- package/scripts/test-railway-db.js +222 -0
- package/scripts/test-redis-storage.ts +490 -0
- package/scripts/test-rlm-basic.sh +122 -0
- package/scripts/test-rlm-comprehensive.sh +260 -0
- package/scripts/test-rlm-e2e.sh +268 -0
- package/scripts/test-rlm-simple.js +90 -0
- package/scripts/test-rlm.js +110 -0
- package/scripts/test-session-handoff.sh +165 -0
- package/scripts/test-shell-integration.sh +275 -0
- package/scripts/testing/ab-test-runner.ts +508 -0
- package/scripts/testing/collect-metrics.ts +457 -0
- package/scripts/testing/quick-effectiveness-demo.js +187 -0
- package/scripts/testing/real-performance-test.js +422 -0
- package/scripts/testing/run-effectiveness-tests.sh +176 -0
- package/scripts/testing/scripts/testing/ab-test-runner.js +363 -0
- package/scripts/testing/scripts/testing/collect-metrics.js +292 -0
- package/scripts/testing/simple-effectiveness-test.js +310 -0
- package/scripts/testing/src/core/context/context-bridge.js +253 -0
- package/scripts/testing/src/core/context/frame-manager.js +746 -0
- package/scripts/testing/src/core/context/shared-context-layer.js +437 -0
- package/scripts/testing/src/core/database/database-adapter.js +54 -0
- package/scripts/testing/src/core/errors/index.js +291 -0
- package/scripts/testing/src/core/errors/recovery.js +268 -0
- package/scripts/testing/src/core/monitoring/logger.js +145 -0
- package/scripts/testing/src/core/retrieval/context-retriever.js +516 -0
- package/scripts/testing/src/core/session/index.js +1 -0
- package/scripts/testing/src/core/session/session-manager.js +323 -0
- package/scripts/testing/src/core/trace/cli-trace-wrapper.js +140 -0
- package/scripts/testing/src/core/trace/db-trace-wrapper.js +251 -0
- package/scripts/testing/src/core/trace/debug-trace.js +398 -0
- package/scripts/testing/src/core/trace/index.js +120 -0
- package/scripts/testing/src/core/trace/linear-api-wrapper.js +204 -0
- package/scripts/update-linear-status.js +268 -0
- package/scripts/update-linear-tasks-fixed.js +284 -0
- package/scripts/verify-railway-schema.ts +35 -0
- package/templates/claude-hooks/hooks.json +5 -0
- package/templates/claude-hooks/on-clear.js +56 -0
- package/templates/claude-hooks/on-startup.js +56 -0
- package/templates/claude-hooks/tool-use-trace.js +67 -0
- package/dist/features/tui/components/analytics-panel.js +0 -157
- package/dist/features/tui/components/analytics-panel.js.map +0 -7
- package/dist/features/tui/components/frame-visualizer.js +0 -377
- package/dist/features/tui/components/frame-visualizer.js.map +0 -7
- package/dist/features/tui/components/pr-tracker.js +0 -135
- package/dist/features/tui/components/pr-tracker.js.map +0 -7
- package/dist/features/tui/components/session-monitor.js +0 -299
- package/dist/features/tui/components/session-monitor.js.map +0 -7
- package/dist/features/tui/components/subagent-fleet.js +0 -395
- package/dist/features/tui/components/subagent-fleet.js.map +0 -7
- package/dist/features/tui/components/task-board.js +0 -1139
- package/dist/features/tui/components/task-board.js.map +0 -7
- package/dist/features/tui/index.js +0 -408
- package/dist/features/tui/index.js.map +0 -7
- package/dist/features/tui/services/data-service.js +0 -641
- package/dist/features/tui/services/data-service.js.map +0 -7
- package/dist/features/tui/services/linear-task-reader.js +0 -102
- package/dist/features/tui/services/linear-task-reader.js.map +0 -7
- package/dist/features/tui/services/websocket-client.js +0 -162
- package/dist/features/tui/services/websocket-client.js.map +0 -7
- package/dist/features/tui/terminal-compat.js +0 -220
- package/dist/features/tui/terminal-compat.js.map +0 -7
- package/dist/features/tui/types.js +0 -1
- package/dist/features/tui/types.js.map +0 -7
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Context Layer for Cross-Session Reference
|
|
3
|
+
*
|
|
4
|
+
* This layer maintains a lightweight shared context across sessions while
|
|
5
|
+
* preserving run_id isolation for write operations. It enables:
|
|
6
|
+
* - Read access to frames from other sessions
|
|
7
|
+
* - Automatic context inheritance
|
|
8
|
+
* - Efficient caching and indexing
|
|
9
|
+
* - Safe concurrent access
|
|
10
|
+
*/
|
|
11
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
12
|
+
import * as fs from 'fs/promises';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import { sessionManager } from '../session/session-manager.js';
|
|
15
|
+
export class SharedContextLayer {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.cache = new Map();
|
|
18
|
+
this.MAX_CACHE_SIZE = 100;
|
|
19
|
+
this.CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
20
|
+
this.lastCacheClean = Date.now();
|
|
21
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
22
|
+
this.contextDir = path.join(homeDir, '.stackmemory', 'shared-context');
|
|
23
|
+
}
|
|
24
|
+
static getInstance() {
|
|
25
|
+
if (!SharedContextLayer.instance) {
|
|
26
|
+
SharedContextLayer.instance = new SharedContextLayer();
|
|
27
|
+
}
|
|
28
|
+
return SharedContextLayer.instance;
|
|
29
|
+
}
|
|
30
|
+
async initialize() {
|
|
31
|
+
await fs.mkdir(this.contextDir, { recursive: true });
|
|
32
|
+
await fs.mkdir(path.join(this.contextDir, 'projects'), { recursive: true });
|
|
33
|
+
await fs.mkdir(path.join(this.contextDir, 'patterns'), { recursive: true });
|
|
34
|
+
await fs.mkdir(path.join(this.contextDir, 'decisions'), {
|
|
35
|
+
recursive: true,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get or create shared context for current project/branch
|
|
40
|
+
*/
|
|
41
|
+
async getSharedContext(options) {
|
|
42
|
+
const session = sessionManager.getCurrentSession();
|
|
43
|
+
const projectId = options?.projectId || session?.projectId || 'global';
|
|
44
|
+
const branch = options?.branch || session?.branch;
|
|
45
|
+
const cacheKey = `${projectId}:${branch || 'main'}`;
|
|
46
|
+
// Check cache first
|
|
47
|
+
if (this.cache.has(cacheKey)) {
|
|
48
|
+
const cached = this.cache.get(cacheKey);
|
|
49
|
+
if (Date.now() - cached.lastUpdated < this.CACHE_TTL) {
|
|
50
|
+
return cached;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Load from disk
|
|
54
|
+
const context = await this.loadProjectContext(projectId, branch);
|
|
55
|
+
// Include other branches if requested
|
|
56
|
+
if (options?.includeOtherBranches) {
|
|
57
|
+
const otherBranches = await this.loadOtherBranchContexts(projectId, branch);
|
|
58
|
+
context.sessions.push(...otherBranches);
|
|
59
|
+
}
|
|
60
|
+
// Update cache
|
|
61
|
+
this.cache.set(cacheKey, context);
|
|
62
|
+
this.cleanCache();
|
|
63
|
+
return context;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Add current session's important frames to shared context
|
|
67
|
+
*/
|
|
68
|
+
async addToSharedContext(frames, options) {
|
|
69
|
+
const session = sessionManager.getCurrentSession();
|
|
70
|
+
if (!session)
|
|
71
|
+
return;
|
|
72
|
+
const context = await this.getSharedContext();
|
|
73
|
+
const minScore = options?.minScore || 0.7;
|
|
74
|
+
// Filter important frames
|
|
75
|
+
const importantFrames = frames.filter((f) => {
|
|
76
|
+
const score = this.calculateFrameScore(f);
|
|
77
|
+
return score >= minScore;
|
|
78
|
+
});
|
|
79
|
+
// Create session context
|
|
80
|
+
const sessionContext = {
|
|
81
|
+
sessionId: session.sessionId,
|
|
82
|
+
runId: session.runId,
|
|
83
|
+
summary: this.generateSessionSummary(importantFrames),
|
|
84
|
+
keyFrames: importantFrames.map((f) => this.summarizeFrame(f)),
|
|
85
|
+
createdAt: session.startedAt,
|
|
86
|
+
lastActiveAt: Date.now(),
|
|
87
|
+
metadata: session.metadata,
|
|
88
|
+
};
|
|
89
|
+
// Update or add session context
|
|
90
|
+
const existingIndex = context.sessions.findIndex((s) => s.sessionId === session.sessionId);
|
|
91
|
+
if (existingIndex >= 0) {
|
|
92
|
+
context.sessions[existingIndex] = sessionContext;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
context.sessions.push(sessionContext);
|
|
96
|
+
}
|
|
97
|
+
// Update patterns
|
|
98
|
+
this.updatePatterns(context, importantFrames);
|
|
99
|
+
// Update reference index
|
|
100
|
+
this.updateReferenceIndex(context, importantFrames);
|
|
101
|
+
// Save context
|
|
102
|
+
await this.saveProjectContext(context);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Query shared context for relevant frames
|
|
106
|
+
*/
|
|
107
|
+
async querySharedContext(query) {
|
|
108
|
+
const context = await this.getSharedContext({ includeOtherBranches: true });
|
|
109
|
+
let results = [];
|
|
110
|
+
// Collect all frames from all sessions
|
|
111
|
+
for (const session of context.sessions) {
|
|
112
|
+
if (query.sessionId && session.sessionId !== query.sessionId)
|
|
113
|
+
continue;
|
|
114
|
+
// Skip sessions without keyFrames
|
|
115
|
+
if (!session.keyFrames || !Array.isArray(session.keyFrames))
|
|
116
|
+
continue;
|
|
117
|
+
const filtered = session.keyFrames.filter((f) => {
|
|
118
|
+
if (query.tags && !query.tags.some((tag) => f.tags.includes(tag)))
|
|
119
|
+
return false;
|
|
120
|
+
if (query.type && f.type !== query.type)
|
|
121
|
+
return false;
|
|
122
|
+
if (query.minScore && f.score < query.minScore)
|
|
123
|
+
return false;
|
|
124
|
+
return true;
|
|
125
|
+
});
|
|
126
|
+
results.push(...filtered);
|
|
127
|
+
}
|
|
128
|
+
// Sort by score and recency
|
|
129
|
+
results.sort((a, b) => {
|
|
130
|
+
const scoreWeight = 0.7;
|
|
131
|
+
const recencyWeight = 0.3;
|
|
132
|
+
const aScore = a.score * scoreWeight +
|
|
133
|
+
(1 - (Date.now() - a.createdAt) / (30 * 24 * 60 * 60 * 1000)) *
|
|
134
|
+
recencyWeight;
|
|
135
|
+
const bScore = b.score * scoreWeight +
|
|
136
|
+
(1 - (Date.now() - b.createdAt) / (30 * 24 * 60 * 60 * 1000)) *
|
|
137
|
+
recencyWeight;
|
|
138
|
+
return bScore - aScore;
|
|
139
|
+
});
|
|
140
|
+
// Apply limit
|
|
141
|
+
if (query.limit) {
|
|
142
|
+
results = results.slice(0, query.limit);
|
|
143
|
+
}
|
|
144
|
+
// Update recently accessed
|
|
145
|
+
const index = context.referenceIndex;
|
|
146
|
+
if (!index.recentlyAccessed) {
|
|
147
|
+
index.recentlyAccessed = [];
|
|
148
|
+
}
|
|
149
|
+
// Add frameIds to recently accessed, removing duplicates
|
|
150
|
+
if (results.length > 0) {
|
|
151
|
+
const frameIds = results.map((r) => r.frameId);
|
|
152
|
+
index.recentlyAccessed = [
|
|
153
|
+
...frameIds,
|
|
154
|
+
...index.recentlyAccessed.filter(id => !frameIds.includes(id))
|
|
155
|
+
].slice(0, 100);
|
|
156
|
+
// Save the updated context with recently accessed frames
|
|
157
|
+
await this.saveProjectContext(context);
|
|
158
|
+
}
|
|
159
|
+
return results;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get relevant patterns from shared context
|
|
163
|
+
*/
|
|
164
|
+
async getPatterns(type) {
|
|
165
|
+
const context = await this.getSharedContext();
|
|
166
|
+
if (type) {
|
|
167
|
+
return context.globalPatterns.filter((p) => p.type === type);
|
|
168
|
+
}
|
|
169
|
+
return context.globalPatterns;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Add a decision to the shared context
|
|
173
|
+
*/
|
|
174
|
+
async addDecision(decision) {
|
|
175
|
+
const session = sessionManager.getCurrentSession();
|
|
176
|
+
if (!session)
|
|
177
|
+
return;
|
|
178
|
+
const context = await this.getSharedContext();
|
|
179
|
+
const newDecision = {
|
|
180
|
+
id: uuidv4(),
|
|
181
|
+
timestamp: Date.now(),
|
|
182
|
+
sessionId: session.sessionId,
|
|
183
|
+
outcome: 'pending',
|
|
184
|
+
...decision,
|
|
185
|
+
};
|
|
186
|
+
context.decisionLog.push(newDecision);
|
|
187
|
+
// Keep only last 100 decisions
|
|
188
|
+
if (context.decisionLog.length > 100) {
|
|
189
|
+
context.decisionLog = context.decisionLog.slice(-100);
|
|
190
|
+
}
|
|
191
|
+
await this.saveProjectContext(context);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get recent decisions from shared context
|
|
195
|
+
*/
|
|
196
|
+
async getDecisions(limit = 10) {
|
|
197
|
+
const context = await this.getSharedContext();
|
|
198
|
+
return context.decisionLog.slice(-limit);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Automatic context discovery on CLI startup
|
|
202
|
+
*/
|
|
203
|
+
async autoDiscoverContext() {
|
|
204
|
+
const context = await this.getSharedContext({
|
|
205
|
+
includeOtherBranches: false,
|
|
206
|
+
});
|
|
207
|
+
// Get recent patterns (last 7 days)
|
|
208
|
+
const recentPatterns = context.globalPatterns
|
|
209
|
+
.filter((p) => Date.now() - p.lastSeen < 7 * 24 * 60 * 60 * 1000)
|
|
210
|
+
.sort((a, b) => b.frequency - a.frequency)
|
|
211
|
+
.slice(0, 5);
|
|
212
|
+
// Get last 5 decisions
|
|
213
|
+
const lastDecisions = context.decisionLog.slice(-5);
|
|
214
|
+
// Get suggested frames based on recent access and score
|
|
215
|
+
const suggestedFrames = await this.querySharedContext({
|
|
216
|
+
minScore: 0.8,
|
|
217
|
+
limit: 5,
|
|
218
|
+
});
|
|
219
|
+
return {
|
|
220
|
+
hasSharedContext: context.sessions.length > 0,
|
|
221
|
+
sessionCount: context.sessions.length,
|
|
222
|
+
recentPatterns,
|
|
223
|
+
lastDecisions,
|
|
224
|
+
suggestedFrames,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
async loadProjectContext(projectId, branch) {
|
|
228
|
+
const contextFile = path.join(this.contextDir, 'projects', `${projectId}_${branch || 'main'}.json`);
|
|
229
|
+
try {
|
|
230
|
+
const data = await fs.readFile(contextFile, 'utf-8');
|
|
231
|
+
const context = JSON.parse(data);
|
|
232
|
+
// Reconstruct Maps
|
|
233
|
+
context.referenceIndex.byTag = new Map(Object.entries(context.referenceIndex.byTag || {}));
|
|
234
|
+
context.referenceIndex.byType = new Map(Object.entries(context.referenceIndex.byType || {}));
|
|
235
|
+
return context;
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
// Return empty context if file doesn't exist
|
|
239
|
+
return {
|
|
240
|
+
projectId,
|
|
241
|
+
branch,
|
|
242
|
+
lastUpdated: Date.now(),
|
|
243
|
+
sessions: [],
|
|
244
|
+
globalPatterns: [],
|
|
245
|
+
decisionLog: [],
|
|
246
|
+
referenceIndex: {
|
|
247
|
+
byTag: new Map(),
|
|
248
|
+
byType: new Map(),
|
|
249
|
+
byScore: [],
|
|
250
|
+
recentlyAccessed: [],
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async saveProjectContext(context) {
|
|
256
|
+
const contextFile = path.join(this.contextDir, 'projects', `${context.projectId}_${context.branch || 'main'}.json`);
|
|
257
|
+
// Convert Maps to objects for JSON serialization
|
|
258
|
+
const serializable = {
|
|
259
|
+
...context,
|
|
260
|
+
lastUpdated: Date.now(),
|
|
261
|
+
referenceIndex: {
|
|
262
|
+
...context.referenceIndex,
|
|
263
|
+
byTag: Object.fromEntries(context.referenceIndex.byTag),
|
|
264
|
+
byType: Object.fromEntries(context.referenceIndex.byType),
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
await fs.writeFile(contextFile, JSON.stringify(serializable, null, 2));
|
|
268
|
+
}
|
|
269
|
+
async loadOtherBranchContexts(projectId, currentBranch) {
|
|
270
|
+
const projectsDir = path.join(this.contextDir, 'projects');
|
|
271
|
+
const files = await fs.readdir(projectsDir);
|
|
272
|
+
const sessions = [];
|
|
273
|
+
for (const file of files) {
|
|
274
|
+
if (file.startsWith(`${projectId}_`) &&
|
|
275
|
+
!file.includes(currentBranch || 'main')) {
|
|
276
|
+
try {
|
|
277
|
+
const data = await fs.readFile(path.join(projectsDir, file), 'utf-8');
|
|
278
|
+
const context = JSON.parse(data);
|
|
279
|
+
sessions.push(...context.sessions);
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
// Skip invalid files
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return sessions;
|
|
287
|
+
}
|
|
288
|
+
calculateFrameScore(frame) {
|
|
289
|
+
// Simple scoring algorithm
|
|
290
|
+
let score = 0.5;
|
|
291
|
+
// Boost for certain types
|
|
292
|
+
if (frame.type === 'task' || frame.type === 'review')
|
|
293
|
+
score += 0.2;
|
|
294
|
+
if (frame.type === 'debug' || frame.type === 'write')
|
|
295
|
+
score += 0.15;
|
|
296
|
+
if (frame.type === 'error')
|
|
297
|
+
score += 0.15; // Error frames are important for pattern extraction
|
|
298
|
+
// Check for data property (used in tests)
|
|
299
|
+
const frameWithData = frame;
|
|
300
|
+
if (frameWithData.data)
|
|
301
|
+
score += 0.2;
|
|
302
|
+
// Boost for having outputs (indicates completion/results)
|
|
303
|
+
if (frame.outputs && Object.keys(frame.outputs).length > 0)
|
|
304
|
+
score += 0.2;
|
|
305
|
+
if (frame.digest_text || (frame.digest_json && Object.keys(frame.digest_json).length > 0))
|
|
306
|
+
score += 0.1;
|
|
307
|
+
// Time decay (reduce score for older frames) - but handle missing created_at
|
|
308
|
+
if (frame.created_at) {
|
|
309
|
+
const age = Date.now() - frame.created_at;
|
|
310
|
+
const daysSinceCreation = age / (24 * 60 * 60 * 1000);
|
|
311
|
+
score *= Math.max(0.3, 1 - daysSinceCreation / 30);
|
|
312
|
+
}
|
|
313
|
+
return Math.min(1, score);
|
|
314
|
+
}
|
|
315
|
+
summarizeFrame(frame) {
|
|
316
|
+
return {
|
|
317
|
+
frameId: frame.frame_id,
|
|
318
|
+
title: frame.name,
|
|
319
|
+
type: frame.type,
|
|
320
|
+
score: this.calculateFrameScore(frame),
|
|
321
|
+
tags: [],
|
|
322
|
+
summary: this.generateFrameSummary(frame),
|
|
323
|
+
createdAt: frame.created_at,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
generateFrameSummary(frame) {
|
|
327
|
+
// Generate a brief summary of the frame
|
|
328
|
+
const parts = [];
|
|
329
|
+
const frameWithData = frame;
|
|
330
|
+
if (frame.type)
|
|
331
|
+
parts.push(`[${frame.type}]`);
|
|
332
|
+
if (frame.name)
|
|
333
|
+
parts.push(frame.name);
|
|
334
|
+
if (frameWithData.title)
|
|
335
|
+
parts.push(frameWithData.title);
|
|
336
|
+
if (frameWithData.data?.error)
|
|
337
|
+
parts.push(`Error: ${frameWithData.data.error}`);
|
|
338
|
+
if (frameWithData.data?.resolution)
|
|
339
|
+
parts.push(`Resolution: ${frameWithData.data.resolution}`);
|
|
340
|
+
return parts.join(' - ').slice(0, 200);
|
|
341
|
+
}
|
|
342
|
+
generateSessionSummary(frames) {
|
|
343
|
+
const types = [...new Set(frames.map((f) => f.type))];
|
|
344
|
+
return `Session with ${frames.length} key frames: ${types.join(', ')}`;
|
|
345
|
+
}
|
|
346
|
+
updatePatterns(context, frames) {
|
|
347
|
+
for (const frame of frames) {
|
|
348
|
+
// Extract patterns from frame data
|
|
349
|
+
// Handle frames with a data property (used in tests)
|
|
350
|
+
const frameWithData = frame;
|
|
351
|
+
if (frameWithData.data?.error) {
|
|
352
|
+
this.addPattern(context, frameWithData.data.error, 'error', frameWithData.data?.resolution);
|
|
353
|
+
}
|
|
354
|
+
else if (frame.type === 'error' && frame.name) {
|
|
355
|
+
// Only extract from name/outputs if no data.error property
|
|
356
|
+
const errorText = frame.outputs?.error || frame.name;
|
|
357
|
+
const resolution = frame.outputs?.resolution;
|
|
358
|
+
if (errorText) {
|
|
359
|
+
this.addPattern(context, errorText, 'error', resolution);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (frame.type === 'decision' && frameWithData.data?.decision) {
|
|
363
|
+
this.addPattern(context, frameWithData.data.decision, 'decision');
|
|
364
|
+
}
|
|
365
|
+
else if (frame.digest_json?.decision) {
|
|
366
|
+
// Only extract from digest_json if no data.decision
|
|
367
|
+
this.addPattern(context, frame.digest_json.decision, 'decision');
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
addPattern(context, pattern, type, resolution) {
|
|
372
|
+
const existing = context.globalPatterns.find((p) => p.pattern === pattern && p.type === type);
|
|
373
|
+
if (existing) {
|
|
374
|
+
existing.frequency++;
|
|
375
|
+
existing.lastSeen = Date.now();
|
|
376
|
+
if (resolution)
|
|
377
|
+
existing.resolution = resolution;
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
context.globalPatterns.push({
|
|
381
|
+
pattern,
|
|
382
|
+
type,
|
|
383
|
+
frequency: 1,
|
|
384
|
+
lastSeen: Date.now(),
|
|
385
|
+
resolution,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
// Keep only top 100 patterns
|
|
389
|
+
if (context.globalPatterns.length > 100) {
|
|
390
|
+
context.globalPatterns.sort((a, b) => b.frequency - a.frequency);
|
|
391
|
+
context.globalPatterns = context.globalPatterns.slice(0, 100);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
updateReferenceIndex(context, frames) {
|
|
395
|
+
for (const frame of frames) {
|
|
396
|
+
const summary = this.summarizeFrame(frame);
|
|
397
|
+
// Index by tags
|
|
398
|
+
for (const tag of summary.tags) {
|
|
399
|
+
if (!context.referenceIndex.byTag.has(tag)) {
|
|
400
|
+
context.referenceIndex.byTag.set(tag, []);
|
|
401
|
+
}
|
|
402
|
+
context.referenceIndex.byTag.get(tag).push(frame.frameId);
|
|
403
|
+
}
|
|
404
|
+
// Index by type
|
|
405
|
+
if (!context.referenceIndex.byType.has(frame.type)) {
|
|
406
|
+
context.referenceIndex.byType.set(frame.type, []);
|
|
407
|
+
}
|
|
408
|
+
context.referenceIndex.byType.get(frame.type).push(frame.frameId);
|
|
409
|
+
// Update score index
|
|
410
|
+
const scoreIndex = context.referenceIndex.byScore;
|
|
411
|
+
const insertIndex = scoreIndex.findIndex((id) => {
|
|
412
|
+
const otherFrame = context.sessions
|
|
413
|
+
.flatMap((s) => s.keyFrames)
|
|
414
|
+
.find((f) => f.frameId === id);
|
|
415
|
+
return otherFrame && otherFrame.score < summary.score;
|
|
416
|
+
});
|
|
417
|
+
if (insertIndex >= 0) {
|
|
418
|
+
scoreIndex.splice(insertIndex, 0, frame.frameId);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
scoreIndex.push(frame.frameId);
|
|
422
|
+
}
|
|
423
|
+
// Keep only top 1000 by score
|
|
424
|
+
context.referenceIndex.byScore = scoreIndex.slice(0, 1000);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
cleanCache() {
|
|
428
|
+
if (Date.now() - this.lastCacheClean < 60000)
|
|
429
|
+
return; // Clean every minute
|
|
430
|
+
if (this.cache.size > this.MAX_CACHE_SIZE) {
|
|
431
|
+
const entries = Array.from(this.cache.entries()).sort((a, b) => b[1].lastUpdated - a[1].lastUpdated);
|
|
432
|
+
this.cache = new Map(entries.slice(0, this.MAX_CACHE_SIZE / 2));
|
|
433
|
+
}
|
|
434
|
+
this.lastCacheClean = Date.now();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
export const sharedContextLayer = SharedContextLayer.getInstance();
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Adapter Interface
|
|
3
|
+
* Provides abstraction layer for different database implementations
|
|
4
|
+
* Supports SQLite (current) and ParadeDB (new) with seamless migration
|
|
5
|
+
*/
|
|
6
|
+
export class DatabaseAdapter {
|
|
7
|
+
constructor(projectId, config) {
|
|
8
|
+
this.projectId = projectId;
|
|
9
|
+
this.config = config || {};
|
|
10
|
+
}
|
|
11
|
+
// Utility methods
|
|
12
|
+
generateId() {
|
|
13
|
+
return crypto.randomUUID();
|
|
14
|
+
}
|
|
15
|
+
sanitizeQuery(query) {
|
|
16
|
+
// DEPRECATED: Use parameterized queries instead
|
|
17
|
+
// This method is kept for legacy compatibility but should not be used
|
|
18
|
+
console.warn('sanitizeQuery() is deprecated and unsafe - use parameterized queries');
|
|
19
|
+
return query.replace(/[;'"\\]/g, '');
|
|
20
|
+
}
|
|
21
|
+
buildWhereClause(conditions) {
|
|
22
|
+
const clauses = Object.entries(conditions).map(([key, value]) => {
|
|
23
|
+
if (value === null) {
|
|
24
|
+
return `${key} IS NULL`;
|
|
25
|
+
}
|
|
26
|
+
else if (Array.isArray(value)) {
|
|
27
|
+
return `${key} IN (${value.map(() => '?').join(',')})`;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
return `${key} = ?`;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return clauses.length > 0 ? `WHERE ${clauses.join(' AND ')}` : '';
|
|
34
|
+
}
|
|
35
|
+
buildOrderByClause(orderBy, direction) {
|
|
36
|
+
if (!orderBy)
|
|
37
|
+
return '';
|
|
38
|
+
return ` ORDER BY ${orderBy} ${direction || 'ASC'}`;
|
|
39
|
+
}
|
|
40
|
+
buildLimitClause(limit, offset) {
|
|
41
|
+
if (!limit)
|
|
42
|
+
return '';
|
|
43
|
+
let clause = ` LIMIT ${limit}`;
|
|
44
|
+
if (offset)
|
|
45
|
+
clause += ` OFFSET ${offset}`;
|
|
46
|
+
return clause;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export class FeatureAwareDatabaseAdapter extends DatabaseAdapter {
|
|
50
|
+
async canUseFeature(feature) {
|
|
51
|
+
const features = this.getFeatures();
|
|
52
|
+
return features[feature] || false;
|
|
53
|
+
}
|
|
54
|
+
}
|