@stackmemoryai/stackmemory 0.3.16 → 0.3.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/README.md +48 -2
  2. package/dist/cli/commands/skills.js +15 -2
  3. package/dist/cli/commands/skills.js.map +2 -2
  4. package/dist/cli/index.js +113 -834
  5. package/dist/cli/index.js.map +3 -3
  6. package/dist/core/context/dual-stack-manager.js +1 -1
  7. package/dist/core/context/dual-stack-manager.js.map +1 -1
  8. package/dist/core/context/frame-manager.js +3 -0
  9. package/dist/core/context/frame-manager.js.map +2 -2
  10. package/dist/integrations/claude-code/subagent-client.js +106 -3
  11. package/dist/integrations/claude-code/subagent-client.js.map +2 -2
  12. package/dist/servers/railway/config.js +51 -0
  13. package/dist/servers/railway/config.js.map +7 -0
  14. package/dist/servers/railway/index-enhanced.js +156 -0
  15. package/dist/servers/railway/index-enhanced.js.map +7 -0
  16. package/dist/servers/railway/minimal.js +48 -3
  17. package/dist/servers/railway/minimal.js.map +2 -2
  18. package/dist/servers/railway/storage-test.js +455 -0
  19. package/dist/servers/railway/storage-test.js.map +7 -0
  20. package/dist/skills/claude-skills.js +13 -12
  21. package/dist/skills/claude-skills.js.map +2 -2
  22. package/dist/skills/recursive-agent-orchestrator.js +27 -18
  23. package/dist/skills/recursive-agent-orchestrator.js.map +2 -2
  24. package/dist/skills/unified-rlm-orchestrator.js.map +2 -2
  25. package/package.json +6 -18
  26. package/scripts/README-TESTING.md +186 -0
  27. package/scripts/analyze-cli-security.js +288 -0
  28. package/scripts/archive/add-phase-tasks-to-linear.js +163 -0
  29. package/scripts/archive/analyze-linear-duplicates.js +214 -0
  30. package/scripts/archive/analyze-remaining-duplicates.js +230 -0
  31. package/scripts/archive/analyze-sta-duplicates.js +292 -0
  32. package/scripts/archive/analyze-sta-graphql.js +399 -0
  33. package/scripts/archive/cancel-duplicate-tasks.ts +246 -0
  34. package/scripts/archive/check-all-duplicates.ts +419 -0
  35. package/scripts/archive/clean-duplicate-tasks.js +114 -0
  36. package/scripts/archive/cleanup-duplicate-tasks.ts +286 -0
  37. package/scripts/archive/create-phase-tasks.js +387 -0
  38. package/scripts/archive/delete-linear-duplicates.js +182 -0
  39. package/scripts/archive/delete-remaining-duplicates.js +158 -0
  40. package/scripts/archive/delete-sta-duplicates.js +201 -0
  41. package/scripts/archive/delete-sta-oauth.js +201 -0
  42. package/scripts/archive/export-sta-tasks.js +62 -0
  43. package/scripts/archive/install-auto-sync.js +266 -0
  44. package/scripts/archive/install-chromadb-hooks.sh +133 -0
  45. package/scripts/archive/install-enhanced-clear-hooks.sh +431 -0
  46. package/scripts/archive/install-post-task-hooks.sh +289 -0
  47. package/scripts/archive/install-stackmemory-hooks.sh +420 -0
  48. package/scripts/archive/merge-linear-duplicates-safe.ts +362 -0
  49. package/scripts/archive/merge-linear-duplicates.ts +180 -0
  50. package/scripts/archive/remove-sta-tasks.js +70 -0
  51. package/scripts/archive/setup-background-sync.sh +168 -0
  52. package/scripts/archive/setup-claude-auto-triggers.sh +181 -0
  53. package/scripts/archive/setup-claude-autostart.sh +305 -0
  54. package/scripts/archive/setup-git-hooks.sh +25 -0
  55. package/scripts/archive/setup-linear-oauth.sh +46 -0
  56. package/scripts/archive/setup-mcp.sh +113 -0
  57. package/scripts/archive/setup-railway-deployment.sh +81 -0
  58. package/scripts/auto-handoff.sh +262 -0
  59. package/scripts/background-sync-manager.js +416 -0
  60. package/scripts/benchmark-performance.ts +57 -0
  61. package/scripts/check-redis.ts +48 -0
  62. package/scripts/chromadb-auto-loader.sh +128 -0
  63. package/scripts/chromadb-context-loader.js +479 -0
  64. package/scripts/claude-chromadb-hook.js +460 -0
  65. package/scripts/claude-code-wrapper.sh +66 -0
  66. package/scripts/claude-linear-skill.js +455 -0
  67. package/scripts/claude-pre-commit.sh +302 -0
  68. package/scripts/claude-sm-autostart.js +532 -0
  69. package/scripts/claude-sm-setup.sh +367 -0
  70. package/scripts/claude-with-chromadb.sh +69 -0
  71. package/scripts/claude-worktree-manager.sh +323 -0
  72. package/scripts/claude-worktree-monitor.sh +371 -0
  73. package/scripts/claude-worktree-setup.sh +327 -0
  74. package/scripts/clean-linear-backlog.js +273 -0
  75. package/scripts/cleanup-old-sessions.sh +57 -0
  76. package/scripts/codex-wrapper.sh +88 -0
  77. package/scripts/create-sandbox.sh +269 -0
  78. package/scripts/debug-linear-update.js +174 -0
  79. package/scripts/delete-linear-tasks.js +167 -0
  80. package/scripts/deploy.sh +89 -0
  81. package/scripts/deployment/railway.sh +352 -0
  82. package/scripts/deployment/test-deployment.js +194 -0
  83. package/scripts/detect-and-rehydrate.js +162 -0
  84. package/scripts/detect-and-rehydrate.mjs +165 -0
  85. package/scripts/development/create-demo-tasks.js +143 -0
  86. package/scripts/development/debug-frame-test.js +16 -0
  87. package/scripts/development/demo-auto-sync.js +128 -0
  88. package/scripts/development/fix-all-imports.js +213 -0
  89. package/scripts/development/fix-imports.js +229 -0
  90. package/scripts/development/fix-lint-loop.cjs +103 -0
  91. package/scripts/development/fix-project-id.ts +161 -0
  92. package/scripts/development/fix-strict-mode-issues.ts +291 -0
  93. package/scripts/development/reorganize-structure.sh +228 -0
  94. package/scripts/development/test-persistence-direct.js +148 -0
  95. package/scripts/development/test-persistence.js +114 -0
  96. package/scripts/development/test-tasks.js +93 -0
  97. package/scripts/development/update-imports.js +212 -0
  98. package/scripts/fetch-linear-status.js +125 -0
  99. package/scripts/git-hooks/README.md +310 -0
  100. package/scripts/git-hooks/branch-context-manager.sh +342 -0
  101. package/scripts/git-hooks/post-checkout-stackmemory.sh +63 -0
  102. package/scripts/git-hooks/post-commit-stackmemory.sh +305 -0
  103. package/scripts/git-hooks/pre-commit-stackmemory.sh +275 -0
  104. package/scripts/hooks/cleanup-shell.sh +130 -0
  105. package/scripts/hooks/task-complete.sh +114 -0
  106. package/scripts/initialize.ts +129 -0
  107. package/scripts/install-claude-hooks-auto.js +104 -0
  108. package/scripts/install-claude-hooks.sh +133 -0
  109. package/scripts/install-global.sh +296 -0
  110. package/scripts/install.sh +235 -0
  111. package/scripts/linear-auto-sync.js +262 -0
  112. package/scripts/linear-auto-sync.sh +161 -0
  113. package/scripts/linear-sync-daemon.js +150 -0
  114. package/scripts/linear-task-review.js +237 -0
  115. package/scripts/list-linear-tasks.ts +178 -0
  116. package/scripts/mcp-proxy.js +66 -0
  117. package/scripts/opencode-wrapper.sh +85 -0
  118. package/scripts/publish-local.js +74 -0
  119. package/scripts/query-chromadb.ts +201 -0
  120. package/scripts/railway-env-setup.sh +39 -0
  121. package/scripts/reconcile-local-tasks.js +170 -0
  122. package/scripts/recreate-frames-db.js +89 -0
  123. package/scripts/setup/claude-integration.js +138 -0
  124. package/scripts/setup/configure-alias.js +125 -0
  125. package/scripts/setup/configure-codex-alias.js +161 -0
  126. package/scripts/setup/configure-opencode-alias.js +175 -0
  127. package/scripts/setup-claude-integration.js +204 -0
  128. package/scripts/setup-claude-integration.sh +183 -0
  129. package/scripts/setup.sh +31 -0
  130. package/scripts/show-linear-summary.ts +172 -0
  131. package/scripts/stackmemory-auto-handoff.sh +231 -0
  132. package/scripts/stackmemory-daemon.sh +40 -0
  133. package/scripts/start-linear-sync-daemon.sh +141 -0
  134. package/scripts/start-temporal-paradox.sh +214 -0
  135. package/scripts/status.ts +159 -0
  136. package/scripts/sync-and-clean-tasks.js +258 -0
  137. package/scripts/sync-frames-from-railway.js +228 -0
  138. package/scripts/sync-linear-graphql.js +303 -0
  139. package/scripts/sync-linear-tasks.js +186 -0
  140. package/scripts/test-auto-triggers.sh +57 -0
  141. package/scripts/test-browser-mcp.js +74 -0
  142. package/scripts/test-chromadb-full.js +115 -0
  143. package/scripts/test-chromadb-hooks.sh +28 -0
  144. package/scripts/test-chromadb-sync.ts +245 -0
  145. package/scripts/test-cli-security.js +293 -0
  146. package/scripts/test-hooks-persistence.sh +220 -0
  147. package/scripts/test-installation-scenarios.sh +359 -0
  148. package/scripts/test-installation.sh +224 -0
  149. package/scripts/test-mcp.js +163 -0
  150. package/scripts/test-pre-publish-quick.sh +75 -0
  151. package/scripts/test-quality-gates.sh +263 -0
  152. package/scripts/test-railway-db.js +222 -0
  153. package/scripts/test-redis-storage.ts +490 -0
  154. package/scripts/test-rlm-basic.sh +122 -0
  155. package/scripts/test-rlm-comprehensive.sh +260 -0
  156. package/scripts/test-rlm-e2e.sh +268 -0
  157. package/scripts/test-rlm-simple.js +90 -0
  158. package/scripts/test-rlm.js +110 -0
  159. package/scripts/test-session-handoff.sh +165 -0
  160. package/scripts/test-shell-integration.sh +275 -0
  161. package/scripts/testing/ab-test-runner.ts +508 -0
  162. package/scripts/testing/collect-metrics.ts +457 -0
  163. package/scripts/testing/quick-effectiveness-demo.js +187 -0
  164. package/scripts/testing/real-performance-test.js +422 -0
  165. package/scripts/testing/run-effectiveness-tests.sh +176 -0
  166. package/scripts/testing/scripts/testing/ab-test-runner.js +363 -0
  167. package/scripts/testing/scripts/testing/collect-metrics.js +292 -0
  168. package/scripts/testing/simple-effectiveness-test.js +310 -0
  169. package/scripts/testing/src/core/context/context-bridge.js +253 -0
  170. package/scripts/testing/src/core/context/frame-manager.js +746 -0
  171. package/scripts/testing/src/core/context/shared-context-layer.js +437 -0
  172. package/scripts/testing/src/core/database/database-adapter.js +54 -0
  173. package/scripts/testing/src/core/errors/index.js +291 -0
  174. package/scripts/testing/src/core/errors/recovery.js +268 -0
  175. package/scripts/testing/src/core/monitoring/logger.js +145 -0
  176. package/scripts/testing/src/core/retrieval/context-retriever.js +516 -0
  177. package/scripts/testing/src/core/session/index.js +1 -0
  178. package/scripts/testing/src/core/session/session-manager.js +323 -0
  179. package/scripts/testing/src/core/trace/cli-trace-wrapper.js +140 -0
  180. package/scripts/testing/src/core/trace/db-trace-wrapper.js +251 -0
  181. package/scripts/testing/src/core/trace/debug-trace.js +398 -0
  182. package/scripts/testing/src/core/trace/index.js +120 -0
  183. package/scripts/testing/src/core/trace/linear-api-wrapper.js +204 -0
  184. package/scripts/update-linear-status.js +268 -0
  185. package/scripts/update-linear-tasks-fixed.js +284 -0
  186. package/templates/claude-hooks/hooks.json +5 -0
  187. package/templates/claude-hooks/on-clear.js +56 -0
  188. package/templates/claude-hooks/on-startup.js +56 -0
  189. package/templates/claude-hooks/tool-use-trace.js +67 -0
  190. package/dist/features/tui/components/analytics-panel.js +0 -157
  191. package/dist/features/tui/components/analytics-panel.js.map +0 -7
  192. package/dist/features/tui/components/frame-visualizer.js +0 -377
  193. package/dist/features/tui/components/frame-visualizer.js.map +0 -7
  194. package/dist/features/tui/components/pr-tracker.js +0 -135
  195. package/dist/features/tui/components/pr-tracker.js.map +0 -7
  196. package/dist/features/tui/components/session-monitor.js +0 -299
  197. package/dist/features/tui/components/session-monitor.js.map +0 -7
  198. package/dist/features/tui/components/subagent-fleet.js +0 -395
  199. package/dist/features/tui/components/subagent-fleet.js.map +0 -7
  200. package/dist/features/tui/components/task-board.js +0 -1139
  201. package/dist/features/tui/components/task-board.js.map +0 -7
  202. package/dist/features/tui/index.js +0 -408
  203. package/dist/features/tui/index.js.map +0 -7
  204. package/dist/features/tui/services/data-service.js +0 -641
  205. package/dist/features/tui/services/data-service.js.map +0 -7
  206. package/dist/features/tui/services/linear-task-reader.js +0 -102
  207. package/dist/features/tui/services/linear-task-reader.js.map +0 -7
  208. package/dist/features/tui/services/websocket-client.js +0 -162
  209. package/dist/features/tui/services/websocket-client.js.map +0 -7
  210. package/dist/features/tui/terminal-compat.js +0 -220
  211. package/dist/features/tui/terminal-compat.js.map +0 -7
  212. package/dist/features/tui/types.js +0 -1
  213. 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
+ }