@stackmemoryai/stackmemory 1.2.2 → 1.2.4

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 (63) hide show
  1. package/dist/src/cli/codex-sm.js +6 -8
  2. package/dist/src/cli/commands/config.js +0 -81
  3. package/dist/src/cli/commands/context-rehydrate.js +133 -47
  4. package/dist/src/cli/commands/db.js +35 -8
  5. package/dist/src/cli/commands/handoff.js +1 -1
  6. package/dist/src/cli/commands/linear.js +9 -0
  7. package/dist/src/cli/commands/ralph.js +2 -2
  8. package/dist/src/cli/commands/setup.js +2 -2
  9. package/dist/src/cli/commands/signup.js +3 -1
  10. package/dist/src/cli/commands/storage-tier.js +26 -8
  11. package/dist/src/cli/index.js +1 -57
  12. package/dist/src/core/config/feature-flags.js +0 -4
  13. package/dist/src/core/context/dual-stack-manager.js +10 -3
  14. package/dist/src/core/context/frame-database.js +32 -0
  15. package/dist/src/core/context/frame-handoff-manager.js +2 -2
  16. package/dist/src/core/context/{refactored-frame-manager.js → frame-manager.js} +3 -3
  17. package/dist/src/core/context/index.js +2 -2
  18. package/dist/src/core/database/sqlite-adapter.js +161 -1
  19. package/dist/src/core/digest/frame-digest-integration.js +1 -1
  20. package/dist/src/core/digest/index.js +1 -1
  21. package/dist/src/core/execution/parallel-executor.js +5 -1
  22. package/dist/src/core/projects/project-isolation.js +18 -4
  23. package/dist/src/core/security/index.js +2 -0
  24. package/dist/src/core/security/input-sanitizer.js +23 -0
  25. package/dist/src/core/utils/update-checker.js +10 -6
  26. package/dist/src/daemon/daemon-config.js +2 -1
  27. package/dist/src/daemon/services/auto-save-service.js +121 -0
  28. package/dist/src/daemon/services/maintenance-service.js +76 -1
  29. package/dist/src/features/sweep/prompt-builder.js +2 -2
  30. package/dist/src/integrations/linear/config.js +3 -1
  31. package/dist/src/integrations/linear/sync.js +18 -5
  32. package/dist/src/integrations/mcp/handlers/code-execution-handlers.js +33 -7
  33. package/dist/src/integrations/mcp/handlers/cord-handlers.js +397 -0
  34. package/dist/src/integrations/mcp/handlers/index.js +55 -1
  35. package/dist/src/integrations/mcp/handlers/task-handlers.js +55 -12
  36. package/dist/src/integrations/mcp/handlers/team-handlers.js +211 -0
  37. package/dist/src/integrations/mcp/handlers/trace-handlers.js +25 -9
  38. package/dist/src/integrations/mcp/index.js +2 -2
  39. package/dist/src/integrations/mcp/refactored-server.js +31 -10
  40. package/dist/src/integrations/mcp/tool-definitions.js +215 -1
  41. package/dist/src/integrations/ralph/context/context-budget-manager.js +10 -2
  42. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +54 -22
  43. package/dist/src/integrations/ralph/learning/pattern-learner.js +59 -24
  44. package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +81 -35
  45. package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +12 -4
  46. package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +32 -9
  47. package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +25 -8
  48. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +17 -5
  49. package/dist/src/integrations/ralph/visualization/ralph-debugger.js +73 -22
  50. package/dist/src/skills/claude-skills.js +0 -102
  51. package/dist/src/utils/hook-installer.js +8 -0
  52. package/package.json +5 -5
  53. package/scripts/install-claude-hooks-auto.js +8 -0
  54. package/templates/claude-hooks/cord-trace.js +225 -0
  55. package/dist/src/core/config/storage-config.js +0 -114
  56. package/dist/src/core/storage/chromadb-adapter.js +0 -379
  57. package/dist/src/integrations/claude-code/enhanced-pre-clear-hooks.js +0 -458
  58. package/dist/src/integrations/ralph/coordination/enhanced-coordination.js +0 -409
  59. package/dist/src/skills/repo-ingestion-skill.js +0 -631
  60. package/templates/claude-hooks/chromadb-wrapper +0 -21
  61. /package/dist/src/core/context/{enhanced-rehydration.js → rehydration.js} +0 -0
  62. /package/dist/src/core/digest/{enhanced-hybrid-digest.js → hybrid-digest.js} +0 -0
  63. /package/dist/src/core/session/{enhanced-handoff.js → handoff.js} +0 -0
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Cord Trace Hook (PostToolUse)
5
+ *
6
+ * Fires on every cord_* MCP tool call, logging metrics to a JSONL trace file
7
+ * and accumulating session metrics in a per-session state file.
8
+ *
9
+ * Trace dir: ~/.stackmemory/cord-traces/
10
+ * cord-trace-{date}.jsonl — append-only trace log
11
+ * cord-state-{session_id}.json — running session metrics
12
+ *
13
+ * Must complete in <50ms — pure file I/O only.
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ const HOME = process.env.HOME || '/tmp';
20
+ const TRACE_DIR = path.join(HOME, '.stackmemory', 'cord-traces');
21
+ const CORD_TOOLS = new Set([
22
+ 'cord_spawn',
23
+ 'cord_fork',
24
+ 'cord_complete',
25
+ 'cord_ask',
26
+ 'cord_tree',
27
+ ]);
28
+
29
+ function ensureDir(dir) {
30
+ if (!fs.existsSync(dir)) {
31
+ fs.mkdirSync(dir, { recursive: true });
32
+ }
33
+ }
34
+
35
+ function safeWriteFile(filePath, content) {
36
+ const tmp = filePath + '.tmp';
37
+ fs.writeFileSync(tmp, content);
38
+ fs.renameSync(tmp, filePath);
39
+ }
40
+
41
+ /**
42
+ * Extract the cord tool name from the full MCP tool name.
43
+ * e.g. "mcp__stackmemory-refactored__cord_spawn" -> "cord_spawn"
44
+ */
45
+ function extractCordTool(toolName) {
46
+ if (!toolName) return null;
47
+ for (const t of CORD_TOOLS) {
48
+ if (toolName.endsWith(t)) return t;
49
+ }
50
+ return null;
51
+ }
52
+
53
+ function loadState(sessionId) {
54
+ const stateFile = path.join(TRACE_DIR, `cord-state-${sessionId}.json`);
55
+ try {
56
+ if (fs.existsSync(stateFile)) {
57
+ return JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
58
+ }
59
+ } catch {
60
+ // Corrupted state, start fresh
61
+ }
62
+ return {
63
+ session_id: sessionId,
64
+ started: new Date().toISOString(),
65
+ tool_counts: {},
66
+ task_ids: [],
67
+ context_modes: {},
68
+ unblocked_total: 0,
69
+ max_depth: 0,
70
+ completion_rate: 0,
71
+ ask_count: 0,
72
+ completed_count: 0,
73
+ spawn_fork_count: 0,
74
+ };
75
+ }
76
+
77
+ function saveState(sessionId, state) {
78
+ const stateFile = path.join(TRACE_DIR, `cord-state-${sessionId}.json`);
79
+ safeWriteFile(stateFile, JSON.stringify(state, null, 2));
80
+ }
81
+
82
+ function appendTrace(entry) {
83
+ const date = new Date().toISOString().slice(0, 10);
84
+ const traceFile = path.join(TRACE_DIR, `cord-trace-${date}.jsonl`);
85
+ fs.appendFileSync(traceFile, JSON.stringify(entry) + '\n');
86
+ }
87
+
88
+ /**
89
+ * Safely extract metadata from tool_response.
90
+ * MCP tool responses come as { content: [{ type, text }], metadata: {...} }.
91
+ * The hook receives tool_response which may be the raw MCP result or a string.
92
+ */
93
+ function extractMetadata(toolResponse) {
94
+ if (!toolResponse) return {};
95
+ if (typeof toolResponse === 'string') {
96
+ try {
97
+ const parsed = JSON.parse(toolResponse);
98
+ return parsed.metadata || parsed;
99
+ } catch {
100
+ return {};
101
+ }
102
+ }
103
+ if (toolResponse.metadata) return toolResponse.metadata;
104
+ return toolResponse;
105
+ }
106
+
107
+ function updateState(state, cordTool, toolInput, toolResponse) {
108
+ // Increment tool count
109
+ state.tool_counts[cordTool] = (state.tool_counts[cordTool] || 0) + 1;
110
+
111
+ const meta = extractMetadata(toolResponse);
112
+
113
+ switch (cordTool) {
114
+ case 'cord_spawn':
115
+ case 'cord_fork': {
116
+ state.spawn_fork_count++;
117
+
118
+ // Track context mode
119
+ const mode = meta.context_mode || cordTool.replace('cord_', '');
120
+ state.context_modes[mode] = (state.context_modes[mode] || 0) + 1;
121
+
122
+ // Track task ID
123
+ if (meta.task_id && !state.task_ids.includes(meta.task_id)) {
124
+ state.task_ids.push(meta.task_id);
125
+ }
126
+
127
+ // Track max depth
128
+ const depth = meta.depth ?? 0;
129
+ if (depth > state.max_depth) {
130
+ state.max_depth = depth;
131
+ }
132
+ break;
133
+ }
134
+
135
+ case 'cord_complete': {
136
+ state.completed_count++;
137
+
138
+ // Count unblocked tasks
139
+ const unblocked = meta.unblocked;
140
+ if (Array.isArray(unblocked)) {
141
+ state.unblocked_total += unblocked.length;
142
+ }
143
+
144
+ // Update completion rate
145
+ if (state.spawn_fork_count > 0) {
146
+ state.completion_rate = +(
147
+ state.completed_count / state.spawn_fork_count
148
+ ).toFixed(3);
149
+ }
150
+ break;
151
+ }
152
+
153
+ case 'cord_ask': {
154
+ state.ask_count++;
155
+
156
+ // Track task ID
157
+ if (meta.task_id && !state.task_ids.includes(meta.task_id)) {
158
+ state.task_ids.push(meta.task_id);
159
+ }
160
+
161
+ // Track context mode
162
+ state.context_modes['ask'] = (state.context_modes['ask'] || 0) + 1;
163
+ break;
164
+ }
165
+
166
+ case 'cord_tree':
167
+ // Read-only — no state change
168
+ break;
169
+ }
170
+ }
171
+
172
+ function buildMetricsSummary(state) {
173
+ return {
174
+ task_count: state.task_ids.length,
175
+ active: state.spawn_fork_count - state.completed_count,
176
+ completed: state.completed_count,
177
+ asks: state.ask_count,
178
+ };
179
+ }
180
+
181
+ async function readInput() {
182
+ let input = '';
183
+ for await (const chunk of process.stdin) {
184
+ input += chunk;
185
+ }
186
+ return JSON.parse(input);
187
+ }
188
+
189
+ async function main() {
190
+ try {
191
+ const input = await readInput();
192
+ const { tool_name, tool_input, tool_response, session_id } = input;
193
+
194
+ const cordTool = extractCordTool(tool_name);
195
+ if (!cordTool) return; // Not a cord tool, shouldn't happen given matcher
196
+
197
+ const sid =
198
+ session_id || process.env.CLAUDE_INSTANCE_ID || String(process.ppid);
199
+
200
+ ensureDir(TRACE_DIR);
201
+
202
+ // Load and update state
203
+ const state = loadState(sid);
204
+ updateState(state, cordTool, tool_input, tool_response);
205
+
206
+ // Build trace entry
207
+ const entry = {
208
+ ts: new Date().toISOString(),
209
+ session_id: sid,
210
+ cwd: input.cwd || process.cwd(),
211
+ tool: cordTool,
212
+ input: tool_input || {},
213
+ response: extractMetadata(tool_response),
214
+ metrics: buildMetricsSummary(state),
215
+ };
216
+
217
+ // Append trace + save state
218
+ appendTrace(entry);
219
+ saveState(sid, state);
220
+ } catch {
221
+ // Silent fail — never block the agent
222
+ }
223
+ }
224
+
225
+ main();
@@ -1,114 +0,0 @@
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 { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
6
- import { join } from "path";
7
- import { homedir } from "os";
8
- const DEFAULT_STORAGE_CONFIG = {
9
- mode: "sqlite",
10
- chromadb: {
11
- enabled: false
12
- }
13
- };
14
- const STACKMEMORY_DIR = join(homedir(), ".stackmemory");
15
- const CONFIG_FILE = join(STACKMEMORY_DIR, "storage-config.json");
16
- function loadStorageConfig() {
17
- try {
18
- if (existsSync(CONFIG_FILE)) {
19
- const content = readFileSync(CONFIG_FILE, "utf-8");
20
- const config = JSON.parse(content);
21
- return {
22
- mode: config.mode || DEFAULT_STORAGE_CONFIG.mode,
23
- chromadb: {
24
- ...DEFAULT_STORAGE_CONFIG.chromadb,
25
- ...config.chromadb
26
- }
27
- };
28
- }
29
- } catch (error) {
30
- console.warn("Failed to load storage config, using defaults:", error);
31
- }
32
- return DEFAULT_STORAGE_CONFIG;
33
- }
34
- function saveStorageConfig(config) {
35
- try {
36
- if (!existsSync(STACKMEMORY_DIR)) {
37
- mkdirSync(STACKMEMORY_DIR, { recursive: true });
38
- }
39
- writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
40
- } catch (error) {
41
- console.error("Failed to save storage config:", error);
42
- throw error;
43
- }
44
- }
45
- function isChromaDBEnabled() {
46
- const config = loadStorageConfig();
47
- if (!config.chromadb.enabled) {
48
- return false;
49
- }
50
- const apiKey = config.chromadb.apiKey || process.env["CHROMADB_API_KEY"];
51
- if (!apiKey) {
52
- return false;
53
- }
54
- return true;
55
- }
56
- function getStorageMode() {
57
- const config = loadStorageConfig();
58
- if (config.mode === "hybrid" && !isChromaDBEnabled()) {
59
- return "sqlite";
60
- }
61
- return config.mode;
62
- }
63
- function getChromaDBConfig() {
64
- if (!isChromaDBEnabled()) {
65
- return null;
66
- }
67
- const config = loadStorageConfig();
68
- const apiKey = config.chromadb.apiKey || process.env["CHROMADB_API_KEY"];
69
- const apiUrl = config.chromadb.apiUrl || process.env["CHROMADB_API_URL"] || "https://api.trychroma.com";
70
- return {
71
- enabled: true,
72
- apiKey,
73
- apiUrl,
74
- tenant: config.chromadb.tenant || process.env["CHROMADB_TENANT"] || "default_tenant",
75
- database: config.chromadb.database || process.env["CHROMADB_DATABASE"] || "default_database"
76
- };
77
- }
78
- function enableChromaDB(chromaConfig) {
79
- const config = loadStorageConfig();
80
- config.mode = "hybrid";
81
- config.chromadb = {
82
- enabled: true,
83
- apiKey: chromaConfig.apiKey,
84
- apiUrl: chromaConfig.apiUrl || "https://api.trychroma.com",
85
- tenant: chromaConfig.tenant || "default_tenant",
86
- database: chromaConfig.database || "default_database"
87
- };
88
- saveStorageConfig(config);
89
- }
90
- function disableChromaDB() {
91
- const config = loadStorageConfig();
92
- config.mode = "sqlite";
93
- config.chromadb = {
94
- enabled: false
95
- };
96
- saveStorageConfig(config);
97
- }
98
- function getStorageModeDescription() {
99
- const mode = getStorageMode();
100
- if (mode === "hybrid") {
101
- return "Hybrid (SQLite + ChromaDB for semantic search and cloud backup)";
102
- }
103
- return "SQLite (local storage only, fast, no external dependencies)";
104
- }
105
- export {
106
- disableChromaDB,
107
- enableChromaDB,
108
- getChromaDBConfig,
109
- getStorageMode,
110
- getStorageModeDescription,
111
- isChromaDBEnabled,
112
- loadStorageConfig,
113
- saveStorageConfig
114
- };
@@ -1,379 +0,0 @@
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 { v4 as uuidv4 } from "uuid";
6
- import { Logger } from "../monitoring/logger.js";
7
- let chromadbModule = null;
8
- async function getChromaDB() {
9
- if (!chromadbModule) {
10
- try {
11
- chromadbModule = await import("chromadb");
12
- } catch {
13
- throw new Error(
14
- "chromadb is not installed. Install it with: npm install chromadb"
15
- );
16
- }
17
- }
18
- return chromadbModule;
19
- }
20
- class ChromaDBAdapter {
21
- client = null;
22
- collection = null;
23
- logger;
24
- config;
25
- userId;
26
- teamId;
27
- constructor(config, userId, teamId) {
28
- this.config = config;
29
- this.userId = userId;
30
- this.teamId = teamId;
31
- this.logger = new Logger("ChromaDBAdapter");
32
- }
33
- /**
34
- * Factory method to create and initialize the adapter
35
- */
36
- static async create(config, userId, teamId) {
37
- const adapter = new ChromaDBAdapter(config, userId, teamId);
38
- await adapter.initClient();
39
- return adapter;
40
- }
41
- async initClient() {
42
- const chromadb = await getChromaDB();
43
- this.client = new chromadb.CloudClient({
44
- apiKey: this.config.apiKey,
45
- tenant: this.config.tenant,
46
- database: this.config.database
47
- });
48
- }
49
- async initialize() {
50
- try {
51
- if (!this.client) {
52
- await this.initClient();
53
- }
54
- const collectionName = this.config.collectionName || "stackmemory_contexts";
55
- this.collection = await this.client.getOrCreateCollection({
56
- name: collectionName,
57
- metadata: {
58
- description: "StackMemory context storage",
59
- version: "1.0.0",
60
- created_at: (/* @__PURE__ */ new Date()).toISOString()
61
- }
62
- });
63
- this.logger.info(`ChromaDB collection '${collectionName}' initialized`);
64
- } catch (error) {
65
- this.logger.error("Failed to initialize ChromaDB collection", error);
66
- throw error;
67
- }
68
- }
69
- /**
70
- * Store a frame in ChromaDB
71
- */
72
- async storeFrame(frame) {
73
- if (!this.collection) {
74
- throw new Error("ChromaDB not initialized");
75
- }
76
- try {
77
- const frameMetadata = {
78
- user_id: this.userId,
79
- frame_id: frame.frameId,
80
- session_id: frame.sessionId || "unknown",
81
- project_name: frame.projectName || "default",
82
- timestamp: frame.timestamp,
83
- type: "frame",
84
- score: frame.score,
85
- tags: frame.tags || []
86
- };
87
- if (this.teamId) {
88
- frameMetadata.team_id = this.teamId;
89
- }
90
- const document = {
91
- id: `frame_${frame.frameId}_${this.userId}`,
92
- document: this.frameToDocument(frame),
93
- metadata: frameMetadata
94
- };
95
- await this.collection.add({
96
- ids: [document.id],
97
- documents: [document.document],
98
- metadatas: [document.metadata]
99
- });
100
- this.logger.debug(
101
- `Stored frame ${frame.frameId} for user ${this.userId}`
102
- );
103
- } catch (error) {
104
- this.logger.error(`Failed to store frame ${frame.frameId}`, error);
105
- throw error;
106
- }
107
- }
108
- /**
109
- * Store a decision or observation
110
- */
111
- async storeContext(type, content, metadata) {
112
- if (!this.collection) {
113
- throw new Error("ChromaDB not initialized");
114
- }
115
- try {
116
- const contextId = `${type}_${uuidv4()}_${this.userId}`;
117
- const documentMetadata = {
118
- user_id: this.userId,
119
- frame_id: metadata?.frame_id || "none",
120
- session_id: metadata?.session_id || "unknown",
121
- project_name: metadata?.project_name || "default",
122
- timestamp: Date.now(),
123
- type,
124
- ...metadata
125
- };
126
- if (this.teamId) {
127
- documentMetadata.team_id = this.teamId;
128
- }
129
- const document = {
130
- id: contextId,
131
- document: content,
132
- metadata: documentMetadata
133
- };
134
- await this.collection.add({
135
- ids: [document.id],
136
- documents: [document.document],
137
- metadatas: [document.metadata]
138
- });
139
- this.logger.debug(`Stored ${type} for user ${this.userId}`);
140
- } catch (error) {
141
- this.logger.error(`Failed to store ${type}`, error);
142
- throw error;
143
- }
144
- }
145
- /**
146
- * Query contexts by semantic similarity
147
- */
148
- async queryContexts(query, limit = 10, filters) {
149
- if (!this.collection) {
150
- throw new Error("ChromaDB not initialized");
151
- }
152
- try {
153
- const whereClause = {
154
- user_id: this.userId
155
- };
156
- if (this.teamId) {
157
- whereClause["$or"] = [
158
- { team_id: this.teamId },
159
- { user_id: this.userId }
160
- ];
161
- }
162
- if (filters?.type && filters.type.length > 0) {
163
- whereClause.type = { $in: filters.type };
164
- }
165
- if (filters?.projectName) {
166
- whereClause.project_name = filters.projectName;
167
- }
168
- if (filters?.sessionId) {
169
- whereClause.session_id = filters.sessionId;
170
- }
171
- if (filters?.startTime || filters?.endTime) {
172
- whereClause.timestamp = {};
173
- if (filters.startTime) {
174
- whereClause.timestamp.$gte = filters.startTime;
175
- }
176
- if (filters.endTime) {
177
- whereClause.timestamp.$lte = filters.endTime;
178
- }
179
- }
180
- const results = await this.collection.query({
181
- queryTexts: [query],
182
- nResults: limit,
183
- where: whereClause,
184
- include: ["documents", "metadatas", "distances"]
185
- });
186
- const contexts = [];
187
- if (results.documents && results.documents[0]) {
188
- for (let i = 0; i < results.documents[0].length; i++) {
189
- contexts.push({
190
- content: results.documents[0][i] || "",
191
- metadata: results.metadatas?.[0]?.[i] || {},
192
- distance: results.distances?.[0]?.[i] || 0
193
- });
194
- }
195
- }
196
- this.logger.debug(`Found ${contexts.length} contexts for query`);
197
- return contexts;
198
- } catch (error) {
199
- this.logger.error("Failed to query contexts", error);
200
- throw error;
201
- }
202
- }
203
- /**
204
- * Get user's recent contexts
205
- */
206
- async getRecentContexts(limit = 20, type) {
207
- if (!this.collection) {
208
- throw new Error("ChromaDB not initialized");
209
- }
210
- try {
211
- const whereClause = {
212
- user_id: this.userId
213
- };
214
- if (type) {
215
- whereClause.type = type;
216
- }
217
- const results = await this.collection.get({
218
- where: whereClause,
219
- include: ["documents", "metadatas"]
220
- });
221
- const contexts = [];
222
- if (results.documents) {
223
- const indexed = results.documents.map((doc, i) => ({
224
- content: doc || "",
225
- metadata: results.metadatas?.[i] || {}
226
- }));
227
- indexed.sort(
228
- (a, b) => (b.metadata.timestamp || 0) - (a.metadata.timestamp || 0)
229
- );
230
- contexts.push(...indexed.slice(0, limit));
231
- }
232
- return contexts;
233
- } catch (error) {
234
- this.logger.error("Failed to get recent contexts", error);
235
- throw error;
236
- }
237
- }
238
- /**
239
- * Delete old contexts (retention policy)
240
- */
241
- async deleteOldContexts(olderThanDays = 30) {
242
- if (!this.collection) {
243
- throw new Error("ChromaDB not initialized");
244
- }
245
- try {
246
- const cutoffTime = Date.now() - olderThanDays * 24 * 60 * 60 * 1e3;
247
- const results = await this.collection.get({
248
- where: {
249
- user_id: this.userId,
250
- timestamp: { $lt: cutoffTime }
251
- },
252
- include: ["ids"]
253
- });
254
- if (!results.ids || results.ids.length === 0) {
255
- return 0;
256
- }
257
- await this.collection.delete({
258
- ids: results.ids
259
- });
260
- this.logger.info(`Deleted ${results.ids.length} old contexts`);
261
- return results.ids.length;
262
- } catch (error) {
263
- this.logger.error("Failed to delete old contexts", error);
264
- throw error;
265
- }
266
- }
267
- /**
268
- * Get team contexts (if user is part of a team)
269
- */
270
- async getTeamContexts(limit = 20) {
271
- if (!this.collection || !this.teamId) {
272
- return [];
273
- }
274
- try {
275
- const results = await this.collection.get({
276
- where: {
277
- team_id: this.teamId
278
- },
279
- include: ["documents", "metadatas"],
280
- limit
281
- });
282
- const contexts = [];
283
- if (results.documents) {
284
- for (let i = 0; i < results.documents.length; i++) {
285
- contexts.push({
286
- content: results.documents[i] || "",
287
- metadata: results.metadatas?.[i] || {}
288
- });
289
- }
290
- }
291
- return contexts;
292
- } catch (error) {
293
- this.logger.error("Failed to get team contexts", error);
294
- return [];
295
- }
296
- }
297
- /**
298
- * Convert frame to searchable document
299
- */
300
- frameToDocument(frame) {
301
- const parts = [
302
- `Frame: ${frame.title}`,
303
- `Type: ${frame.type}`,
304
- `Status: ${frame.status}`
305
- ];
306
- if (frame.description) {
307
- parts.push(`Description: ${frame.description}`);
308
- }
309
- if (frame.inputs && frame.inputs.length > 0) {
310
- parts.push(`Inputs: ${frame.inputs.join(", ")}`);
311
- }
312
- if (frame.outputs && frame.outputs.length > 0) {
313
- parts.push(`Outputs: ${frame.outputs.join(", ")}`);
314
- }
315
- if (frame.tags && frame.tags.length > 0) {
316
- parts.push(`Tags: ${frame.tags.join(", ")}`);
317
- }
318
- if (frame.digest_json) {
319
- try {
320
- const digest = JSON.parse(frame.digest_json);
321
- if (digest.summary) {
322
- parts.push(`Summary: ${digest.summary}`);
323
- }
324
- if (digest.keyDecisions) {
325
- parts.push(`Decisions: ${digest.keyDecisions.join(". ")}`);
326
- }
327
- } catch {
328
- }
329
- }
330
- return parts.join("\n");
331
- }
332
- /**
333
- * Update team ID for a user
334
- */
335
- async updateTeamId(newTeamId) {
336
- this.teamId = newTeamId;
337
- this.logger.info(`Updated team ID to ${newTeamId} for user ${this.userId}`);
338
- }
339
- /**
340
- * Get storage statistics
341
- */
342
- async getStats() {
343
- if (!this.collection) {
344
- throw new Error("ChromaDB not initialized");
345
- }
346
- try {
347
- const userResults = await this.collection.get({
348
- where: { user_id: this.userId },
349
- include: ["metadatas"]
350
- });
351
- const stats = {
352
- totalDocuments: 0,
353
- userDocuments: userResults.ids?.length || 0,
354
- documentsByType: {}
355
- };
356
- if (userResults.metadatas) {
357
- for (const metadata of userResults.metadatas) {
358
- const type = metadata?.type || "unknown";
359
- stats.documentsByType[type] = (stats.documentsByType[type] || 0) + 1;
360
- }
361
- }
362
- if (this.teamId) {
363
- const teamResults = await this.collection.get({
364
- where: { team_id: this.teamId },
365
- include: ["ids"]
366
- });
367
- stats.teamDocuments = teamResults.ids?.length || 0;
368
- }
369
- stats.totalDocuments = stats.userDocuments + (stats.teamDocuments || 0);
370
- return stats;
371
- } catch (error) {
372
- this.logger.error("Failed to get stats", error);
373
- throw error;
374
- }
375
- }
376
- }
377
- export {
378
- ChromaDBAdapter
379
- };