@ulpi/codemap 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +73 -0
- package/README.md +386 -0
- package/dist/chunk-5ISZDDN7.js +80 -0
- package/dist/chunk-7SDODQZD.js +200 -0
- package/dist/chunk-SNG7R3UC.js +38 -0
- package/dist/chunk-WUVKW5JG.js +72 -0
- package/dist/index.js +4440 -0
- package/dist/ollama-3XCUZMZT-J6Z4WWWO.js +7 -0
- package/dist/openai-RC75RP4O-NSFZC5O6.js +8 -0
- package/dist/ulpi-KLKEAQC3-5ATUONU7.js +10 -0
- package/package.json +35 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4440 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "./chunk-5ISZDDN7.js";
|
|
3
|
+
import "./chunk-WUVKW5JG.js";
|
|
4
|
+
import "./chunk-7SDODQZD.js";
|
|
5
|
+
import "./chunk-SNG7R3UC.js";
|
|
6
|
+
|
|
7
|
+
// src/index.ts
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
|
|
10
|
+
// src/commands/search.ts
|
|
11
|
+
import chalk from "chalk";
|
|
12
|
+
|
|
13
|
+
// ../../packages/intelligence/codemap-engine/dist/index.js
|
|
14
|
+
import fs8 from "fs";
|
|
15
|
+
import nodePath from "path";
|
|
16
|
+
|
|
17
|
+
// ../../packages/foundation/contracts/dist/index.js
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
import { z as z2 } from "zod";
|
|
20
|
+
import { z as z3 } from "zod";
|
|
21
|
+
import { z as z4 } from "zod";
|
|
22
|
+
import { z as z5 } from "zod";
|
|
23
|
+
import { z as z6 } from "zod";
|
|
24
|
+
import { z as z7 } from "zod";
|
|
25
|
+
import { z as z9 } from "zod";
|
|
26
|
+
import { z as z8 } from "zod";
|
|
27
|
+
import { z as z10 } from "zod";
|
|
28
|
+
import { z as z11 } from "zod";
|
|
29
|
+
import { z as z12 } from "zod";
|
|
30
|
+
var EmbeddingProviderSchema = z.enum(["openai", "ollama", "ulpi"]);
|
|
31
|
+
var EmbeddingConfigSchema = z.object({
|
|
32
|
+
provider: EmbeddingProviderSchema.default("ulpi"),
|
|
33
|
+
model: z.string().max(100).default("code"),
|
|
34
|
+
dimensions: z.number().int().positive().max(4096).default(1024),
|
|
35
|
+
baseUrl: z.string().url().max(200).optional()
|
|
36
|
+
});
|
|
37
|
+
var CodemapChunkingStrategySchema = z2.enum(["sliding-window", "ast"]);
|
|
38
|
+
var CodemapChunkingConfigSchema = z2.object({
|
|
39
|
+
strategy: CodemapChunkingStrategySchema.default("ast"),
|
|
40
|
+
windowSize: z2.number().int().positive().max(500).default(60),
|
|
41
|
+
overlap: z2.number().int().nonnegative().max(100).default(10),
|
|
42
|
+
maxChunkSize: z2.number().int().positive().max(1e3).default(300),
|
|
43
|
+
minChunkSize: z2.number().int().positive().max(50).default(5),
|
|
44
|
+
contextLines: z2.number().int().nonnegative().max(20).default(3),
|
|
45
|
+
version: z2.number().int().positive().default(2)
|
|
46
|
+
});
|
|
47
|
+
var CodemapBatchConfigSchema = z2.object({
|
|
48
|
+
size: z2.number().int().positive().max(1e4).default(1e4),
|
|
49
|
+
debounceMs: z2.number().int().nonnegative().max(3e4).default(500),
|
|
50
|
+
timeoutMs: z2.number().int().nonnegative().max(36e5).default(6e5),
|
|
51
|
+
pollIntervalMs: z2.number().int().positive().max(6e4).default(2e3)
|
|
52
|
+
});
|
|
53
|
+
var CodemapHybridWeightsSchema = z2.object({
|
|
54
|
+
vector: z2.number().min(0).max(1).default(0.6),
|
|
55
|
+
bm25: z2.number().min(0).max(1).default(0.25),
|
|
56
|
+
symbolBoost: z2.number().min(0).max(1).default(0.1),
|
|
57
|
+
pathBoost: z2.number().min(0).max(1).default(0.05),
|
|
58
|
+
graphRank: z2.number().min(0).max(1).default(0)
|
|
59
|
+
});
|
|
60
|
+
var CodemapHybridConfigSchema = z2.object({
|
|
61
|
+
enabled: z2.boolean().default(true),
|
|
62
|
+
vectorK: z2.number().int().positive().max(200).default(30),
|
|
63
|
+
bm25K: z2.number().int().positive().max(200).default(30),
|
|
64
|
+
weights: CodemapHybridWeightsSchema.default({})
|
|
65
|
+
});
|
|
66
|
+
var CodemapConfigSchema = z2.object({
|
|
67
|
+
embedding: EmbeddingConfigSchema.default({}),
|
|
68
|
+
chunking: CodemapChunkingConfigSchema.default({}),
|
|
69
|
+
batch: CodemapBatchConfigSchema.default({}),
|
|
70
|
+
hybrid: CodemapHybridConfigSchema.default({}),
|
|
71
|
+
deny: z2.array(z2.string().max(500)).max(200).default(["**/.env*", "**/secrets/**", "**/certs/**"]),
|
|
72
|
+
maxFileSize: z2.number().int().positive().default(1048576),
|
|
73
|
+
searchTimeoutMs: z2.number().int().positive().default(15e3),
|
|
74
|
+
autoExport: z2.boolean().default(true),
|
|
75
|
+
autoImport: z2.boolean().default(true)
|
|
76
|
+
});
|
|
77
|
+
var CodemapSymbolTypeSchema = z2.enum([
|
|
78
|
+
"function",
|
|
79
|
+
"class",
|
|
80
|
+
"method",
|
|
81
|
+
"interface",
|
|
82
|
+
"type",
|
|
83
|
+
"const",
|
|
84
|
+
"variable",
|
|
85
|
+
"enum",
|
|
86
|
+
"unknown"
|
|
87
|
+
]);
|
|
88
|
+
var DepgraphPersonalizationConfigSchema = z3.object({
|
|
89
|
+
activeFileBoost: z3.number().positive().default(100),
|
|
90
|
+
mentionedFileBoost: z3.number().positive().default(5),
|
|
91
|
+
mentionedIdentifierBoost: z3.number().positive().default(10)
|
|
92
|
+
});
|
|
93
|
+
var DepgraphPagerankConfigSchema = z3.object({
|
|
94
|
+
dampingFactor: z3.number().min(0).max(1).default(0.85),
|
|
95
|
+
maxIterations: z3.number().int().positive().max(1e4).default(100),
|
|
96
|
+
convergenceThreshold: z3.number().positive().default(1e-6)
|
|
97
|
+
});
|
|
98
|
+
var DepgraphConfigSchema = z3.object({
|
|
99
|
+
enabled: z3.boolean().default(true),
|
|
100
|
+
personalization: DepgraphPersonalizationConfigSchema.default({}),
|
|
101
|
+
pagerank: DepgraphPagerankConfigSchema.default({}),
|
|
102
|
+
excludeUnranked: z3.boolean().default(false),
|
|
103
|
+
excludeUnrankedThreshold: z3.number().nonnegative().default(1e-4)
|
|
104
|
+
});
|
|
105
|
+
var MemoryTypeSchema = z4.enum([
|
|
106
|
+
"DECISION",
|
|
107
|
+
"PATTERN",
|
|
108
|
+
"BUG_ROOT_CAUSE",
|
|
109
|
+
"PREFERENCE",
|
|
110
|
+
"CONSTRAINT",
|
|
111
|
+
"CONTEXT",
|
|
112
|
+
"LESSON",
|
|
113
|
+
"RELATIONSHIP"
|
|
114
|
+
]);
|
|
115
|
+
var MemoryImportanceSchema = z4.enum([
|
|
116
|
+
"critical",
|
|
117
|
+
"high",
|
|
118
|
+
"medium",
|
|
119
|
+
"low"
|
|
120
|
+
]);
|
|
121
|
+
var CaptureModeSchema = z4.enum([
|
|
122
|
+
"continuous",
|
|
123
|
+
"end_of_session"
|
|
124
|
+
]);
|
|
125
|
+
var MemorySourceSchema = z4.object({
|
|
126
|
+
type: z4.enum(["explicit", "classifier", "import"]),
|
|
127
|
+
sessionId: z4.string().max(200).optional(),
|
|
128
|
+
author: z4.string().max(100).optional()
|
|
129
|
+
});
|
|
130
|
+
var MemoryEntrySchema = z4.object({
|
|
131
|
+
id: z4.string().max(64),
|
|
132
|
+
type: MemoryTypeSchema,
|
|
133
|
+
summary: z4.string().max(2e3),
|
|
134
|
+
detail: z4.string().max(1e4).nullable().default(null),
|
|
135
|
+
importance: MemoryImportanceSchema.default("medium"),
|
|
136
|
+
tags: z4.array(z4.string().max(50)).max(20).default([]),
|
|
137
|
+
relatedFiles: z4.array(z4.string().max(500)).max(50).default([]),
|
|
138
|
+
source: MemorySourceSchema,
|
|
139
|
+
createdAt: z4.string(),
|
|
140
|
+
updatedAt: z4.string(),
|
|
141
|
+
accessCount: z4.number().int().nonnegative().default(0),
|
|
142
|
+
lastAccessedAt: z4.string().optional(),
|
|
143
|
+
supersededBy: z4.string().max(64).optional()
|
|
144
|
+
});
|
|
145
|
+
var MemoryClassifierConfigSchema = z4.object({
|
|
146
|
+
enabled: z4.boolean().default(true),
|
|
147
|
+
model: z4.string().max(100).default("claude-haiku-4-5-20251001"),
|
|
148
|
+
windowSize: z4.number().int().positive().max(200).default(50),
|
|
149
|
+
windowOverlap: z4.number().int().nonnegative().max(50).default(10),
|
|
150
|
+
maxTranscriptChars: z4.number().int().positive().default(2097152),
|
|
151
|
+
timeout: z4.number().int().positive().default(12e4),
|
|
152
|
+
includeTranscript: z4.boolean().default(true),
|
|
153
|
+
maxSinglePromptEvents: z4.number().int().positive().max(500).default(100),
|
|
154
|
+
minSignificantEvents: z4.number().int().nonnegative().max(50).default(5),
|
|
155
|
+
filterNoiseEvents: z4.boolean().default(true)
|
|
156
|
+
});
|
|
157
|
+
var MemoryRetentionConfigSchema = z4.object({
|
|
158
|
+
maxMemories: z4.number().int().positive().max(1e4).default(5e3),
|
|
159
|
+
maxAgeDays: z4.number().int().positive().default(365),
|
|
160
|
+
deduplicationThreshold: z4.number().min(0).max(1).default(0.92)
|
|
161
|
+
});
|
|
162
|
+
var MemoryRankingConfigSchema = z4.object({
|
|
163
|
+
importanceWeights: z4.object({
|
|
164
|
+
critical: z4.number().min(0).max(1).default(1),
|
|
165
|
+
high: z4.number().min(0).max(1).default(0.8),
|
|
166
|
+
medium: z4.number().min(0).max(1).default(0.6),
|
|
167
|
+
low: z4.number().min(0).max(1).default(0.4)
|
|
168
|
+
}).default({}),
|
|
169
|
+
accessBoostPerHit: z4.number().min(0).max(0.1).default(0.05),
|
|
170
|
+
accessBoostCap: z4.number().min(0).max(1).default(0.2)
|
|
171
|
+
});
|
|
172
|
+
var MemoryPresetSchema = z4.enum(["minimal", "balanced", "thorough"]);
|
|
173
|
+
var MemoryConfigSchema = z4.object({
|
|
174
|
+
enabled: z4.boolean().default(true),
|
|
175
|
+
preset: MemoryPresetSchema.optional(),
|
|
176
|
+
captureMode: CaptureModeSchema.default("continuous"),
|
|
177
|
+
surfaceOnStart: z4.boolean().default(true),
|
|
178
|
+
surfaceLimit: z4.number().int().positive().max(20).default(5),
|
|
179
|
+
embedding: EmbeddingConfigSchema.default({ model: "memory" }),
|
|
180
|
+
classifier: MemoryClassifierConfigSchema.default({}),
|
|
181
|
+
retention: MemoryRetentionConfigSchema.default({}),
|
|
182
|
+
ranking: MemoryRankingConfigSchema.default({}),
|
|
183
|
+
redactPatterns: z4.array(z4.string().max(500)).max(50).default([]),
|
|
184
|
+
autoExport: z4.boolean().default(false),
|
|
185
|
+
reindexBatchSize: z4.number().int().positive().max(500).default(100)
|
|
186
|
+
});
|
|
187
|
+
var ClassificationResultSchema = z4.object({
|
|
188
|
+
memories: z4.array(z4.object({
|
|
189
|
+
type: MemoryTypeSchema,
|
|
190
|
+
summary: z4.string().max(2e3),
|
|
191
|
+
detail: z4.string().max(1e4).optional(),
|
|
192
|
+
importance: MemoryImportanceSchema.default("medium"),
|
|
193
|
+
tags: z4.array(z4.string().max(50)).max(20).default([]),
|
|
194
|
+
relatedFiles: z4.array(z4.string().max(500)).max(50).default([])
|
|
195
|
+
})).max(20)
|
|
196
|
+
});
|
|
197
|
+
var BlockTypeSchema = z5.enum([
|
|
198
|
+
"heading",
|
|
199
|
+
"paragraph",
|
|
200
|
+
"code",
|
|
201
|
+
"list-item",
|
|
202
|
+
"blockquote",
|
|
203
|
+
"table",
|
|
204
|
+
"hr"
|
|
205
|
+
]);
|
|
206
|
+
var BlockSchema = z5.object({
|
|
207
|
+
id: z5.string().max(200),
|
|
208
|
+
type: BlockTypeSchema,
|
|
209
|
+
content: z5.string().max(1e5),
|
|
210
|
+
level: z5.number().optional(),
|
|
211
|
+
language: z5.string().max(100).optional(),
|
|
212
|
+
checked: z5.boolean().optional(),
|
|
213
|
+
order: z5.number().int(),
|
|
214
|
+
startLine: z5.number().int()
|
|
215
|
+
});
|
|
216
|
+
var SectionStatusSchema = z5.enum([
|
|
217
|
+
"pending",
|
|
218
|
+
"in_progress",
|
|
219
|
+
"completed",
|
|
220
|
+
"skipped"
|
|
221
|
+
]);
|
|
222
|
+
var PlanSectionSchema = z5.object({
|
|
223
|
+
id: z5.string().max(200),
|
|
224
|
+
blockIds: z5.array(z5.string().max(200)),
|
|
225
|
+
title: z5.string().max(1e3),
|
|
226
|
+
level: z5.number().int().min(0).max(6),
|
|
227
|
+
path: z5.string().max(2e3),
|
|
228
|
+
status: SectionStatusSchema,
|
|
229
|
+
order: z5.number().int()
|
|
230
|
+
});
|
|
231
|
+
var PriorityLevelSchema = z5.enum([
|
|
232
|
+
"must-have",
|
|
233
|
+
"should-have",
|
|
234
|
+
"nice-to-have",
|
|
235
|
+
"out-of-scope"
|
|
236
|
+
]);
|
|
237
|
+
var SectionPrioritySchema = z5.object({
|
|
238
|
+
sectionId: z5.string().max(200),
|
|
239
|
+
priority: PriorityLevelSchema,
|
|
240
|
+
note: z5.string().max(5e3).optional()
|
|
241
|
+
});
|
|
242
|
+
var RiskLevelSchema = z5.enum(["low", "medium", "high"]);
|
|
243
|
+
var SectionRiskSchema = z5.object({
|
|
244
|
+
sectionId: z5.string().max(200),
|
|
245
|
+
level: RiskLevelSchema,
|
|
246
|
+
description: z5.string().max(1e4)
|
|
247
|
+
});
|
|
248
|
+
var SectionInstructionSchema = z5.object({
|
|
249
|
+
id: z5.string().max(200),
|
|
250
|
+
sectionId: z5.string().max(200),
|
|
251
|
+
instruction: z5.string().max(1e4),
|
|
252
|
+
priority: z5.enum(["must", "should", "nice-to-have"]).optional(),
|
|
253
|
+
createdAt: z5.number(),
|
|
254
|
+
author: z5.string().max(200).optional()
|
|
255
|
+
});
|
|
256
|
+
var AnnotationTypeSchema = z5.enum([
|
|
257
|
+
"comment",
|
|
258
|
+
"suggestion",
|
|
259
|
+
"concern",
|
|
260
|
+
"question",
|
|
261
|
+
"approval",
|
|
262
|
+
"instruction",
|
|
263
|
+
"global_comment"
|
|
264
|
+
]);
|
|
265
|
+
var ReviewAnnotationSchema = z5.object({
|
|
266
|
+
id: z5.string().max(200),
|
|
267
|
+
type: AnnotationTypeSchema,
|
|
268
|
+
blockId: z5.string().max(200),
|
|
269
|
+
sectionId: z5.string().max(200).optional(),
|
|
270
|
+
startOffset: z5.number().optional(),
|
|
271
|
+
endOffset: z5.number().optional(),
|
|
272
|
+
originalText: z5.string().max(5e4),
|
|
273
|
+
text: z5.string().max(5e4),
|
|
274
|
+
author: z5.string().max(200).optional(),
|
|
275
|
+
authorColor: z5.string().max(50).optional(),
|
|
276
|
+
resolved: z5.boolean().optional(),
|
|
277
|
+
createdAt: z5.number(),
|
|
278
|
+
imagePaths: z5.array(z5.string().max(500)).optional()
|
|
279
|
+
});
|
|
280
|
+
var DiffChunkTypeSchema = z5.enum(["equal", "insert", "delete"]);
|
|
281
|
+
var DiffChunkSchema = z5.object({
|
|
282
|
+
type: DiffChunkTypeSchema,
|
|
283
|
+
value: z5.string().max(5e5)
|
|
284
|
+
});
|
|
285
|
+
var InlineEditSchema = z5.object({
|
|
286
|
+
id: z5.string().max(200),
|
|
287
|
+
blockId: z5.string().max(200),
|
|
288
|
+
sectionId: z5.string().max(200).optional(),
|
|
289
|
+
originalContent: z5.string().max(1e5),
|
|
290
|
+
editedContent: z5.string().max(1e5),
|
|
291
|
+
characterDiff: z5.array(DiffChunkSchema),
|
|
292
|
+
author: z5.string().max(200).optional(),
|
|
293
|
+
createdAt: z5.number()
|
|
294
|
+
});
|
|
295
|
+
var ReviewDecisionSchema = z5.object({
|
|
296
|
+
behavior: z5.enum(["allow", "deny"]),
|
|
297
|
+
message: z5.string().max(1e4).optional(),
|
|
298
|
+
decidedAt: z5.number()
|
|
299
|
+
});
|
|
300
|
+
var RepoInfoSchema = z5.object({
|
|
301
|
+
remote: z5.string().max(2e3).optional(),
|
|
302
|
+
branch: z5.string().max(500).optional(),
|
|
303
|
+
display: z5.string().max(500).optional()
|
|
304
|
+
});
|
|
305
|
+
var PlanStatusSchema = z5.enum([
|
|
306
|
+
"reviewing",
|
|
307
|
+
"approved",
|
|
308
|
+
"denied",
|
|
309
|
+
"executing",
|
|
310
|
+
"completed"
|
|
311
|
+
]);
|
|
312
|
+
var TrackerTaskStatusSchema = z6.enum([
|
|
313
|
+
"open",
|
|
314
|
+
"in_progress",
|
|
315
|
+
"done",
|
|
316
|
+
"failed",
|
|
317
|
+
"skipped"
|
|
318
|
+
]);
|
|
319
|
+
var TaskPrioritySchema = z6.union([
|
|
320
|
+
z6.literal(0),
|
|
321
|
+
z6.literal(1),
|
|
322
|
+
z6.literal(2),
|
|
323
|
+
z6.literal(3),
|
|
324
|
+
z6.literal(4)
|
|
325
|
+
]);
|
|
326
|
+
var TrackerTaskSchema = z6.object({
|
|
327
|
+
id: z6.string(),
|
|
328
|
+
title: z6.string(),
|
|
329
|
+
status: TrackerTaskStatusSchema,
|
|
330
|
+
priority: TaskPrioritySchema,
|
|
331
|
+
description: z6.string().optional(),
|
|
332
|
+
labels: z6.array(z6.string()).optional(),
|
|
333
|
+
type: z6.string().optional(),
|
|
334
|
+
parentId: z6.string().optional(),
|
|
335
|
+
dependsOn: z6.array(z6.string()).optional(),
|
|
336
|
+
blocks: z6.array(z6.string()).optional(),
|
|
337
|
+
assignee: z6.string().optional(),
|
|
338
|
+
createdAt: z6.string().optional(),
|
|
339
|
+
updatedAt: z6.string().optional(),
|
|
340
|
+
metadata: z6.record(z6.unknown()).optional()
|
|
341
|
+
});
|
|
342
|
+
var TaskFilterSchema = z6.object({
|
|
343
|
+
status: z6.union([TrackerTaskStatusSchema, z6.array(TrackerTaskStatusSchema)]).optional(),
|
|
344
|
+
priority: z6.union([TaskPrioritySchema, z6.array(TaskPrioritySchema)]).optional(),
|
|
345
|
+
labels: z6.array(z6.string()).optional(),
|
|
346
|
+
assignee: z6.string().optional(),
|
|
347
|
+
type: z6.union([z6.string(), z6.array(z6.string())]).optional(),
|
|
348
|
+
parentId: z6.string().optional(),
|
|
349
|
+
ready: z6.boolean().optional(),
|
|
350
|
+
limit: z6.number().int().positive().optional(),
|
|
351
|
+
offset: z6.number().int().nonnegative().optional(),
|
|
352
|
+
excludeIds: z6.array(z6.string()).optional()
|
|
353
|
+
});
|
|
354
|
+
var TaskCompletionResultSchema = z6.object({
|
|
355
|
+
taskId: z6.string(),
|
|
356
|
+
status: TrackerTaskStatusSchema,
|
|
357
|
+
summary: z6.string(),
|
|
358
|
+
filesChanged: z6.array(z6.string()).optional(),
|
|
359
|
+
commitSha: z6.string().optional()
|
|
360
|
+
});
|
|
361
|
+
var AgentPluginMetaSchema = z6.object({
|
|
362
|
+
name: z6.string(),
|
|
363
|
+
version: z6.string(),
|
|
364
|
+
description: z6.string(),
|
|
365
|
+
cliCommand: z6.string(),
|
|
366
|
+
supportsHooks: z6.boolean(),
|
|
367
|
+
supportsSkills: z6.boolean(),
|
|
368
|
+
supportsStreaming: z6.boolean(),
|
|
369
|
+
supportsSubagentTracing: z6.boolean()
|
|
370
|
+
});
|
|
371
|
+
var ExecuteOptionsSchema = z6.object({
|
|
372
|
+
prompt: z6.string(),
|
|
373
|
+
workingDir: z6.string(),
|
|
374
|
+
timeout: z6.number().positive().optional(),
|
|
375
|
+
env: z6.record(z6.string()).optional(),
|
|
376
|
+
sandboxConfig: z6.record(z6.unknown()).optional()
|
|
377
|
+
});
|
|
378
|
+
var ExecuteResultSchema = z6.object({
|
|
379
|
+
exitCode: z6.number(),
|
|
380
|
+
stdout: z6.string(),
|
|
381
|
+
stderr: z6.string(),
|
|
382
|
+
duration: z6.number(),
|
|
383
|
+
tokenUsage: z6.object({
|
|
384
|
+
input: z6.number().optional(),
|
|
385
|
+
output: z6.number().optional()
|
|
386
|
+
}).optional()
|
|
387
|
+
});
|
|
388
|
+
var TrackerPluginMetaSchema = z6.object({
|
|
389
|
+
id: z6.string(),
|
|
390
|
+
name: z6.string(),
|
|
391
|
+
description: z6.string(),
|
|
392
|
+
version: z6.string(),
|
|
393
|
+
supportsBidirectionalSync: z6.boolean(),
|
|
394
|
+
supportsHierarchy: z6.boolean(),
|
|
395
|
+
supportsDependencies: z6.boolean()
|
|
396
|
+
});
|
|
397
|
+
var SyncResultSchema = z6.object({
|
|
398
|
+
success: z6.boolean(),
|
|
399
|
+
message: z6.string(),
|
|
400
|
+
added: z6.number().optional(),
|
|
401
|
+
updated: z6.number().optional(),
|
|
402
|
+
removed: z6.number().optional(),
|
|
403
|
+
error: z6.string().optional(),
|
|
404
|
+
syncedAt: z6.string()
|
|
405
|
+
});
|
|
406
|
+
var McpConfigFieldSchema = z7.object({
|
|
407
|
+
type: z7.enum(["string", "number", "boolean"]).default("string"),
|
|
408
|
+
format: z7.enum(["env-ref", "plain"]).optional(),
|
|
409
|
+
required: z7.boolean().default(false),
|
|
410
|
+
sensitive: z7.boolean().default(false),
|
|
411
|
+
description: z7.string().optional(),
|
|
412
|
+
default: z7.union([z7.string(), z7.number(), z7.boolean()]).optional()
|
|
413
|
+
});
|
|
414
|
+
var McpTransportSchema = z7.enum(["stdio", "sse", "http"]).default("stdio");
|
|
415
|
+
var McpCategorySchema = z7.enum([
|
|
416
|
+
"database",
|
|
417
|
+
"api",
|
|
418
|
+
"devtools",
|
|
419
|
+
"comms",
|
|
420
|
+
"analytics",
|
|
421
|
+
"cloud",
|
|
422
|
+
"ai",
|
|
423
|
+
"search",
|
|
424
|
+
"monitoring",
|
|
425
|
+
"project-management",
|
|
426
|
+
"design",
|
|
427
|
+
"custom"
|
|
428
|
+
]).default("custom");
|
|
429
|
+
var McpSourceSchema = z7.enum(["catalog", "manual"]).default("manual");
|
|
430
|
+
var McpDetectSignalsSchema = z7.object({
|
|
431
|
+
dependencies: z7.array(z7.string()).default([]),
|
|
432
|
+
env_vars: z7.array(z7.string()).default([]),
|
|
433
|
+
docker_services: z7.array(z7.string()).default([]),
|
|
434
|
+
config_files: z7.array(z7.string()).default([])
|
|
435
|
+
}).partial();
|
|
436
|
+
var McpDefinitionSchema = z7.object({
|
|
437
|
+
name: z7.string().min(1).max(128),
|
|
438
|
+
display_name: z7.string().max(256).optional(),
|
|
439
|
+
description: z7.string().max(1024).optional(),
|
|
440
|
+
command: z7.string().min(1).max(256),
|
|
441
|
+
args: z7.array(z7.string()).default([]),
|
|
442
|
+
transport: McpTransportSchema,
|
|
443
|
+
category: McpCategorySchema,
|
|
444
|
+
config_schema: z7.record(z7.string(), McpConfigFieldSchema).optional(),
|
|
445
|
+
tags: z7.array(z7.string()).default([]),
|
|
446
|
+
source: McpSourceSchema,
|
|
447
|
+
added_at: z7.string().optional()
|
|
448
|
+
});
|
|
449
|
+
var McpLibrarySchema = z7.object({
|
|
450
|
+
mcps: z7.record(z7.string(), McpDefinitionSchema).default({})
|
|
451
|
+
});
|
|
452
|
+
var McpActivationSchema = z7.object({
|
|
453
|
+
enabled: z7.boolean().default(true),
|
|
454
|
+
config: z7.record(z7.string(), z7.union([z7.string(), z7.number(), z7.boolean()])).default({})
|
|
455
|
+
});
|
|
456
|
+
var McpRepoConfigSchema = z7.object({
|
|
457
|
+
mcps: z7.record(z7.string(), McpActivationSchema).default({})
|
|
458
|
+
});
|
|
459
|
+
var McpCatalogEntrySchema = McpDefinitionSchema.extend({
|
|
460
|
+
detect_signals: McpDetectSignalsSchema.optional()
|
|
461
|
+
});
|
|
462
|
+
var McpCatalogSchema = z7.object({
|
|
463
|
+
catalog: z7.array(McpCatalogEntrySchema).default([])
|
|
464
|
+
});
|
|
465
|
+
var SquadStepSchema = z8.object({
|
|
466
|
+
name: z8.string(),
|
|
467
|
+
agent: z8.string(),
|
|
468
|
+
agentType: z8.string().optional(),
|
|
469
|
+
model: z8.string().optional(),
|
|
470
|
+
permissions: z8.enum(["default", "skip", "dangerouslySkip"]).optional(),
|
|
471
|
+
readOnly: z8.boolean().default(false),
|
|
472
|
+
systemPrompt: z8.string().optional(),
|
|
473
|
+
systemPromptFile: z8.string().optional(),
|
|
474
|
+
timeout: z8.number().int().positive().optional(),
|
|
475
|
+
canReject: z8.boolean().default(false),
|
|
476
|
+
rejectTo: z8.string().optional()
|
|
477
|
+
});
|
|
478
|
+
var SquadDefinitionSchema = z8.object({
|
|
479
|
+
description: z8.string().optional(),
|
|
480
|
+
steps: z8.array(SquadStepSchema).min(1),
|
|
481
|
+
maxLoopRounds: z8.number().int().positive().default(3),
|
|
482
|
+
loopExhaustedBehavior: z8.enum(["fail-task", "merge-with-warning", "skip-remaining"]).default("fail-task"),
|
|
483
|
+
timeout: z8.number().int().positive().optional()
|
|
484
|
+
});
|
|
485
|
+
var UlpiProjectSchema = z9.object({
|
|
486
|
+
name: z9.string(),
|
|
487
|
+
repo: z9.string().optional(),
|
|
488
|
+
defaultBranch: z9.string().default("main"),
|
|
489
|
+
targetBranch: z9.string().optional(),
|
|
490
|
+
// Stack detection fields (ref: StackConfig in template.ts)
|
|
491
|
+
runtime: z9.string().optional(),
|
|
492
|
+
language: z9.string().optional(),
|
|
493
|
+
framework: z9.string().optional(),
|
|
494
|
+
package_manager: z9.string().optional(),
|
|
495
|
+
orm: z9.string().optional(),
|
|
496
|
+
test_runner: z9.string().optional(),
|
|
497
|
+
formatter: z9.string().optional(),
|
|
498
|
+
linter: z9.string().optional(),
|
|
499
|
+
git_workflow: z9.string().optional(),
|
|
500
|
+
// Command overrides
|
|
501
|
+
test_command: z9.string().optional(),
|
|
502
|
+
build_command: z9.string().optional(),
|
|
503
|
+
lint_command: z9.string().optional(),
|
|
504
|
+
format_command: z9.string().optional()
|
|
505
|
+
});
|
|
506
|
+
var UlpiPluginsSchema = z9.object({
|
|
507
|
+
runtime: z9.string().default("tmux"),
|
|
508
|
+
agent: z9.string().default("claude"),
|
|
509
|
+
workspace: z9.string().default("worktree"),
|
|
510
|
+
tracker: z9.string().optional(),
|
|
511
|
+
scm: z9.string().optional(),
|
|
512
|
+
notifier: z9.union([z9.string(), z9.array(z9.string())]).optional(),
|
|
513
|
+
sandbox: z9.string().nullable().optional(),
|
|
514
|
+
terminal: z9.string().optional(),
|
|
515
|
+
guard: z9.string().default("default"),
|
|
516
|
+
model: z9.string().optional()
|
|
517
|
+
});
|
|
518
|
+
var UlpiAgentConfigSchema = z9.object({
|
|
519
|
+
model: z9.string().optional(),
|
|
520
|
+
skipPermissions: z9.boolean().optional(),
|
|
521
|
+
maxTurns: z9.number().int().positive().optional(),
|
|
522
|
+
timeout: z9.number().int().positive().optional()
|
|
523
|
+
}).passthrough();
|
|
524
|
+
var UlpiFallbackSchema = z9.object({
|
|
525
|
+
chain: z9.array(z9.string()).default([]),
|
|
526
|
+
backoffMs: z9.number().int().nonnegative().default(5e3)
|
|
527
|
+
});
|
|
528
|
+
var UlpiSprintSchema = z9.object({
|
|
529
|
+
maxWorkersPerSprint: z9.number().int().positive().default(3),
|
|
530
|
+
maxWorkersGlobal: z9.number().int().positive().default(10),
|
|
531
|
+
maxConcurrentSprints: z9.number().int().positive().default(5),
|
|
532
|
+
maxRetries: z9.number().int().nonnegative().default(3),
|
|
533
|
+
timeout: z9.number().int().positive().default(6e5),
|
|
534
|
+
pollInterval: z9.number().int().positive().default(3e4),
|
|
535
|
+
branchPrefix: z9.string().default("sprint/")
|
|
536
|
+
});
|
|
537
|
+
var UlpiMergeSchema = z9.object({
|
|
538
|
+
strategy: z9.enum(["sequential", "rebase", "octopus"]).default("sequential"),
|
|
539
|
+
conflictResolution: z9.enum(["ai", "manual", "abort"]).default("ai"),
|
|
540
|
+
enableValidation: z9.boolean().default(true)
|
|
541
|
+
});
|
|
542
|
+
var UlpiGuardsSchema = z9.object({
|
|
543
|
+
preconditions: z9.record(z9.string(), z9.record(z9.unknown())).default({}),
|
|
544
|
+
permissions: z9.record(z9.string(), z9.record(z9.unknown())).default({}),
|
|
545
|
+
postconditions: z9.record(z9.string(), z9.record(z9.unknown())).default({}),
|
|
546
|
+
pipelines: z9.record(z9.string(), z9.record(z9.unknown())).default({})
|
|
547
|
+
}).passthrough();
|
|
548
|
+
var ReactionTriggerSchema = z9.object({
|
|
549
|
+
type: z9.enum(["status", "event", "activity"]),
|
|
550
|
+
to: z9.string().optional(),
|
|
551
|
+
eventType: z9.string().optional(),
|
|
552
|
+
state: z9.string().optional(),
|
|
553
|
+
durationMs: z9.number().int().positive().optional()
|
|
554
|
+
}).passthrough();
|
|
555
|
+
var ReactionActionSchema = z9.object({
|
|
556
|
+
type: z9.enum(["send_message", "notify", "kill_session", "auto_merge"]),
|
|
557
|
+
template: z9.string().optional(),
|
|
558
|
+
urgency: z9.string().optional()
|
|
559
|
+
}).passthrough();
|
|
560
|
+
var EscalationLevelSchema = z9.object({
|
|
561
|
+
afterMs: z9.number().int().positive(),
|
|
562
|
+
action: ReactionActionSchema
|
|
563
|
+
});
|
|
564
|
+
var UlpiReactionConfigSchema = z9.object({
|
|
565
|
+
trigger: ReactionTriggerSchema,
|
|
566
|
+
action: ReactionActionSchema,
|
|
567
|
+
cooldownMs: z9.number().int().nonnegative().optional(),
|
|
568
|
+
maxPerSession: z9.number().int().positive().optional(),
|
|
569
|
+
enrichWithMemory: z9.boolean().optional(),
|
|
570
|
+
enrichWithCodemap: z9.boolean().optional(),
|
|
571
|
+
customPrompt: z9.string().optional(),
|
|
572
|
+
escalation: z9.object({
|
|
573
|
+
levels: z9.array(EscalationLevelSchema)
|
|
574
|
+
}).optional()
|
|
575
|
+
});
|
|
576
|
+
var UlpiNotificationsSchema = z9.object({
|
|
577
|
+
routing: z9.record(z9.string(), z9.array(z9.string())).optional()
|
|
578
|
+
}).passthrough();
|
|
579
|
+
var UlpiMemorySchema = z9.object({
|
|
580
|
+
enabled: z9.boolean().default(true),
|
|
581
|
+
crossAgentSharing: z9.boolean().default(true),
|
|
582
|
+
autoClassify: z9.boolean().default(true),
|
|
583
|
+
embedding: z9.object({
|
|
584
|
+
provider: z9.string().default("openai"),
|
|
585
|
+
model: z9.string().default("text-embedding-3-small")
|
|
586
|
+
}).optional()
|
|
587
|
+
});
|
|
588
|
+
var UlpiCodemapSchema = z9.object({
|
|
589
|
+
enabled: z9.boolean().default(true),
|
|
590
|
+
embedding: z9.object({
|
|
591
|
+
provider: z9.string().default("openai"),
|
|
592
|
+
model: z9.string().default("text-embedding-3-small")
|
|
593
|
+
}).optional(),
|
|
594
|
+
watch: z9.boolean().default(true)
|
|
595
|
+
});
|
|
596
|
+
var UlpiBudgetSchema = z9.object({
|
|
597
|
+
maxCostPerTask: z9.number().nonnegative().optional(),
|
|
598
|
+
maxCostPerFleet: z9.number().nonnegative().optional(),
|
|
599
|
+
warnAt: z9.number().min(0).max(1).default(0.8)
|
|
600
|
+
});
|
|
601
|
+
var UlpiConfigSchema = z9.object({
|
|
602
|
+
project: UlpiProjectSchema,
|
|
603
|
+
plugins: UlpiPluginsSchema.default({}),
|
|
604
|
+
agents: z9.record(z9.string(), UlpiAgentConfigSchema).default({}),
|
|
605
|
+
fallback: UlpiFallbackSchema.default({}),
|
|
606
|
+
sprint: UlpiSprintSchema.default({}),
|
|
607
|
+
merge: UlpiMergeSchema.default({}),
|
|
608
|
+
guards: UlpiGuardsSchema.default({}),
|
|
609
|
+
reactions: z9.record(z9.string(), UlpiReactionConfigSchema).default({}),
|
|
610
|
+
notifications: UlpiNotificationsSchema.default({}),
|
|
611
|
+
memory: UlpiMemorySchema.default({}),
|
|
612
|
+
codemap: UlpiCodemapSchema.default({}),
|
|
613
|
+
budget: UlpiBudgetSchema.default({}),
|
|
614
|
+
squads: z9.record(z9.string(), SquadDefinitionSchema).default({}),
|
|
615
|
+
agentRules: z9.string().optional()
|
|
616
|
+
});
|
|
617
|
+
var PrdTaskPrioritySchema = z10.enum(["P0", "P1", "P2", "P3"]);
|
|
618
|
+
var PrdTaskTypeSchema = z10.enum([
|
|
619
|
+
"feature",
|
|
620
|
+
"bug",
|
|
621
|
+
"chore",
|
|
622
|
+
"refactor",
|
|
623
|
+
"test",
|
|
624
|
+
"docs",
|
|
625
|
+
"infra"
|
|
626
|
+
]);
|
|
627
|
+
var PrdTaskSchema = z10.object({
|
|
628
|
+
id: z10.string(),
|
|
629
|
+
title: z10.string(),
|
|
630
|
+
description: z10.string(),
|
|
631
|
+
priority: PrdTaskPrioritySchema,
|
|
632
|
+
type: PrdTaskTypeSchema.default("feature"),
|
|
633
|
+
dependsOn: z10.array(z10.string()).default([]),
|
|
634
|
+
labels: z10.array(z10.string()).default([]),
|
|
635
|
+
acceptanceCriteria: z10.array(z10.string()).default([]),
|
|
636
|
+
effort: z10.enum(["S", "M", "L", "XL"]).optional(),
|
|
637
|
+
assignee: z10.string().optional(),
|
|
638
|
+
parentId: z10.string().optional(),
|
|
639
|
+
sectionPath: z10.array(z10.string()).default([])
|
|
640
|
+
});
|
|
641
|
+
var PrdMetaSchema = z10.object({
|
|
642
|
+
title: z10.string(),
|
|
643
|
+
version: z10.string().optional(),
|
|
644
|
+
author: z10.string().optional(),
|
|
645
|
+
date: z10.string().optional(),
|
|
646
|
+
project: z10.string().optional(),
|
|
647
|
+
tags: z10.array(z10.string()).default([]),
|
|
648
|
+
frontmatter: z10.record(z10.string()).default({})
|
|
649
|
+
});
|
|
650
|
+
var PrdDocumentSchema = z10.object({
|
|
651
|
+
meta: PrdMetaSchema,
|
|
652
|
+
tasks: z10.array(PrdTaskSchema),
|
|
653
|
+
rawMarkdown: z10.string(),
|
|
654
|
+
unparsedSections: z10.array(z10.string()).default([])
|
|
655
|
+
});
|
|
656
|
+
var CiCommandTypeSchema = z11.enum([
|
|
657
|
+
"run",
|
|
658
|
+
"cancel",
|
|
659
|
+
"fork",
|
|
660
|
+
"status",
|
|
661
|
+
"deploy"
|
|
662
|
+
]);
|
|
663
|
+
var CiCommandSchema = z11.object({
|
|
664
|
+
type: CiCommandTypeSchema,
|
|
665
|
+
taskId: z11.string().optional(),
|
|
666
|
+
taskIds: z11.array(z11.string()).optional(),
|
|
667
|
+
sprintId: z11.string().optional(),
|
|
668
|
+
branch: z11.string().optional(),
|
|
669
|
+
maxWorkers: z11.number().int().positive().optional()
|
|
670
|
+
});
|
|
671
|
+
var CiJobStatusSchema = z11.enum([
|
|
672
|
+
"queued",
|
|
673
|
+
"starting",
|
|
674
|
+
"running",
|
|
675
|
+
"completed",
|
|
676
|
+
"failed",
|
|
677
|
+
"cancelled"
|
|
678
|
+
]);
|
|
679
|
+
var CiJobConfigSchema = z11.object({
|
|
680
|
+
projectId: z11.string(),
|
|
681
|
+
taskId: z11.string(),
|
|
682
|
+
branch: z11.string(),
|
|
683
|
+
prNumber: z11.number().int().optional(),
|
|
684
|
+
timeout: z11.number().int().positive().optional(),
|
|
685
|
+
env: z11.record(z11.string()).optional()
|
|
686
|
+
});
|
|
687
|
+
var CiJobResultSchema = z11.object({
|
|
688
|
+
jobId: z11.string(),
|
|
689
|
+
status: CiJobStatusSchema,
|
|
690
|
+
exitCode: z11.number().int().optional(),
|
|
691
|
+
duration: z11.number().optional(),
|
|
692
|
+
commitSha: z11.string().optional(),
|
|
693
|
+
filesChanged: z11.array(z11.string()).optional(),
|
|
694
|
+
error: z11.string().optional()
|
|
695
|
+
});
|
|
696
|
+
var CiJobSchema = z11.object({
|
|
697
|
+
id: z11.string(),
|
|
698
|
+
config: CiJobConfigSchema,
|
|
699
|
+
status: CiJobStatusSchema,
|
|
700
|
+
result: CiJobResultSchema.optional(),
|
|
701
|
+
queuedAt: z11.string(),
|
|
702
|
+
startedAt: z11.string().optional(),
|
|
703
|
+
completedAt: z11.string().optional(),
|
|
704
|
+
workerId: z11.string().optional(),
|
|
705
|
+
retryCount: z11.number().int().default(0)
|
|
706
|
+
});
|
|
707
|
+
var WorkerInputSchema = z11.object({
|
|
708
|
+
jobId: z11.string(),
|
|
709
|
+
taskId: z11.string(),
|
|
710
|
+
projectId: z11.string(),
|
|
711
|
+
branch: z11.string(),
|
|
712
|
+
repoUrl: z11.string(),
|
|
713
|
+
prompt: z11.string(),
|
|
714
|
+
env: z11.record(z11.string()).optional(),
|
|
715
|
+
timeout: z11.number().int().positive().optional()
|
|
716
|
+
});
|
|
717
|
+
var WorkerOutputSchema = z11.object({
|
|
718
|
+
jobId: z11.string(),
|
|
719
|
+
exitCode: z11.number().int(),
|
|
720
|
+
stdout: z11.string(),
|
|
721
|
+
stderr: z11.string(),
|
|
722
|
+
duration: z11.number(),
|
|
723
|
+
commitSha: z11.string().optional(),
|
|
724
|
+
filesChanged: z11.array(z11.string()).optional()
|
|
725
|
+
});
|
|
726
|
+
var WorkerProgressSchema = z11.object({
|
|
727
|
+
jobId: z11.string(),
|
|
728
|
+
phase: z11.string(),
|
|
729
|
+
progress: z11.number().min(0).max(100),
|
|
730
|
+
message: z11.string().optional()
|
|
731
|
+
});
|
|
732
|
+
var GitHubAuthConfigSchema = z11.object({
|
|
733
|
+
appId: z11.number().int().positive().optional(),
|
|
734
|
+
privateKeyPath: z11.string().optional(),
|
|
735
|
+
webhookSecret: z11.string(),
|
|
736
|
+
personalAccessToken: z11.string().optional()
|
|
737
|
+
});
|
|
738
|
+
var DockerConfigSchema = z11.object({
|
|
739
|
+
image: z11.string().default("ulpi/worker:latest"),
|
|
740
|
+
memory: z11.string().default("4g"),
|
|
741
|
+
cpus: z11.number().positive().default(2),
|
|
742
|
+
network: z11.string().default("ulpi-ci"),
|
|
743
|
+
volumeMounts: z11.array(z11.string()).optional(),
|
|
744
|
+
env: z11.record(z11.string()).optional()
|
|
745
|
+
});
|
|
746
|
+
var SecurityConfigSchema = z11.object({
|
|
747
|
+
allowedRepos: z11.array(z11.string()).default([]),
|
|
748
|
+
allowedAuthors: z11.array(z11.string()).default([]),
|
|
749
|
+
requireApproval: z11.boolean().default(false),
|
|
750
|
+
rateLimitPerRepo: z11.number().int().positive().default(10),
|
|
751
|
+
rateLimitWindowSeconds: z11.number().int().positive().default(3600)
|
|
752
|
+
});
|
|
753
|
+
var JiraReferenceSchema = z11.object({
|
|
754
|
+
key: z11.string(),
|
|
755
|
+
url: z11.string().url().optional()
|
|
756
|
+
});
|
|
757
|
+
var JiraTicketSchema = z11.object({
|
|
758
|
+
key: z11.string(),
|
|
759
|
+
summary: z11.string(),
|
|
760
|
+
status: z11.string(),
|
|
761
|
+
assignee: z11.string().optional(),
|
|
762
|
+
url: z11.string().url().optional(),
|
|
763
|
+
labels: z11.array(z11.string()).default([])
|
|
764
|
+
});
|
|
765
|
+
var ClaudeCredentialsSchema = z11.object({
|
|
766
|
+
apiKey: z11.string().optional(),
|
|
767
|
+
maxTokens: z11.number().int().positive().optional(),
|
|
768
|
+
model: z11.string().optional()
|
|
769
|
+
});
|
|
770
|
+
var AgentCredentialsSchema = z11.object({
|
|
771
|
+
apiKey: z11.string().optional(),
|
|
772
|
+
model: z11.string().optional(),
|
|
773
|
+
maxTokens: z11.number().int().positive().optional()
|
|
774
|
+
});
|
|
775
|
+
var CloudUserSchema = z12.object({
|
|
776
|
+
id: z12.string().uuid(),
|
|
777
|
+
github_id: z12.number().int().positive(),
|
|
778
|
+
email: z12.string().email().optional(),
|
|
779
|
+
name: z12.string().min(1),
|
|
780
|
+
avatar_url: z12.string().url().optional(),
|
|
781
|
+
created_at: z12.string().datetime()
|
|
782
|
+
});
|
|
783
|
+
var CloudOrganizationSchema = z12.object({
|
|
784
|
+
id: z12.string().uuid(),
|
|
785
|
+
name: z12.string().min(1),
|
|
786
|
+
slug: z12.string().min(1),
|
|
787
|
+
owner_id: z12.string().uuid(),
|
|
788
|
+
plan: z12.enum(["free", "pro", "enterprise"]).default("free"),
|
|
789
|
+
created_at: z12.string().datetime()
|
|
790
|
+
});
|
|
791
|
+
var CloudMembershipSchema = z12.object({
|
|
792
|
+
user_id: z12.string().uuid(),
|
|
793
|
+
org_id: z12.string().uuid(),
|
|
794
|
+
role: z12.enum(["owner", "admin", "member"]),
|
|
795
|
+
joined_at: z12.string().datetime()
|
|
796
|
+
});
|
|
797
|
+
var CloudPlanTierSchema = z12.enum(["free", "pro", "enterprise"]);
|
|
798
|
+
var CloudSubscriptionSchema = z12.object({
|
|
799
|
+
id: z12.string().uuid(),
|
|
800
|
+
org_id: z12.string().uuid(),
|
|
801
|
+
plan: CloudPlanTierSchema,
|
|
802
|
+
status: z12.enum(["active", "past_due", "cancelled", "trialing"]),
|
|
803
|
+
stripe_subscription_id: z12.string().optional(),
|
|
804
|
+
current_period_start: z12.string().datetime(),
|
|
805
|
+
current_period_end: z12.string().datetime()
|
|
806
|
+
});
|
|
807
|
+
var CloudApiKeySchema = z12.object({
|
|
808
|
+
id: z12.string().uuid(),
|
|
809
|
+
org_id: z12.string().uuid(),
|
|
810
|
+
name: z12.string().min(1),
|
|
811
|
+
prefix: z12.string(),
|
|
812
|
+
scopes: z12.array(z12.string()).default([]),
|
|
813
|
+
created_at: z12.string().datetime(),
|
|
814
|
+
expires_at: z12.string().datetime().optional(),
|
|
815
|
+
last_used_at: z12.string().datetime().optional()
|
|
816
|
+
});
|
|
817
|
+
var CloudRepositorySchema = z12.object({
|
|
818
|
+
id: z12.string().uuid(),
|
|
819
|
+
org_id: z12.string().uuid(),
|
|
820
|
+
github_repo: z12.string(),
|
|
821
|
+
name: z12.string().min(1),
|
|
822
|
+
is_active: z12.boolean().default(true),
|
|
823
|
+
created_at: z12.string().datetime()
|
|
824
|
+
});
|
|
825
|
+
var CloudUsageRecordSchema = z12.object({
|
|
826
|
+
id: z12.string().uuid(),
|
|
827
|
+
org_id: z12.string().uuid(),
|
|
828
|
+
metric: z12.string(),
|
|
829
|
+
value: z12.number(),
|
|
830
|
+
recorded_at: z12.string().datetime()
|
|
831
|
+
});
|
|
832
|
+
var CloudAuditEntrySchema = z12.object({
|
|
833
|
+
id: z12.string().uuid(),
|
|
834
|
+
org_id: z12.string().uuid(),
|
|
835
|
+
actor_id: z12.string().uuid(),
|
|
836
|
+
action: z12.string(),
|
|
837
|
+
resource_type: z12.string(),
|
|
838
|
+
resource_id: z12.string(),
|
|
839
|
+
metadata: z12.record(z12.unknown()).optional(),
|
|
840
|
+
created_at: z12.string().datetime()
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
// ../../packages/foundation/config/dist/index.js
|
|
844
|
+
import { homedir, platform, userInfo } from "os";
|
|
845
|
+
import { basename, join, resolve } from "path";
|
|
846
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
847
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
848
|
+
import { z as z13 } from "zod";
|
|
849
|
+
import { parse as parseYaml2 } from "yaml";
|
|
850
|
+
import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
|
|
851
|
+
import { parse as parseYaml4 } from "yaml";
|
|
852
|
+
import { parse as parseYaml5 } from "yaml";
|
|
853
|
+
import { parse as parseYaml6, stringify as stringifyYaml3 } from "yaml";
|
|
854
|
+
import { parse as parseYaml7, stringify as stringifyYaml4 } from "yaml";
|
|
855
|
+
function getDataDir() {
|
|
856
|
+
return process.env.ULPI_DATA_DIR || `${homedir()}/.ulpi`;
|
|
857
|
+
}
|
|
858
|
+
var ULPI_GLOBAL_DIR = getDataDir();
|
|
859
|
+
var SESSIONS_DIR = join(ULPI_GLOBAL_DIR, "sessions");
|
|
860
|
+
var REVIEWS_DIR = join(ULPI_GLOBAL_DIR, "reviews");
|
|
861
|
+
var REVIEW_FLAGS_DIR = join(ULPI_GLOBAL_DIR, "review-flags");
|
|
862
|
+
var REVIEW_IMAGES_DIR = join(ULPI_GLOBAL_DIR, "review-images");
|
|
863
|
+
var REPOS_FILE = join(ULPI_GLOBAL_DIR, "repos.json");
|
|
864
|
+
var USER_TEMPLATES_DIR = join(ULPI_GLOBAL_DIR, "templates");
|
|
865
|
+
var USER_SKILLS_DIR = join(ULPI_GLOBAL_DIR, "skills");
|
|
866
|
+
var VERSION_CACHE_FILE = join(ULPI_GLOBAL_DIR, ".version-cache.json");
|
|
867
|
+
var SETTINGS_FILE = join(ULPI_GLOBAL_DIR, "settings.json");
|
|
868
|
+
var ULPI_CONFIG_FILE = join(ULPI_GLOBAL_DIR, "config.yml");
|
|
869
|
+
var LOGS_DIR = join(ULPI_GLOBAL_DIR, "logs");
|
|
870
|
+
var NOTIFICATIONS_LOG_FILE = join(ULPI_GLOBAL_DIR, "notifications.log");
|
|
871
|
+
var API_LOCK_FILE = join(ULPI_GLOBAL_DIR, "api.lock");
|
|
872
|
+
var CACHE_DIR = join(ULPI_GLOBAL_DIR, "cache");
|
|
873
|
+
var FLEET_DIR = join(ULPI_GLOBAL_DIR, "fleet");
|
|
874
|
+
var STAGING_DIR = join(ULPI_GLOBAL_DIR, "staging");
|
|
875
|
+
var CODEMAP_DIR_NAME = "codemap";
|
|
876
|
+
var CODEMAP_DIR = join(ULPI_GLOBAL_DIR, CODEMAP_DIR_NAME);
|
|
877
|
+
var MEMORY_DIR = join(ULPI_GLOBAL_DIR, "memory");
|
|
878
|
+
var MCPS_LIBRARY_FILE = join(ULPI_GLOBAL_DIR, "mcps.yml");
|
|
879
|
+
var JOBS_DIR = join(ULPI_GLOBAL_DIR, "jobs");
|
|
880
|
+
function branchToSlug(branch) {
|
|
881
|
+
const slug = branch.replace(/\//g, "--").replace(/\.\./g, "__").replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-{3,}/g, "--").replace(/^-|-$/g, "").toLowerCase();
|
|
882
|
+
return slug || "main";
|
|
883
|
+
}
|
|
884
|
+
function dirToSlug(dir) {
|
|
885
|
+
const slug = dir.replace(/^\//, "").replace(/\//g, "-").replace(/\./g, "_").replace(/-{2,}/g, "-").replace(/^-|-$/g, "").toLowerCase();
|
|
886
|
+
return slug || "unknown-project";
|
|
887
|
+
}
|
|
888
|
+
function requireProjectDir(projectDir) {
|
|
889
|
+
if (!projectDir || !projectDir.trim()) {
|
|
890
|
+
throw new Error("projectDir must be a non-empty string");
|
|
891
|
+
}
|
|
892
|
+
return projectDir;
|
|
893
|
+
}
|
|
894
|
+
function getCurrentBranch(projectDir) {
|
|
895
|
+
try {
|
|
896
|
+
const branch = execFileSync2("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
897
|
+
cwd: requireProjectDir(projectDir),
|
|
898
|
+
encoding: "utf-8",
|
|
899
|
+
timeout: 5e3,
|
|
900
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
901
|
+
}).trim();
|
|
902
|
+
if (branch && branch !== "HEAD") return branch;
|
|
903
|
+
} catch {
|
|
904
|
+
}
|
|
905
|
+
return "main";
|
|
906
|
+
}
|
|
907
|
+
function projectCodemapDir(projectDir, dataDir) {
|
|
908
|
+
return join(dataDir ?? CODEMAP_DIR, dirToSlug(requireProjectDir(projectDir)));
|
|
909
|
+
}
|
|
910
|
+
function codemapBranchDir(projectDir, branch, dataDir) {
|
|
911
|
+
return join(projectCodemapDir(projectDir, dataDir), branchToSlug(branch));
|
|
912
|
+
}
|
|
913
|
+
function codemapConfigFile(projectDir, dataDir) {
|
|
914
|
+
return join(projectCodemapDir(projectDir, dataDir), "config.json");
|
|
915
|
+
}
|
|
916
|
+
function codemapStatsFile(projectDir, branch, dataDir) {
|
|
917
|
+
return join(codemapBranchDir(projectDir, branch, dataDir), "stats.json");
|
|
918
|
+
}
|
|
919
|
+
function codemapLanceDir(projectDir, branch, dataDir) {
|
|
920
|
+
return join(codemapBranchDir(projectDir, branch, dataDir), "index", "lance");
|
|
921
|
+
}
|
|
922
|
+
function codemapMetadataDir(projectDir, branch, dataDir) {
|
|
923
|
+
return join(codemapBranchDir(projectDir, branch, dataDir), "index", "metadata");
|
|
924
|
+
}
|
|
925
|
+
function depgraphDir(projectDir, branch, dataDir) {
|
|
926
|
+
return join(codemapBranchDir(projectDir, branch, dataDir), "index", "metadata", "depgraph");
|
|
927
|
+
}
|
|
928
|
+
function depgraphGraphFile(projectDir, branch, dataDir) {
|
|
929
|
+
return join(depgraphDir(projectDir, branch, dataDir), "graph.json");
|
|
930
|
+
}
|
|
931
|
+
function depgraphPagerankFile(projectDir, branch, dataDir) {
|
|
932
|
+
return join(depgraphDir(projectDir, branch, dataDir), "pagerank.json");
|
|
933
|
+
}
|
|
934
|
+
function depgraphMetricsFile(projectDir, branch, dataDir) {
|
|
935
|
+
return join(depgraphDir(projectDir, branch, dataDir), "metrics.json");
|
|
936
|
+
}
|
|
937
|
+
var CLI_NPM_PACKAGE = "@ulpi/cli";
|
|
938
|
+
var CLI_REGISTRY_URL = `https://registry.npmjs.org/${CLI_NPM_PACKAGE}/latest`;
|
|
939
|
+
var ApiKeysSettingsSchema = z13.object({
|
|
940
|
+
openai: z13.string().optional(),
|
|
941
|
+
anthropic: z13.string().optional(),
|
|
942
|
+
ulpi: z13.string().optional()
|
|
943
|
+
});
|
|
944
|
+
var ReviewSettingsSchema = z13.object({
|
|
945
|
+
enabled: z13.boolean().default(true),
|
|
946
|
+
plan_review: z13.boolean().default(true),
|
|
947
|
+
code_review: z13.boolean().default(true),
|
|
948
|
+
auto_open_browser: z13.boolean().default(true)
|
|
949
|
+
});
|
|
950
|
+
var UlpiSettingsSchema = z13.object({
|
|
951
|
+
review: ReviewSettingsSchema.default({}),
|
|
952
|
+
apiKeys: ApiKeysSettingsSchema.optional(),
|
|
953
|
+
username: z13.string().optional(),
|
|
954
|
+
ulpiUrl: z13.string().optional()
|
|
955
|
+
});
|
|
956
|
+
var DashboardAiConfigSchema = z13.object({
|
|
957
|
+
cli: z13.string(),
|
|
958
|
+
model: z13.string()
|
|
959
|
+
}).partial();
|
|
960
|
+
var DashboardSettingsSchema = z13.object({
|
|
961
|
+
prdDefaults: DashboardAiConfigSchema.extend({
|
|
962
|
+
maxWorkers: z13.number().optional()
|
|
963
|
+
}).optional(),
|
|
964
|
+
historyAi: DashboardAiConfigSchema.optional().nullable(),
|
|
965
|
+
memoryAi: DashboardAiConfigSchema.optional().nullable(),
|
|
966
|
+
preferences: z13.object({
|
|
967
|
+
theme: z13.enum(["dark", "light", "system"]).default("dark"),
|
|
968
|
+
sidebarCollapsed: z13.boolean().default(false),
|
|
969
|
+
autoRefresh: z13.boolean().default(true),
|
|
970
|
+
refreshInterval: z13.number().default(5)
|
|
971
|
+
}).optional()
|
|
972
|
+
}).optional();
|
|
973
|
+
var ProjectSettingsSchema = z13.object({
|
|
974
|
+
project: z13.object({
|
|
975
|
+
name: z13.string(),
|
|
976
|
+
runtime: z13.string().optional(),
|
|
977
|
+
language: z13.string().optional(),
|
|
978
|
+
framework: z13.string().optional(),
|
|
979
|
+
package_manager: z13.string().optional()
|
|
980
|
+
}).optional(),
|
|
981
|
+
plugins: z13.object({
|
|
982
|
+
agent: z13.string().optional()
|
|
983
|
+
}).passthrough().optional(),
|
|
984
|
+
agents: z13.record(z13.object({
|
|
985
|
+
model: z13.string().optional()
|
|
986
|
+
}).passthrough()).optional(),
|
|
987
|
+
sprint: z13.object({
|
|
988
|
+
maxWorkersPerSprint: z13.number().optional()
|
|
989
|
+
}).passthrough().optional(),
|
|
990
|
+
notifications: z13.object({
|
|
991
|
+
enabled: z13.boolean().optional(),
|
|
992
|
+
desktop: z13.object({ sound: z13.boolean().optional() }).passthrough().optional()
|
|
993
|
+
}).passthrough().optional(),
|
|
994
|
+
dashboard: DashboardSettingsSchema,
|
|
995
|
+
preconditions: z13.record(z13.unknown()).optional(),
|
|
996
|
+
postconditions: z13.record(z13.unknown()).optional(),
|
|
997
|
+
permissions: z13.record(z13.unknown()).optional(),
|
|
998
|
+
pipelines: z13.record(z13.unknown()).optional()
|
|
999
|
+
}).passthrough();
|
|
1000
|
+
|
|
1001
|
+
// ../../packages/intelligence/embed-engine/dist/index.js
|
|
1002
|
+
async function createEmbedder(config) {
|
|
1003
|
+
switch (config.provider) {
|
|
1004
|
+
case "openai": {
|
|
1005
|
+
const { OpenAIEmbedder: OpenAIEmbedder2 } = await import("./openai-RC75RP4O-NSFZC5O6.js");
|
|
1006
|
+
return new OpenAIEmbedder2(config.model, config.dimensions);
|
|
1007
|
+
}
|
|
1008
|
+
case "ollama": {
|
|
1009
|
+
const { OllamaEmbedder: OllamaEmbedder2 } = await import("./ollama-3XCUZMZT-J6Z4WWWO.js");
|
|
1010
|
+
return new OllamaEmbedder2(config.model, config.dimensions);
|
|
1011
|
+
}
|
|
1012
|
+
case "ulpi": {
|
|
1013
|
+
const { UlpiEmbedder: UlpiEmbedder2 } = await import("./ulpi-KLKEAQC3-5ATUONU7.js");
|
|
1014
|
+
return new UlpiEmbedder2(config.model, config.dimensions, config.baseUrl);
|
|
1015
|
+
}
|
|
1016
|
+
default:
|
|
1017
|
+
throw new Error(`Unknown embedding provider: ${config.provider}`);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
async function embedOne(embedder, text2) {
|
|
1021
|
+
const [vector] = await embedder.embed([text2]);
|
|
1022
|
+
return vector;
|
|
1023
|
+
}
|
|
1024
|
+
async function embedAll(embedder, texts, options) {
|
|
1025
|
+
if (texts.length === 0) return [];
|
|
1026
|
+
const {
|
|
1027
|
+
onProgress,
|
|
1028
|
+
onBatch,
|
|
1029
|
+
chunkSize = 100,
|
|
1030
|
+
timeoutMs = 3e5,
|
|
1031
|
+
pollIntervalMs = 2e3,
|
|
1032
|
+
batchSubmitSize = 1e3
|
|
1033
|
+
} = options ?? {};
|
|
1034
|
+
if (embedder.supportsBatch && embedder.submitBatch && embedder.pollBatch) {
|
|
1035
|
+
return embedAllViaBatch(embedder, texts, {
|
|
1036
|
+
onProgress,
|
|
1037
|
+
onBatch,
|
|
1038
|
+
timeoutMs,
|
|
1039
|
+
pollIntervalMs,
|
|
1040
|
+
batchSubmitSize
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
return embedAllViaSync(embedder, texts, {
|
|
1044
|
+
onProgress,
|
|
1045
|
+
onBatch,
|
|
1046
|
+
chunkSize
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
async function embedAllViaBatch(embedder, texts, opts) {
|
|
1050
|
+
const { onProgress, onBatch, timeoutMs, pollIntervalMs, batchSubmitSize } = opts;
|
|
1051
|
+
const total = texts.length;
|
|
1052
|
+
const collected = onBatch ? [] : new Array(total);
|
|
1053
|
+
let globalProcessed = 0;
|
|
1054
|
+
const MAX_POLL_ERRORS = 5;
|
|
1055
|
+
for (let offset = 0; offset < total; offset += batchSubmitSize) {
|
|
1056
|
+
const end = Math.min(offset + batchSubmitSize, total);
|
|
1057
|
+
const batchTexts = texts.slice(offset, end);
|
|
1058
|
+
const batchNum = Math.floor(offset / batchSubmitSize) + 1;
|
|
1059
|
+
const totalBatches = Math.ceil(total / batchSubmitSize);
|
|
1060
|
+
onProgress?.({
|
|
1061
|
+
current: globalProcessed,
|
|
1062
|
+
total,
|
|
1063
|
+
message: `Submitting batch ${batchNum}/${totalBatches} (${batchTexts.length} texts)...`
|
|
1064
|
+
});
|
|
1065
|
+
const batchId = await embedder.submitBatch(batchTexts);
|
|
1066
|
+
console.error(
|
|
1067
|
+
`[embed-engine] Batch ${batchNum}/${totalBatches} submitted (${batchId.slice(0, 8)}...), starting poll loop...`
|
|
1068
|
+
);
|
|
1069
|
+
onProgress?.({
|
|
1070
|
+
current: globalProcessed,
|
|
1071
|
+
total,
|
|
1072
|
+
message: `Batch ${batchNum}/${totalBatches} (${batchId.slice(0, 8)}...) polling...`
|
|
1073
|
+
});
|
|
1074
|
+
const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : 0;
|
|
1075
|
+
let watermark = 0;
|
|
1076
|
+
let pollCount = 0;
|
|
1077
|
+
let consecutiveErrors = 0;
|
|
1078
|
+
while (deadline === 0 || Date.now() < deadline) {
|
|
1079
|
+
pollCount++;
|
|
1080
|
+
let status;
|
|
1081
|
+
try {
|
|
1082
|
+
status = await embedder.pollBatch(batchId);
|
|
1083
|
+
consecutiveErrors = 0;
|
|
1084
|
+
} catch (err) {
|
|
1085
|
+
consecutiveErrors++;
|
|
1086
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1087
|
+
console.error(
|
|
1088
|
+
`[embed-engine] Poll error #${consecutiveErrors} for batch ${batchId.slice(0, 8)}...: ${msg}`
|
|
1089
|
+
);
|
|
1090
|
+
if (consecutiveErrors >= MAX_POLL_ERRORS) {
|
|
1091
|
+
throw new Error(
|
|
1092
|
+
`Batch polling failed after ${MAX_POLL_ERRORS} consecutive errors (batch ${batchId}). Last error: ${msg}`
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollIntervalMs * 2));
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
1098
|
+
if (status.results && status.results.length > watermark) {
|
|
1099
|
+
const newStart = watermark;
|
|
1100
|
+
const newCount = status.results.length - watermark;
|
|
1101
|
+
const newEmbeddings = status.results.slice(newStart, newStart + newCount);
|
|
1102
|
+
if (onBatch) {
|
|
1103
|
+
await onBatch(newEmbeddings, offset + newStart, newCount);
|
|
1104
|
+
} else {
|
|
1105
|
+
for (let i = 0; i < newCount; i++) {
|
|
1106
|
+
collected[offset + newStart + i] = newEmbeddings[i];
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
watermark = status.results.length;
|
|
1110
|
+
console.error(
|
|
1111
|
+
`[embed-engine] Batch ${batchId.slice(0, 8)}... delivered ${watermark}/${batchTexts.length} embeddings`
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
if (status.status === "failed") {
|
|
1115
|
+
throw new Error(
|
|
1116
|
+
`Batch embedding failed (batch ${batchId}). ${globalProcessed + watermark} of ${total} texts were processed before failure.`
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
const currentProcessed = globalProcessed + watermark;
|
|
1120
|
+
onProgress?.({
|
|
1121
|
+
current: currentProcessed,
|
|
1122
|
+
total,
|
|
1123
|
+
message: `Embedded ${currentProcessed}/${total}`
|
|
1124
|
+
});
|
|
1125
|
+
if (status.status === "completed") {
|
|
1126
|
+
console.error(
|
|
1127
|
+
`[embed-engine] Batch ${batchId.slice(0, 8)}... completed after ${pollCount} polls, ${watermark} embeddings`
|
|
1128
|
+
);
|
|
1129
|
+
break;
|
|
1130
|
+
}
|
|
1131
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollIntervalMs));
|
|
1132
|
+
}
|
|
1133
|
+
if (watermark < batchTexts.length && deadline > 0 && Date.now() >= deadline) {
|
|
1134
|
+
throw new Error(
|
|
1135
|
+
`Batch embedding timed out after ${Math.round(timeoutMs / 1e3)}s (batch ${batchId}). ${globalProcessed + watermark} of ${total} texts were processed.`
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
globalProcessed += batchTexts.length;
|
|
1139
|
+
}
|
|
1140
|
+
console.error(
|
|
1141
|
+
`[embed-engine] All ${Math.ceil(total / batchSubmitSize)} batches complete, ${globalProcessed} embeddings total`
|
|
1142
|
+
);
|
|
1143
|
+
return onBatch ? [] : collected;
|
|
1144
|
+
}
|
|
1145
|
+
async function embedAllViaSync(embedder, texts, opts) {
|
|
1146
|
+
const { onProgress, onBatch, chunkSize } = opts;
|
|
1147
|
+
const total = texts.length;
|
|
1148
|
+
const collected = [];
|
|
1149
|
+
for (let i = 0; i < total; i += chunkSize) {
|
|
1150
|
+
const chunk = texts.slice(i, i + chunkSize);
|
|
1151
|
+
const embeddings = await embedder.embed(chunk);
|
|
1152
|
+
if (onBatch) {
|
|
1153
|
+
await onBatch(embeddings, i, embeddings.length);
|
|
1154
|
+
} else {
|
|
1155
|
+
collected.push(...embeddings);
|
|
1156
|
+
}
|
|
1157
|
+
const processed = Math.min(i + chunkSize, total);
|
|
1158
|
+
onProgress?.({
|
|
1159
|
+
current: processed,
|
|
1160
|
+
total,
|
|
1161
|
+
message: `Embedded ${processed}/${total}`
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
return onBatch ? [] : collected;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// ../../packages/intelligence/codemap-engine/dist/index.js
|
|
1168
|
+
import fs from "fs";
|
|
1169
|
+
import path from "path";
|
|
1170
|
+
import crypto from "crypto";
|
|
1171
|
+
import { execFileSync } from "child_process";
|
|
1172
|
+
import fs2 from "fs";
|
|
1173
|
+
import path2 from "path";
|
|
1174
|
+
import crypto2 from "crypto";
|
|
1175
|
+
import fs3 from "fs";
|
|
1176
|
+
import path3 from "path";
|
|
1177
|
+
import fs4 from "fs";
|
|
1178
|
+
import path4 from "path";
|
|
1179
|
+
import fs5 from "fs";
|
|
1180
|
+
import path5 from "path";
|
|
1181
|
+
import fs6 from "fs";
|
|
1182
|
+
import path6 from "path";
|
|
1183
|
+
import os from "os";
|
|
1184
|
+
import crypto3 from "crypto";
|
|
1185
|
+
import fs7 from "fs";
|
|
1186
|
+
import path7 from "path";
|
|
1187
|
+
import fs9 from "fs";
|
|
1188
|
+
import path8 from "path";
|
|
1189
|
+
import fs10 from "fs";
|
|
1190
|
+
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1191
|
+
// Images
|
|
1192
|
+
".png",
|
|
1193
|
+
".jpg",
|
|
1194
|
+
".jpeg",
|
|
1195
|
+
".gif",
|
|
1196
|
+
".bmp",
|
|
1197
|
+
".ico",
|
|
1198
|
+
".svg",
|
|
1199
|
+
".webp",
|
|
1200
|
+
".avif",
|
|
1201
|
+
".tiff",
|
|
1202
|
+
// Fonts
|
|
1203
|
+
".woff",
|
|
1204
|
+
".woff2",
|
|
1205
|
+
".ttf",
|
|
1206
|
+
".otf",
|
|
1207
|
+
".eot",
|
|
1208
|
+
// Media
|
|
1209
|
+
".mp3",
|
|
1210
|
+
".mp4",
|
|
1211
|
+
".avi",
|
|
1212
|
+
".mov",
|
|
1213
|
+
".wav",
|
|
1214
|
+
".flac",
|
|
1215
|
+
".ogg",
|
|
1216
|
+
".webm",
|
|
1217
|
+
// Archives
|
|
1218
|
+
".zip",
|
|
1219
|
+
".tar",
|
|
1220
|
+
".gz",
|
|
1221
|
+
".bz2",
|
|
1222
|
+
".7z",
|
|
1223
|
+
".rar",
|
|
1224
|
+
".xz",
|
|
1225
|
+
// Documents
|
|
1226
|
+
".pdf",
|
|
1227
|
+
".doc",
|
|
1228
|
+
".docx",
|
|
1229
|
+
".xls",
|
|
1230
|
+
".xlsx",
|
|
1231
|
+
".ppt",
|
|
1232
|
+
".pptx",
|
|
1233
|
+
// Executables
|
|
1234
|
+
".exe",
|
|
1235
|
+
".dll",
|
|
1236
|
+
".so",
|
|
1237
|
+
".dylib",
|
|
1238
|
+
".a",
|
|
1239
|
+
".o",
|
|
1240
|
+
".class",
|
|
1241
|
+
".pyc",
|
|
1242
|
+
".pyo",
|
|
1243
|
+
".wasm",
|
|
1244
|
+
// Lock files (large, not useful for search)
|
|
1245
|
+
".lock",
|
|
1246
|
+
// Misc binary
|
|
1247
|
+
".db",
|
|
1248
|
+
".sqlite",
|
|
1249
|
+
".sqlite3",
|
|
1250
|
+
".bin",
|
|
1251
|
+
".dat"
|
|
1252
|
+
]);
|
|
1253
|
+
function loadCodemapIgnore(projectDir) {
|
|
1254
|
+
const ignoreFile = path.join(projectDir, ".codemapignore");
|
|
1255
|
+
try {
|
|
1256
|
+
const raw = fs.readFileSync(ignoreFile, "utf-8");
|
|
1257
|
+
return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
1258
|
+
} catch {
|
|
1259
|
+
return [];
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
function matchesDenyPattern(filePath, pattern) {
|
|
1263
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
1264
|
+
let pat = pattern.replace(/\\/g, "/");
|
|
1265
|
+
const isDir = pat.endsWith("/");
|
|
1266
|
+
if (isDir) pat = pat.slice(0, -1);
|
|
1267
|
+
let regex = "";
|
|
1268
|
+
const anchorStart = pat.startsWith("/");
|
|
1269
|
+
if (anchorStart) {
|
|
1270
|
+
regex = "^";
|
|
1271
|
+
} else {
|
|
1272
|
+
regex = "(^|.*/)";
|
|
1273
|
+
}
|
|
1274
|
+
let i = pat.startsWith("/") ? 1 : 0;
|
|
1275
|
+
while (i < pat.length) {
|
|
1276
|
+
if (pat[i] === "*" && pat[i + 1] === "*") {
|
|
1277
|
+
regex += ".*";
|
|
1278
|
+
i += 2;
|
|
1279
|
+
if (pat[i] === "/") i++;
|
|
1280
|
+
} else if (pat[i] === "*") {
|
|
1281
|
+
regex += "[^/]*";
|
|
1282
|
+
i++;
|
|
1283
|
+
} else if (pat[i] === "?") {
|
|
1284
|
+
regex += "[^/]";
|
|
1285
|
+
i++;
|
|
1286
|
+
} else if (pat[i] === ".") {
|
|
1287
|
+
regex += "\\.";
|
|
1288
|
+
i++;
|
|
1289
|
+
} else {
|
|
1290
|
+
regex += pat[i];
|
|
1291
|
+
i++;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
regex += isDir ? "(/.*)?$" : "$";
|
|
1295
|
+
return new RegExp(regex).test(normalized);
|
|
1296
|
+
}
|
|
1297
|
+
function hashFileContent(filePath) {
|
|
1298
|
+
const content = fs.readFileSync(filePath);
|
|
1299
|
+
return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
1300
|
+
}
|
|
1301
|
+
function scanRepository(projectDir, config) {
|
|
1302
|
+
const ignorePatterns = loadCodemapIgnore(projectDir);
|
|
1303
|
+
const allDeny = [...config.deny, ...ignorePatterns];
|
|
1304
|
+
let rawFiles;
|
|
1305
|
+
try {
|
|
1306
|
+
rawFiles = execFileSync("git", ["ls-files", "--exclude-standard"], {
|
|
1307
|
+
cwd: projectDir,
|
|
1308
|
+
encoding: "utf-8",
|
|
1309
|
+
maxBuffer: 50 * 1024 * 1024
|
|
1310
|
+
});
|
|
1311
|
+
} catch {
|
|
1312
|
+
rawFiles = "";
|
|
1313
|
+
}
|
|
1314
|
+
const files = rawFiles.split("\n").map((f) => f.trim()).filter((f) => f.length > 0);
|
|
1315
|
+
const results = [];
|
|
1316
|
+
for (const relPath of files) {
|
|
1317
|
+
const ext = path.extname(relPath).toLowerCase();
|
|
1318
|
+
if (BINARY_EXTENSIONS.has(ext)) continue;
|
|
1319
|
+
if (allDeny.some((pattern) => matchesDenyPattern(relPath, pattern))) continue;
|
|
1320
|
+
const absPath = path.join(projectDir, relPath);
|
|
1321
|
+
let stat;
|
|
1322
|
+
try {
|
|
1323
|
+
stat = fs.statSync(absPath);
|
|
1324
|
+
} catch {
|
|
1325
|
+
continue;
|
|
1326
|
+
}
|
|
1327
|
+
if (!stat.isFile()) continue;
|
|
1328
|
+
if (stat.size === 0) continue;
|
|
1329
|
+
if (stat.size > config.maxFileSize) continue;
|
|
1330
|
+
results.push({
|
|
1331
|
+
filePath: relPath,
|
|
1332
|
+
contentHash: hashFileContent(absPath),
|
|
1333
|
+
mtime: stat.mtimeMs,
|
|
1334
|
+
sizeBytes: stat.size
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
results.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
1338
|
+
return results;
|
|
1339
|
+
}
|
|
1340
|
+
var TS_JS_PATTERNS = [
|
|
1341
|
+
{ re: /^(?:export\s+)?(?:async\s+)?function\s+\w+/, type: "function" },
|
|
1342
|
+
{ re: /^(?:export\s+)?(?:abstract\s+)?class\s+\w+/, type: "class" },
|
|
1343
|
+
{ re: /^(?:export\s+)?(?:default\s+)?interface\s+\w+/, type: "class" },
|
|
1344
|
+
{ re: /^(?:export\s+)?type\s+\w+\s*=/, type: "block" },
|
|
1345
|
+
{ re: /^(?:export\s+)?(?:const|let|var)\s+\w+\s*=\s*(?:async\s+)?\(/, type: "function" },
|
|
1346
|
+
{ re: /^(?:export\s+)?enum\s+\w+/, type: "block" },
|
|
1347
|
+
{ re: /^\s+(?:(?:public|private|protected|static|async|readonly|abstract|override)\s+)*\w+\s*\(/, type: "method" }
|
|
1348
|
+
];
|
|
1349
|
+
var PYTHON_PATTERNS = [
|
|
1350
|
+
{ re: /^(?:async\s+)?def\s+\w+/, type: "function" },
|
|
1351
|
+
{ re: /^class\s+\w+/, type: "class" },
|
|
1352
|
+
{ re: /^\s+(?:async\s+)?def\s+\w+/, type: "method" }
|
|
1353
|
+
];
|
|
1354
|
+
var GO_PATTERNS = [
|
|
1355
|
+
{ re: /^func\s+(?:\(\w+\s+\*?\w+\)\s+)?\w+/, type: "function" },
|
|
1356
|
+
{ re: /^type\s+\w+\s+struct\s*\{/, type: "class" },
|
|
1357
|
+
{ re: /^type\s+\w+\s+interface\s*\{/, type: "class" }
|
|
1358
|
+
];
|
|
1359
|
+
var RUST_PATTERNS = [
|
|
1360
|
+
{ re: /^(?:pub(?:\(crate\))?\s+)?(?:async\s+)?fn\s+\w+/, type: "function" },
|
|
1361
|
+
{ re: /^(?:pub(?:\(crate\))?\s+)?struct\s+\w+/, type: "class" },
|
|
1362
|
+
{ re: /^(?:pub(?:\(crate\))?\s+)?enum\s+\w+/, type: "class" },
|
|
1363
|
+
{ re: /^(?:pub(?:\(crate\))?\s+)?trait\s+\w+/, type: "class" },
|
|
1364
|
+
{ re: /^impl(?:<[^>]*>)?\s+\w+/, type: "class" }
|
|
1365
|
+
];
|
|
1366
|
+
var JAVA_PATTERNS = [
|
|
1367
|
+
{ re: /^(?:public|private|protected)?\s*(?:static\s+)?(?:abstract\s+)?class\s+\w+/, type: "class" },
|
|
1368
|
+
{ re: /^(?:public|private|protected)?\s*(?:static\s+)?interface\s+\w+/, type: "class" },
|
|
1369
|
+
{ re: /^(?:public|private|protected)?\s*(?:static\s+)?(?:abstract\s+)?\w+(?:<[^>]*>)?\s+\w+\s*\(/, type: "method" }
|
|
1370
|
+
];
|
|
1371
|
+
function getPatternsForExt(ext) {
|
|
1372
|
+
switch (ext) {
|
|
1373
|
+
case ".ts":
|
|
1374
|
+
case ".tsx":
|
|
1375
|
+
case ".js":
|
|
1376
|
+
case ".jsx":
|
|
1377
|
+
case ".mts":
|
|
1378
|
+
case ".mjs":
|
|
1379
|
+
case ".cts":
|
|
1380
|
+
case ".cjs":
|
|
1381
|
+
return TS_JS_PATTERNS;
|
|
1382
|
+
case ".py":
|
|
1383
|
+
case ".pyi":
|
|
1384
|
+
return PYTHON_PATTERNS;
|
|
1385
|
+
case ".go":
|
|
1386
|
+
return GO_PATTERNS;
|
|
1387
|
+
case ".rs":
|
|
1388
|
+
return RUST_PATTERNS;
|
|
1389
|
+
case ".java":
|
|
1390
|
+
case ".kt":
|
|
1391
|
+
case ".kts":
|
|
1392
|
+
return JAVA_PATTERNS;
|
|
1393
|
+
default:
|
|
1394
|
+
return [];
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
function detectBoundaries(lines, ext) {
|
|
1398
|
+
const patterns = getPatternsForExt(ext);
|
|
1399
|
+
if (patterns.length === 0) return [];
|
|
1400
|
+
const boundaries = [];
|
|
1401
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1402
|
+
const trimmed = lines[i].trimStart();
|
|
1403
|
+
for (const { re } of patterns) {
|
|
1404
|
+
if (re.test(trimmed)) {
|
|
1405
|
+
boundaries.push(i);
|
|
1406
|
+
break;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
return boundaries;
|
|
1411
|
+
}
|
|
1412
|
+
function chunkId(filePath, startLine, endLine) {
|
|
1413
|
+
const hash = crypto2.createHash("sha256").update(`${filePath}:${startLine}:${endLine}`).digest("hex").slice(0, 12);
|
|
1414
|
+
return `${filePath}:${startLine}-${endLine}:${hash}`;
|
|
1415
|
+
}
|
|
1416
|
+
function chunkHash(content) {
|
|
1417
|
+
return crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
1418
|
+
}
|
|
1419
|
+
function chunkByAST(filePath, lines, config) {
|
|
1420
|
+
const ext = path2.extname(filePath).toLowerCase();
|
|
1421
|
+
const boundaries = detectBoundaries(lines, ext);
|
|
1422
|
+
if (boundaries.length === 0) {
|
|
1423
|
+
return chunkBySlidingWindow(filePath, lines, config);
|
|
1424
|
+
}
|
|
1425
|
+
const chunks = [];
|
|
1426
|
+
for (let i = 0; i < boundaries.length; i++) {
|
|
1427
|
+
const start = Math.max(0, boundaries[i] - config.contextLines);
|
|
1428
|
+
const naturalEnd = (i + 1 < boundaries.length ? boundaries[i + 1] : lines.length) - 1;
|
|
1429
|
+
const end = Math.min(start + config.maxChunkSize - 1, naturalEnd);
|
|
1430
|
+
const content = lines.slice(start, end + 1).join("\n");
|
|
1431
|
+
if (content.trim().length === 0) continue;
|
|
1432
|
+
const lineCount = end - start + 1;
|
|
1433
|
+
if (lineCount < config.minChunkSize && i + 1 < boundaries.length) continue;
|
|
1434
|
+
chunks.push({
|
|
1435
|
+
id: chunkId(filePath, start + 1, end + 1),
|
|
1436
|
+
filePath,
|
|
1437
|
+
startLine: start + 1,
|
|
1438
|
+
endLine: end + 1,
|
|
1439
|
+
content,
|
|
1440
|
+
chunkHash: chunkHash(content)
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
if (boundaries[0] > config.contextLines) {
|
|
1444
|
+
const preEnd = boundaries[0] - 1;
|
|
1445
|
+
const preContent = lines.slice(0, preEnd + 1).join("\n");
|
|
1446
|
+
if (preContent.trim().length > 0 && preEnd + 1 >= config.minChunkSize) {
|
|
1447
|
+
chunks.unshift({
|
|
1448
|
+
id: chunkId(filePath, 1, preEnd + 1),
|
|
1449
|
+
filePath,
|
|
1450
|
+
startLine: 1,
|
|
1451
|
+
endLine: preEnd + 1,
|
|
1452
|
+
content: preContent,
|
|
1453
|
+
chunkHash: chunkHash(preContent)
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
return chunks;
|
|
1458
|
+
}
|
|
1459
|
+
function chunkBySlidingWindow(filePath, lines, config) {
|
|
1460
|
+
const chunks = [];
|
|
1461
|
+
const step = config.windowSize - config.overlap;
|
|
1462
|
+
for (let i = 0; i < lines.length; i += step) {
|
|
1463
|
+
const start = i;
|
|
1464
|
+
const end = Math.min(i + config.windowSize - 1, lines.length - 1);
|
|
1465
|
+
const content = lines.slice(start, end + 1).join("\n");
|
|
1466
|
+
if (content.trim().length === 0) continue;
|
|
1467
|
+
chunks.push({
|
|
1468
|
+
id: chunkId(filePath, start + 1, end + 1),
|
|
1469
|
+
filePath,
|
|
1470
|
+
startLine: start + 1,
|
|
1471
|
+
endLine: end + 1,
|
|
1472
|
+
content,
|
|
1473
|
+
chunkHash: chunkHash(content)
|
|
1474
|
+
});
|
|
1475
|
+
if (end >= lines.length - 1) break;
|
|
1476
|
+
}
|
|
1477
|
+
return chunks;
|
|
1478
|
+
}
|
|
1479
|
+
function chunkFile(projectDir, filePath, config) {
|
|
1480
|
+
const absPath = path2.join(projectDir, filePath);
|
|
1481
|
+
let content;
|
|
1482
|
+
try {
|
|
1483
|
+
content = fs2.readFileSync(absPath, "utf-8");
|
|
1484
|
+
} catch {
|
|
1485
|
+
return [];
|
|
1486
|
+
}
|
|
1487
|
+
const lines = content.split("\n");
|
|
1488
|
+
if (lines.length < config.minChunkSize) {
|
|
1489
|
+
const fullContent = lines.join("\n");
|
|
1490
|
+
if (fullContent.trim().length === 0) return [];
|
|
1491
|
+
return [
|
|
1492
|
+
{
|
|
1493
|
+
id: chunkId(filePath, 1, lines.length),
|
|
1494
|
+
filePath,
|
|
1495
|
+
startLine: 1,
|
|
1496
|
+
endLine: lines.length,
|
|
1497
|
+
content: fullContent,
|
|
1498
|
+
chunkHash: chunkHash(fullContent)
|
|
1499
|
+
}
|
|
1500
|
+
];
|
|
1501
|
+
}
|
|
1502
|
+
if (config.strategy === "ast") {
|
|
1503
|
+
return chunkByAST(filePath, lines, config);
|
|
1504
|
+
}
|
|
1505
|
+
return chunkBySlidingWindow(filePath, lines, config);
|
|
1506
|
+
}
|
|
1507
|
+
function chunkFiles(projectDir, filePaths, config) {
|
|
1508
|
+
const allChunks = [];
|
|
1509
|
+
for (const fp of filePaths) {
|
|
1510
|
+
allChunks.push(...chunkFile(projectDir, fp, config));
|
|
1511
|
+
}
|
|
1512
|
+
return allChunks;
|
|
1513
|
+
}
|
|
1514
|
+
var CodemapStore = class {
|
|
1515
|
+
constructor(lanceDir, dimensions) {
|
|
1516
|
+
this.lanceDir = lanceDir;
|
|
1517
|
+
this.dimensions = dimensions;
|
|
1518
|
+
}
|
|
1519
|
+
db = null;
|
|
1520
|
+
table = null;
|
|
1521
|
+
/**
|
|
1522
|
+
* Initialize the LanceDB connection and open/create the chunks table.
|
|
1523
|
+
*/
|
|
1524
|
+
async initialize() {
|
|
1525
|
+
fs3.mkdirSync(this.lanceDir, { recursive: true });
|
|
1526
|
+
const { connect } = await import("@lancedb/lancedb");
|
|
1527
|
+
this.db = await connect(this.lanceDir);
|
|
1528
|
+
const tableNames = await this.db.tableNames();
|
|
1529
|
+
if (tableNames.includes("chunks")) {
|
|
1530
|
+
this.table = await this.db.openTable("chunks");
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* Drop the existing chunks table (used before full re-index).
|
|
1535
|
+
*/
|
|
1536
|
+
async dropTable() {
|
|
1537
|
+
if (!this.db) return;
|
|
1538
|
+
try {
|
|
1539
|
+
const tableNames = await this.db.tableNames();
|
|
1540
|
+
if (tableNames.includes("chunks")) {
|
|
1541
|
+
await this.db.dropTable("chunks");
|
|
1542
|
+
}
|
|
1543
|
+
} catch {
|
|
1544
|
+
}
|
|
1545
|
+
this.table = null;
|
|
1546
|
+
}
|
|
1547
|
+
async reinitialize() {
|
|
1548
|
+
this.table = null;
|
|
1549
|
+
fs3.mkdirSync(this.lanceDir, { recursive: true });
|
|
1550
|
+
const { connect } = await import("@lancedb/lancedb");
|
|
1551
|
+
this.db = await connect(this.lanceDir);
|
|
1552
|
+
const tableNames = await this.db.tableNames();
|
|
1553
|
+
if (tableNames.includes("chunks")) {
|
|
1554
|
+
this.table = await this.db.openTable("chunks");
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
isCorruptionError(err) {
|
|
1558
|
+
const msg = err.message ?? "";
|
|
1559
|
+
return msg.includes("Not found") || msg.includes("No such file");
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1562
|
+
* Append items to the store (bulk add for initial indexing).
|
|
1563
|
+
*/
|
|
1564
|
+
async appendItems(items) {
|
|
1565
|
+
if (!this.db || items.length === 0) return;
|
|
1566
|
+
const rows = items.map((item) => ({
|
|
1567
|
+
id: item.id,
|
|
1568
|
+
vector: item.vector,
|
|
1569
|
+
filePath: item.filePath,
|
|
1570
|
+
startLine: item.startLine,
|
|
1571
|
+
endLine: item.endLine,
|
|
1572
|
+
snippet: item.snippet
|
|
1573
|
+
}));
|
|
1574
|
+
if (!this.table) {
|
|
1575
|
+
this.table = await this.db.createTable("chunks", rows);
|
|
1576
|
+
} else {
|
|
1577
|
+
try {
|
|
1578
|
+
await this.table.add(rows);
|
|
1579
|
+
} catch (err) {
|
|
1580
|
+
if (this.isCorruptionError(err)) {
|
|
1581
|
+
await this.reinitialize();
|
|
1582
|
+
this.table = await this.db.createTable("chunks", rows);
|
|
1583
|
+
} else {
|
|
1584
|
+
throw err;
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Upsert items using merge-insert semantics (for incremental updates).
|
|
1591
|
+
*/
|
|
1592
|
+
async upsertItems(items) {
|
|
1593
|
+
if (!this.db || items.length === 0) return;
|
|
1594
|
+
if (!this.table) {
|
|
1595
|
+
await this.appendItems(items);
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
const rows = items.map((item) => ({
|
|
1599
|
+
id: item.id,
|
|
1600
|
+
vector: item.vector,
|
|
1601
|
+
filePath: item.filePath,
|
|
1602
|
+
startLine: item.startLine,
|
|
1603
|
+
endLine: item.endLine,
|
|
1604
|
+
snippet: item.snippet
|
|
1605
|
+
}));
|
|
1606
|
+
try {
|
|
1607
|
+
await this.table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(rows);
|
|
1608
|
+
} catch (err) {
|
|
1609
|
+
if (this.isCorruptionError(err)) {
|
|
1610
|
+
await this.reinitialize();
|
|
1611
|
+
this.table = await this.db.createTable("chunks", rows);
|
|
1612
|
+
} else {
|
|
1613
|
+
throw err;
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Remove items by chunk IDs.
|
|
1619
|
+
*/
|
|
1620
|
+
async removeItems(ids) {
|
|
1621
|
+
if (!this.table || ids.length === 0) return;
|
|
1622
|
+
const BATCH = 500;
|
|
1623
|
+
for (let i = 0; i < ids.length; i += BATCH) {
|
|
1624
|
+
const batch = ids.slice(i, i + BATCH);
|
|
1625
|
+
const escaped = batch.map((id) => `'${id.replace(/'/g, "''")}'`).join(", ");
|
|
1626
|
+
try {
|
|
1627
|
+
await this.table.delete(`id IN (${escaped})`);
|
|
1628
|
+
} catch {
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Vector similarity search using cosine distance.
|
|
1634
|
+
*/
|
|
1635
|
+
async query(vector, topK) {
|
|
1636
|
+
if (!this.table) return [];
|
|
1637
|
+
try {
|
|
1638
|
+
return await this.executeQuery(vector, topK);
|
|
1639
|
+
} catch (err) {
|
|
1640
|
+
if (this.isCorruptionError(err)) {
|
|
1641
|
+
await this.reinitialize();
|
|
1642
|
+
if (!this.table) return [];
|
|
1643
|
+
return this.executeQuery(vector, topK);
|
|
1644
|
+
}
|
|
1645
|
+
throw err;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
async executeQuery(vector, topK) {
|
|
1649
|
+
const q = this.table.search(vector);
|
|
1650
|
+
const vq = q;
|
|
1651
|
+
const results = await vq.distanceType("cosine").limit(topK).toArray();
|
|
1652
|
+
return results.map((row) => ({
|
|
1653
|
+
id: row.id,
|
|
1654
|
+
filePath: row.filePath,
|
|
1655
|
+
startLine: row.startLine,
|
|
1656
|
+
endLine: row.endLine,
|
|
1657
|
+
snippet: row.snippet,
|
|
1658
|
+
score: 1 - row._distance
|
|
1659
|
+
}));
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* Get the total number of indexed chunks.
|
|
1663
|
+
*/
|
|
1664
|
+
async getItemCount() {
|
|
1665
|
+
if (!this.table) return 0;
|
|
1666
|
+
return this.table.countRows();
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Get the on-disk size of the index in bytes.
|
|
1670
|
+
*/
|
|
1671
|
+
getIndexSizeBytes() {
|
|
1672
|
+
return dirSizeSync(this.lanceDir);
|
|
1673
|
+
}
|
|
1674
|
+
/**
|
|
1675
|
+
* Release native resources.
|
|
1676
|
+
*/
|
|
1677
|
+
async close() {
|
|
1678
|
+
this.table = null;
|
|
1679
|
+
this.db = null;
|
|
1680
|
+
}
|
|
1681
|
+
};
|
|
1682
|
+
function saveSchema(dir, schema) {
|
|
1683
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
1684
|
+
const file = path3.join(dir, "schema.json");
|
|
1685
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
1686
|
+
fs3.writeFileSync(tmp, JSON.stringify(schema, null, 2));
|
|
1687
|
+
fs3.renameSync(tmp, file);
|
|
1688
|
+
}
|
|
1689
|
+
function dirSizeSync(dir) {
|
|
1690
|
+
let total = 0;
|
|
1691
|
+
try {
|
|
1692
|
+
const entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
1693
|
+
for (const entry of entries) {
|
|
1694
|
+
const full = path3.join(dir, entry.name);
|
|
1695
|
+
if (entry.isDirectory()) {
|
|
1696
|
+
total += dirSizeSync(full);
|
|
1697
|
+
} else {
|
|
1698
|
+
total += fs3.statSync(full).size;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
} catch {
|
|
1702
|
+
}
|
|
1703
|
+
return total;
|
|
1704
|
+
}
|
|
1705
|
+
function chunkToVectorItem(chunk, vector) {
|
|
1706
|
+
return {
|
|
1707
|
+
id: chunk.id,
|
|
1708
|
+
vector,
|
|
1709
|
+
filePath: chunk.filePath,
|
|
1710
|
+
startLine: chunk.startLine,
|
|
1711
|
+
endLine: chunk.endLine,
|
|
1712
|
+
snippet: chunk.content.slice(0, 200)
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
var K1 = 1.2;
|
|
1716
|
+
var B = 0.75;
|
|
1717
|
+
var MIN_TOKEN_LEN = 2;
|
|
1718
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
1719
|
+
"a",
|
|
1720
|
+
"an",
|
|
1721
|
+
"and",
|
|
1722
|
+
"are",
|
|
1723
|
+
"as",
|
|
1724
|
+
"at",
|
|
1725
|
+
"be",
|
|
1726
|
+
"but",
|
|
1727
|
+
"by",
|
|
1728
|
+
"for",
|
|
1729
|
+
"from",
|
|
1730
|
+
"had",
|
|
1731
|
+
"has",
|
|
1732
|
+
"have",
|
|
1733
|
+
"he",
|
|
1734
|
+
"her",
|
|
1735
|
+
"his",
|
|
1736
|
+
"if",
|
|
1737
|
+
"in",
|
|
1738
|
+
"into",
|
|
1739
|
+
"is",
|
|
1740
|
+
"it",
|
|
1741
|
+
"its",
|
|
1742
|
+
"no",
|
|
1743
|
+
"not",
|
|
1744
|
+
"of",
|
|
1745
|
+
"on",
|
|
1746
|
+
"or",
|
|
1747
|
+
"so",
|
|
1748
|
+
"such",
|
|
1749
|
+
"that",
|
|
1750
|
+
"the",
|
|
1751
|
+
"their",
|
|
1752
|
+
"then",
|
|
1753
|
+
"there",
|
|
1754
|
+
"these",
|
|
1755
|
+
"they",
|
|
1756
|
+
"this",
|
|
1757
|
+
"to",
|
|
1758
|
+
"was",
|
|
1759
|
+
"which",
|
|
1760
|
+
"with"
|
|
1761
|
+
]);
|
|
1762
|
+
function tokenize(text2) {
|
|
1763
|
+
return text2.toLowerCase().split(/[^a-z0-9_]+/).filter((t) => t.length >= MIN_TOKEN_LEN && !STOP_WORDS.has(t));
|
|
1764
|
+
}
|
|
1765
|
+
function buildBM25Index(docs) {
|
|
1766
|
+
const index = {
|
|
1767
|
+
docCount: 0,
|
|
1768
|
+
avgDocLen: 0,
|
|
1769
|
+
df: {},
|
|
1770
|
+
tf: {},
|
|
1771
|
+
docLen: {},
|
|
1772
|
+
docPaths: {}
|
|
1773
|
+
};
|
|
1774
|
+
let totalLen = 0;
|
|
1775
|
+
for (const doc of docs) {
|
|
1776
|
+
const tokens = tokenize(doc.content);
|
|
1777
|
+
index.docCount++;
|
|
1778
|
+
index.docLen[doc.id] = tokens.length;
|
|
1779
|
+
index.docPaths[doc.id] = doc.filePath;
|
|
1780
|
+
totalLen += tokens.length;
|
|
1781
|
+
const termFreq = {};
|
|
1782
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1783
|
+
for (const token of tokens) {
|
|
1784
|
+
termFreq[token] = (termFreq[token] ?? 0) + 1;
|
|
1785
|
+
if (!seen.has(token)) {
|
|
1786
|
+
seen.add(token);
|
|
1787
|
+
index.df[token] = (index.df[token] ?? 0) + 1;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
index.tf[doc.id] = termFreq;
|
|
1791
|
+
}
|
|
1792
|
+
index.avgDocLen = index.docCount > 0 ? totalLen / index.docCount : 0;
|
|
1793
|
+
return index;
|
|
1794
|
+
}
|
|
1795
|
+
function addDocuments(index, docs) {
|
|
1796
|
+
let totalLen = index.avgDocLen * index.docCount;
|
|
1797
|
+
for (const doc of docs) {
|
|
1798
|
+
if (index.tf[doc.id]) {
|
|
1799
|
+
removeDocumentInternal(index, doc.id);
|
|
1800
|
+
totalLen = index.avgDocLen * index.docCount;
|
|
1801
|
+
}
|
|
1802
|
+
const tokens = tokenize(doc.content);
|
|
1803
|
+
index.docCount++;
|
|
1804
|
+
index.docLen[doc.id] = tokens.length;
|
|
1805
|
+
index.docPaths[doc.id] = doc.filePath;
|
|
1806
|
+
totalLen += tokens.length;
|
|
1807
|
+
const termFreq = {};
|
|
1808
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1809
|
+
for (const token of tokens) {
|
|
1810
|
+
termFreq[token] = (termFreq[token] ?? 0) + 1;
|
|
1811
|
+
if (!seen.has(token)) {
|
|
1812
|
+
seen.add(token);
|
|
1813
|
+
index.df[token] = (index.df[token] ?? 0) + 1;
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
index.tf[doc.id] = termFreq;
|
|
1817
|
+
}
|
|
1818
|
+
index.avgDocLen = index.docCount > 0 ? totalLen / index.docCount : 0;
|
|
1819
|
+
}
|
|
1820
|
+
function removeDocuments(index, ids) {
|
|
1821
|
+
for (const id of ids) {
|
|
1822
|
+
removeDocumentInternal(index, id);
|
|
1823
|
+
}
|
|
1824
|
+
const totalLen = Object.values(index.docLen).reduce((sum2, len) => sum2 + len, 0);
|
|
1825
|
+
index.avgDocLen = index.docCount > 0 ? totalLen / index.docCount : 0;
|
|
1826
|
+
}
|
|
1827
|
+
function removeDocumentInternal(index, id) {
|
|
1828
|
+
const termFreq = index.tf[id];
|
|
1829
|
+
if (!termFreq) return;
|
|
1830
|
+
for (const term of Object.keys(termFreq)) {
|
|
1831
|
+
index.df[term]--;
|
|
1832
|
+
if (index.df[term] <= 0) delete index.df[term];
|
|
1833
|
+
}
|
|
1834
|
+
delete index.tf[id];
|
|
1835
|
+
delete index.docLen[id];
|
|
1836
|
+
delete index.docPaths[id];
|
|
1837
|
+
index.docCount--;
|
|
1838
|
+
}
|
|
1839
|
+
function queryBM25(index, query, topK) {
|
|
1840
|
+
const queryTokens = tokenize(query);
|
|
1841
|
+
if (queryTokens.length === 0 || index.docCount === 0) return [];
|
|
1842
|
+
const scores = {};
|
|
1843
|
+
for (const term of queryTokens) {
|
|
1844
|
+
const docFreq = index.df[term];
|
|
1845
|
+
if (!docFreq) continue;
|
|
1846
|
+
const idf = Math.log((index.docCount - docFreq + 0.5) / (docFreq + 0.5) + 1);
|
|
1847
|
+
for (const [docId, termFreq] of Object.entries(index.tf)) {
|
|
1848
|
+
const tf = termFreq[term];
|
|
1849
|
+
if (!tf) continue;
|
|
1850
|
+
const dl = index.docLen[docId];
|
|
1851
|
+
const norm = tf + K1 * (1 - B + B * (dl / index.avgDocLen));
|
|
1852
|
+
const termScore = idf * (tf * (K1 + 1) / norm);
|
|
1853
|
+
scores[docId] = (scores[docId] ?? 0) + termScore;
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
return Object.entries(scores).map(([id, score]) => ({ id, filePath: index.docPaths[id], score })).sort((a, b) => b.score - a.score).slice(0, topK);
|
|
1857
|
+
}
|
|
1858
|
+
function saveBM25Index(metadataDir, index) {
|
|
1859
|
+
fs4.mkdirSync(metadataDir, { recursive: true });
|
|
1860
|
+
const file = path4.join(metadataDir, "bm25.json");
|
|
1861
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
1862
|
+
fs4.writeFileSync(tmp, JSON.stringify(index));
|
|
1863
|
+
fs4.renameSync(tmp, file);
|
|
1864
|
+
}
|
|
1865
|
+
function loadBM25Index(metadataDir) {
|
|
1866
|
+
const file = path4.join(metadataDir, "bm25.json");
|
|
1867
|
+
try {
|
|
1868
|
+
return JSON.parse(fs4.readFileSync(file, "utf-8"));
|
|
1869
|
+
} catch {
|
|
1870
|
+
return null;
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
var TS_JS_SYMBOLS = [
|
|
1874
|
+
{ re: /(?:export\s+)?(?:async\s+)?function\s+(\w+)/g, type: "function", nameGroup: 1 },
|
|
1875
|
+
{ re: /(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/g, type: "class", nameGroup: 1 },
|
|
1876
|
+
{ re: /(?:export\s+)?(?:default\s+)?interface\s+(\w+)/g, type: "interface", nameGroup: 1 },
|
|
1877
|
+
{ re: /(?:export\s+)?type\s+(\w+)\s*=/g, type: "type", nameGroup: 1 },
|
|
1878
|
+
{ re: /(?:export\s+)?enum\s+(\w+)/g, type: "enum", nameGroup: 1 },
|
|
1879
|
+
{ re: /(?:export\s+)?const\s+(\w+)\s*=/g, type: "const", nameGroup: 1 },
|
|
1880
|
+
{ re: /(?:export\s+)?(?:let|var)\s+(\w+)\s*=/g, type: "variable", nameGroup: 1 }
|
|
1881
|
+
];
|
|
1882
|
+
var PYTHON_SYMBOLS = [
|
|
1883
|
+
{ re: /(?:async\s+)?def\s+(\w+)/g, type: "function", nameGroup: 1 },
|
|
1884
|
+
{ re: /class\s+(\w+)/g, type: "class", nameGroup: 1 }
|
|
1885
|
+
];
|
|
1886
|
+
var GO_SYMBOLS = [
|
|
1887
|
+
{ re: /func\s+(?:\(\w+\s+\*?\w+\)\s+)?(\w+)/g, type: "function", nameGroup: 1 },
|
|
1888
|
+
{ re: /type\s+(\w+)\s+struct/g, type: "class", nameGroup: 1 },
|
|
1889
|
+
{ re: /type\s+(\w+)\s+interface/g, type: "interface", nameGroup: 1 }
|
|
1890
|
+
];
|
|
1891
|
+
var RUST_SYMBOLS = [
|
|
1892
|
+
{ re: /(?:pub(?:\(crate\))?\s+)?(?:async\s+)?fn\s+(\w+)/g, type: "function", nameGroup: 1 },
|
|
1893
|
+
{ re: /(?:pub(?:\(crate\))?\s+)?struct\s+(\w+)/g, type: "class", nameGroup: 1 },
|
|
1894
|
+
{ re: /(?:pub(?:\(crate\))?\s+)?enum\s+(\w+)/g, type: "enum", nameGroup: 1 },
|
|
1895
|
+
{ re: /(?:pub(?:\(crate\))?\s+)?trait\s+(\w+)/g, type: "interface", nameGroup: 1 }
|
|
1896
|
+
];
|
|
1897
|
+
var JAVA_SYMBOLS = [
|
|
1898
|
+
{ re: /(?:public|private|protected)?\s*(?:static\s+)?(?:abstract\s+)?class\s+(\w+)/g, type: "class", nameGroup: 1 },
|
|
1899
|
+
{ re: /(?:public|private|protected)?\s*(?:static\s+)?interface\s+(\w+)/g, type: "interface", nameGroup: 1 },
|
|
1900
|
+
{ re: /(?:public|private|protected)?\s*(?:static\s+)?enum\s+(\w+)/g, type: "enum", nameGroup: 1 }
|
|
1901
|
+
];
|
|
1902
|
+
function getSymbolPatterns(ext) {
|
|
1903
|
+
switch (ext) {
|
|
1904
|
+
case ".ts":
|
|
1905
|
+
case ".tsx":
|
|
1906
|
+
case ".js":
|
|
1907
|
+
case ".jsx":
|
|
1908
|
+
case ".mts":
|
|
1909
|
+
case ".mjs":
|
|
1910
|
+
case ".cts":
|
|
1911
|
+
case ".cjs":
|
|
1912
|
+
return TS_JS_SYMBOLS;
|
|
1913
|
+
case ".py":
|
|
1914
|
+
case ".pyi":
|
|
1915
|
+
return PYTHON_SYMBOLS;
|
|
1916
|
+
case ".go":
|
|
1917
|
+
return GO_SYMBOLS;
|
|
1918
|
+
case ".rs":
|
|
1919
|
+
return RUST_SYMBOLS;
|
|
1920
|
+
case ".java":
|
|
1921
|
+
case ".kt":
|
|
1922
|
+
case ".kts":
|
|
1923
|
+
return JAVA_SYMBOLS;
|
|
1924
|
+
default:
|
|
1925
|
+
return [];
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
function extractSymbols(chunk) {
|
|
1929
|
+
const ext = path5.extname(chunk.filePath).toLowerCase();
|
|
1930
|
+
const patterns = getSymbolPatterns(ext);
|
|
1931
|
+
if (patterns.length === 0) return [];
|
|
1932
|
+
const symbols = [];
|
|
1933
|
+
const lines = chunk.content.split("\n");
|
|
1934
|
+
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
1935
|
+
const line = lines[lineIdx];
|
|
1936
|
+
for (const { re, type, nameGroup } of patterns) {
|
|
1937
|
+
re.lastIndex = 0;
|
|
1938
|
+
let match;
|
|
1939
|
+
while ((match = re.exec(line)) !== null) {
|
|
1940
|
+
const name = match[nameGroup];
|
|
1941
|
+
if (!name || name.length < 2) continue;
|
|
1942
|
+
symbols.push({
|
|
1943
|
+
name,
|
|
1944
|
+
symbolType: type,
|
|
1945
|
+
filePath: chunk.filePath,
|
|
1946
|
+
startLine: chunk.startLine + lineIdx,
|
|
1947
|
+
endLine: chunk.endLine,
|
|
1948
|
+
chunkId: chunk.id
|
|
1949
|
+
});
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
return symbols;
|
|
1954
|
+
}
|
|
1955
|
+
function extractSymbolsFromChunks(chunks) {
|
|
1956
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1957
|
+
const allSymbols = [];
|
|
1958
|
+
for (const chunk of chunks) {
|
|
1959
|
+
const symbols = extractSymbols(chunk);
|
|
1960
|
+
for (const sym of symbols) {
|
|
1961
|
+
const key = `${sym.name}:${sym.filePath}:${sym.startLine}`;
|
|
1962
|
+
if (seen.has(key)) continue;
|
|
1963
|
+
seen.add(key);
|
|
1964
|
+
allSymbols.push(sym);
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
return allSymbols;
|
|
1968
|
+
}
|
|
1969
|
+
function searchSymbols(symbols, query, topK = 20) {
|
|
1970
|
+
const q = query.toLowerCase();
|
|
1971
|
+
const results = [];
|
|
1972
|
+
for (const entry of symbols) {
|
|
1973
|
+
const name = entry.name.toLowerCase();
|
|
1974
|
+
let score = 0;
|
|
1975
|
+
if (name === q) {
|
|
1976
|
+
score = 1;
|
|
1977
|
+
} else if (name.startsWith(q)) {
|
|
1978
|
+
score = 0.8;
|
|
1979
|
+
} else if (name.includes(q)) {
|
|
1980
|
+
score = 0.5;
|
|
1981
|
+
} else {
|
|
1982
|
+
continue;
|
|
1983
|
+
}
|
|
1984
|
+
results.push({ entry, score });
|
|
1985
|
+
}
|
|
1986
|
+
results.sort((a, b) => b.score - a.score || a.entry.name.localeCompare(b.entry.name));
|
|
1987
|
+
return results.slice(0, topK);
|
|
1988
|
+
}
|
|
1989
|
+
function saveSymbolIndex(metadataDir, symbols) {
|
|
1990
|
+
fs5.mkdirSync(metadataDir, { recursive: true });
|
|
1991
|
+
const file = path5.join(metadataDir, "symbols.jsonl");
|
|
1992
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
1993
|
+
const content = symbols.map((s) => JSON.stringify(s)).join("\n");
|
|
1994
|
+
fs5.writeFileSync(tmp, content);
|
|
1995
|
+
fs5.renameSync(tmp, file);
|
|
1996
|
+
}
|
|
1997
|
+
function loadSymbolIndex(metadataDir) {
|
|
1998
|
+
const file = path5.join(metadataDir, "symbols.jsonl");
|
|
1999
|
+
try {
|
|
2000
|
+
const raw = fs5.readFileSync(file, "utf-8");
|
|
2001
|
+
return raw.split("\n").filter((line) => line.trim().length > 0).map((line) => JSON.parse(line));
|
|
2002
|
+
} catch {
|
|
2003
|
+
return [];
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
var POLL_MS = 500;
|
|
2007
|
+
var ACQUIRE_TIMEOUT_MS = 1e4;
|
|
2008
|
+
var STALE_TIMEOUT_MS = 6e4;
|
|
2009
|
+
function lockPath(projectDir, branch) {
|
|
2010
|
+
const projHash = crypto3.createHash("sha256").update(projectDir).digest("hex").slice(0, 12);
|
|
2011
|
+
const branchHash = crypto3.createHash("sha256").update(branch).digest("hex").slice(0, 12);
|
|
2012
|
+
return path6.join(os.tmpdir(), `ulpi-codemap-${projHash}-${branchHash}.lock`);
|
|
2013
|
+
}
|
|
2014
|
+
function isPidAlive(pid) {
|
|
2015
|
+
try {
|
|
2016
|
+
process.kill(pid, 0);
|
|
2017
|
+
return true;
|
|
2018
|
+
} catch {
|
|
2019
|
+
return false;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
function readLock(file) {
|
|
2023
|
+
try {
|
|
2024
|
+
const raw = fs6.readFileSync(file, "utf-8");
|
|
2025
|
+
return JSON.parse(raw);
|
|
2026
|
+
} catch {
|
|
2027
|
+
return null;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
function isStale(info) {
|
|
2031
|
+
if (!isPidAlive(info.pid)) return true;
|
|
2032
|
+
return Date.now() - info.timestamp > STALE_TIMEOUT_MS;
|
|
2033
|
+
}
|
|
2034
|
+
async function acquireCodemapLock(projectDir, branch, timeoutMs = ACQUIRE_TIMEOUT_MS) {
|
|
2035
|
+
const file = lockPath(projectDir, branch);
|
|
2036
|
+
const deadline = Date.now() + timeoutMs;
|
|
2037
|
+
while (Date.now() < deadline) {
|
|
2038
|
+
try {
|
|
2039
|
+
const fd = fs6.openSync(file, "wx");
|
|
2040
|
+
const info = { pid: process.pid, timestamp: Date.now() };
|
|
2041
|
+
fs6.writeSync(fd, JSON.stringify(info));
|
|
2042
|
+
fs6.closeSync(fd);
|
|
2043
|
+
return;
|
|
2044
|
+
} catch (err) {
|
|
2045
|
+
if (err.code !== "EEXIST") throw err;
|
|
2046
|
+
}
|
|
2047
|
+
const existing = readLock(file);
|
|
2048
|
+
if (existing && isStale(existing)) {
|
|
2049
|
+
try {
|
|
2050
|
+
fs6.unlinkSync(file);
|
|
2051
|
+
} catch {
|
|
2052
|
+
}
|
|
2053
|
+
continue;
|
|
2054
|
+
}
|
|
2055
|
+
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
2056
|
+
}
|
|
2057
|
+
throw new Error(`Failed to acquire codemap lock within ${timeoutMs}ms (${file})`);
|
|
2058
|
+
}
|
|
2059
|
+
function releaseCodemapLock(projectDir, branch) {
|
|
2060
|
+
const file = lockPath(projectDir, branch);
|
|
2061
|
+
const info = readLock(file);
|
|
2062
|
+
if (!info || info.pid !== process.pid) return;
|
|
2063
|
+
try {
|
|
2064
|
+
fs6.unlinkSync(file);
|
|
2065
|
+
} catch {
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
function saveManifest(branchDir, manifest) {
|
|
2069
|
+
fs7.mkdirSync(branchDir, { recursive: true });
|
|
2070
|
+
const file = path7.join(branchDir, "manifest.json");
|
|
2071
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
2072
|
+
fs7.writeFileSync(tmp, JSON.stringify(manifest, null, 2));
|
|
2073
|
+
fs7.renameSync(tmp, file);
|
|
2074
|
+
}
|
|
2075
|
+
function loadManifest(branchDir) {
|
|
2076
|
+
const file = path7.join(branchDir, "manifest.json");
|
|
2077
|
+
try {
|
|
2078
|
+
return JSON.parse(fs7.readFileSync(file, "utf-8"));
|
|
2079
|
+
} catch {
|
|
2080
|
+
return null;
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
function createManifest(chunkerVersion) {
|
|
2084
|
+
return {
|
|
2085
|
+
version: 1,
|
|
2086
|
+
chunkerVersion,
|
|
2087
|
+
files: {}
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
function diffManifest(manifest, scannedFiles) {
|
|
2091
|
+
const added = [];
|
|
2092
|
+
const changed = [];
|
|
2093
|
+
const removed = [];
|
|
2094
|
+
const currentPaths = new Set(scannedFiles.map((f) => f.filePath));
|
|
2095
|
+
const manifestFiles = manifest?.files ?? {};
|
|
2096
|
+
for (const file of scannedFiles) {
|
|
2097
|
+
const existing = manifestFiles[file.filePath];
|
|
2098
|
+
if (!existing) {
|
|
2099
|
+
added.push(file);
|
|
2100
|
+
} else if (existing.contentHash !== file.contentHash) {
|
|
2101
|
+
changed.push(file);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
for (const filePath of Object.keys(manifestFiles)) {
|
|
2105
|
+
if (!currentPaths.has(filePath)) {
|
|
2106
|
+
removed.push(filePath);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
return { added, changed, removed };
|
|
2110
|
+
}
|
|
2111
|
+
function updateManifestEntries(manifest, files, chunkIds) {
|
|
2112
|
+
for (const file of files) {
|
|
2113
|
+
manifest.files[file.filePath] = {
|
|
2114
|
+
contentHash: file.contentHash,
|
|
2115
|
+
mtime: file.mtime,
|
|
2116
|
+
sizeBytes: file.sizeBytes,
|
|
2117
|
+
chunkIds: chunkIds.get(file.filePath) ?? []
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
function removeManifestEntries(manifest, filePaths) {
|
|
2122
|
+
const removedChunkIds = [];
|
|
2123
|
+
for (const fp of filePaths) {
|
|
2124
|
+
const entry = manifest.files[fp];
|
|
2125
|
+
if (entry) {
|
|
2126
|
+
removedChunkIds.push(...entry.chunkIds);
|
|
2127
|
+
delete manifest.files[fp];
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
return removedChunkIds;
|
|
2131
|
+
}
|
|
2132
|
+
var STORE_BATCH_SIZE = 100;
|
|
2133
|
+
async function runInitPipeline(projectDir, config, options) {
|
|
2134
|
+
const cfg = CodemapConfigSchema.parse(config ?? {});
|
|
2135
|
+
const branch = options?.branch ?? "main";
|
|
2136
|
+
const onProgress = options?.onProgress;
|
|
2137
|
+
const dataDir = options?.dataDir;
|
|
2138
|
+
await acquireCodemapLock(projectDir, branch);
|
|
2139
|
+
try {
|
|
2140
|
+
return await runInitPipelineInner(projectDir, cfg, branch, onProgress, dataDir);
|
|
2141
|
+
} finally {
|
|
2142
|
+
releaseCodemapLock(projectDir, branch);
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
async function runInitPipelineInner(projectDir, config, branch, onProgress, dataDir) {
|
|
2146
|
+
const startMs = Date.now();
|
|
2147
|
+
const lanceDir = codemapLanceDir(projectDir, branch, dataDir);
|
|
2148
|
+
const metadataDir = codemapMetadataDir(projectDir, branch, dataDir);
|
|
2149
|
+
const branchDir = codemapBranchDir(projectDir, branch, dataDir);
|
|
2150
|
+
onProgress?.({ phase: "scanning", message: "Scanning repository..." });
|
|
2151
|
+
const scannedFiles = scanRepository(projectDir, config);
|
|
2152
|
+
onProgress?.({
|
|
2153
|
+
phase: "scanning",
|
|
2154
|
+
message: `Found ${scannedFiles.length} files`,
|
|
2155
|
+
current: scannedFiles.length,
|
|
2156
|
+
total: scannedFiles.length
|
|
2157
|
+
});
|
|
2158
|
+
if (scannedFiles.length === 0) {
|
|
2159
|
+
return emptyStats();
|
|
2160
|
+
}
|
|
2161
|
+
onProgress?.({ phase: "chunking", message: "Chunking files..." });
|
|
2162
|
+
const allChunks = chunkFiles(
|
|
2163
|
+
projectDir,
|
|
2164
|
+
scannedFiles.map((f) => f.filePath),
|
|
2165
|
+
config.chunking
|
|
2166
|
+
);
|
|
2167
|
+
onProgress?.({
|
|
2168
|
+
phase: "chunking",
|
|
2169
|
+
message: `Generated ${allChunks.length} chunks from ${scannedFiles.length} files`,
|
|
2170
|
+
current: allChunks.length,
|
|
2171
|
+
total: allChunks.length
|
|
2172
|
+
});
|
|
2173
|
+
const embedder = await createEmbedder(config.embedding);
|
|
2174
|
+
const store = new CodemapStore(lanceDir, embedder.dimensions);
|
|
2175
|
+
await store.initialize();
|
|
2176
|
+
await store.dropTable();
|
|
2177
|
+
await embedAndStore(embedder, store, allChunks, onProgress);
|
|
2178
|
+
onProgress?.({ phase: "symbols", message: "Extracting symbols..." });
|
|
2179
|
+
const symbols = extractSymbolsFromChunks(allChunks);
|
|
2180
|
+
saveSymbolIndex(metadataDir, symbols);
|
|
2181
|
+
onProgress?.({
|
|
2182
|
+
phase: "symbols",
|
|
2183
|
+
message: `Extracted ${symbols.length} symbols`,
|
|
2184
|
+
current: symbols.length,
|
|
2185
|
+
total: symbols.length
|
|
2186
|
+
});
|
|
2187
|
+
onProgress?.({ phase: "bm25", message: "Building BM25 index..." });
|
|
2188
|
+
const bm25Docs = allChunks.map((c) => ({
|
|
2189
|
+
id: c.id,
|
|
2190
|
+
filePath: c.filePath,
|
|
2191
|
+
content: c.content
|
|
2192
|
+
}));
|
|
2193
|
+
const bm25Index = buildBM25Index(bm25Docs);
|
|
2194
|
+
saveBM25Index(metadataDir, bm25Index);
|
|
2195
|
+
onProgress?.({ phase: "finalizing", message: "Saving metadata..." });
|
|
2196
|
+
const manifest = createManifest(config.chunking.version);
|
|
2197
|
+
const chunkIdsByFile = groupChunkIdsByFile(allChunks);
|
|
2198
|
+
updateManifestEntries(manifest, scannedFiles, chunkIdsByFile);
|
|
2199
|
+
saveManifest(branchDir, manifest);
|
|
2200
|
+
const schema = {
|
|
2201
|
+
formatVersion: 1,
|
|
2202
|
+
vectorStore: { engine: "lancedb", dimensions: embedder.dimensions },
|
|
2203
|
+
embedding: { provider: embedder.provider, model: embedder.model },
|
|
2204
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2205
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2206
|
+
};
|
|
2207
|
+
saveSchema(branchDir, schema);
|
|
2208
|
+
saveCodemapConfig(projectDir, config, dataDir);
|
|
2209
|
+
const stats = {
|
|
2210
|
+
totalFiles: scannedFiles.length,
|
|
2211
|
+
totalChunks: allChunks.length,
|
|
2212
|
+
staleFiles: 0,
|
|
2213
|
+
indexSizeBytes: store.getIndexSizeBytes(),
|
|
2214
|
+
lastFullIndexMs: Date.now() - startMs,
|
|
2215
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
2216
|
+
};
|
|
2217
|
+
saveStats(branchDir, stats);
|
|
2218
|
+
await store.close();
|
|
2219
|
+
onProgress?.({
|
|
2220
|
+
phase: "finalizing",
|
|
2221
|
+
message: `Indexed ${stats.totalFiles} files, ${stats.totalChunks} chunks in ${stats.lastFullIndexMs}ms`
|
|
2222
|
+
});
|
|
2223
|
+
return stats;
|
|
2224
|
+
}
|
|
2225
|
+
async function runIncrementalPipeline(projectDir, changedPaths, options) {
|
|
2226
|
+
const branch = options?.branch ?? "main";
|
|
2227
|
+
const dataDir = options?.dataDir;
|
|
2228
|
+
const branchDir = codemapBranchDir(projectDir, branch, dataDir);
|
|
2229
|
+
const onProgress = options?.onProgress;
|
|
2230
|
+
const config = loadConfig(projectDir, dataDir);
|
|
2231
|
+
const manifest = loadManifest(branchDir);
|
|
2232
|
+
if (!manifest) {
|
|
2233
|
+
return runInitPipeline(projectDir, config, options);
|
|
2234
|
+
}
|
|
2235
|
+
await acquireCodemapLock(projectDir, branch);
|
|
2236
|
+
try {
|
|
2237
|
+
return await runIncrementalInner(projectDir, config, branch, manifest, changedPaths, onProgress, dataDir);
|
|
2238
|
+
} finally {
|
|
2239
|
+
releaseCodemapLock(projectDir, branch);
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
async function runIncrementalInner(projectDir, config, branch, manifest, changedPaths, onProgress, dataDir) {
|
|
2243
|
+
const startMs = Date.now();
|
|
2244
|
+
const lanceDir = codemapLanceDir(projectDir, branch, dataDir);
|
|
2245
|
+
const metadataDir = codemapMetadataDir(projectDir, branch, dataDir);
|
|
2246
|
+
const branchDir = codemapBranchDir(projectDir, branch, dataDir);
|
|
2247
|
+
onProgress?.({ phase: "scanning", message: "Scanning for changes..." });
|
|
2248
|
+
const scannedFiles = scanRepository(projectDir, config);
|
|
2249
|
+
const diff = diffManifest(manifest, scannedFiles);
|
|
2250
|
+
if (changedPaths && changedPaths.length > 0) {
|
|
2251
|
+
const changedSet = new Set(changedPaths);
|
|
2252
|
+
diff.added = diff.added.filter((f) => changedSet.has(f.filePath));
|
|
2253
|
+
diff.changed = diff.changed.filter((f) => changedSet.has(f.filePath));
|
|
2254
|
+
diff.removed = diff.removed.filter((fp) => changedSet.has(fp));
|
|
2255
|
+
}
|
|
2256
|
+
const totalChanges = diff.added.length + diff.changed.length + diff.removed.length;
|
|
2257
|
+
if (totalChanges === 0) {
|
|
2258
|
+
onProgress?.({ phase: "finalizing", message: "No changes detected" });
|
|
2259
|
+
return loadStats(branchDir) ?? emptyStats();
|
|
2260
|
+
}
|
|
2261
|
+
onProgress?.({
|
|
2262
|
+
phase: "scanning",
|
|
2263
|
+
message: `Found ${diff.added.length} added, ${diff.changed.length} changed, ${diff.removed.length} removed`
|
|
2264
|
+
});
|
|
2265
|
+
const embedder = await createEmbedder(config.embedding);
|
|
2266
|
+
const store = new CodemapStore(lanceDir, embedder.dimensions);
|
|
2267
|
+
await store.initialize();
|
|
2268
|
+
const filesToRemove = [...diff.changed.map((f) => f.filePath), ...diff.removed];
|
|
2269
|
+
const removedChunkIds = removeManifestEntries(manifest, filesToRemove);
|
|
2270
|
+
if (removedChunkIds.length > 0) {
|
|
2271
|
+
await store.removeItems(removedChunkIds);
|
|
2272
|
+
}
|
|
2273
|
+
const filesToIndex = [...diff.added, ...diff.changed];
|
|
2274
|
+
if (filesToIndex.length > 0) {
|
|
2275
|
+
onProgress?.({ phase: "chunking", message: `Chunking ${filesToIndex.length} files...` });
|
|
2276
|
+
const newChunks = chunkFiles(
|
|
2277
|
+
projectDir,
|
|
2278
|
+
filesToIndex.map((f) => f.filePath),
|
|
2279
|
+
config.chunking
|
|
2280
|
+
);
|
|
2281
|
+
await embedAndStore(embedder, store, newChunks, onProgress);
|
|
2282
|
+
const chunkIdsByFile = groupChunkIdsByFile(newChunks);
|
|
2283
|
+
updateManifestEntries(manifest, filesToIndex, chunkIdsByFile);
|
|
2284
|
+
const bm25Index = loadBM25Index(metadataDir) ?? buildBM25Index([]);
|
|
2285
|
+
if (removedChunkIds.length > 0) {
|
|
2286
|
+
removeDocuments(bm25Index, removedChunkIds);
|
|
2287
|
+
}
|
|
2288
|
+
addDocuments(
|
|
2289
|
+
bm25Index,
|
|
2290
|
+
newChunks.map((c) => ({ id: c.id, filePath: c.filePath, content: c.content }))
|
|
2291
|
+
);
|
|
2292
|
+
saveBM25Index(metadataDir, bm25Index);
|
|
2293
|
+
const allFilePaths = scannedFiles.map((f) => f.filePath);
|
|
2294
|
+
const allChunks = chunkFiles(projectDir, allFilePaths, config.chunking);
|
|
2295
|
+
const symbols = extractSymbolsFromChunks(allChunks);
|
|
2296
|
+
saveSymbolIndex(metadataDir, symbols);
|
|
2297
|
+
} else {
|
|
2298
|
+
const bm25Index = loadBM25Index(metadataDir);
|
|
2299
|
+
if (bm25Index && removedChunkIds.length > 0) {
|
|
2300
|
+
removeDocuments(bm25Index, removedChunkIds);
|
|
2301
|
+
saveBM25Index(metadataDir, bm25Index);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
saveManifest(branchDir, manifest);
|
|
2305
|
+
const stats = {
|
|
2306
|
+
totalFiles: Object.keys(manifest.files).length,
|
|
2307
|
+
totalChunks: await store.getItemCount(),
|
|
2308
|
+
staleFiles: 0,
|
|
2309
|
+
indexSizeBytes: store.getIndexSizeBytes(),
|
|
2310
|
+
lastFullIndexMs: Date.now() - startMs,
|
|
2311
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
2312
|
+
};
|
|
2313
|
+
saveStats(branchDir, stats);
|
|
2314
|
+
await store.close();
|
|
2315
|
+
onProgress?.({
|
|
2316
|
+
phase: "finalizing",
|
|
2317
|
+
message: `Incremental update: ${totalChanges} changes processed in ${stats.lastFullIndexMs}ms`
|
|
2318
|
+
});
|
|
2319
|
+
return stats;
|
|
2320
|
+
}
|
|
2321
|
+
async function embedAndStore(embedder, store, chunks, onProgress) {
|
|
2322
|
+
if (chunks.length === 0) return;
|
|
2323
|
+
const texts = chunks.map((c) => buildEmbeddingText(c));
|
|
2324
|
+
onProgress?.({
|
|
2325
|
+
phase: "embedding",
|
|
2326
|
+
message: `Embedding ${chunks.length} chunks...`,
|
|
2327
|
+
current: 0,
|
|
2328
|
+
total: chunks.length
|
|
2329
|
+
});
|
|
2330
|
+
await embedAll(embedder, texts, {
|
|
2331
|
+
onProgress: (info) => {
|
|
2332
|
+
onProgress?.({
|
|
2333
|
+
phase: "embedding",
|
|
2334
|
+
message: info.message,
|
|
2335
|
+
current: info.current,
|
|
2336
|
+
total: info.total
|
|
2337
|
+
});
|
|
2338
|
+
},
|
|
2339
|
+
onBatch: async (embeddings, startIndex, count) => {
|
|
2340
|
+
onProgress?.({
|
|
2341
|
+
phase: "storing",
|
|
2342
|
+
message: `Storing batch at offset ${startIndex}...`,
|
|
2343
|
+
current: startIndex + count,
|
|
2344
|
+
total: chunks.length
|
|
2345
|
+
});
|
|
2346
|
+
for (let i = 0; i < count; i += STORE_BATCH_SIZE) {
|
|
2347
|
+
const end = Math.min(i + STORE_BATCH_SIZE, count);
|
|
2348
|
+
const items = [];
|
|
2349
|
+
for (let j = i; j < end; j++) {
|
|
2350
|
+
items.push(chunkToVectorItem(chunks[startIndex + j], embeddings[j]));
|
|
2351
|
+
}
|
|
2352
|
+
await store.appendItems(items);
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
function buildEmbeddingText(chunk) {
|
|
2358
|
+
const header = `File: ${chunk.filePath} (lines ${chunk.startLine}-${chunk.endLine})`;
|
|
2359
|
+
return `${header}
|
|
2360
|
+
${chunk.content}`;
|
|
2361
|
+
}
|
|
2362
|
+
function groupChunkIdsByFile(chunks) {
|
|
2363
|
+
const map = /* @__PURE__ */ new Map();
|
|
2364
|
+
for (const c of chunks) {
|
|
2365
|
+
const ids = map.get(c.filePath) ?? [];
|
|
2366
|
+
ids.push(c.id);
|
|
2367
|
+
map.set(c.filePath, ids);
|
|
2368
|
+
}
|
|
2369
|
+
return map;
|
|
2370
|
+
}
|
|
2371
|
+
function loadConfig(projectDir, dataDir) {
|
|
2372
|
+
try {
|
|
2373
|
+
const raw = fs8.readFileSync(codemapConfigFile(projectDir, dataDir), "utf-8");
|
|
2374
|
+
return CodemapConfigSchema.parse(JSON.parse(raw));
|
|
2375
|
+
} catch {
|
|
2376
|
+
return CodemapConfigSchema.parse({});
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
function saveCodemapConfig(projectDir, config, dataDir) {
|
|
2380
|
+
const file = codemapConfigFile(projectDir, dataDir);
|
|
2381
|
+
const dir = nodePath.dirname(file);
|
|
2382
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
2383
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
2384
|
+
fs8.writeFileSync(tmp, JSON.stringify(config, null, 2));
|
|
2385
|
+
fs8.renameSync(tmp, file);
|
|
2386
|
+
}
|
|
2387
|
+
function saveStats(branchDir, stats) {
|
|
2388
|
+
fs8.mkdirSync(branchDir, { recursive: true });
|
|
2389
|
+
const file = nodePath.join(branchDir, "stats.json");
|
|
2390
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
2391
|
+
fs8.writeFileSync(tmp, JSON.stringify(stats, null, 2));
|
|
2392
|
+
fs8.renameSync(tmp, file);
|
|
2393
|
+
}
|
|
2394
|
+
function loadStats(branchDir) {
|
|
2395
|
+
try {
|
|
2396
|
+
const file = nodePath.join(branchDir, "stats.json");
|
|
2397
|
+
return JSON.parse(fs8.readFileSync(file, "utf-8"));
|
|
2398
|
+
} catch {
|
|
2399
|
+
return null;
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
function emptyStats() {
|
|
2403
|
+
return {
|
|
2404
|
+
totalFiles: 0,
|
|
2405
|
+
totalChunks: 0,
|
|
2406
|
+
staleFiles: 0,
|
|
2407
|
+
indexSizeBytes: 0,
|
|
2408
|
+
lastFullIndexMs: 0,
|
|
2409
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
function normalizeScores(scores) {
|
|
2413
|
+
if (scores.length === 0) return [];
|
|
2414
|
+
const min = Math.min(...scores);
|
|
2415
|
+
const max = Math.max(...scores);
|
|
2416
|
+
const range = max - min;
|
|
2417
|
+
if (range === 0) return scores.map(() => 1);
|
|
2418
|
+
return scores.map((s) => (s - min) / range);
|
|
2419
|
+
}
|
|
2420
|
+
function computePathBoost(filePath, query) {
|
|
2421
|
+
const pathParts = filePath.toLowerCase().replace(/[^a-z0-9]/g, " ").split(/\s+/).filter(Boolean);
|
|
2422
|
+
const queryTerms = query.toLowerCase().replace(/[^a-z0-9]/g, " ").split(/\s+/).filter(Boolean);
|
|
2423
|
+
if (queryTerms.length === 0) return 0;
|
|
2424
|
+
let matches = 0;
|
|
2425
|
+
for (const qt of queryTerms) {
|
|
2426
|
+
if (pathParts.some((pp) => pp.includes(qt))) matches++;
|
|
2427
|
+
}
|
|
2428
|
+
return matches / queryTerms.length;
|
|
2429
|
+
}
|
|
2430
|
+
function fuseResults(vectorResults, bm25Results, symbolResults, graphRanks, query, weights, topK) {
|
|
2431
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
2432
|
+
const vScores = vectorResults.map((r) => r.score);
|
|
2433
|
+
const vNorm = normalizeScores(vScores);
|
|
2434
|
+
for (let i = 0; i < vectorResults.length; i++) {
|
|
2435
|
+
const r = vectorResults[i];
|
|
2436
|
+
candidates.set(r.id, {
|
|
2437
|
+
id: r.id,
|
|
2438
|
+
filePath: r.filePath,
|
|
2439
|
+
startLine: r.startLine,
|
|
2440
|
+
endLine: r.endLine,
|
|
2441
|
+
snippet: r.snippet,
|
|
2442
|
+
vectorScore: vNorm[i],
|
|
2443
|
+
fusedScore: 0
|
|
2444
|
+
});
|
|
2445
|
+
}
|
|
2446
|
+
const bScores = bm25Results.map((r) => r.score);
|
|
2447
|
+
const bNorm = normalizeScores(bScores);
|
|
2448
|
+
for (let i = 0; i < bm25Results.length; i++) {
|
|
2449
|
+
const r = bm25Results[i];
|
|
2450
|
+
const existing = candidates.get(r.id);
|
|
2451
|
+
if (existing) {
|
|
2452
|
+
existing.bm25Score = bNorm[i];
|
|
2453
|
+
} else {
|
|
2454
|
+
candidates.set(r.id, {
|
|
2455
|
+
id: r.id,
|
|
2456
|
+
filePath: r.filePath,
|
|
2457
|
+
startLine: 0,
|
|
2458
|
+
endLine: 0,
|
|
2459
|
+
snippet: "",
|
|
2460
|
+
bm25Score: bNorm[i],
|
|
2461
|
+
fusedScore: 0
|
|
2462
|
+
});
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
for (const sym of symbolResults) {
|
|
2466
|
+
const existing = candidates.get(sym.chunkId);
|
|
2467
|
+
if (existing) {
|
|
2468
|
+
existing.symbolScore = Math.max(existing.symbolScore ?? 0, sym.score);
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
for (const c of candidates.values()) {
|
|
2472
|
+
c.pathBoost = computePathBoost(c.filePath, query);
|
|
2473
|
+
if (graphRanks) {
|
|
2474
|
+
c.graphRank = graphRanks.get(c.filePath) ?? 0;
|
|
2475
|
+
}
|
|
2476
|
+
c.fusedScore = weights.vector * (c.vectorScore ?? 0) + weights.bm25 * (c.bm25Score ?? 0) + weights.symbolBoost * (c.symbolScore ?? 0) + weights.pathBoost * (c.pathBoost ?? 0) + weights.graphRank * (c.graphRank ?? 0);
|
|
2477
|
+
}
|
|
2478
|
+
return Array.from(candidates.values()).sort((a, b) => b.fusedScore - a.fusedScore).slice(0, topK);
|
|
2479
|
+
}
|
|
2480
|
+
var TEST_PATTERNS = [/\btest[s]?\b/i, /__tests__/, /\.test\./, /\.spec\./];
|
|
2481
|
+
var DOC_EXTENSIONS = [".md", ".mdx", ".rst", ".txt"];
|
|
2482
|
+
function isTestFile(filePath) {
|
|
2483
|
+
return TEST_PATTERNS.some((p) => p.test(filePath));
|
|
2484
|
+
}
|
|
2485
|
+
function isDocFile(filePath) {
|
|
2486
|
+
return DOC_EXTENSIONS.some((ext) => filePath.endsWith(ext));
|
|
2487
|
+
}
|
|
2488
|
+
function loadCodemapConfig(projectDir, dataDir) {
|
|
2489
|
+
try {
|
|
2490
|
+
const raw = fs9.readFileSync(codemapConfigFile(projectDir, dataDir), "utf-8");
|
|
2491
|
+
return CodemapConfigSchema.parse(JSON.parse(raw));
|
|
2492
|
+
} catch {
|
|
2493
|
+
return CodemapConfigSchema.parse({});
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
async function searchCode(projectDir, query, options) {
|
|
2497
|
+
const {
|
|
2498
|
+
limit = 10,
|
|
2499
|
+
threshold = 0,
|
|
2500
|
+
pathPrefix,
|
|
2501
|
+
includeTests = true,
|
|
2502
|
+
includeDocs = true,
|
|
2503
|
+
branch = "main",
|
|
2504
|
+
dataDir
|
|
2505
|
+
} = options ?? {};
|
|
2506
|
+
const config = loadCodemapConfig(projectDir, dataDir);
|
|
2507
|
+
const lanceDir = codemapLanceDir(projectDir, branch, dataDir);
|
|
2508
|
+
const metadataDir = codemapMetadataDir(projectDir, branch, dataDir);
|
|
2509
|
+
const embedder = await createEmbedder(config.embedding);
|
|
2510
|
+
const queryVector = await embedOne(embedder, query);
|
|
2511
|
+
const store = new CodemapStore(lanceDir, embedder.dimensions);
|
|
2512
|
+
await store.initialize();
|
|
2513
|
+
try {
|
|
2514
|
+
const hybridConfig = config.hybrid;
|
|
2515
|
+
if (hybridConfig.enabled) {
|
|
2516
|
+
return await hybridSearch(
|
|
2517
|
+
store,
|
|
2518
|
+
metadataDir,
|
|
2519
|
+
queryVector,
|
|
2520
|
+
query,
|
|
2521
|
+
hybridConfig.weights,
|
|
2522
|
+
hybridConfig.vectorK,
|
|
2523
|
+
hybridConfig.bm25K,
|
|
2524
|
+
limit,
|
|
2525
|
+
threshold,
|
|
2526
|
+
pathPrefix,
|
|
2527
|
+
includeTests,
|
|
2528
|
+
includeDocs
|
|
2529
|
+
);
|
|
2530
|
+
}
|
|
2531
|
+
return await vectorOnlySearch(
|
|
2532
|
+
store,
|
|
2533
|
+
queryVector,
|
|
2534
|
+
limit,
|
|
2535
|
+
threshold,
|
|
2536
|
+
pathPrefix,
|
|
2537
|
+
includeTests,
|
|
2538
|
+
includeDocs
|
|
2539
|
+
);
|
|
2540
|
+
} finally {
|
|
2541
|
+
await store.close();
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
async function hybridSearch(store, metadataDir, queryVector, query, weights, vectorK, bm25K, limit, threshold, pathPrefix, includeTests, includeDocs) {
|
|
2545
|
+
const vectorResults = await store.query(queryVector, vectorK);
|
|
2546
|
+
const bm25Index = loadBM25Index(metadataDir);
|
|
2547
|
+
const bm25Results = bm25Index ? queryBM25(bm25Index, query, bm25K) : [];
|
|
2548
|
+
const symbolIndex = loadSymbolIndex(metadataDir);
|
|
2549
|
+
const symbolMatches = searchSymbols(symbolIndex, query, 50);
|
|
2550
|
+
const symbolResults = symbolMatches.map((m) => ({
|
|
2551
|
+
chunkId: m.entry.chunkId,
|
|
2552
|
+
filePath: m.entry.filePath,
|
|
2553
|
+
score: m.score
|
|
2554
|
+
}));
|
|
2555
|
+
let graphRanks = null;
|
|
2556
|
+
if (weights.graphRank > 0) {
|
|
2557
|
+
graphRanks = loadGraphRanks(metadataDir);
|
|
2558
|
+
}
|
|
2559
|
+
const fused = fuseResults(
|
|
2560
|
+
vectorResults,
|
|
2561
|
+
bm25Results,
|
|
2562
|
+
symbolResults,
|
|
2563
|
+
graphRanks,
|
|
2564
|
+
query,
|
|
2565
|
+
weights,
|
|
2566
|
+
Math.max(limit * 3, 80)
|
|
2567
|
+
);
|
|
2568
|
+
return applyFilters(fused, limit, threshold, pathPrefix, includeTests, includeDocs);
|
|
2569
|
+
}
|
|
2570
|
+
async function vectorOnlySearch(store, queryVector, limit, threshold, pathPrefix, includeTests, includeDocs) {
|
|
2571
|
+
const candidateK = Math.min(limit * 3, 80);
|
|
2572
|
+
const results = await store.query(queryVector, candidateK);
|
|
2573
|
+
const fused = results.map((r) => ({
|
|
2574
|
+
...r,
|
|
2575
|
+
vectorScore: r.score,
|
|
2576
|
+
fusedScore: r.score
|
|
2577
|
+
}));
|
|
2578
|
+
return applyFilters(fused, limit, threshold, pathPrefix, includeTests, includeDocs);
|
|
2579
|
+
}
|
|
2580
|
+
function applyFilters(candidates, limit, threshold, pathPrefix, includeTests, includeDocs) {
|
|
2581
|
+
let filtered = candidates;
|
|
2582
|
+
if (threshold > 0) {
|
|
2583
|
+
filtered = filtered.filter((c) => c.fusedScore >= threshold);
|
|
2584
|
+
}
|
|
2585
|
+
if (pathPrefix) {
|
|
2586
|
+
filtered = filtered.filter((c) => c.filePath.startsWith(pathPrefix));
|
|
2587
|
+
}
|
|
2588
|
+
if (!includeTests) {
|
|
2589
|
+
filtered = filtered.filter((c) => !isTestFile(c.filePath));
|
|
2590
|
+
}
|
|
2591
|
+
if (!includeDocs) {
|
|
2592
|
+
filtered = filtered.filter((c) => !isDocFile(c.filePath));
|
|
2593
|
+
}
|
|
2594
|
+
return filtered.slice(0, limit).map((c) => ({
|
|
2595
|
+
id: c.id,
|
|
2596
|
+
filePath: c.filePath,
|
|
2597
|
+
startLine: c.startLine,
|
|
2598
|
+
endLine: c.endLine,
|
|
2599
|
+
snippet: c.snippet,
|
|
2600
|
+
score: c.fusedScore,
|
|
2601
|
+
vectorScore: c.vectorScore,
|
|
2602
|
+
bm25Score: c.bm25Score,
|
|
2603
|
+
symbolScore: c.symbolScore
|
|
2604
|
+
}));
|
|
2605
|
+
}
|
|
2606
|
+
function loadGraphRanks(metadataDir) {
|
|
2607
|
+
try {
|
|
2608
|
+
const file = path8.join(metadataDir, "depgraph", "pagerank.json");
|
|
2609
|
+
const raw = JSON.parse(fs9.readFileSync(file, "utf-8"));
|
|
2610
|
+
return new Map(Object.entries(raw));
|
|
2611
|
+
} catch {
|
|
2612
|
+
return null;
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
var DEBOUNCE_MS = 300;
|
|
2616
|
+
var RECONCILE_INTERVAL_MS = 6e4;
|
|
2617
|
+
var RECOVERY_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
2618
|
+
var CORRUPTION_PATTERNS = [
|
|
2619
|
+
/Not found.*lance/i,
|
|
2620
|
+
/LanceError/i,
|
|
2621
|
+
/corrupted/i,
|
|
2622
|
+
/panic/i
|
|
2623
|
+
];
|
|
2624
|
+
var CodemapWatcher = class {
|
|
2625
|
+
constructor(projectDir, options = {}) {
|
|
2626
|
+
this.projectDir = projectDir;
|
|
2627
|
+
this.options = options;
|
|
2628
|
+
this.debounceMs = options.debounceMs ?? DEBOUNCE_MS;
|
|
2629
|
+
this.reconcileIntervalMs = options.reconcileIntervalMs ?? RECONCILE_INTERVAL_MS;
|
|
2630
|
+
}
|
|
2631
|
+
watcher = null;
|
|
2632
|
+
pendingPaths = /* @__PURE__ */ new Set();
|
|
2633
|
+
debounceTimer = null;
|
|
2634
|
+
reconcileTimer = null;
|
|
2635
|
+
processing = false;
|
|
2636
|
+
bufferedPaths = /* @__PURE__ */ new Set();
|
|
2637
|
+
lastRecoveryAt = 0;
|
|
2638
|
+
_isRunning = false;
|
|
2639
|
+
_totalEventsProcessed = 0;
|
|
2640
|
+
_lastFlushAt = null;
|
|
2641
|
+
_recovering = false;
|
|
2642
|
+
_recoveryPromise = null;
|
|
2643
|
+
_denyPatterns = [];
|
|
2644
|
+
debounceMs;
|
|
2645
|
+
reconcileIntervalMs;
|
|
2646
|
+
get isRunning() {
|
|
2647
|
+
return this._isRunning;
|
|
2648
|
+
}
|
|
2649
|
+
get queueDepth() {
|
|
2650
|
+
return this.pendingPaths.size + this.bufferedPaths.size;
|
|
2651
|
+
}
|
|
2652
|
+
get totalEventsProcessed() {
|
|
2653
|
+
return this._totalEventsProcessed;
|
|
2654
|
+
}
|
|
2655
|
+
get lastFlushAt() {
|
|
2656
|
+
return this._lastFlushAt;
|
|
2657
|
+
}
|
|
2658
|
+
/**
|
|
2659
|
+
* Start watching for file changes.
|
|
2660
|
+
*/
|
|
2661
|
+
start() {
|
|
2662
|
+
if (this._isRunning) return;
|
|
2663
|
+
this.reloadDenyPatterns();
|
|
2664
|
+
this.watcher = fs10.watch(this.projectDir, { recursive: true }, (_event, filename) => {
|
|
2665
|
+
if (!filename) return;
|
|
2666
|
+
const relPath = filename.replace(/\\/g, "/");
|
|
2667
|
+
if (this._denyPatterns.some((p) => matchesDenyPattern(relPath, p))) return;
|
|
2668
|
+
if (relPath.split("/").some((part) => part.startsWith("."))) return;
|
|
2669
|
+
this.enqueue(relPath);
|
|
2670
|
+
});
|
|
2671
|
+
this.reconcileTimer = setInterval(() => {
|
|
2672
|
+
this.reconcile();
|
|
2673
|
+
}, this.reconcileIntervalMs);
|
|
2674
|
+
this._isRunning = true;
|
|
2675
|
+
console.error(`[codemap-engine] Watcher started for ${this.projectDir}`);
|
|
2676
|
+
}
|
|
2677
|
+
/**
|
|
2678
|
+
* Stop watching and flush any remaining events.
|
|
2679
|
+
*/
|
|
2680
|
+
async stop() {
|
|
2681
|
+
if (!this._isRunning) return;
|
|
2682
|
+
this._isRunning = false;
|
|
2683
|
+
if (this.watcher) {
|
|
2684
|
+
this.watcher.close();
|
|
2685
|
+
this.watcher = null;
|
|
2686
|
+
}
|
|
2687
|
+
if (this.debounceTimer) {
|
|
2688
|
+
clearTimeout(this.debounceTimer);
|
|
2689
|
+
this.debounceTimer = null;
|
|
2690
|
+
}
|
|
2691
|
+
if (this.reconcileTimer) {
|
|
2692
|
+
clearInterval(this.reconcileTimer);
|
|
2693
|
+
this.reconcileTimer = null;
|
|
2694
|
+
}
|
|
2695
|
+
if (this._recoveryPromise) {
|
|
2696
|
+
await this._recoveryPromise;
|
|
2697
|
+
}
|
|
2698
|
+
for (const p of this.bufferedPaths) this.pendingPaths.add(p);
|
|
2699
|
+
this.bufferedPaths.clear();
|
|
2700
|
+
if (this.pendingPaths.size > 0) {
|
|
2701
|
+
await this.flush();
|
|
2702
|
+
}
|
|
2703
|
+
console.error(`[codemap-engine] Watcher stopped for ${this.projectDir}`);
|
|
2704
|
+
}
|
|
2705
|
+
// ---------------------------------------------------------------------------
|
|
2706
|
+
// Internal
|
|
2707
|
+
// ---------------------------------------------------------------------------
|
|
2708
|
+
enqueue(relPath) {
|
|
2709
|
+
if (this.processing || this._recovering) {
|
|
2710
|
+
this.bufferedPaths.add(relPath);
|
|
2711
|
+
return;
|
|
2712
|
+
}
|
|
2713
|
+
this.pendingPaths.add(relPath);
|
|
2714
|
+
this.scheduleFlush();
|
|
2715
|
+
}
|
|
2716
|
+
scheduleFlush() {
|
|
2717
|
+
if (this.debounceTimer) {
|
|
2718
|
+
clearTimeout(this.debounceTimer);
|
|
2719
|
+
}
|
|
2720
|
+
this.debounceTimer = setTimeout(() => {
|
|
2721
|
+
this.flush().catch((err) => {
|
|
2722
|
+
console.error("[codemap-engine] Flush error:", err);
|
|
2723
|
+
this.handleError(err);
|
|
2724
|
+
});
|
|
2725
|
+
}, this.debounceMs);
|
|
2726
|
+
}
|
|
2727
|
+
async flush() {
|
|
2728
|
+
if (this.processing || this._recovering) return;
|
|
2729
|
+
if (this.pendingPaths.size === 0) return;
|
|
2730
|
+
this.processing = true;
|
|
2731
|
+
this.reloadDenyPatterns();
|
|
2732
|
+
const paths = [...this.pendingPaths];
|
|
2733
|
+
this.pendingPaths.clear();
|
|
2734
|
+
try {
|
|
2735
|
+
await runIncrementalPipeline(this.projectDir, paths, {
|
|
2736
|
+
branch: this.options.branch ?? "main",
|
|
2737
|
+
dataDir: this.options.dataDir
|
|
2738
|
+
});
|
|
2739
|
+
this._totalEventsProcessed += paths.length;
|
|
2740
|
+
this._lastFlushAt = /* @__PURE__ */ new Date();
|
|
2741
|
+
} catch (err) {
|
|
2742
|
+
console.error("[codemap-engine] Incremental pipeline error:", err);
|
|
2743
|
+
this.handleError(err);
|
|
2744
|
+
} finally {
|
|
2745
|
+
this.processing = false;
|
|
2746
|
+
}
|
|
2747
|
+
if (this.bufferedPaths.size > 0) {
|
|
2748
|
+
for (const p of this.bufferedPaths) {
|
|
2749
|
+
this.pendingPaths.add(p);
|
|
2750
|
+
}
|
|
2751
|
+
this.bufferedPaths.clear();
|
|
2752
|
+
this.scheduleFlush();
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
async reconcile() {
|
|
2756
|
+
if (this.processing || this._recovering) return;
|
|
2757
|
+
this.processing = true;
|
|
2758
|
+
this.reloadDenyPatterns();
|
|
2759
|
+
try {
|
|
2760
|
+
await runIncrementalPipeline(this.projectDir, void 0, {
|
|
2761
|
+
branch: this.options.branch ?? "main",
|
|
2762
|
+
dataDir: this.options.dataDir
|
|
2763
|
+
});
|
|
2764
|
+
} catch (err) {
|
|
2765
|
+
console.error("[codemap-engine] Reconciliation error:", err);
|
|
2766
|
+
} finally {
|
|
2767
|
+
this.processing = false;
|
|
2768
|
+
}
|
|
2769
|
+
if (this.bufferedPaths.size > 0) {
|
|
2770
|
+
for (const p of this.bufferedPaths) {
|
|
2771
|
+
this.pendingPaths.add(p);
|
|
2772
|
+
}
|
|
2773
|
+
this.bufferedPaths.clear();
|
|
2774
|
+
this.scheduleFlush();
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
handleError(err) {
|
|
2778
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2779
|
+
if (CORRUPTION_PATTERNS.some((p) => p.test(msg))) {
|
|
2780
|
+
const now = Date.now();
|
|
2781
|
+
if (now - this.lastRecoveryAt < RECOVERY_COOLDOWN_MS) {
|
|
2782
|
+
console.error("[codemap-engine] Corruption detected but in cooldown, skipping recovery");
|
|
2783
|
+
return;
|
|
2784
|
+
}
|
|
2785
|
+
if (this._recovering) return;
|
|
2786
|
+
this.lastRecoveryAt = now;
|
|
2787
|
+
this._recovering = true;
|
|
2788
|
+
console.error("[codemap-engine] Corruption detected, triggering full re-index");
|
|
2789
|
+
this._recoveryPromise = runInitPipeline(this.projectDir, void 0, {
|
|
2790
|
+
branch: this.options.branch ?? "main",
|
|
2791
|
+
dataDir: this.options.dataDir
|
|
2792
|
+
}).then(() => {
|
|
2793
|
+
}).catch((e) => {
|
|
2794
|
+
console.error("[codemap-engine] Recovery re-index failed:", e);
|
|
2795
|
+
}).finally(() => {
|
|
2796
|
+
this._recovering = false;
|
|
2797
|
+
this._recoveryPromise = null;
|
|
2798
|
+
if (this.bufferedPaths.size > 0) {
|
|
2799
|
+
for (const p of this.bufferedPaths) {
|
|
2800
|
+
this.pendingPaths.add(p);
|
|
2801
|
+
}
|
|
2802
|
+
this.bufferedPaths.clear();
|
|
2803
|
+
if (this._isRunning) this.scheduleFlush();
|
|
2804
|
+
}
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
reloadDenyPatterns() {
|
|
2809
|
+
const config = this.loadConfig();
|
|
2810
|
+
const ignorePatterns = loadCodemapIgnore(this.projectDir);
|
|
2811
|
+
this._denyPatterns = [...config.deny, ...ignorePatterns];
|
|
2812
|
+
}
|
|
2813
|
+
loadConfig() {
|
|
2814
|
+
try {
|
|
2815
|
+
const raw = fs10.readFileSync(codemapConfigFile(this.projectDir, this.options.dataDir), "utf-8");
|
|
2816
|
+
return CodemapConfigSchema.parse(JSON.parse(raw));
|
|
2817
|
+
} catch {
|
|
2818
|
+
return CodemapConfigSchema.parse({});
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
};
|
|
2822
|
+
|
|
2823
|
+
// src/config.ts
|
|
2824
|
+
import fs11 from "fs";
|
|
2825
|
+
import { homedir as homedir2 } from "os";
|
|
2826
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
2827
|
+
var DEFAULT_DATA_DIR = join2(homedir2(), ".codemap");
|
|
2828
|
+
function getDataDir2() {
|
|
2829
|
+
return process.env["CODEMAP_DATA_DIR"] || DEFAULT_DATA_DIR;
|
|
2830
|
+
}
|
|
2831
|
+
function globalConfigFile() {
|
|
2832
|
+
return join2(getDataDir2(), "config.json");
|
|
2833
|
+
}
|
|
2834
|
+
function projectConfigFile(projectDir) {
|
|
2835
|
+
return join2(resolve2(projectDir), ".codemap.json");
|
|
2836
|
+
}
|
|
2837
|
+
function loadCliConfig(projectDir) {
|
|
2838
|
+
let global = {};
|
|
2839
|
+
let project = {};
|
|
2840
|
+
try {
|
|
2841
|
+
global = JSON.parse(fs11.readFileSync(globalConfigFile(), "utf-8"));
|
|
2842
|
+
} catch {
|
|
2843
|
+
}
|
|
2844
|
+
try {
|
|
2845
|
+
project = JSON.parse(fs11.readFileSync(projectConfigFile(projectDir), "utf-8"));
|
|
2846
|
+
} catch {
|
|
2847
|
+
}
|
|
2848
|
+
return {
|
|
2849
|
+
embedding: { ...global.embedding, ...project.embedding },
|
|
2850
|
+
ollama: { ...global.ollama, ...project.ollama }
|
|
2851
|
+
};
|
|
2852
|
+
}
|
|
2853
|
+
function saveCliConfig(config) {
|
|
2854
|
+
const dir = getDataDir2();
|
|
2855
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
2856
|
+
fs11.writeFileSync(globalConfigFile(), JSON.stringify(config, null, 2) + "\n");
|
|
2857
|
+
}
|
|
2858
|
+
function bridgeEnvVars(config) {
|
|
2859
|
+
if (config.embedding?.apiKey && config.embedding.provider === "openai") {
|
|
2860
|
+
process.env["OPENAI_API_KEY"] ??= config.embedding.apiKey;
|
|
2861
|
+
}
|
|
2862
|
+
if (config.embedding?.apiKey && config.embedding.provider === "ulpi") {
|
|
2863
|
+
process.env["ULPI_API_KEY"] ??= config.embedding.apiKey;
|
|
2864
|
+
}
|
|
2865
|
+
if (config.ollama?.host) {
|
|
2866
|
+
process.env["OLLAMA_HOST"] ??= config.ollama.host;
|
|
2867
|
+
}
|
|
2868
|
+
if (config.embedding?.baseUrl) {
|
|
2869
|
+
process.env["ULPI_EMBED_URL"] ??= config.embedding.baseUrl;
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
function resolveCodemapConfig(cliConfig) {
|
|
2873
|
+
if (!cliConfig.embedding?.provider || !cliConfig.embedding.model || !cliConfig.embedding.dimensions) {
|
|
2874
|
+
throw new Error(
|
|
2875
|
+
"No embedding configuration found. Run `codemap init` to set up your provider and model."
|
|
2876
|
+
);
|
|
2877
|
+
}
|
|
2878
|
+
return CodemapConfigSchema.parse({
|
|
2879
|
+
embedding: {
|
|
2880
|
+
provider: cliConfig.embedding.provider,
|
|
2881
|
+
model: cliConfig.embedding.model,
|
|
2882
|
+
dimensions: cliConfig.embedding.dimensions,
|
|
2883
|
+
baseUrl: cliConfig.embedding.baseUrl
|
|
2884
|
+
}
|
|
2885
|
+
});
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
// src/commands/search.ts
|
|
2889
|
+
function registerSearch(program2) {
|
|
2890
|
+
program2.command("search <query>").description("Search code using hybrid vector + BM25").option("-l, --limit <n>", "Max results", "10").option("--json", "Output as JSON").action(async (query, opts) => {
|
|
2891
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
2892
|
+
const config = loadCliConfig(projectDir);
|
|
2893
|
+
bridgeEnvVars(config);
|
|
2894
|
+
const branch = getCurrentBranch(projectDir);
|
|
2895
|
+
const limit = parseInt(opts.limit, 10);
|
|
2896
|
+
const results = await searchCode(projectDir, query, {
|
|
2897
|
+
limit,
|
|
2898
|
+
branch,
|
|
2899
|
+
dataDir: getDataDir2()
|
|
2900
|
+
});
|
|
2901
|
+
if (opts.json) {
|
|
2902
|
+
console.log(JSON.stringify(results, null, 2));
|
|
2903
|
+
return;
|
|
2904
|
+
}
|
|
2905
|
+
if (results.length === 0) {
|
|
2906
|
+
console.log(chalk.yellow("No results found."));
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2909
|
+
for (const r of results) {
|
|
2910
|
+
const score = Math.round(r.score * 1e3) / 1e3;
|
|
2911
|
+
console.log(chalk.cyan(`${r.filePath}:${r.startLine}`) + chalk.dim(` (score: ${score})`));
|
|
2912
|
+
const lines = r.snippet.split("\n").slice(0, 5);
|
|
2913
|
+
for (const line of lines) {
|
|
2914
|
+
console.log(chalk.dim(" ") + line);
|
|
2915
|
+
}
|
|
2916
|
+
console.log();
|
|
2917
|
+
}
|
|
2918
|
+
});
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
// src/commands/symbols.ts
|
|
2922
|
+
import chalk2 from "chalk";
|
|
2923
|
+
function registerSymbols(program2) {
|
|
2924
|
+
program2.command("symbols <query>").description("Find functions, classes, and types by name").option("-l, --limit <n>", "Max results", "20").option("--json", "Output as JSON").action(async (query, opts) => {
|
|
2925
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
2926
|
+
const branch = getCurrentBranch(projectDir);
|
|
2927
|
+
const metaDir = codemapMetadataDir(projectDir, branch, getDataDir2());
|
|
2928
|
+
const symbolIndex = loadSymbolIndex(metaDir);
|
|
2929
|
+
const limit = parseInt(opts.limit, 10);
|
|
2930
|
+
const matches = searchSymbols(symbolIndex, query, limit);
|
|
2931
|
+
if (opts.json) {
|
|
2932
|
+
console.log(JSON.stringify(matches.map((m) => ({
|
|
2933
|
+
name: m.entry.name,
|
|
2934
|
+
type: m.entry.symbolType,
|
|
2935
|
+
file: m.entry.filePath,
|
|
2936
|
+
line: m.entry.startLine,
|
|
2937
|
+
score: Math.round(m.score * 1e3) / 1e3
|
|
2938
|
+
})), null, 2));
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2941
|
+
if (matches.length === 0) {
|
|
2942
|
+
console.log(chalk2.yellow("No symbols found."));
|
|
2943
|
+
return;
|
|
2944
|
+
}
|
|
2945
|
+
console.log(
|
|
2946
|
+
chalk2.bold("Name".padEnd(40)) + chalk2.bold("Type".padEnd(12)) + chalk2.bold("File".padEnd(50)) + chalk2.bold("Line")
|
|
2947
|
+
);
|
|
2948
|
+
console.log(chalk2.dim("-".repeat(110)));
|
|
2949
|
+
for (const m of matches) {
|
|
2950
|
+
console.log(
|
|
2951
|
+
chalk2.cyan(m.entry.name.padEnd(40)) + chalk2.dim(m.entry.symbolType.padEnd(12)) + m.entry.filePath.padEnd(50) + String(m.entry.startLine)
|
|
2952
|
+
);
|
|
2953
|
+
}
|
|
2954
|
+
});
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
// src/commands/index-cmd.ts
|
|
2958
|
+
import chalk4 from "chalk";
|
|
2959
|
+
|
|
2960
|
+
// src/commands/init.ts
|
|
2961
|
+
import chalk3 from "chalk";
|
|
2962
|
+
import fs12 from "fs";
|
|
2963
|
+
import path9 from "path";
|
|
2964
|
+
import readline from "readline";
|
|
2965
|
+
var DEFAULT_CODEMAPIGNORE = `# ============================================================
|
|
2966
|
+
# .codemapignore \u2014 files excluded from codemap indexing
|
|
2967
|
+
# Syntax: same as .gitignore (glob patterns, # comments)
|
|
2968
|
+
# Note: .gitignore is already respected via git ls-files
|
|
2969
|
+
# ============================================================
|
|
2970
|
+
|
|
2971
|
+
# -----------------------------------------------------------
|
|
2972
|
+
# Build output
|
|
2973
|
+
# -----------------------------------------------------------
|
|
2974
|
+
dist/
|
|
2975
|
+
build/
|
|
2976
|
+
out/
|
|
2977
|
+
*.tsbuildinfo
|
|
2978
|
+
|
|
2979
|
+
# -----------------------------------------------------------
|
|
2980
|
+
# Dependencies
|
|
2981
|
+
# -----------------------------------------------------------
|
|
2982
|
+
node_modules/
|
|
2983
|
+
vendor/
|
|
2984
|
+
.pnp.*
|
|
2985
|
+
|
|
2986
|
+
# -----------------------------------------------------------
|
|
2987
|
+
# Lock files (large, low signal)
|
|
2988
|
+
# -----------------------------------------------------------
|
|
2989
|
+
pnpm-lock.yaml
|
|
2990
|
+
package-lock.json
|
|
2991
|
+
yarn.lock
|
|
2992
|
+
composer.lock
|
|
2993
|
+
Gemfile.lock
|
|
2994
|
+
Pipfile.lock
|
|
2995
|
+
poetry.lock
|
|
2996
|
+
go.sum
|
|
2997
|
+
Cargo.lock
|
|
2998
|
+
|
|
2999
|
+
# -----------------------------------------------------------
|
|
3000
|
+
# Static assets
|
|
3001
|
+
# -----------------------------------------------------------
|
|
3002
|
+
*.png
|
|
3003
|
+
*.jpg
|
|
3004
|
+
*.jpeg
|
|
3005
|
+
*.gif
|
|
3006
|
+
*.svg
|
|
3007
|
+
*.ico
|
|
3008
|
+
*.webp
|
|
3009
|
+
*.avif
|
|
3010
|
+
*.bmp
|
|
3011
|
+
*.tiff
|
|
3012
|
+
*.woff
|
|
3013
|
+
*.woff2
|
|
3014
|
+
*.ttf
|
|
3015
|
+
*.otf
|
|
3016
|
+
*.eot
|
|
3017
|
+
*.mp3
|
|
3018
|
+
*.mp4
|
|
3019
|
+
*.wav
|
|
3020
|
+
*.webm
|
|
3021
|
+
*.pdf
|
|
3022
|
+
|
|
3023
|
+
# -----------------------------------------------------------
|
|
3024
|
+
# Generated / minified
|
|
3025
|
+
# -----------------------------------------------------------
|
|
3026
|
+
*.min.js
|
|
3027
|
+
*.min.css
|
|
3028
|
+
*.map
|
|
3029
|
+
*.bundle.js
|
|
3030
|
+
*.chunk.js
|
|
3031
|
+
|
|
3032
|
+
# -----------------------------------------------------------
|
|
3033
|
+
# Test artifacts
|
|
3034
|
+
# -----------------------------------------------------------
|
|
3035
|
+
__snapshots__/
|
|
3036
|
+
*.snap
|
|
3037
|
+
coverage/
|
|
3038
|
+
.nyc_output/
|
|
3039
|
+
lcov.info
|
|
3040
|
+
|
|
3041
|
+
# -----------------------------------------------------------
|
|
3042
|
+
# Cache
|
|
3043
|
+
# -----------------------------------------------------------
|
|
3044
|
+
.cache/
|
|
3045
|
+
*.cache
|
|
3046
|
+
.eslintcache
|
|
3047
|
+
.stylelintcache
|
|
3048
|
+
.parcel-cache/
|
|
3049
|
+
|
|
3050
|
+
# -----------------------------------------------------------
|
|
3051
|
+
# Environment & secrets
|
|
3052
|
+
# -----------------------------------------------------------
|
|
3053
|
+
.env
|
|
3054
|
+
.env.*
|
|
3055
|
+
|
|
3056
|
+
# -----------------------------------------------------------
|
|
3057
|
+
# AI agent config (prompts, not code)
|
|
3058
|
+
# -----------------------------------------------------------
|
|
3059
|
+
.claude/
|
|
3060
|
+
.codex/
|
|
3061
|
+
.kiro/
|
|
3062
|
+
.gemini/
|
|
3063
|
+
.factory/
|
|
3064
|
+
.opencode/
|
|
3065
|
+
AGENTS.md
|
|
3066
|
+
CLAUDE.md
|
|
3067
|
+
|
|
3068
|
+
# -----------------------------------------------------------
|
|
3069
|
+
# OS / editor
|
|
3070
|
+
# -----------------------------------------------------------
|
|
3071
|
+
.DS_Store
|
|
3072
|
+
Thumbs.db
|
|
3073
|
+
.idea/
|
|
3074
|
+
.vscode/settings.json
|
|
3075
|
+
*.swp
|
|
3076
|
+
*.swo
|
|
3077
|
+
|
|
3078
|
+
# -----------------------------------------------------------
|
|
3079
|
+
# Monorepo / project config (low signal)
|
|
3080
|
+
# -----------------------------------------------------------
|
|
3081
|
+
.editorconfig
|
|
3082
|
+
.npmrc
|
|
3083
|
+
.nvmrc
|
|
3084
|
+
.node-version
|
|
3085
|
+
turbo.json
|
|
3086
|
+
nx.json
|
|
3087
|
+
lerna.json
|
|
3088
|
+
|
|
3089
|
+
# -----------------------------------------------------------
|
|
3090
|
+
# Next.js
|
|
3091
|
+
# -----------------------------------------------------------
|
|
3092
|
+
.next/
|
|
3093
|
+
_buildManifest.js
|
|
3094
|
+
_ssgManifest.js
|
|
3095
|
+
|
|
3096
|
+
# -----------------------------------------------------------
|
|
3097
|
+
# Nuxt / Vue
|
|
3098
|
+
# -----------------------------------------------------------
|
|
3099
|
+
.nuxt/
|
|
3100
|
+
.output/
|
|
3101
|
+
|
|
3102
|
+
# -----------------------------------------------------------
|
|
3103
|
+
# Svelte
|
|
3104
|
+
# -----------------------------------------------------------
|
|
3105
|
+
.svelte-kit/
|
|
3106
|
+
|
|
3107
|
+
# -----------------------------------------------------------
|
|
3108
|
+
# Vite / Turbopack
|
|
3109
|
+
# -----------------------------------------------------------
|
|
3110
|
+
.turbo/
|
|
3111
|
+
|
|
3112
|
+
# -----------------------------------------------------------
|
|
3113
|
+
# React Native / Expo
|
|
3114
|
+
# -----------------------------------------------------------
|
|
3115
|
+
ios/Pods/
|
|
3116
|
+
ios/build/
|
|
3117
|
+
android/.gradle/
|
|
3118
|
+
android/build/
|
|
3119
|
+
android/app/build/
|
|
3120
|
+
.expo/
|
|
3121
|
+
*.jsbundle
|
|
3122
|
+
*.hbc
|
|
3123
|
+
|
|
3124
|
+
# -----------------------------------------------------------
|
|
3125
|
+
# Laravel / PHP
|
|
3126
|
+
# -----------------------------------------------------------
|
|
3127
|
+
bootstrap/cache/
|
|
3128
|
+
storage/framework/
|
|
3129
|
+
storage/logs/
|
|
3130
|
+
public/build/
|
|
3131
|
+
public/hot
|
|
3132
|
+
public/storage
|
|
3133
|
+
public/mix-manifest.json
|
|
3134
|
+
_ide_helper*.php
|
|
3135
|
+
.phpstorm.meta.php
|
|
3136
|
+
.php-cs-fixer.cache
|
|
3137
|
+
|
|
3138
|
+
# -----------------------------------------------------------
|
|
3139
|
+
# Magento 2
|
|
3140
|
+
# -----------------------------------------------------------
|
|
3141
|
+
generated/
|
|
3142
|
+
pub/static/
|
|
3143
|
+
var/cache/
|
|
3144
|
+
var/page_cache/
|
|
3145
|
+
var/view_preprocessed/
|
|
3146
|
+
var/generation/
|
|
3147
|
+
var/di/
|
|
3148
|
+
var/log/
|
|
3149
|
+
var/report/
|
|
3150
|
+
setup/src/
|
|
3151
|
+
lib/internal/
|
|
3152
|
+
dev/tests/
|
|
3153
|
+
*.less.css
|
|
3154
|
+
`;
|
|
3155
|
+
function writeCodemapIgnore(projectDir, force = false) {
|
|
3156
|
+
const ignorePath = path9.join(projectDir, ".codemapignore");
|
|
3157
|
+
if (!force && fs12.existsSync(ignorePath)) return false;
|
|
3158
|
+
fs12.writeFileSync(ignorePath, DEFAULT_CODEMAPIGNORE);
|
|
3159
|
+
return true;
|
|
3160
|
+
}
|
|
3161
|
+
function registerInit(program2) {
|
|
3162
|
+
program2.command("init").description("Interactive first-time setup for codemap").option("--provider <provider>", "Embedding provider (ollama, openai, or ulpi)").option("--force", "Reconfigure even if already set up").action(async (opts) => {
|
|
3163
|
+
console.log(chalk3.bold("Codemap Setup"));
|
|
3164
|
+
console.log(chalk3.dim("-".repeat(40)));
|
|
3165
|
+
console.log();
|
|
3166
|
+
const config = loadCliConfig(program2.opts().cwd || process.cwd());
|
|
3167
|
+
if (!opts.force && config.embedding?.provider && config.embedding.model && config.embedding.dimensions) {
|
|
3168
|
+
console.log("Existing configuration found:");
|
|
3169
|
+
console.log(` Provider: ${chalk3.cyan(config.embedding.provider)}`);
|
|
3170
|
+
console.log(` Model: ${chalk3.cyan(config.embedding.model)}`);
|
|
3171
|
+
console.log(` Dimensions: ${chalk3.cyan(String(config.embedding.dimensions))}`);
|
|
3172
|
+
if (config.embedding.apiKey) {
|
|
3173
|
+
const masked = config.embedding.apiKey.slice(0, 8) + "...";
|
|
3174
|
+
console.log(` API key: ${chalk3.cyan(masked)}`);
|
|
3175
|
+
}
|
|
3176
|
+
console.log();
|
|
3177
|
+
const answer = await ask("Reconfigure? (y/N): ");
|
|
3178
|
+
if (answer.toLowerCase() !== "y") {
|
|
3179
|
+
console.log(chalk3.dim("Keeping existing config. Use --force to skip this prompt."));
|
|
3180
|
+
return;
|
|
3181
|
+
}
|
|
3182
|
+
console.log();
|
|
3183
|
+
}
|
|
3184
|
+
let provider = opts.provider;
|
|
3185
|
+
if (!provider) {
|
|
3186
|
+
console.log("Available embedding providers:");
|
|
3187
|
+
console.log(` ${chalk3.cyan("1")} ulpi \u2014 managed cloud service by ULPI`);
|
|
3188
|
+
console.log(` ${chalk3.cyan("2")} ollama \u2014 free, local, requires Ollama running`);
|
|
3189
|
+
console.log(` ${chalk3.cyan("3")} openai \u2014 cloud, requires API key`);
|
|
3190
|
+
console.log();
|
|
3191
|
+
const choice = await ask("Choose provider (1, 2, or 3): ");
|
|
3192
|
+
provider = choice === "1" ? "ulpi" : choice === "2" ? "ollama" : choice === "3" ? "openai" : choice;
|
|
3193
|
+
}
|
|
3194
|
+
if (provider !== "ollama" && provider !== "openai" && provider !== "ulpi") {
|
|
3195
|
+
console.error(chalk3.red(`Unknown provider: ${provider}. Use "ollama", "openai", or "ulpi".`));
|
|
3196
|
+
process.exit(1);
|
|
3197
|
+
}
|
|
3198
|
+
config.embedding = config.embedding ?? {};
|
|
3199
|
+
config.embedding.provider = provider;
|
|
3200
|
+
if (provider === "ulpi") {
|
|
3201
|
+
config.embedding.model = "code";
|
|
3202
|
+
config.embedding.dimensions = 1024;
|
|
3203
|
+
config.embedding.baseUrl = ULPI_API_BASE;
|
|
3204
|
+
console.log();
|
|
3205
|
+
console.log(chalk3.dim('ULPI Cloud uses the "code" embedding model (1024 dimensions).'));
|
|
3206
|
+
console.log();
|
|
3207
|
+
const hasAccount = await ask("Do you have a ULPI account? (y/n): ");
|
|
3208
|
+
if (hasAccount.toLowerCase() === "y") {
|
|
3209
|
+
const apiKey = await ask("API key: ");
|
|
3210
|
+
if (apiKey) {
|
|
3211
|
+
config.embedding.apiKey = apiKey;
|
|
3212
|
+
} else {
|
|
3213
|
+
console.log(chalk3.yellow("No API key provided. Set it later with:"));
|
|
3214
|
+
console.log(chalk3.dim(" codemap config set embedding.apiKey <key>"));
|
|
3215
|
+
}
|
|
3216
|
+
} else {
|
|
3217
|
+
console.log();
|
|
3218
|
+
console.log(chalk3.bold("Create your ULPI account"));
|
|
3219
|
+
console.log();
|
|
3220
|
+
const name = await ask("Name: ");
|
|
3221
|
+
const email = await ask("Email: ");
|
|
3222
|
+
const password = await askSecret("Password: ");
|
|
3223
|
+
const passwordConfirm = await askSecret("Confirm password: ");
|
|
3224
|
+
if (password !== passwordConfirm) {
|
|
3225
|
+
console.error(chalk3.red("Passwords do not match."));
|
|
3226
|
+
process.exit(1);
|
|
3227
|
+
}
|
|
3228
|
+
try {
|
|
3229
|
+
console.log();
|
|
3230
|
+
console.log(chalk3.dim("Registering..."));
|
|
3231
|
+
const token = await ulpiRegister(name, email, password);
|
|
3232
|
+
console.log(chalk3.green("Account created."));
|
|
3233
|
+
console.log(chalk3.dim("Creating API key..."));
|
|
3234
|
+
const apiKey = await ulpiCreateKey(token);
|
|
3235
|
+
config.embedding.apiKey = apiKey;
|
|
3236
|
+
console.log(chalk3.green("API key: ") + chalk3.cyan(apiKey));
|
|
3237
|
+
} catch (err) {
|
|
3238
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3239
|
+
console.error(chalk3.red(`Registration failed: ${msg}`));
|
|
3240
|
+
console.log();
|
|
3241
|
+
console.log(chalk3.yellow("You can set the API key manually later:"));
|
|
3242
|
+
console.log(chalk3.dim(" codemap config set embedding.apiKey <key>"));
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
} else if (provider === "ollama") {
|
|
3246
|
+
console.log();
|
|
3247
|
+
console.log("Available Ollama embedding models:");
|
|
3248
|
+
console.log(` ${chalk3.cyan("1")} nomic-embed-text \u2014 768 dimensions`);
|
|
3249
|
+
console.log(` ${chalk3.cyan("2")} mxbai-embed-large \u2014 1024 dimensions`);
|
|
3250
|
+
console.log(` ${chalk3.cyan("3")} all-minilm \u2014 384 dimensions`);
|
|
3251
|
+
console.log();
|
|
3252
|
+
const modelChoice = await ask("Choose model (1, 2, or 3): ");
|
|
3253
|
+
if (modelChoice === "2") {
|
|
3254
|
+
config.embedding.model = "mxbai-embed-large";
|
|
3255
|
+
config.embedding.dimensions = 1024;
|
|
3256
|
+
} else if (modelChoice === "3") {
|
|
3257
|
+
config.embedding.model = "all-minilm";
|
|
3258
|
+
config.embedding.dimensions = 384;
|
|
3259
|
+
} else {
|
|
3260
|
+
config.embedding.model = "nomic-embed-text";
|
|
3261
|
+
config.embedding.dimensions = 768;
|
|
3262
|
+
}
|
|
3263
|
+
console.log();
|
|
3264
|
+
const host = await ask("Ollama host (leave empty for http://localhost:11434): ");
|
|
3265
|
+
if (host) {
|
|
3266
|
+
config.ollama = { host };
|
|
3267
|
+
}
|
|
3268
|
+
console.log();
|
|
3269
|
+
console.log(chalk3.dim(`Make sure Ollama is running and has ${config.embedding.model}:`));
|
|
3270
|
+
console.log(chalk3.dim(` ollama pull ${config.embedding.model}`));
|
|
3271
|
+
} else {
|
|
3272
|
+
console.log();
|
|
3273
|
+
console.log("Available OpenAI embedding models:");
|
|
3274
|
+
console.log(` ${chalk3.cyan("1")} text-embedding-3-small \u2014 1536 dimensions`);
|
|
3275
|
+
console.log(` ${chalk3.cyan("2")} text-embedding-3-large \u2014 3072 dimensions`);
|
|
3276
|
+
console.log();
|
|
3277
|
+
const modelChoice = await ask("Choose model (1 or 2): ");
|
|
3278
|
+
if (modelChoice === "2") {
|
|
3279
|
+
config.embedding.model = "text-embedding-3-large";
|
|
3280
|
+
config.embedding.dimensions = 3072;
|
|
3281
|
+
} else {
|
|
3282
|
+
config.embedding.model = "text-embedding-3-small";
|
|
3283
|
+
config.embedding.dimensions = 1536;
|
|
3284
|
+
}
|
|
3285
|
+
console.log();
|
|
3286
|
+
const apiKey = await ask("OpenAI API key: ");
|
|
3287
|
+
if (apiKey) {
|
|
3288
|
+
config.embedding.apiKey = apiKey;
|
|
3289
|
+
} else {
|
|
3290
|
+
console.log(chalk3.yellow("No API key provided. Set it later with:"));
|
|
3291
|
+
console.log(chalk3.dim(" codemap config set embedding.apiKey sk-..."));
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
saveCliConfig(config);
|
|
3295
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
3296
|
+
const wroteIgnore = writeCodemapIgnore(projectDir);
|
|
3297
|
+
console.log();
|
|
3298
|
+
console.log(chalk3.green("Config saved to ") + chalk3.cyan(getDataDir2() + "/config.json"));
|
|
3299
|
+
if (wroteIgnore) {
|
|
3300
|
+
console.log(chalk3.green("Created ") + chalk3.cyan(".codemapignore") + chalk3.green(" with default patterns"));
|
|
3301
|
+
}
|
|
3302
|
+
console.log();
|
|
3303
|
+
console.log("Next steps:");
|
|
3304
|
+
console.log(` ${chalk3.cyan("codemap index")} Index your project`);
|
|
3305
|
+
console.log(` ${chalk3.cyan("codemap search")} Search your code`);
|
|
3306
|
+
console.log(` ${chalk3.cyan("codemap serve")} Start MCP server`);
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
var ULPI_API_BASE = "https://codemap.ulpi.io";
|
|
3310
|
+
var ULPI_AUTH_BASE = "https://codemap.ulpi.io/api/v1";
|
|
3311
|
+
function ask(prompt) {
|
|
3312
|
+
const rl = readline.createInterface({
|
|
3313
|
+
input: process.stdin,
|
|
3314
|
+
output: process.stdout
|
|
3315
|
+
});
|
|
3316
|
+
return new Promise((resolve3) => {
|
|
3317
|
+
rl.question(prompt, (answer) => {
|
|
3318
|
+
rl.close();
|
|
3319
|
+
resolve3(answer.trim());
|
|
3320
|
+
});
|
|
3321
|
+
});
|
|
3322
|
+
}
|
|
3323
|
+
function askSecret(prompt) {
|
|
3324
|
+
return new Promise((resolve3) => {
|
|
3325
|
+
process.stdout.write(prompt);
|
|
3326
|
+
const stdin = process.stdin;
|
|
3327
|
+
const wasRaw = stdin.isRaw;
|
|
3328
|
+
if (stdin.isTTY && stdin.setRawMode) {
|
|
3329
|
+
stdin.setRawMode(true);
|
|
3330
|
+
}
|
|
3331
|
+
stdin.resume();
|
|
3332
|
+
let input = "";
|
|
3333
|
+
const onData = (ch) => {
|
|
3334
|
+
const c = ch.toString("utf-8");
|
|
3335
|
+
if (c === "\n" || c === "\r" || c === "") {
|
|
3336
|
+
if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(wasRaw ?? false);
|
|
3337
|
+
stdin.removeListener("data", onData);
|
|
3338
|
+
stdin.pause();
|
|
3339
|
+
process.stdout.write("\n");
|
|
3340
|
+
resolve3(input.trim());
|
|
3341
|
+
} else if (c === "") {
|
|
3342
|
+
if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(wasRaw ?? false);
|
|
3343
|
+
stdin.removeListener("data", onData);
|
|
3344
|
+
stdin.pause();
|
|
3345
|
+
process.exit(1);
|
|
3346
|
+
} else if (c === "\x7F" || c === "\b") {
|
|
3347
|
+
if (input.length > 0) {
|
|
3348
|
+
input = input.slice(0, -1);
|
|
3349
|
+
process.stdout.write("\b \b");
|
|
3350
|
+
}
|
|
3351
|
+
} else if (c.charCodeAt(0) >= 32) {
|
|
3352
|
+
input += c;
|
|
3353
|
+
process.stdout.write("*");
|
|
3354
|
+
}
|
|
3355
|
+
};
|
|
3356
|
+
stdin.on("data", onData);
|
|
3357
|
+
});
|
|
3358
|
+
}
|
|
3359
|
+
async function ulpiRegister(name, email, password) {
|
|
3360
|
+
const res = await fetch(`${ULPI_AUTH_BASE}/register`, {
|
|
3361
|
+
method: "POST",
|
|
3362
|
+
headers: { "Content-Type": "application/json", "Accept": "application/json" },
|
|
3363
|
+
body: JSON.stringify({ name, email, password, password_confirmation: password })
|
|
3364
|
+
});
|
|
3365
|
+
const data = await res.json();
|
|
3366
|
+
if (!res.ok || !data.token) {
|
|
3367
|
+
if (data.errors) {
|
|
3368
|
+
const msgs = Object.values(data.errors).flat();
|
|
3369
|
+
throw new Error(msgs.join(", "));
|
|
3370
|
+
}
|
|
3371
|
+
throw new Error(data.message ?? `HTTP ${res.status}`);
|
|
3372
|
+
}
|
|
3373
|
+
return data.token;
|
|
3374
|
+
}
|
|
3375
|
+
async function ulpiCreateKey(token) {
|
|
3376
|
+
const res = await fetch(`${ULPI_AUTH_BASE}/keys`, {
|
|
3377
|
+
method: "POST",
|
|
3378
|
+
headers: {
|
|
3379
|
+
"Content-Type": "application/json",
|
|
3380
|
+
"Accept": "application/json",
|
|
3381
|
+
"Authorization": `Bearer ${token}`
|
|
3382
|
+
},
|
|
3383
|
+
body: JSON.stringify({ name: "codemap-cli" })
|
|
3384
|
+
});
|
|
3385
|
+
const data = await res.json();
|
|
3386
|
+
if (!res.ok) {
|
|
3387
|
+
throw new Error(data.message ?? `HTTP ${res.status}`);
|
|
3388
|
+
}
|
|
3389
|
+
const key = data.plain_text_key ?? data.key;
|
|
3390
|
+
if (!key) {
|
|
3391
|
+
throw new Error("No API key in response");
|
|
3392
|
+
}
|
|
3393
|
+
return key;
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
// src/commands/index-cmd.ts
|
|
3397
|
+
function registerIndex(program2) {
|
|
3398
|
+
program2.command("index").description("Index the current project for code search").option("--full", "Force full re-index (ignore incremental state)").action(async (_opts) => {
|
|
3399
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
3400
|
+
const cliConfig = loadCliConfig(projectDir);
|
|
3401
|
+
bridgeEnvVars(cliConfig);
|
|
3402
|
+
const config = resolveCodemapConfig(cliConfig);
|
|
3403
|
+
const branch = getCurrentBranch(projectDir);
|
|
3404
|
+
if (writeCodemapIgnore(projectDir)) {
|
|
3405
|
+
console.log(chalk4.green("Created ") + chalk4.cyan(".codemapignore") + chalk4.green(" with default patterns"));
|
|
3406
|
+
console.log();
|
|
3407
|
+
}
|
|
3408
|
+
console.log(chalk4.bold("Indexing ") + chalk4.cyan(projectDir));
|
|
3409
|
+
console.log(chalk4.dim(`Branch: ${branch} | Provider: ${config.embedding.provider} | Model: ${config.embedding.model}`));
|
|
3410
|
+
console.log();
|
|
3411
|
+
const stats = await runInitPipeline(projectDir, config, {
|
|
3412
|
+
branch,
|
|
3413
|
+
dataDir: getDataDir2(),
|
|
3414
|
+
onProgress: (progress) => {
|
|
3415
|
+
const pct = progress.total ? ` (${progress.current ?? 0}/${progress.total})` : "";
|
|
3416
|
+
console.log(chalk4.dim(`[${progress.phase}]`) + ` ${progress.message}${pct}`);
|
|
3417
|
+
}
|
|
3418
|
+
});
|
|
3419
|
+
console.log();
|
|
3420
|
+
console.log(chalk4.green("Index complete:"));
|
|
3421
|
+
console.log(` Files: ${stats.totalFiles}`);
|
|
3422
|
+
console.log(` Chunks: ${stats.totalChunks}`);
|
|
3423
|
+
console.log(` Time: ${stats.lastFullIndexMs}ms`);
|
|
3424
|
+
});
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3427
|
+
// src/commands/watch.ts
|
|
3428
|
+
import chalk5 from "chalk";
|
|
3429
|
+
function registerWatch(program2) {
|
|
3430
|
+
program2.command("watch").description("Watch for file changes and update index in real-time").option("--debounce <ms>", "Debounce interval in milliseconds", "300").action(async (opts) => {
|
|
3431
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
3432
|
+
const config = loadCliConfig(projectDir);
|
|
3433
|
+
bridgeEnvVars(config);
|
|
3434
|
+
const branch = getCurrentBranch(projectDir);
|
|
3435
|
+
const debounceMs = parseInt(opts.debounce, 10);
|
|
3436
|
+
console.log(chalk5.bold("Watching ") + chalk5.cyan(projectDir));
|
|
3437
|
+
console.log(chalk5.dim(`Branch: ${branch} | Press Ctrl+C to stop`));
|
|
3438
|
+
console.log();
|
|
3439
|
+
const watcher = new CodemapWatcher(projectDir, {
|
|
3440
|
+
branch,
|
|
3441
|
+
debounceMs,
|
|
3442
|
+
dataDir: getDataDir2()
|
|
3443
|
+
});
|
|
3444
|
+
watcher.start();
|
|
3445
|
+
const cleanup = async () => {
|
|
3446
|
+
console.log(chalk5.dim("\nStopping watcher..."));
|
|
3447
|
+
await watcher.stop();
|
|
3448
|
+
process.exit(0);
|
|
3449
|
+
};
|
|
3450
|
+
process.on("SIGINT", cleanup);
|
|
3451
|
+
process.on("SIGTERM", cleanup);
|
|
3452
|
+
setInterval(() => {
|
|
3453
|
+
const depth = watcher.queueDepth;
|
|
3454
|
+
const total = watcher.totalEventsProcessed;
|
|
3455
|
+
if (depth > 0) {
|
|
3456
|
+
console.log(chalk5.dim(`Queue: ${depth} | Processed: ${total}`));
|
|
3457
|
+
}
|
|
3458
|
+
}, 5e3);
|
|
3459
|
+
});
|
|
3460
|
+
}
|
|
3461
|
+
|
|
3462
|
+
// src/commands/status.ts
|
|
3463
|
+
import chalk6 from "chalk";
|
|
3464
|
+
import fs13 from "fs";
|
|
3465
|
+
function registerStatus(program2) {
|
|
3466
|
+
program2.command("status").description("Show index statistics for the current project").option("--json", "Output as JSON").action(async (opts) => {
|
|
3467
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
3468
|
+
const branch = getCurrentBranch(projectDir);
|
|
3469
|
+
const statsFile = codemapStatsFile(projectDir, branch, getDataDir2());
|
|
3470
|
+
let stats = null;
|
|
3471
|
+
try {
|
|
3472
|
+
stats = JSON.parse(fs13.readFileSync(statsFile, "utf-8"));
|
|
3473
|
+
} catch {
|
|
3474
|
+
}
|
|
3475
|
+
if (opts.json) {
|
|
3476
|
+
console.log(JSON.stringify({ projectDir, branch, stats }, null, 2));
|
|
3477
|
+
return;
|
|
3478
|
+
}
|
|
3479
|
+
console.log(chalk6.bold("Codemap Status"));
|
|
3480
|
+
console.log(chalk6.dim("-".repeat(40)));
|
|
3481
|
+
console.log(`Project: ${chalk6.cyan(projectDir)}`);
|
|
3482
|
+
console.log(`Branch: ${branch}`);
|
|
3483
|
+
console.log(`Data dir: ${getDataDir2()}`);
|
|
3484
|
+
console.log();
|
|
3485
|
+
if (!stats) {
|
|
3486
|
+
console.log(chalk6.yellow("No index found. Run `codemap index` first."));
|
|
3487
|
+
return;
|
|
3488
|
+
}
|
|
3489
|
+
console.log(`Files: ${stats.totalFiles}`);
|
|
3490
|
+
console.log(`Chunks: ${stats.totalChunks}`);
|
|
3491
|
+
console.log(`Stale files: ${stats.staleFiles}`);
|
|
3492
|
+
console.log(`Index size: ${formatBytes(stats.indexSizeBytes)}`);
|
|
3493
|
+
console.log(`Last index: ${stats.lastFullIndexMs}ms`);
|
|
3494
|
+
console.log(`Updated: ${stats.lastUpdated}`);
|
|
3495
|
+
});
|
|
3496
|
+
}
|
|
3497
|
+
function formatBytes(bytes) {
|
|
3498
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
3499
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
3500
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3503
|
+
// src/commands/serve.ts
|
|
3504
|
+
import fs14 from "fs";
|
|
3505
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3506
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3507
|
+
import { z as z14 } from "zod";
|
|
3508
|
+
|
|
3509
|
+
// ../../packages/intelligence/depgraph-engine/dist/index.js
|
|
3510
|
+
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
3511
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
3512
|
+
import { dirname as dirname2 } from "path";
|
|
3513
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
3514
|
+
import { dirname as dirname3 } from "path";
|
|
3515
|
+
function getIncomingEdges(graph, filePath) {
|
|
3516
|
+
return graph.edges.filter((e) => e.target === filePath);
|
|
3517
|
+
}
|
|
3518
|
+
function getOutgoingEdges(graph, filePath) {
|
|
3519
|
+
return graph.edges.filter((e) => e.source === filePath);
|
|
3520
|
+
}
|
|
3521
|
+
function loadGraph(filePath) {
|
|
3522
|
+
try {
|
|
3523
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
3524
|
+
return JSON.parse(raw);
|
|
3525
|
+
} catch {
|
|
3526
|
+
return null;
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
var DEFAULT_DAMPING = 0.85;
|
|
3530
|
+
var DEFAULT_MAX_ITERATIONS = 100;
|
|
3531
|
+
var DEFAULT_CONVERGENCE = 1e-6;
|
|
3532
|
+
var ACTIVE_FILE_BOOST = 100;
|
|
3533
|
+
var MENTIONED_FILE_BOOST = 5;
|
|
3534
|
+
var MENTIONED_IDENTIFIER_BOOST = 10;
|
|
3535
|
+
function computePageRank(graph, config, personalization) {
|
|
3536
|
+
const d2 = config?.dampingFactor ?? DEFAULT_DAMPING;
|
|
3537
|
+
const maxIter = config?.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
3538
|
+
const threshold = config?.convergenceThreshold ?? DEFAULT_CONVERGENCE;
|
|
3539
|
+
const files = Object.keys(graph.nodes);
|
|
3540
|
+
const N = files.length;
|
|
3541
|
+
if (N === 0) {
|
|
3542
|
+
return { ranks: {}, iterations: 0, converged: true, dampingFactor: d2 };
|
|
3543
|
+
}
|
|
3544
|
+
const outNeighbors = /* @__PURE__ */ new Map();
|
|
3545
|
+
const outDegree = /* @__PURE__ */ new Map();
|
|
3546
|
+
for (const file of files) {
|
|
3547
|
+
outNeighbors.set(file, []);
|
|
3548
|
+
outDegree.set(file, 0);
|
|
3549
|
+
}
|
|
3550
|
+
for (const edge of graph.edges) {
|
|
3551
|
+
const neighbors = outNeighbors.get(edge.source);
|
|
3552
|
+
if (neighbors) {
|
|
3553
|
+
neighbors.push(edge.target);
|
|
3554
|
+
outDegree.set(edge.source, (outDegree.get(edge.source) ?? 0) + 1);
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
const inNeighbors = /* @__PURE__ */ new Map();
|
|
3558
|
+
for (const file of files) {
|
|
3559
|
+
inNeighbors.set(file, []);
|
|
3560
|
+
}
|
|
3561
|
+
for (const edge of graph.edges) {
|
|
3562
|
+
const incoming = inNeighbors.get(edge.target);
|
|
3563
|
+
if (incoming) {
|
|
3564
|
+
incoming.push(edge.source);
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
const personalVector = buildPersonalizationVector(files, graph, personalization);
|
|
3568
|
+
let ranks = /* @__PURE__ */ new Map();
|
|
3569
|
+
const initial = 1 / N;
|
|
3570
|
+
for (const file of files) {
|
|
3571
|
+
ranks.set(file, initial);
|
|
3572
|
+
}
|
|
3573
|
+
let converged = false;
|
|
3574
|
+
let iterations = 0;
|
|
3575
|
+
for (let iter = 0; iter < maxIter; iter++) {
|
|
3576
|
+
iterations = iter + 1;
|
|
3577
|
+
const newRanks = /* @__PURE__ */ new Map();
|
|
3578
|
+
let danglingSum = 0;
|
|
3579
|
+
for (const file of files) {
|
|
3580
|
+
if ((outDegree.get(file) ?? 0) === 0) {
|
|
3581
|
+
danglingSum += ranks.get(file) ?? 0;
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
const personalTotal = personalVector ? Array.from(personalVector.values()).reduce((a, b) => a + b, 0) : 0;
|
|
3585
|
+
for (const file of files) {
|
|
3586
|
+
let linkSum = 0;
|
|
3587
|
+
const incoming = inNeighbors.get(file) ?? [];
|
|
3588
|
+
for (const src of incoming) {
|
|
3589
|
+
const srcRank = ranks.get(src) ?? 0;
|
|
3590
|
+
const srcDegree = outDegree.get(src) ?? 1;
|
|
3591
|
+
linkSum += srcRank / srcDegree;
|
|
3592
|
+
}
|
|
3593
|
+
const danglingContrib = danglingSum / N;
|
|
3594
|
+
let teleport;
|
|
3595
|
+
if (personalVector && personalTotal > 0) {
|
|
3596
|
+
teleport = (personalVector.get(file) ?? 0) / personalTotal;
|
|
3597
|
+
} else {
|
|
3598
|
+
teleport = 1 / N;
|
|
3599
|
+
}
|
|
3600
|
+
const rank = (1 - d2) * teleport + d2 * (linkSum + danglingContrib);
|
|
3601
|
+
newRanks.set(file, rank);
|
|
3602
|
+
}
|
|
3603
|
+
let diff = 0;
|
|
3604
|
+
for (const file of files) {
|
|
3605
|
+
diff += Math.abs((newRanks.get(file) ?? 0) - (ranks.get(file) ?? 0));
|
|
3606
|
+
}
|
|
3607
|
+
ranks = newRanks;
|
|
3608
|
+
if (diff < threshold) {
|
|
3609
|
+
converged = true;
|
|
3610
|
+
break;
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
const result = {};
|
|
3614
|
+
for (const [file, rank] of ranks) {
|
|
3615
|
+
result[file] = rank;
|
|
3616
|
+
}
|
|
3617
|
+
return {
|
|
3618
|
+
ranks: result,
|
|
3619
|
+
iterations,
|
|
3620
|
+
converged,
|
|
3621
|
+
dampingFactor: d2
|
|
3622
|
+
};
|
|
3623
|
+
}
|
|
3624
|
+
function savePageRank(result, filePath) {
|
|
3625
|
+
mkdirSync2(dirname2(filePath), { recursive: true });
|
|
3626
|
+
writeFileSync2(filePath, JSON.stringify(result, null, 2), "utf-8");
|
|
3627
|
+
}
|
|
3628
|
+
function loadPageRank(filePath) {
|
|
3629
|
+
try {
|
|
3630
|
+
const raw = readFileSync2(filePath, "utf-8");
|
|
3631
|
+
return JSON.parse(raw);
|
|
3632
|
+
} catch {
|
|
3633
|
+
return null;
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
function buildPersonalizationVector(files, graph, personalization) {
|
|
3637
|
+
if (!personalization) return null;
|
|
3638
|
+
const {
|
|
3639
|
+
activeFiles = [],
|
|
3640
|
+
mentionedFiles = [],
|
|
3641
|
+
mentionedIdentifiers = []
|
|
3642
|
+
} = personalization;
|
|
3643
|
+
if (activeFiles.length === 0 && mentionedFiles.length === 0 && mentionedIdentifiers.length === 0) {
|
|
3644
|
+
return null;
|
|
3645
|
+
}
|
|
3646
|
+
const vector = /* @__PURE__ */ new Map();
|
|
3647
|
+
for (const file of files) {
|
|
3648
|
+
vector.set(file, 1);
|
|
3649
|
+
}
|
|
3650
|
+
const activeSet = new Set(activeFiles);
|
|
3651
|
+
for (const file of files) {
|
|
3652
|
+
if (activeSet.has(file)) {
|
|
3653
|
+
vector.set(file, (vector.get(file) ?? 0) + ACTIVE_FILE_BOOST);
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
const mentionedSet = new Set(mentionedFiles);
|
|
3657
|
+
for (const file of files) {
|
|
3658
|
+
if (mentionedSet.has(file)) {
|
|
3659
|
+
vector.set(file, (vector.get(file) ?? 0) + MENTIONED_FILE_BOOST);
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
if (mentionedIdentifiers.length > 0) {
|
|
3663
|
+
const identSet = new Set(mentionedIdentifiers);
|
|
3664
|
+
for (const edge of graph.edges) {
|
|
3665
|
+
for (const sym of edge.symbols) {
|
|
3666
|
+
if (identSet.has(sym)) {
|
|
3667
|
+
vector.set(
|
|
3668
|
+
edge.target,
|
|
3669
|
+
(vector.get(edge.target) ?? 0) + MENTIONED_IDENTIFIER_BOOST
|
|
3670
|
+
);
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
return vector;
|
|
3676
|
+
}
|
|
3677
|
+
function detectCycles(graph) {
|
|
3678
|
+
const files = Object.keys(graph.nodes);
|
|
3679
|
+
const adj = /* @__PURE__ */ new Map();
|
|
3680
|
+
for (const file of files) {
|
|
3681
|
+
adj.set(file, []);
|
|
3682
|
+
}
|
|
3683
|
+
for (const edge of graph.edges) {
|
|
3684
|
+
const neighbors = adj.get(edge.source);
|
|
3685
|
+
if (neighbors) {
|
|
3686
|
+
neighbors.push(edge.target);
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
let index = 0;
|
|
3690
|
+
const nodeIndex = /* @__PURE__ */ new Map();
|
|
3691
|
+
const nodeLowlink = /* @__PURE__ */ new Map();
|
|
3692
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
3693
|
+
const stack = [];
|
|
3694
|
+
const sccs = [];
|
|
3695
|
+
function strongConnect(v) {
|
|
3696
|
+
nodeIndex.set(v, index);
|
|
3697
|
+
nodeLowlink.set(v, index);
|
|
3698
|
+
index++;
|
|
3699
|
+
stack.push(v);
|
|
3700
|
+
onStack.add(v);
|
|
3701
|
+
for (const w of adj.get(v) ?? []) {
|
|
3702
|
+
if (!nodeIndex.has(w)) {
|
|
3703
|
+
strongConnect(w);
|
|
3704
|
+
nodeLowlink.set(v, Math.min(nodeLowlink.get(v), nodeLowlink.get(w)));
|
|
3705
|
+
} else if (onStack.has(w)) {
|
|
3706
|
+
nodeLowlink.set(v, Math.min(nodeLowlink.get(v), nodeIndex.get(w)));
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
if (nodeLowlink.get(v) === nodeIndex.get(v)) {
|
|
3710
|
+
const scc = [];
|
|
3711
|
+
let w;
|
|
3712
|
+
do {
|
|
3713
|
+
w = stack.pop();
|
|
3714
|
+
onStack.delete(w);
|
|
3715
|
+
scc.push(w);
|
|
3716
|
+
} while (w !== v);
|
|
3717
|
+
if (scc.length >= 2) {
|
|
3718
|
+
sccs.push(scc);
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
for (const file of files) {
|
|
3723
|
+
if (!nodeIndex.has(file)) {
|
|
3724
|
+
strongConnect(file);
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
return sccs.map((scc) => {
|
|
3728
|
+
const sccSet = new Set(scc);
|
|
3729
|
+
let edgeCount = 0;
|
|
3730
|
+
for (const edge of graph.edges) {
|
|
3731
|
+
if (sccSet.has(edge.source) && sccSet.has(edge.target)) {
|
|
3732
|
+
edgeCount++;
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
return {
|
|
3736
|
+
files: scc.sort(),
|
|
3737
|
+
edgeCount
|
|
3738
|
+
};
|
|
3739
|
+
});
|
|
3740
|
+
}
|
|
3741
|
+
function computeCoupling(graph) {
|
|
3742
|
+
const files = Object.keys(graph.nodes);
|
|
3743
|
+
const fanIn = /* @__PURE__ */ new Map();
|
|
3744
|
+
const fanOut = /* @__PURE__ */ new Map();
|
|
3745
|
+
for (const file of files) {
|
|
3746
|
+
fanIn.set(file, /* @__PURE__ */ new Set());
|
|
3747
|
+
fanOut.set(file, /* @__PURE__ */ new Set());
|
|
3748
|
+
}
|
|
3749
|
+
for (const edge of graph.edges) {
|
|
3750
|
+
fanIn.get(edge.target)?.add(edge.source);
|
|
3751
|
+
fanOut.get(edge.source)?.add(edge.target);
|
|
3752
|
+
}
|
|
3753
|
+
return files.map((file) => {
|
|
3754
|
+
const ca = fanIn.get(file)?.size ?? 0;
|
|
3755
|
+
const ce = fanOut.get(file)?.size ?? 0;
|
|
3756
|
+
const total = ca + ce;
|
|
3757
|
+
return {
|
|
3758
|
+
filePath: file,
|
|
3759
|
+
afferentCoupling: ca,
|
|
3760
|
+
efferentCoupling: ce,
|
|
3761
|
+
instability: total > 0 ? ce / total : 0
|
|
3762
|
+
};
|
|
3763
|
+
});
|
|
3764
|
+
}
|
|
3765
|
+
function detectHotspots(graph, pageRank) {
|
|
3766
|
+
const files = Object.keys(graph.nodes);
|
|
3767
|
+
const connectivity = /* @__PURE__ */ new Map();
|
|
3768
|
+
for (const file of files) {
|
|
3769
|
+
connectivity.set(file, 0);
|
|
3770
|
+
}
|
|
3771
|
+
for (const edge of graph.edges) {
|
|
3772
|
+
connectivity.set(edge.source, (connectivity.get(edge.source) ?? 0) + 1);
|
|
3773
|
+
connectivity.set(edge.target, (connectivity.get(edge.target) ?? 0) + 1);
|
|
3774
|
+
}
|
|
3775
|
+
return files.map((file) => ({
|
|
3776
|
+
filePath: file,
|
|
3777
|
+
connectivity: connectivity.get(file) ?? 0,
|
|
3778
|
+
pageRank: pageRank.ranks[file] ?? 0,
|
|
3779
|
+
definitionCount: graph.nodes[file]?.definitionCount ?? 0
|
|
3780
|
+
})).sort((a, b) => {
|
|
3781
|
+
if (b.pageRank !== a.pageRank) return b.pageRank - a.pageRank;
|
|
3782
|
+
if (b.connectivity !== a.connectivity) return b.connectivity - a.connectivity;
|
|
3783
|
+
return b.definitionCount - a.definitionCount;
|
|
3784
|
+
});
|
|
3785
|
+
}
|
|
3786
|
+
function computeMetrics(graph, pageRank) {
|
|
3787
|
+
const files = Object.keys(graph.nodes);
|
|
3788
|
+
const cycles = detectCycles(graph);
|
|
3789
|
+
const coupling = computeCoupling(graph);
|
|
3790
|
+
const hotspots = detectHotspots(graph, pageRank);
|
|
3791
|
+
let totalDefinitions = 0;
|
|
3792
|
+
let totalReferences = 0;
|
|
3793
|
+
for (const node of Object.values(graph.nodes)) {
|
|
3794
|
+
totalDefinitions += node.definitionCount;
|
|
3795
|
+
totalReferences += node.referenceCount;
|
|
3796
|
+
}
|
|
3797
|
+
const fanIns = coupling.map((c2) => c2.afferentCoupling);
|
|
3798
|
+
const fanOuts = coupling.map((c2) => c2.efferentCoupling);
|
|
3799
|
+
const totalFiles = files.length;
|
|
3800
|
+
return {
|
|
3801
|
+
totalFiles,
|
|
3802
|
+
totalEdges: graph.edges.length,
|
|
3803
|
+
totalDefinitions,
|
|
3804
|
+
totalReferences,
|
|
3805
|
+
cycles,
|
|
3806
|
+
coupling,
|
|
3807
|
+
hotspots,
|
|
3808
|
+
avgFanIn: totalFiles > 0 ? sum(fanIns) / totalFiles : 0,
|
|
3809
|
+
avgFanOut: totalFiles > 0 ? sum(fanOuts) / totalFiles : 0,
|
|
3810
|
+
maxFanIn: fanIns.length > 0 ? Math.max(...fanIns) : 0,
|
|
3811
|
+
maxFanOut: fanOuts.length > 0 ? Math.max(...fanOuts) : 0
|
|
3812
|
+
};
|
|
3813
|
+
}
|
|
3814
|
+
function saveMetrics(metrics, filePath) {
|
|
3815
|
+
mkdirSync3(dirname3(filePath), { recursive: true });
|
|
3816
|
+
writeFileSync3(filePath, JSON.stringify(metrics, null, 2), "utf-8");
|
|
3817
|
+
}
|
|
3818
|
+
function loadMetrics(filePath) {
|
|
3819
|
+
try {
|
|
3820
|
+
const raw = readFileSync3(filePath, "utf-8");
|
|
3821
|
+
return JSON.parse(raw);
|
|
3822
|
+
} catch {
|
|
3823
|
+
return null;
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
function sum(values) {
|
|
3827
|
+
let total = 0;
|
|
3828
|
+
for (const v of values) {
|
|
3829
|
+
total += v;
|
|
3830
|
+
}
|
|
3831
|
+
return total;
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
// src/commands/serve.ts
|
|
3835
|
+
function text(value) {
|
|
3836
|
+
return { content: [{ type: "text", text: value }] };
|
|
3837
|
+
}
|
|
3838
|
+
function json(value) {
|
|
3839
|
+
return text(JSON.stringify(value, null, 2));
|
|
3840
|
+
}
|
|
3841
|
+
function resolveProjectDir(override) {
|
|
3842
|
+
const dir = override || process.env["CODEMAP_PROJECT_DIR"] || process.cwd();
|
|
3843
|
+
if (!fs14.existsSync(dir)) {
|
|
3844
|
+
throw new Error(`Project directory not found: ${dir}`);
|
|
3845
|
+
}
|
|
3846
|
+
return dir;
|
|
3847
|
+
}
|
|
3848
|
+
function loadGraphSafe(projectDir, branch) {
|
|
3849
|
+
try {
|
|
3850
|
+
return loadGraph(depgraphGraphFile(projectDir, branch, getDataDir2()));
|
|
3851
|
+
} catch {
|
|
3852
|
+
return null;
|
|
3853
|
+
}
|
|
3854
|
+
}
|
|
3855
|
+
function loadPageRankSafe(projectDir, branch) {
|
|
3856
|
+
try {
|
|
3857
|
+
return loadPageRank(depgraphPagerankFile(projectDir, branch, getDataDir2()));
|
|
3858
|
+
} catch {
|
|
3859
|
+
return null;
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
function loadMetricsSafe(projectDir, branch) {
|
|
3863
|
+
try {
|
|
3864
|
+
return loadMetrics(depgraphMetricsFile(projectDir, branch, getDataDir2()));
|
|
3865
|
+
} catch {
|
|
3866
|
+
return null;
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
function createCodemapMcpServer() {
|
|
3870
|
+
const server = new McpServer({
|
|
3871
|
+
name: "codemap",
|
|
3872
|
+
version: "0.1.0"
|
|
3873
|
+
});
|
|
3874
|
+
const dataDir = getDataDir2();
|
|
3875
|
+
server.tool(
|
|
3876
|
+
"search_code",
|
|
3877
|
+
"Search code using hybrid vector + BM25",
|
|
3878
|
+
{
|
|
3879
|
+
query: z14.string().describe("Natural language search query"),
|
|
3880
|
+
limit: z14.number().int().min(1).max(50).optional().describe("Max results (default 10)"),
|
|
3881
|
+
projectDir: z14.string().optional().describe("Project directory (default: cwd)")
|
|
3882
|
+
},
|
|
3883
|
+
async ({ query, limit, projectDir: pd }) => {
|
|
3884
|
+
const projectDir = resolveProjectDir(pd);
|
|
3885
|
+
const config = loadCliConfig(projectDir);
|
|
3886
|
+
bridgeEnvVars(config);
|
|
3887
|
+
const branch = getCurrentBranch(projectDir);
|
|
3888
|
+
const results = await searchCode(projectDir, query, {
|
|
3889
|
+
limit: limit ?? 10,
|
|
3890
|
+
branch,
|
|
3891
|
+
dataDir
|
|
3892
|
+
});
|
|
3893
|
+
return json(
|
|
3894
|
+
results.map((r) => ({
|
|
3895
|
+
file: r.filePath,
|
|
3896
|
+
lines: `${r.startLine}-${r.endLine}`,
|
|
3897
|
+
score: Math.round(r.score * 1e3) / 1e3,
|
|
3898
|
+
snippet: r.snippet.slice(0, 500)
|
|
3899
|
+
}))
|
|
3900
|
+
);
|
|
3901
|
+
}
|
|
3902
|
+
);
|
|
3903
|
+
server.tool(
|
|
3904
|
+
"search_symbols",
|
|
3905
|
+
"Find functions, classes, and types by name",
|
|
3906
|
+
{
|
|
3907
|
+
query: z14.string().describe("Symbol name or pattern to search for"),
|
|
3908
|
+
limit: z14.number().int().min(1).max(100).optional().describe("Max results (default 20)"),
|
|
3909
|
+
projectDir: z14.string().optional().describe("Project directory (default: cwd)")
|
|
3910
|
+
},
|
|
3911
|
+
async ({ query, limit, projectDir: pd }) => {
|
|
3912
|
+
const projectDir = resolveProjectDir(pd);
|
|
3913
|
+
const branch = getCurrentBranch(projectDir);
|
|
3914
|
+
const metaDir = codemapMetadataDir(projectDir, branch, dataDir);
|
|
3915
|
+
const symbolIndex = loadSymbolIndex(metaDir);
|
|
3916
|
+
const matches = searchSymbols(symbolIndex, query, limit ?? 20);
|
|
3917
|
+
return json(
|
|
3918
|
+
matches.map((m) => ({
|
|
3919
|
+
name: m.entry.name,
|
|
3920
|
+
type: m.entry.symbolType,
|
|
3921
|
+
file: m.entry.filePath,
|
|
3922
|
+
line: m.entry.startLine,
|
|
3923
|
+
score: Math.round(m.score * 1e3) / 1e3
|
|
3924
|
+
}))
|
|
3925
|
+
);
|
|
3926
|
+
}
|
|
3927
|
+
);
|
|
3928
|
+
server.tool(
|
|
3929
|
+
"get_file_summary",
|
|
3930
|
+
"Get file overview with symbols and size",
|
|
3931
|
+
{
|
|
3932
|
+
filePath: z14.string().describe("Repo-relative file path"),
|
|
3933
|
+
projectDir: z14.string().optional().describe("Project directory (default: cwd)")
|
|
3934
|
+
},
|
|
3935
|
+
async ({ filePath, projectDir: pd }) => {
|
|
3936
|
+
const projectDir = resolveProjectDir(pd);
|
|
3937
|
+
const branch = getCurrentBranch(projectDir);
|
|
3938
|
+
const fullPath = `${projectDir}/${filePath}`;
|
|
3939
|
+
if (!fs14.existsSync(fullPath)) {
|
|
3940
|
+
return text(`File not found: ${filePath}`);
|
|
3941
|
+
}
|
|
3942
|
+
const content = fs14.readFileSync(fullPath, "utf-8");
|
|
3943
|
+
const lines = content.split("\n");
|
|
3944
|
+
const metaDir = codemapMetadataDir(projectDir, branch, dataDir);
|
|
3945
|
+
const symbolIndex = loadSymbolIndex(metaDir);
|
|
3946
|
+
const fileSymbols = symbolIndex.filter((s) => s.filePath === filePath);
|
|
3947
|
+
return json({
|
|
3948
|
+
filePath,
|
|
3949
|
+
sizeBytes: Buffer.byteLength(content, "utf-8"),
|
|
3950
|
+
lineCount: lines.length,
|
|
3951
|
+
symbols: fileSymbols.map((s) => ({
|
|
3952
|
+
name: s.name,
|
|
3953
|
+
type: s.symbolType,
|
|
3954
|
+
line: s.startLine
|
|
3955
|
+
}))
|
|
3956
|
+
});
|
|
3957
|
+
}
|
|
3958
|
+
);
|
|
3959
|
+
server.tool(
|
|
3960
|
+
"get_index_stats",
|
|
3961
|
+
"Get code search index statistics",
|
|
3962
|
+
{
|
|
3963
|
+
projectDir: z14.string().optional().describe("Project directory (default: cwd)")
|
|
3964
|
+
},
|
|
3965
|
+
async ({ projectDir: pd }) => {
|
|
3966
|
+
const projectDir = resolveProjectDir(pd);
|
|
3967
|
+
const branch = getCurrentBranch(projectDir);
|
|
3968
|
+
const statsFile = codemapStatsFile(projectDir, branch, dataDir);
|
|
3969
|
+
let stats = null;
|
|
3970
|
+
try {
|
|
3971
|
+
stats = JSON.parse(fs14.readFileSync(statsFile, "utf-8"));
|
|
3972
|
+
} catch {
|
|
3973
|
+
}
|
|
3974
|
+
return json({ projectDir, branch, stats });
|
|
3975
|
+
}
|
|
3976
|
+
);
|
|
3977
|
+
server.tool(
|
|
3978
|
+
"reindex",
|
|
3979
|
+
"Trigger a full code search re-index",
|
|
3980
|
+
{
|
|
3981
|
+
projectDir: z14.string().optional().describe("Project directory (default: cwd)")
|
|
3982
|
+
},
|
|
3983
|
+
async ({ projectDir: pd }) => {
|
|
3984
|
+
const projectDir = resolveProjectDir(pd);
|
|
3985
|
+
try {
|
|
3986
|
+
await runInitPipeline(projectDir, void 0, { dataDir });
|
|
3987
|
+
return text(`Reindex complete for ${projectDir}`);
|
|
3988
|
+
} catch (err) {
|
|
3989
|
+
return text(`Reindex failed: ${String(err)}`);
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
);
|
|
3993
|
+
server.tool(
|
|
3994
|
+
"get_dependencies",
|
|
3995
|
+
"Get files this file imports (outgoing edges)",
|
|
3996
|
+
{
|
|
3997
|
+
filePath: z14.string().describe("Repo-relative file path"),
|
|
3998
|
+
projectDir: z14.string().optional().describe("Project directory (default: cwd)")
|
|
3999
|
+
},
|
|
4000
|
+
async ({ filePath, projectDir: pd }) => {
|
|
4001
|
+
const projectDir = resolveProjectDir(pd);
|
|
4002
|
+
const branch = getCurrentBranch(projectDir);
|
|
4003
|
+
const graph = loadGraphSafe(projectDir, branch);
|
|
4004
|
+
if (!graph) return text("Dependency graph not available. Run reindex first.");
|
|
4005
|
+
const outgoing = getOutgoingEdges(graph, filePath);
|
|
4006
|
+
return json(outgoing.map((e) => ({ target: e.target, symbols: e.symbols })));
|
|
4007
|
+
}
|
|
4008
|
+
);
|
|
4009
|
+
server.tool(
|
|
4010
|
+
"get_dependents",
|
|
4011
|
+
"Get files that import this file (incoming edges)",
|
|
4012
|
+
{
|
|
4013
|
+
filePath: z14.string().describe("Repo-relative file path"),
|
|
4014
|
+
projectDir: z14.string().optional().describe("Project directory (default: cwd)")
|
|
4015
|
+
},
|
|
4016
|
+
async ({ filePath, projectDir: pd }) => {
|
|
4017
|
+
const projectDir = resolveProjectDir(pd);
|
|
4018
|
+
const branch = getCurrentBranch(projectDir);
|
|
4019
|
+
const graph = loadGraphSafe(projectDir, branch);
|
|
4020
|
+
if (!graph) return text("Dependency graph not available. Run reindex first.");
|
|
4021
|
+
const incoming = getIncomingEdges(graph, filePath);
|
|
4022
|
+
return json(incoming.map((e) => ({ source: e.source, symbols: e.symbols })));
|
|
4023
|
+
}
|
|
4024
|
+
);
|
|
4025
|
+
server.tool(
|
|
4026
|
+
"get_file_rank",
|
|
4027
|
+
"Get PageRank importance score for files",
|
|
4028
|
+
{
|
|
4029
|
+
filePath: z14.string().optional().describe("Specific file (omit for top-ranked files)"),
|
|
4030
|
+
limit: z14.number().int().min(1).max(100).optional().describe("Number of top files (default 20)"),
|
|
4031
|
+
projectDir: z14.string().optional().describe("Project directory (default: cwd)")
|
|
4032
|
+
},
|
|
4033
|
+
async ({ filePath, limit, projectDir: pd }) => {
|
|
4034
|
+
const projectDir = resolveProjectDir(pd);
|
|
4035
|
+
const branch = getCurrentBranch(projectDir);
|
|
4036
|
+
const pageRank = loadPageRankSafe(projectDir, branch);
|
|
4037
|
+
if (!pageRank) return text("PageRank not available. Run rebuild_depgraph first.");
|
|
4038
|
+
if (filePath) {
|
|
4039
|
+
const rank = pageRank.ranks[filePath] ?? 0;
|
|
4040
|
+
return json({ filePath, rank });
|
|
4041
|
+
}
|
|
4042
|
+
const sorted = Object.entries(pageRank.ranks).sort(([, a], [, b]) => b - a).slice(0, limit ?? 20);
|
|
4043
|
+
return json(sorted.map(([file, rank]) => ({
|
|
4044
|
+
filePath: file,
|
|
4045
|
+
rank: Math.round(rank * 1e4) / 1e4
|
|
4046
|
+
})));
|
|
4047
|
+
}
|
|
4048
|
+
);
|
|
4049
|
+
server.tool(
|
|
4050
|
+
"find_cycles",
|
|
4051
|
+
"Detect circular dependencies",
|
|
4052
|
+
{
|
|
4053
|
+
projectDir: z14.string().optional().describe("Project directory (default: cwd)")
|
|
4054
|
+
},
|
|
4055
|
+
async ({ projectDir: pd }) => {
|
|
4056
|
+
const projectDir = resolveProjectDir(pd);
|
|
4057
|
+
const branch = getCurrentBranch(projectDir);
|
|
4058
|
+
const graph = loadGraphSafe(projectDir, branch);
|
|
4059
|
+
if (!graph) return text("Dependency graph not available. Run reindex first.");
|
|
4060
|
+
const cycles = detectCycles(graph);
|
|
4061
|
+
return json({
|
|
4062
|
+
cycleCount: cycles.length,
|
|
4063
|
+
cycles: cycles.map((c) => ({ files: c.files, edgeCount: c.edgeCount }))
|
|
4064
|
+
});
|
|
4065
|
+
}
|
|
4066
|
+
);
|
|
4067
|
+
server.tool(
|
|
4068
|
+
"get_coupling_metrics",
|
|
4069
|
+
"Analyze afferent/efferent coupling and instability",
|
|
4070
|
+
{
|
|
4071
|
+
modulePath: z14.string().optional().describe("Filter to a directory prefix (e.g. src/routes/)"),
|
|
4072
|
+
limit: z14.number().int().min(1).max(200).optional().describe("Max results (default 50)"),
|
|
4073
|
+
projectDir: z14.string().optional().describe("Project directory (default: cwd)")
|
|
4074
|
+
},
|
|
4075
|
+
async ({ modulePath, limit, projectDir: pd }) => {
|
|
4076
|
+
const projectDir = resolveProjectDir(pd);
|
|
4077
|
+
const branch = getCurrentBranch(projectDir);
|
|
4078
|
+
const graph = loadGraphSafe(projectDir, branch);
|
|
4079
|
+
if (!graph) return text("Dependency graph not available. Run reindex first.");
|
|
4080
|
+
let coupling = computeCoupling(graph);
|
|
4081
|
+
if (modulePath) coupling = coupling.filter((c) => c.filePath.startsWith(modulePath));
|
|
4082
|
+
coupling.sort((a, b) => b.instability - a.instability);
|
|
4083
|
+
coupling = coupling.slice(0, limit ?? 50);
|
|
4084
|
+
return json(coupling.map((c) => ({
|
|
4085
|
+
file: c.filePath,
|
|
4086
|
+
fanIn: c.afferentCoupling,
|
|
4087
|
+
fanOut: c.efferentCoupling,
|
|
4088
|
+
instability: Math.round(c.instability * 1e3) / 1e3
|
|
4089
|
+
})));
|
|
4090
|
+
}
|
|
4091
|
+
);
|
|
4092
|
+
server.tool(
|
|
4093
|
+
"get_depgraph_stats",
|
|
4094
|
+
"Get aggregate dependency graph statistics",
|
|
4095
|
+
{
|
|
4096
|
+
projectDir: z14.string().optional().describe("Project directory (default: cwd)")
|
|
4097
|
+
},
|
|
4098
|
+
async ({ projectDir: pd }) => {
|
|
4099
|
+
const projectDir = resolveProjectDir(pd);
|
|
4100
|
+
const branch = getCurrentBranch(projectDir);
|
|
4101
|
+
const metrics = loadMetricsSafe(projectDir, branch);
|
|
4102
|
+
if (!metrics) return text("Dependency metrics not available. Run rebuild_depgraph first.");
|
|
4103
|
+
return json({
|
|
4104
|
+
totalFiles: metrics.totalFiles,
|
|
4105
|
+
totalEdges: metrics.totalEdges,
|
|
4106
|
+
totalDefinitions: metrics.totalDefinitions,
|
|
4107
|
+
totalReferences: metrics.totalReferences,
|
|
4108
|
+
cycleCount: metrics.cycles.length,
|
|
4109
|
+
avgFanIn: Math.round(metrics.avgFanIn * 100) / 100,
|
|
4110
|
+
avgFanOut: Math.round(metrics.avgFanOut * 100) / 100,
|
|
4111
|
+
maxFanIn: metrics.maxFanIn,
|
|
4112
|
+
maxFanOut: metrics.maxFanOut,
|
|
4113
|
+
hotspots: metrics.hotspots.slice(0, 10).map((h) => ({
|
|
4114
|
+
file: h.filePath,
|
|
4115
|
+
connectivity: h.connectivity,
|
|
4116
|
+
pageRank: Math.round(h.pageRank * 1e4) / 1e4
|
|
4117
|
+
}))
|
|
4118
|
+
});
|
|
4119
|
+
}
|
|
4120
|
+
);
|
|
4121
|
+
server.tool(
|
|
4122
|
+
"rebuild_depgraph",
|
|
4123
|
+
"Recompute PageRank and dependency metrics",
|
|
4124
|
+
{
|
|
4125
|
+
projectDir: z14.string().optional().describe("Project directory (default: cwd)")
|
|
4126
|
+
},
|
|
4127
|
+
async ({ projectDir: pd }) => {
|
|
4128
|
+
const projectDir = resolveProjectDir(pd);
|
|
4129
|
+
const branch = getCurrentBranch(projectDir);
|
|
4130
|
+
const graph = loadGraphSafe(projectDir, branch);
|
|
4131
|
+
if (!graph) {
|
|
4132
|
+
return text("No dependency graph data found. Run reindex first to generate the graph data.");
|
|
4133
|
+
}
|
|
4134
|
+
const pageRank = computePageRank(graph);
|
|
4135
|
+
savePageRank(pageRank, depgraphPagerankFile(projectDir, branch, dataDir));
|
|
4136
|
+
const metrics = computeMetrics(graph, pageRank);
|
|
4137
|
+
saveMetrics(metrics, depgraphMetricsFile(projectDir, branch, dataDir));
|
|
4138
|
+
return json({
|
|
4139
|
+
message: "Dependency graph metrics rebuilt",
|
|
4140
|
+
totalFiles: Object.keys(graph.nodes).length,
|
|
4141
|
+
totalEdges: graph.edges.length,
|
|
4142
|
+
cycleCount: metrics.cycles.length
|
|
4143
|
+
});
|
|
4144
|
+
}
|
|
4145
|
+
);
|
|
4146
|
+
return server;
|
|
4147
|
+
}
|
|
4148
|
+
function registerServe(program2) {
|
|
4149
|
+
program2.command("serve").description("Start MCP server over stdio (for AI agent integration)").action(async () => {
|
|
4150
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
4151
|
+
const config = loadCliConfig(projectDir);
|
|
4152
|
+
bridgeEnvVars(config);
|
|
4153
|
+
writeCodemapIgnore(projectDir);
|
|
4154
|
+
const server = createCodemapMcpServer();
|
|
4155
|
+
const transport = new StdioServerTransport();
|
|
4156
|
+
await server.connect(transport);
|
|
4157
|
+
});
|
|
4158
|
+
}
|
|
4159
|
+
|
|
4160
|
+
// src/commands/deps.ts
|
|
4161
|
+
import chalk7 from "chalk";
|
|
4162
|
+
function registerDeps(program2) {
|
|
4163
|
+
program2.command("deps <file>").description("Show files this file depends on (outgoing imports)").option("--json", "Output as JSON").action(async (file, opts) => {
|
|
4164
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
4165
|
+
const branch = getCurrentBranch(projectDir);
|
|
4166
|
+
const graphFile = depgraphGraphFile(projectDir, branch, getDataDir2());
|
|
4167
|
+
let graph;
|
|
4168
|
+
try {
|
|
4169
|
+
graph = loadGraph(graphFile);
|
|
4170
|
+
} catch {
|
|
4171
|
+
console.error(chalk7.red("Dependency graph not available. Run `codemap index` first."));
|
|
4172
|
+
process.exit(1);
|
|
4173
|
+
return;
|
|
4174
|
+
}
|
|
4175
|
+
if (!graph) {
|
|
4176
|
+
console.error(chalk7.red("Dependency graph not available. Run `codemap index` first."));
|
|
4177
|
+
process.exit(1);
|
|
4178
|
+
return;
|
|
4179
|
+
}
|
|
4180
|
+
const edges = getOutgoingEdges(graph, file);
|
|
4181
|
+
if (opts.json) {
|
|
4182
|
+
console.log(JSON.stringify(edges.map((e) => ({
|
|
4183
|
+
target: e.target,
|
|
4184
|
+
symbols: e.symbols
|
|
4185
|
+
})), null, 2));
|
|
4186
|
+
return;
|
|
4187
|
+
}
|
|
4188
|
+
if (edges.length === 0) {
|
|
4189
|
+
console.log(chalk7.yellow(`No dependencies found for ${file}`));
|
|
4190
|
+
return;
|
|
4191
|
+
}
|
|
4192
|
+
console.log(chalk7.bold(`Dependencies of ${chalk7.cyan(file)}`));
|
|
4193
|
+
console.log();
|
|
4194
|
+
for (const e of edges) {
|
|
4195
|
+
console.log(` ${chalk7.cyan(e.target)}`);
|
|
4196
|
+
if (e.symbols.length > 0) {
|
|
4197
|
+
console.log(chalk7.dim(` symbols: ${e.symbols.join(", ")}`));
|
|
4198
|
+
}
|
|
4199
|
+
}
|
|
4200
|
+
});
|
|
4201
|
+
}
|
|
4202
|
+
|
|
4203
|
+
// src/commands/dependents.ts
|
|
4204
|
+
import chalk8 from "chalk";
|
|
4205
|
+
function registerDependents(program2) {
|
|
4206
|
+
program2.command("dependents <file>").description("Show files that depend on this file (incoming imports)").option("--json", "Output as JSON").action(async (file, opts) => {
|
|
4207
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
4208
|
+
const branch = getCurrentBranch(projectDir);
|
|
4209
|
+
const graphFile = depgraphGraphFile(projectDir, branch, getDataDir2());
|
|
4210
|
+
let graph;
|
|
4211
|
+
try {
|
|
4212
|
+
graph = loadGraph(graphFile);
|
|
4213
|
+
} catch {
|
|
4214
|
+
console.error(chalk8.red("Dependency graph not available. Run `codemap index` first."));
|
|
4215
|
+
process.exit(1);
|
|
4216
|
+
return;
|
|
4217
|
+
}
|
|
4218
|
+
if (!graph) {
|
|
4219
|
+
console.error(chalk8.red("Dependency graph not available. Run `codemap index` first."));
|
|
4220
|
+
process.exit(1);
|
|
4221
|
+
return;
|
|
4222
|
+
}
|
|
4223
|
+
const edges = getIncomingEdges(graph, file);
|
|
4224
|
+
if (opts.json) {
|
|
4225
|
+
console.log(JSON.stringify(edges.map((e) => ({
|
|
4226
|
+
source: e.source,
|
|
4227
|
+
symbols: e.symbols
|
|
4228
|
+
})), null, 2));
|
|
4229
|
+
return;
|
|
4230
|
+
}
|
|
4231
|
+
if (edges.length === 0) {
|
|
4232
|
+
console.log(chalk8.yellow(`No dependents found for ${file}`));
|
|
4233
|
+
return;
|
|
4234
|
+
}
|
|
4235
|
+
console.log(chalk8.bold(`Dependents of ${chalk8.cyan(file)}`));
|
|
4236
|
+
console.log();
|
|
4237
|
+
for (const e of edges) {
|
|
4238
|
+
console.log(` ${chalk8.cyan(e.source)}`);
|
|
4239
|
+
if (e.symbols.length > 0) {
|
|
4240
|
+
console.log(chalk8.dim(` symbols: ${e.symbols.join(", ")}`));
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
});
|
|
4244
|
+
}
|
|
4245
|
+
|
|
4246
|
+
// src/commands/rank.ts
|
|
4247
|
+
import chalk9 from "chalk";
|
|
4248
|
+
function registerRank(program2) {
|
|
4249
|
+
program2.command("rank [file]").description("Show PageRank importance scores (top files or single file)").option("-l, --limit <n>", "Number of top files to show", "20").option("--json", "Output as JSON").action(async (file, opts) => {
|
|
4250
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
4251
|
+
const branch = getCurrentBranch(projectDir);
|
|
4252
|
+
const prFile = depgraphPagerankFile(projectDir, branch, getDataDir2());
|
|
4253
|
+
let pageRank;
|
|
4254
|
+
try {
|
|
4255
|
+
pageRank = loadPageRank(prFile);
|
|
4256
|
+
} catch {
|
|
4257
|
+
console.error(chalk9.red("PageRank not available. Run `codemap index` first."));
|
|
4258
|
+
process.exit(1);
|
|
4259
|
+
return;
|
|
4260
|
+
}
|
|
4261
|
+
if (!pageRank) {
|
|
4262
|
+
console.error(chalk9.red("PageRank not available. Run `codemap index` first."));
|
|
4263
|
+
process.exit(1);
|
|
4264
|
+
return;
|
|
4265
|
+
}
|
|
4266
|
+
if (file) {
|
|
4267
|
+
const rank = pageRank.ranks[file] ?? 0;
|
|
4268
|
+
if (opts.json) {
|
|
4269
|
+
console.log(JSON.stringify({ filePath: file, rank }));
|
|
4270
|
+
return;
|
|
4271
|
+
}
|
|
4272
|
+
console.log(`${chalk9.cyan(file)}: ${chalk9.bold(String(Math.round(rank * 1e4) / 1e4))}`);
|
|
4273
|
+
return;
|
|
4274
|
+
}
|
|
4275
|
+
const limit = parseInt(opts.limit, 10);
|
|
4276
|
+
const sorted = Object.entries(pageRank.ranks).sort(([, a], [, b]) => b - a).slice(0, limit);
|
|
4277
|
+
if (opts.json) {
|
|
4278
|
+
console.log(JSON.stringify(sorted.map(([f, r]) => ({
|
|
4279
|
+
filePath: f,
|
|
4280
|
+
rank: Math.round(r * 1e4) / 1e4
|
|
4281
|
+
})), null, 2));
|
|
4282
|
+
return;
|
|
4283
|
+
}
|
|
4284
|
+
console.log(chalk9.bold("Top files by PageRank"));
|
|
4285
|
+
console.log(chalk9.dim("-".repeat(60)));
|
|
4286
|
+
for (const [f, r] of sorted) {
|
|
4287
|
+
const rank = Math.round(r * 1e4) / 1e4;
|
|
4288
|
+
console.log(` ${String(rank).padStart(8)} ${chalk9.cyan(f)}`);
|
|
4289
|
+
}
|
|
4290
|
+
});
|
|
4291
|
+
}
|
|
4292
|
+
|
|
4293
|
+
// src/commands/cycles.ts
|
|
4294
|
+
import chalk10 from "chalk";
|
|
4295
|
+
function registerCycles(program2) {
|
|
4296
|
+
program2.command("cycles").description("Detect circular dependencies in the codebase").option("--json", "Output as JSON").action(async (opts) => {
|
|
4297
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
4298
|
+
const branch = getCurrentBranch(projectDir);
|
|
4299
|
+
const graphFile = depgraphGraphFile(projectDir, branch, getDataDir2());
|
|
4300
|
+
let graph;
|
|
4301
|
+
try {
|
|
4302
|
+
graph = loadGraph(graphFile);
|
|
4303
|
+
} catch {
|
|
4304
|
+
console.error(chalk10.red("Dependency graph not available. Run `codemap index` first."));
|
|
4305
|
+
process.exit(1);
|
|
4306
|
+
return;
|
|
4307
|
+
}
|
|
4308
|
+
if (!graph) {
|
|
4309
|
+
console.error(chalk10.red("Dependency graph not available. Run `codemap index` first."));
|
|
4310
|
+
process.exit(1);
|
|
4311
|
+
return;
|
|
4312
|
+
}
|
|
4313
|
+
const cycles = detectCycles(graph);
|
|
4314
|
+
if (opts.json) {
|
|
4315
|
+
console.log(JSON.stringify({
|
|
4316
|
+
cycleCount: cycles.length,
|
|
4317
|
+
cycles: cycles.map((c) => ({
|
|
4318
|
+
files: c.files,
|
|
4319
|
+
edgeCount: c.edgeCount
|
|
4320
|
+
}))
|
|
4321
|
+
}, null, 2));
|
|
4322
|
+
return;
|
|
4323
|
+
}
|
|
4324
|
+
if (cycles.length === 0) {
|
|
4325
|
+
console.log(chalk10.green("No circular dependencies found."));
|
|
4326
|
+
return;
|
|
4327
|
+
}
|
|
4328
|
+
console.log(chalk10.bold(`Found ${chalk10.red(String(cycles.length))} circular dependencies:`));
|
|
4329
|
+
console.log();
|
|
4330
|
+
for (let i = 0; i < cycles.length; i++) {
|
|
4331
|
+
const c = cycles[i];
|
|
4332
|
+
console.log(chalk10.yellow(`Cycle ${i + 1}`) + chalk10.dim(` (${c.edgeCount} edges)`));
|
|
4333
|
+
for (const f of c.files) {
|
|
4334
|
+
console.log(` ${chalk10.cyan(f)}`);
|
|
4335
|
+
}
|
|
4336
|
+
console.log();
|
|
4337
|
+
}
|
|
4338
|
+
});
|
|
4339
|
+
}
|
|
4340
|
+
|
|
4341
|
+
// src/commands/config-cmd.ts
|
|
4342
|
+
import chalk11 from "chalk";
|
|
4343
|
+
function registerConfig(program2) {
|
|
4344
|
+
const cmd = program2.command("config").description("Get, set, or list configuration values");
|
|
4345
|
+
cmd.command("list").description("Show all configuration values").action(() => {
|
|
4346
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
4347
|
+
const config = loadCliConfig(projectDir);
|
|
4348
|
+
console.log(chalk11.bold("Configuration") + chalk11.dim(` (${globalConfigFile()})`));
|
|
4349
|
+
console.log(JSON.stringify(config, null, 2));
|
|
4350
|
+
});
|
|
4351
|
+
cmd.command("get <key>").description("Get a configuration value (e.g., embedding.provider)").action((key) => {
|
|
4352
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
4353
|
+
const config = loadCliConfig(projectDir);
|
|
4354
|
+
const value = getNestedValue(config, key);
|
|
4355
|
+
if (value === void 0) {
|
|
4356
|
+
console.log(chalk11.yellow(`Key "${key}" is not set`));
|
|
4357
|
+
} else {
|
|
4358
|
+
console.log(String(value));
|
|
4359
|
+
}
|
|
4360
|
+
});
|
|
4361
|
+
cmd.command("set <key> <value>").description("Set a configuration value (e.g., embedding.provider openai)").action((key, value) => {
|
|
4362
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
4363
|
+
const config = loadCliConfig(projectDir);
|
|
4364
|
+
setNestedValue(config, key, value);
|
|
4365
|
+
saveCliConfig(config);
|
|
4366
|
+
console.log(chalk11.green(`Set ${key} = ${value}`));
|
|
4367
|
+
});
|
|
4368
|
+
}
|
|
4369
|
+
function getNestedValue(obj, key) {
|
|
4370
|
+
const parts = key.split(".");
|
|
4371
|
+
let current = obj;
|
|
4372
|
+
for (const part of parts) {
|
|
4373
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
4374
|
+
current = current[part];
|
|
4375
|
+
}
|
|
4376
|
+
return current;
|
|
4377
|
+
}
|
|
4378
|
+
function setNestedValue(obj, key, value) {
|
|
4379
|
+
const parts = key.split(".");
|
|
4380
|
+
let current = obj;
|
|
4381
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
4382
|
+
const part = parts[i];
|
|
4383
|
+
if (!(part in current) || typeof current[part] !== "object") {
|
|
4384
|
+
current[part] = {};
|
|
4385
|
+
}
|
|
4386
|
+
current = current[part];
|
|
4387
|
+
}
|
|
4388
|
+
const lastKey = parts[parts.length - 1];
|
|
4389
|
+
const numVal = Number(value);
|
|
4390
|
+
if (!isNaN(numVal) && value.trim() !== "") {
|
|
4391
|
+
current[lastKey] = numVal;
|
|
4392
|
+
} else if (value === "true") {
|
|
4393
|
+
current[lastKey] = true;
|
|
4394
|
+
} else if (value === "false") {
|
|
4395
|
+
current[lastKey] = false;
|
|
4396
|
+
} else {
|
|
4397
|
+
current[lastKey] = value;
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
|
|
4401
|
+
// src/commands/ignore.ts
|
|
4402
|
+
import fs15 from "fs";
|
|
4403
|
+
import path10 from "path";
|
|
4404
|
+
import chalk12 from "chalk";
|
|
4405
|
+
function registerIgnore(program2) {
|
|
4406
|
+
program2.command("ignore").description("Generate or reset .codemapignore with default patterns").option("--force", "Overwrite existing .codemapignore").action((opts) => {
|
|
4407
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
4408
|
+
const ignorePath = path10.join(projectDir, ".codemapignore");
|
|
4409
|
+
const exists = fs15.existsSync(ignorePath);
|
|
4410
|
+
if (exists && !opts.force) {
|
|
4411
|
+
console.log(chalk12.yellow(".codemapignore already exists."));
|
|
4412
|
+
console.log(chalk12.dim("Use --force to overwrite with the default template."));
|
|
4413
|
+
return;
|
|
4414
|
+
}
|
|
4415
|
+
writeCodemapIgnore(projectDir, true);
|
|
4416
|
+
if (exists) {
|
|
4417
|
+
console.log(chalk12.green("Regenerated ") + chalk12.cyan(".codemapignore") + chalk12.green(" with default patterns"));
|
|
4418
|
+
} else {
|
|
4419
|
+
console.log(chalk12.green("Created ") + chalk12.cyan(".codemapignore") + chalk12.green(" with default patterns"));
|
|
4420
|
+
}
|
|
4421
|
+
});
|
|
4422
|
+
}
|
|
4423
|
+
|
|
4424
|
+
// src/index.ts
|
|
4425
|
+
var program = new Command();
|
|
4426
|
+
program.name("codemap").description("Code intelligence CLI \u2014 hybrid search, dependency analysis, PageRank").version("0.1.0").option("--cwd <dir>", "Project directory (default: cwd)");
|
|
4427
|
+
registerSearch(program);
|
|
4428
|
+
registerSymbols(program);
|
|
4429
|
+
registerIndex(program);
|
|
4430
|
+
registerWatch(program);
|
|
4431
|
+
registerStatus(program);
|
|
4432
|
+
registerServe(program);
|
|
4433
|
+
registerDeps(program);
|
|
4434
|
+
registerDependents(program);
|
|
4435
|
+
registerRank(program);
|
|
4436
|
+
registerCycles(program);
|
|
4437
|
+
registerConfig(program);
|
|
4438
|
+
registerInit(program);
|
|
4439
|
+
registerIgnore(program);
|
|
4440
|
+
program.parse();
|