@stackmemoryai/stackmemory 1.2.1 → 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.
- package/dist/src/cli/codex-sm.js +6 -8
- package/dist/src/cli/commands/config.js +0 -81
- package/dist/src/cli/commands/context-rehydrate.js +133 -47
- package/dist/src/cli/commands/db.js +35 -8
- package/dist/src/cli/commands/handoff.js +1 -1
- package/dist/src/cli/commands/linear.js +9 -0
- package/dist/src/cli/commands/ralph.js +2 -2
- package/dist/src/cli/commands/setup.js +2 -2
- package/dist/src/cli/commands/signup.js +3 -1
- package/dist/src/cli/commands/skills.js +123 -1
- package/dist/src/cli/commands/storage-tier.js +26 -8
- package/dist/src/cli/index.js +1 -57
- package/dist/src/core/config/feature-flags.js +0 -4
- package/dist/src/core/context/dual-stack-manager.js +10 -3
- package/dist/src/core/context/frame-database.js +32 -0
- package/dist/src/core/context/frame-handoff-manager.js +2 -2
- package/dist/src/core/context/{refactored-frame-manager.js → frame-manager.js} +3 -3
- package/dist/src/core/context/index.js +2 -2
- package/dist/src/core/database/sqlite-adapter.js +161 -1
- package/dist/src/core/digest/frame-digest-integration.js +1 -1
- package/dist/src/core/digest/index.js +1 -1
- package/dist/src/core/execution/parallel-executor.js +5 -1
- package/dist/src/core/projects/project-isolation.js +18 -4
- package/dist/src/core/security/index.js +2 -0
- package/dist/src/core/security/input-sanitizer.js +23 -0
- package/dist/src/core/utils/update-checker.js +10 -6
- package/dist/src/daemon/daemon-config.js +2 -1
- package/dist/src/daemon/services/auto-save-service.js +121 -0
- package/dist/src/daemon/services/maintenance-service.js +76 -1
- package/dist/src/features/sweep/prompt-builder.js +2 -2
- package/dist/src/hooks/graphiti-hooks.js +149 -0
- package/dist/src/hooks/session-summary.js +30 -0
- package/dist/src/integrations/graphiti/linear-graphiti-bridge.js +115 -0
- package/dist/src/integrations/greptile/client.js +101 -0
- package/dist/src/integrations/greptile/config.js +14 -0
- package/dist/src/integrations/greptile/index.js +11 -0
- package/dist/src/integrations/greptile/types.js +4 -0
- package/dist/src/integrations/linear/config.js +3 -1
- package/dist/src/integrations/linear/sync.js +18 -5
- package/dist/src/integrations/linear/webhook.js +16 -0
- package/dist/src/integrations/mcp/handlers/code-execution-handlers.js +33 -7
- package/dist/src/integrations/mcp/handlers/cord-handlers.js +397 -0
- package/dist/src/integrations/mcp/handlers/greptile-handlers.js +456 -0
- package/dist/src/integrations/mcp/handlers/index.js +55 -1
- package/dist/src/integrations/mcp/handlers/task-handlers.js +55 -12
- package/dist/src/integrations/mcp/handlers/team-handlers.js +211 -0
- package/dist/src/integrations/mcp/handlers/trace-handlers.js +25 -9
- package/dist/src/integrations/mcp/index.js +2 -2
- package/dist/src/integrations/mcp/refactored-server.js +31 -10
- package/dist/src/integrations/mcp/server.js +27 -0
- package/dist/src/integrations/mcp/tool-definitions.js +215 -1
- package/dist/src/integrations/ralph/context/context-budget-manager.js +10 -2
- package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +54 -22
- package/dist/src/integrations/ralph/learning/pattern-learner.js +59 -24
- package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +81 -35
- package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +12 -4
- package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +32 -9
- package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +25 -8
- package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +17 -5
- package/dist/src/integrations/ralph/visualization/ralph-debugger.js +73 -22
- package/dist/src/skills/claude-skills.js +46 -103
- package/dist/src/skills/parallel-agent-skill.js +514 -0
- package/dist/src/utils/hook-installer.js +8 -0
- package/package.json +5 -5
- package/scripts/gepa/.before-optimize.md +140 -159
- package/scripts/gepa/config.json +7 -1
- package/scripts/gepa/evals/fixtures/api-endpoint.ts +31 -0
- package/scripts/gepa/evals/fixtures/brittle-integration.ts +38 -0
- package/scripts/gepa/evals/fixtures/fts5-triggers.sql +23 -0
- package/scripts/gepa/evals/fixtures/leaky-service.ts +70 -0
- package/scripts/gepa/evals/fixtures/mcp-dispatch-stub.ts +39 -0
- package/scripts/gepa/evals/fixtures/pr-diff.txt +24 -0
- package/scripts/gepa/evals/fixtures/unsafe-webhook.ts +34 -0
- package/scripts/gepa/evals/fixtures/unwrapped-db-op.ts +42 -0
- package/scripts/gepa/evals/stackmemory-tasks.jsonl +8 -0
- package/scripts/gepa/generations/gen-000/baseline.md +172 -159
- package/scripts/gepa/generations/gen-001/baseline.md +172 -159
- package/scripts/gepa/generations/gen-001/variant-a.md +156 -146
- package/scripts/gepa/generations/gen-001/variant-b.md +199 -170
- package/scripts/gepa/generations/gen-001/variant-c.md +127 -46
- package/scripts/gepa/generations/gen-001/variant-d.md +160 -107
- package/scripts/gepa/hooks/reflect.js +44 -5
- package/scripts/gepa/optimize.js +281 -39
- package/scripts/gepa/results/eval-1-baseline.json +187 -10
- package/scripts/gepa/results/eval-1-variant-a.json +188 -11
- package/scripts/gepa/results/eval-1-variant-b.json +188 -11
- package/scripts/gepa/results/eval-1-variant-c.json +168 -11
- package/scripts/gepa/results/eval-1-variant-d.json +169 -12
- package/scripts/gepa/state.json +18 -18
- package/scripts/install-claude-hooks-auto.js +8 -0
- package/templates/claude-hooks/cord-trace.js +225 -0
- package/dist/src/core/config/storage-config.js +0 -114
- package/dist/src/core/storage/chromadb-adapter.js +0 -379
- package/dist/src/integrations/claude-code/enhanced-pre-clear-hooks.js +0 -458
- package/dist/src/integrations/ralph/coordination/enhanced-coordination.js +0 -409
- package/dist/src/skills/repo-ingestion-skill.js +0 -631
- package/templates/claude-hooks/chromadb-wrapper +0 -21
- /package/dist/src/core/context/{enhanced-rehydration.js → rehydration.js} +0 -0
- /package/dist/src/core/digest/{enhanced-hybrid-digest.js → hybrid-digest.js} +0 -0
- /package/dist/src/core/session/{enhanced-handoff.js → handoff.js} +0 -0
|
@@ -1,631 +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 { Logger } from "../core/monitoring/logger.js";
|
|
6
|
-
import {
|
|
7
|
-
isChromaDBEnabled,
|
|
8
|
-
getChromaDBConfig
|
|
9
|
-
} from "../core/config/storage-config.js";
|
|
10
|
-
import * as fs from "fs";
|
|
11
|
-
import * as path from "path";
|
|
12
|
-
import * as crypto from "crypto";
|
|
13
|
-
import { execSync } from "child_process";
|
|
14
|
-
import ignore from "ignore";
|
|
15
|
-
async function getChromaDBAdapter() {
|
|
16
|
-
try {
|
|
17
|
-
return await import("../core/storage/chromadb-adapter.js");
|
|
18
|
-
} catch {
|
|
19
|
-
throw new Error(
|
|
20
|
-
"chromadb is not installed. Install it with: npm install chromadb"
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
class RepoIngestionSkill {
|
|
25
|
-
constructor(config, userId, teamId) {
|
|
26
|
-
this.config = config;
|
|
27
|
-
this.userId = userId;
|
|
28
|
-
this.teamId = teamId;
|
|
29
|
-
this.logger = new Logger("RepoIngestionSkill");
|
|
30
|
-
this.chromaEnabled = isChromaDBEnabled();
|
|
31
|
-
if (this.chromaEnabled) {
|
|
32
|
-
const chromaConfig = getChromaDBConfig();
|
|
33
|
-
if (chromaConfig && chromaConfig.apiKey) {
|
|
34
|
-
this.adapterConfig = {
|
|
35
|
-
apiKey: config?.apiKey || chromaConfig.apiKey,
|
|
36
|
-
tenant: config?.tenant || chromaConfig.tenant || "default_tenant",
|
|
37
|
-
database: config?.database || chromaConfig.database || "default_database",
|
|
38
|
-
collectionName: config?.collectionName || "stackmemory_repos"
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
logger;
|
|
44
|
-
adapter = null;
|
|
45
|
-
metadataCache = /* @__PURE__ */ new Map();
|
|
46
|
-
fileHashCache = /* @__PURE__ */ new Map();
|
|
47
|
-
chromaEnabled = false;
|
|
48
|
-
adapterConfig = null;
|
|
49
|
-
/**
|
|
50
|
-
* Check if ChromaDB is available for use
|
|
51
|
-
*/
|
|
52
|
-
isAvailable() {
|
|
53
|
-
return this.chromaEnabled && this.adapter !== null;
|
|
54
|
-
}
|
|
55
|
-
async initialize() {
|
|
56
|
-
if (!this.chromaEnabled || !this.adapterConfig) {
|
|
57
|
-
this.logger.warn(
|
|
58
|
-
"ChromaDB not enabled. Repository ingestion features are unavailable."
|
|
59
|
-
);
|
|
60
|
-
this.logger.warn('Run "stackmemory init --chromadb" to enable ChromaDB.');
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
if (!this.adapter && this.adapterConfig) {
|
|
64
|
-
try {
|
|
65
|
-
const { ChromaDBAdapter } = await getChromaDBAdapter();
|
|
66
|
-
this.adapter = await ChromaDBAdapter.create(
|
|
67
|
-
this.adapterConfig,
|
|
68
|
-
this.userId,
|
|
69
|
-
this.teamId
|
|
70
|
-
);
|
|
71
|
-
} catch (error) {
|
|
72
|
-
this.logger.warn(
|
|
73
|
-
`Failed to initialize ChromaDB: ${error instanceof Error ? error.message : String(error)}`
|
|
74
|
-
);
|
|
75
|
-
this.logger.warn(
|
|
76
|
-
"chromadb is optional. Install it with: npm install chromadb"
|
|
77
|
-
);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
if (this.adapter) {
|
|
82
|
-
await this.adapter.initialize();
|
|
83
|
-
}
|
|
84
|
-
await this.loadMetadataCache();
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Ingest a repository into ChromaDB
|
|
88
|
-
*/
|
|
89
|
-
async ingestRepository(repoPath, repoName, options = {}) {
|
|
90
|
-
if (!this.isAvailable()) {
|
|
91
|
-
return {
|
|
92
|
-
success: false,
|
|
93
|
-
message: 'ChromaDB not enabled. Run "stackmemory init --chromadb" to enable semantic search features.'
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
const startTime = Date.now();
|
|
97
|
-
try {
|
|
98
|
-
this.logger.info(`Starting repository ingestion for ${repoName}`);
|
|
99
|
-
if (!fs.existsSync(repoPath)) {
|
|
100
|
-
throw new Error(`Repository path not found: ${repoPath}`);
|
|
101
|
-
}
|
|
102
|
-
const metadata = await this.getRepoMetadata(repoPath, repoName);
|
|
103
|
-
const existingMetadata = this.metadataCache.get(metadata.repoId);
|
|
104
|
-
if (options.incremental && existingMetadata && !options.forceUpdate) {
|
|
105
|
-
const changedFiles = await this.getChangedFiles(
|
|
106
|
-
repoPath,
|
|
107
|
-
existingMetadata.lastCommit,
|
|
108
|
-
metadata.lastCommit
|
|
109
|
-
);
|
|
110
|
-
if (changedFiles.length === 0) {
|
|
111
|
-
return {
|
|
112
|
-
success: true,
|
|
113
|
-
message: "No changes detected since last ingestion"
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
this.logger.info(
|
|
117
|
-
`Incremental update: ${changedFiles.length} files changed`
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
const files = await this.getRepoFiles(repoPath, options);
|
|
121
|
-
this.logger.info(`Found ${files.length} files to process`);
|
|
122
|
-
let filesProcessed = 0;
|
|
123
|
-
let chunksCreated = 0;
|
|
124
|
-
let totalSize = 0;
|
|
125
|
-
for (const file of files) {
|
|
126
|
-
try {
|
|
127
|
-
const chunks = await this.processFile(
|
|
128
|
-
file,
|
|
129
|
-
repoPath,
|
|
130
|
-
repoName,
|
|
131
|
-
metadata,
|
|
132
|
-
options
|
|
133
|
-
);
|
|
134
|
-
for (const chunk of chunks) {
|
|
135
|
-
await this.storeChunk(chunk, metadata);
|
|
136
|
-
chunksCreated++;
|
|
137
|
-
}
|
|
138
|
-
filesProcessed++;
|
|
139
|
-
totalSize += fs.statSync(file).size;
|
|
140
|
-
if (filesProcessed % 100 === 0) {
|
|
141
|
-
this.logger.info(
|
|
142
|
-
`Processed ${filesProcessed}/${files.length} files`
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
} catch (error) {
|
|
146
|
-
this.logger.warn(`Failed to process file ${file}:`, error);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
metadata.filesCount = filesProcessed;
|
|
150
|
-
metadata.totalSize = totalSize;
|
|
151
|
-
metadata.lastIngested = Date.now();
|
|
152
|
-
await this.saveMetadata(metadata);
|
|
153
|
-
const timeElapsed = Date.now() - startTime;
|
|
154
|
-
this.logger.info(
|
|
155
|
-
`Repository ingestion complete: ${filesProcessed} files, ${chunksCreated} chunks in ${timeElapsed}ms`
|
|
156
|
-
);
|
|
157
|
-
return {
|
|
158
|
-
success: true,
|
|
159
|
-
message: `Successfully ingested ${repoName}`,
|
|
160
|
-
stats: {
|
|
161
|
-
filesProcessed,
|
|
162
|
-
chunksCreated,
|
|
163
|
-
timeElapsed,
|
|
164
|
-
totalSize
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
} catch (error) {
|
|
168
|
-
this.logger.error("Repository ingestion failed:", error);
|
|
169
|
-
return {
|
|
170
|
-
success: false,
|
|
171
|
-
message: `Failed to ingest repository: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Update an existing repository in ChromaDB
|
|
177
|
-
*/
|
|
178
|
-
async updateRepository(repoPath, repoName, options = {}) {
|
|
179
|
-
const startTime = Date.now();
|
|
180
|
-
try {
|
|
181
|
-
const metadata = await this.getRepoMetadata(repoPath, repoName);
|
|
182
|
-
const existingMetadata = this.metadataCache.get(metadata.repoId);
|
|
183
|
-
if (!existingMetadata) {
|
|
184
|
-
return this.ingestRepository(repoPath, repoName, options);
|
|
185
|
-
}
|
|
186
|
-
const changedFiles = await this.getChangedFiles(
|
|
187
|
-
repoPath,
|
|
188
|
-
existingMetadata.lastCommit,
|
|
189
|
-
metadata.lastCommit
|
|
190
|
-
);
|
|
191
|
-
if (changedFiles.length === 0) {
|
|
192
|
-
return {
|
|
193
|
-
success: true,
|
|
194
|
-
message: "No changes detected",
|
|
195
|
-
stats: {
|
|
196
|
-
filesUpdated: 0,
|
|
197
|
-
filesAdded: 0,
|
|
198
|
-
filesRemoved: 0,
|
|
199
|
-
timeElapsed: Date.now() - startTime
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
let filesUpdated = 0;
|
|
204
|
-
let filesAdded = 0;
|
|
205
|
-
let filesRemoved = 0;
|
|
206
|
-
for (const change of changedFiles) {
|
|
207
|
-
const filePath = path.join(repoPath, change.path);
|
|
208
|
-
if (change.status === "deleted") {
|
|
209
|
-
await this.removeFileChunks(change.path, metadata.repoId);
|
|
210
|
-
filesRemoved++;
|
|
211
|
-
} else if (change.status === "added") {
|
|
212
|
-
const chunks = await this.processFile(
|
|
213
|
-
filePath,
|
|
214
|
-
repoPath,
|
|
215
|
-
repoName,
|
|
216
|
-
metadata,
|
|
217
|
-
options
|
|
218
|
-
);
|
|
219
|
-
for (const chunk of chunks) {
|
|
220
|
-
await this.storeChunk(chunk, metadata);
|
|
221
|
-
}
|
|
222
|
-
filesAdded++;
|
|
223
|
-
} else if (change.status === "modified") {
|
|
224
|
-
await this.removeFileChunks(change.path, metadata.repoId);
|
|
225
|
-
const chunks = await this.processFile(
|
|
226
|
-
filePath,
|
|
227
|
-
repoPath,
|
|
228
|
-
repoName,
|
|
229
|
-
metadata,
|
|
230
|
-
options
|
|
231
|
-
);
|
|
232
|
-
for (const chunk of chunks) {
|
|
233
|
-
await this.storeChunk(chunk, metadata);
|
|
234
|
-
}
|
|
235
|
-
filesUpdated++;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
metadata.lastIngested = Date.now();
|
|
239
|
-
await this.saveMetadata(metadata);
|
|
240
|
-
const timeElapsed = Date.now() - startTime;
|
|
241
|
-
return {
|
|
242
|
-
success: true,
|
|
243
|
-
message: `Successfully updated ${repoName}`,
|
|
244
|
-
stats: {
|
|
245
|
-
filesUpdated,
|
|
246
|
-
filesAdded,
|
|
247
|
-
filesRemoved,
|
|
248
|
-
timeElapsed
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
} catch (error) {
|
|
252
|
-
this.logger.error("Repository update failed:", error);
|
|
253
|
-
return {
|
|
254
|
-
success: false,
|
|
255
|
-
message: `Failed to update repository: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Search code in ingested repositories
|
|
261
|
-
*/
|
|
262
|
-
async searchCode(query, options) {
|
|
263
|
-
if (!this.isAvailable() || !this.adapter) {
|
|
264
|
-
this.logger.warn("ChromaDB not enabled. Code search unavailable.");
|
|
265
|
-
return [];
|
|
266
|
-
}
|
|
267
|
-
try {
|
|
268
|
-
const filters = {
|
|
269
|
-
type: ["code_chunk"]
|
|
270
|
-
};
|
|
271
|
-
if (options?.repoName) {
|
|
272
|
-
filters.repo_name = options.repoName;
|
|
273
|
-
}
|
|
274
|
-
if (options?.language) {
|
|
275
|
-
filters.language = options.language;
|
|
276
|
-
}
|
|
277
|
-
const results = await this.adapter.queryContexts(
|
|
278
|
-
query,
|
|
279
|
-
options?.limit || 20,
|
|
280
|
-
filters
|
|
281
|
-
);
|
|
282
|
-
return results.map((result) => ({
|
|
283
|
-
filePath: result.metadata.file_path,
|
|
284
|
-
content: result.content,
|
|
285
|
-
score: 1 - result.distance,
|
|
286
|
-
// Convert distance to similarity score
|
|
287
|
-
startLine: result.metadata.start_line,
|
|
288
|
-
endLine: result.metadata.end_line,
|
|
289
|
-
repoName: result.metadata.repo_name
|
|
290
|
-
}));
|
|
291
|
-
} catch (error) {
|
|
292
|
-
this.logger.error("Code search failed:", error);
|
|
293
|
-
return [];
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Get repository metadata
|
|
298
|
-
*/
|
|
299
|
-
async getRepoMetadata(repoPath, repoName) {
|
|
300
|
-
const branch = this.getCurrentBranch(repoPath);
|
|
301
|
-
const lastCommit = this.getLastCommit(repoPath);
|
|
302
|
-
const repoId = `${repoName}_${branch}`.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
303
|
-
const { language, framework } = await this.detectLanguageAndFramework(repoPath);
|
|
304
|
-
return {
|
|
305
|
-
repoId,
|
|
306
|
-
repoName,
|
|
307
|
-
branch,
|
|
308
|
-
lastCommit,
|
|
309
|
-
lastIngested: Date.now(),
|
|
310
|
-
filesCount: 0,
|
|
311
|
-
totalSize: 0,
|
|
312
|
-
language,
|
|
313
|
-
framework
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Get current git branch
|
|
318
|
-
*/
|
|
319
|
-
getCurrentBranch(repoPath) {
|
|
320
|
-
try {
|
|
321
|
-
return execSync("git rev-parse --abbrev-ref HEAD", {
|
|
322
|
-
cwd: repoPath,
|
|
323
|
-
encoding: "utf8"
|
|
324
|
-
}).trim();
|
|
325
|
-
} catch {
|
|
326
|
-
return "main";
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
/**
|
|
330
|
-
* Get last commit hash
|
|
331
|
-
*/
|
|
332
|
-
getLastCommit(repoPath) {
|
|
333
|
-
try {
|
|
334
|
-
return execSync("git rev-parse HEAD", {
|
|
335
|
-
cwd: repoPath,
|
|
336
|
-
encoding: "utf8"
|
|
337
|
-
}).trim();
|
|
338
|
-
} catch {
|
|
339
|
-
return "unknown";
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Get changed files between commits
|
|
344
|
-
*/
|
|
345
|
-
async getChangedFiles(repoPath, fromCommit, toCommit) {
|
|
346
|
-
try {
|
|
347
|
-
const diff = execSync(
|
|
348
|
-
`git diff --name-status ${fromCommit}..${toCommit}`,
|
|
349
|
-
{
|
|
350
|
-
cwd: repoPath,
|
|
351
|
-
encoding: "utf8"
|
|
352
|
-
}
|
|
353
|
-
);
|
|
354
|
-
return diff.split("\n").filter((line) => line.trim()).map((line) => {
|
|
355
|
-
const [status, ...pathParts] = line.split(" ");
|
|
356
|
-
return {
|
|
357
|
-
path: pathParts.join(" "),
|
|
358
|
-
status: status === "A" ? "added" : status === "D" ? "deleted" : "modified"
|
|
359
|
-
};
|
|
360
|
-
});
|
|
361
|
-
} catch {
|
|
362
|
-
return [];
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* Get repository files to process
|
|
367
|
-
*/
|
|
368
|
-
async getRepoFiles(repoPath, options) {
|
|
369
|
-
const files = [];
|
|
370
|
-
const ig = ignore();
|
|
371
|
-
const gitignorePath = path.join(repoPath, ".gitignore");
|
|
372
|
-
if (fs.existsSync(gitignorePath)) {
|
|
373
|
-
ig.add(fs.readFileSync(gitignorePath, "utf8"));
|
|
374
|
-
}
|
|
375
|
-
const defaultExcludes = [
|
|
376
|
-
"node_modules",
|
|
377
|
-
".git",
|
|
378
|
-
"dist",
|
|
379
|
-
"build",
|
|
380
|
-
"coverage",
|
|
381
|
-
".env",
|
|
382
|
-
"*.log",
|
|
383
|
-
...options.excludePatterns || []
|
|
384
|
-
];
|
|
385
|
-
ig.add(defaultExcludes);
|
|
386
|
-
const extensions = options.extensions || [
|
|
387
|
-
".ts",
|
|
388
|
-
".tsx",
|
|
389
|
-
".js",
|
|
390
|
-
".jsx",
|
|
391
|
-
".py",
|
|
392
|
-
".java",
|
|
393
|
-
".go",
|
|
394
|
-
".rs",
|
|
395
|
-
".c",
|
|
396
|
-
".cpp",
|
|
397
|
-
".h",
|
|
398
|
-
".hpp",
|
|
399
|
-
".cs",
|
|
400
|
-
".rb",
|
|
401
|
-
".php",
|
|
402
|
-
".swift",
|
|
403
|
-
".kt",
|
|
404
|
-
".scala",
|
|
405
|
-
".r",
|
|
406
|
-
".m",
|
|
407
|
-
".sql",
|
|
408
|
-
".yaml",
|
|
409
|
-
".yml",
|
|
410
|
-
".json"
|
|
411
|
-
];
|
|
412
|
-
if (options.includeDocs) {
|
|
413
|
-
extensions.push(".md", ".rst", ".txt");
|
|
414
|
-
}
|
|
415
|
-
const maxFileSize = options.maxFileSize || 1024 * 1024;
|
|
416
|
-
const walkDir = (dir, baseDir = repoPath) => {
|
|
417
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
418
|
-
for (const entry of entries) {
|
|
419
|
-
const fullPath = path.join(dir, entry.name);
|
|
420
|
-
const relativePath = path.relative(baseDir, fullPath);
|
|
421
|
-
if (ig.ignores(relativePath)) {
|
|
422
|
-
continue;
|
|
423
|
-
}
|
|
424
|
-
if (entry.isDirectory()) {
|
|
425
|
-
walkDir(fullPath, baseDir);
|
|
426
|
-
} else if (entry.isFile()) {
|
|
427
|
-
const ext = path.extname(entry.name);
|
|
428
|
-
if (!extensions.includes(ext)) {
|
|
429
|
-
continue;
|
|
430
|
-
}
|
|
431
|
-
if (!options.includeTests && (entry.name.includes(".test.") || entry.name.includes(".spec.") || relativePath.includes("__tests__") || relativePath.includes("test/") || relativePath.includes("tests/"))) {
|
|
432
|
-
continue;
|
|
433
|
-
}
|
|
434
|
-
const stats = fs.statSync(fullPath);
|
|
435
|
-
if (stats.size > maxFileSize) {
|
|
436
|
-
this.logger.debug(`Skipping large file: ${relativePath}`);
|
|
437
|
-
continue;
|
|
438
|
-
}
|
|
439
|
-
files.push(fullPath);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
walkDir(repoPath);
|
|
444
|
-
return files;
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Process a file into chunks
|
|
448
|
-
*/
|
|
449
|
-
async processFile(filePath, repoPath, repoName, metadata, options) {
|
|
450
|
-
const relativePath = path.relative(repoPath, filePath);
|
|
451
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
452
|
-
const lines = content.split("\n");
|
|
453
|
-
const language = this.detectFileLanguage(filePath);
|
|
454
|
-
const chunkSize = options.chunkSize || 100;
|
|
455
|
-
const chunks = [];
|
|
456
|
-
const fileHash = crypto.createHash("md5").update(content).digest("hex");
|
|
457
|
-
const cachedHash = this.fileHashCache.get(relativePath);
|
|
458
|
-
if (cachedHash === fileHash && !options.forceUpdate) {
|
|
459
|
-
return [];
|
|
460
|
-
}
|
|
461
|
-
this.fileHashCache.set(relativePath, fileHash);
|
|
462
|
-
for (let i = 0; i < lines.length; i += chunkSize) {
|
|
463
|
-
const chunkLines = lines.slice(i, Math.min(i + chunkSize, lines.length));
|
|
464
|
-
const chunkContent = chunkLines.join("\n");
|
|
465
|
-
if (chunkContent.trim().length === 0) {
|
|
466
|
-
continue;
|
|
467
|
-
}
|
|
468
|
-
const chunkId = `${metadata.repoId}_${relativePath}_${i}`;
|
|
469
|
-
const chunkHash = crypto.createHash("md5").update(chunkContent).digest("hex");
|
|
470
|
-
chunks.push({
|
|
471
|
-
id: chunkId,
|
|
472
|
-
filePath: relativePath,
|
|
473
|
-
content: chunkContent,
|
|
474
|
-
startLine: i + 1,
|
|
475
|
-
endLine: Math.min(i + chunkSize, lines.length),
|
|
476
|
-
hash: chunkHash,
|
|
477
|
-
language
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
return chunks;
|
|
481
|
-
}
|
|
482
|
-
/**
|
|
483
|
-
* Store a chunk in ChromaDB
|
|
484
|
-
*/
|
|
485
|
-
async storeChunk(chunk, metadata) {
|
|
486
|
-
if (!this.adapter) {
|
|
487
|
-
throw new Error("ChromaDB adapter not available");
|
|
488
|
-
}
|
|
489
|
-
const documentContent = `File: ${chunk.filePath} (Lines ${chunk.startLine}-${chunk.endLine})
|
|
490
|
-
Language: ${chunk.language}
|
|
491
|
-
Repository: ${metadata.repoName}/${metadata.branch}
|
|
492
|
-
|
|
493
|
-
${chunk.content}`;
|
|
494
|
-
if (!this.adapter) {
|
|
495
|
-
throw new Error("ChromaDB adapter not initialized");
|
|
496
|
-
}
|
|
497
|
-
await this.adapter.storeContext("observation", documentContent, {
|
|
498
|
-
type: "code_chunk",
|
|
499
|
-
repo_id: metadata.repoId,
|
|
500
|
-
repo_name: metadata.repoName,
|
|
501
|
-
branch: metadata.branch,
|
|
502
|
-
file_path: chunk.filePath,
|
|
503
|
-
start_line: chunk.startLine,
|
|
504
|
-
end_line: chunk.endLine,
|
|
505
|
-
language: chunk.language,
|
|
506
|
-
framework: metadata.framework,
|
|
507
|
-
chunk_hash: chunk.hash,
|
|
508
|
-
last_commit: metadata.lastCommit
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
/**
|
|
512
|
-
* Remove file chunks from ChromaDB
|
|
513
|
-
*/
|
|
514
|
-
async removeFileChunks(filePath, repoId) {
|
|
515
|
-
this.logger.debug(
|
|
516
|
-
`Would remove chunks for file: ${filePath} from repo: ${repoId}`
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
/**
|
|
520
|
-
* Detect file language
|
|
521
|
-
*/
|
|
522
|
-
detectFileLanguage(filePath) {
|
|
523
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
524
|
-
const languageMap = {
|
|
525
|
-
".ts": "typescript",
|
|
526
|
-
".tsx": "typescript",
|
|
527
|
-
".js": "javascript",
|
|
528
|
-
".jsx": "javascript",
|
|
529
|
-
".py": "python",
|
|
530
|
-
".java": "java",
|
|
531
|
-
".go": "go",
|
|
532
|
-
".rs": "rust",
|
|
533
|
-
".c": "c",
|
|
534
|
-
".cpp": "cpp",
|
|
535
|
-
".cs": "csharp",
|
|
536
|
-
".rb": "ruby",
|
|
537
|
-
".php": "php",
|
|
538
|
-
".swift": "swift",
|
|
539
|
-
".kt": "kotlin",
|
|
540
|
-
".scala": "scala",
|
|
541
|
-
".r": "r",
|
|
542
|
-
".sql": "sql",
|
|
543
|
-
".yaml": "yaml",
|
|
544
|
-
".yml": "yaml",
|
|
545
|
-
".json": "json",
|
|
546
|
-
".md": "markdown"
|
|
547
|
-
};
|
|
548
|
-
return languageMap[ext] || "unknown";
|
|
549
|
-
}
|
|
550
|
-
/**
|
|
551
|
-
* Detect language and framework
|
|
552
|
-
*/
|
|
553
|
-
async detectLanguageAndFramework(repoPath) {
|
|
554
|
-
const packageJsonPath = path.join(repoPath, "package.json");
|
|
555
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
556
|
-
try {
|
|
557
|
-
const packageJson = JSON.parse(
|
|
558
|
-
fs.readFileSync(packageJsonPath, "utf8")
|
|
559
|
-
);
|
|
560
|
-
const deps = {
|
|
561
|
-
...packageJson.dependencies,
|
|
562
|
-
...packageJson.devDependencies
|
|
563
|
-
};
|
|
564
|
-
let framework;
|
|
565
|
-
if (deps.react) framework = "react";
|
|
566
|
-
else if (deps.vue) framework = "vue";
|
|
567
|
-
else if (deps.angular) framework = "angular";
|
|
568
|
-
else if (deps.express) framework = "express";
|
|
569
|
-
else if (deps.next) framework = "nextjs";
|
|
570
|
-
else if (deps.svelte) framework = "svelte";
|
|
571
|
-
return {
|
|
572
|
-
language: deps.typescript ? "typescript" : "javascript",
|
|
573
|
-
framework
|
|
574
|
-
};
|
|
575
|
-
} catch {
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
if (fs.existsSync(path.join(repoPath, "requirements.txt")) || fs.existsSync(path.join(repoPath, "setup.py"))) {
|
|
579
|
-
return { language: "python" };
|
|
580
|
-
}
|
|
581
|
-
if (fs.existsSync(path.join(repoPath, "go.mod"))) {
|
|
582
|
-
return { language: "go" };
|
|
583
|
-
}
|
|
584
|
-
if (fs.existsSync(path.join(repoPath, "Cargo.toml"))) {
|
|
585
|
-
return { language: "rust" };
|
|
586
|
-
}
|
|
587
|
-
if (fs.existsSync(path.join(repoPath, "pom.xml")) || fs.existsSync(path.join(repoPath, "build.gradle"))) {
|
|
588
|
-
return { language: "java" };
|
|
589
|
-
}
|
|
590
|
-
return { language: "unknown" };
|
|
591
|
-
}
|
|
592
|
-
/**
|
|
593
|
-
* Load metadata cache
|
|
594
|
-
*/
|
|
595
|
-
async loadMetadataCache() {
|
|
596
|
-
this.metadataCache.clear();
|
|
597
|
-
}
|
|
598
|
-
/**
|
|
599
|
-
* Save metadata
|
|
600
|
-
*/
|
|
601
|
-
async saveMetadata(metadata) {
|
|
602
|
-
this.metadataCache.set(metadata.repoId, metadata);
|
|
603
|
-
}
|
|
604
|
-
/**
|
|
605
|
-
* Get repository statistics
|
|
606
|
-
*/
|
|
607
|
-
async getRepoStats(repoName) {
|
|
608
|
-
const stats = {
|
|
609
|
-
totalRepos: this.metadataCache.size,
|
|
610
|
-
totalFiles: 0,
|
|
611
|
-
totalChunks: 0,
|
|
612
|
-
languages: {},
|
|
613
|
-
frameworks: {}
|
|
614
|
-
};
|
|
615
|
-
for (const metadata of this.metadataCache.values()) {
|
|
616
|
-
if (!repoName || metadata.repoName === repoName) {
|
|
617
|
-
stats.totalFiles += metadata.filesCount;
|
|
618
|
-
if (metadata.language) {
|
|
619
|
-
stats.languages[metadata.language] = (stats.languages[metadata.language] || 0) + 1;
|
|
620
|
-
}
|
|
621
|
-
if (metadata.framework) {
|
|
622
|
-
stats.frameworks[metadata.framework] = (stats.frameworks[metadata.framework] || 0) + 1;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
return stats;
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
export {
|
|
630
|
-
RepoIngestionSkill
|
|
631
|
-
};
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# ChromaDB Hook Wrapper - Routes Claude events to ChromaDB
|
|
3
|
-
|
|
4
|
-
HOOK_TYPE="$1"
|
|
5
|
-
HOOK_DATA="$2"
|
|
6
|
-
PROJECT_DIR="$(pwd)"
|
|
7
|
-
|
|
8
|
-
# Find StackMemory installation
|
|
9
|
-
if [ -d "$PROJECT_DIR/scripts" ] && [ -f "$PROJECT_DIR/scripts/claude-chromadb-hook.js" ]; then
|
|
10
|
-
HOOK_SCRIPT="$PROJECT_DIR/scripts/claude-chromadb-hook.js"
|
|
11
|
-
elif [ -f "$HOME/.stackmemory/bin/chromadb-hook.js" ]; then
|
|
12
|
-
HOOK_SCRIPT="$HOME/.stackmemory/bin/chromadb-hook.js"
|
|
13
|
-
else
|
|
14
|
-
# Skip if ChromaDB hook not found
|
|
15
|
-
exit 0
|
|
16
|
-
fi
|
|
17
|
-
|
|
18
|
-
# Execute ChromaDB hook in background to not block Claude
|
|
19
|
-
nohup node "$HOOK_SCRIPT" "$HOOK_TYPE" "$HOOK_DATA" >> "$HOME/.stackmemory/logs/chromadb-hook.log" 2>&1 &
|
|
20
|
-
|
|
21
|
-
exit 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|