grepmax 0.23.0 → 0.25.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/.claude-plugin/marketplace.json +1 -1
- package/README.md +25 -6
- package/dist/commands/add.js +38 -31
- package/dist/commands/config.js +16 -15
- package/dist/commands/context.js +38 -13
- package/dist/commands/doctor.js +76 -83
- package/dist/commands/extract.js +12 -1
- package/dist/commands/impact.js +33 -1
- package/dist/commands/index.js +23 -24
- package/dist/commands/list.js +23 -14
- package/dist/commands/mcp.js +494 -162
- package/dist/commands/peek.js +16 -5
- package/dist/commands/repair.js +54 -120
- package/dist/commands/search-output.js +30 -9
- package/dist/commands/search-run.js +75 -47
- package/dist/commands/search-skeletons.js +28 -18
- package/dist/commands/search.js +45 -49
- package/dist/commands/serve.js +415 -373
- package/dist/commands/setup.js +2 -2
- package/dist/commands/similar.js +5 -5
- package/dist/commands/skeleton.js +67 -41
- package/dist/commands/status.js +5 -2
- package/dist/commands/summarize.js +6 -0
- package/dist/commands/surprises.js +150 -0
- package/dist/commands/watch.js +102 -38
- package/dist/config.js +3 -0
- package/dist/eval-surprising-connections.js +191 -0
- package/dist/index.js +2 -0
- package/dist/lib/analysis/surprising-connections.js +600 -0
- package/dist/lib/daemon/daemon.js +1101 -379
- package/dist/lib/daemon/ipc-handler.js +122 -13
- package/dist/lib/daemon/mlx-server-manager.js +268 -125
- package/dist/lib/daemon/process-manager.js +7 -4
- package/dist/lib/daemon/search-handler.js +23 -9
- package/dist/lib/daemon/watcher-manager.js +353 -110
- package/dist/lib/graph/impact-rollup.js +202 -0
- package/dist/lib/graph/impact.js +15 -1
- package/dist/lib/help/agent-cheatsheet.js +1 -0
- package/dist/lib/index/batch-processor.js +231 -67
- package/dist/lib/index/embedding-generation.js +109 -0
- package/dist/lib/index/embedding-status.js +23 -0
- package/dist/lib/index/file-policy.js +273 -0
- package/dist/lib/index/ignore-patterns.js +4 -0
- package/dist/lib/index/index-config.js +18 -4
- package/dist/lib/index/syncer.js +256 -79
- package/dist/lib/index/walker.js +66 -86
- package/dist/lib/index/watcher-batch.js +9 -0
- package/dist/lib/index/watcher.js +129 -3
- package/dist/lib/llm/server.js +6 -0
- package/dist/lib/search/searcher.js +30 -19
- package/dist/lib/skeleton/skeletonizer.js +118 -0
- package/dist/lib/skeleton/summary-formatter.js +8 -1
- package/dist/lib/store/store-lease.js +473 -0
- package/dist/lib/store/vector-db.js +302 -57
- package/dist/lib/utils/blocked-roots.js +63 -0
- package/dist/lib/utils/cross-project.js +7 -3
- package/dist/lib/utils/daemon-client.js +24 -1
- package/dist/lib/utils/daemon-launcher.js +38 -13
- package/dist/lib/utils/doctor-status.js +76 -0
- package/dist/lib/utils/file-utils.js +74 -4
- package/dist/lib/utils/keyed-mutex.js +101 -0
- package/dist/lib/utils/logger.js +57 -1
- package/dist/lib/utils/mlx-hf-cache.js +114 -0
- package/dist/lib/utils/operation-coordinator.js +146 -0
- package/dist/lib/utils/path-containment.js +106 -0
- package/dist/lib/utils/process.js +44 -0
- package/dist/lib/utils/project-registry.js +351 -3
- package/dist/lib/utils/scope-filter.js +3 -9
- package/dist/lib/utils/stale-hint.js +12 -19
- package/dist/lib/utils/watcher-launcher.js +5 -1
- package/dist/lib/utils/watcher-store.js +2 -2
- package/dist/lib/workers/colbert-math.js +15 -12
- package/dist/lib/workers/embeddings/colbert.js +3 -2
- package/dist/lib/workers/embeddings/granite.js +4 -3
- package/dist/lib/workers/embeddings/mlx-client.js +59 -16
- package/dist/lib/workers/orchestrator.js +39 -8
- package/dist/lib/workers/pool.js +150 -83
- package/dist/lib/workers/process-child.js +11 -2
- package/dist/lib/workers/serialized-handler.js +10 -0
- package/dist/lib/workers/worker.js +13 -4
- package/mlx-embed-server/server.py +21 -3
- package/mlx-embed-server/summarizer.py +8 -0
- package/package.json +6 -3
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/start.js +3 -170
package/dist/commands/setup.js
CHANGED
|
@@ -142,10 +142,10 @@ exports.setup = new commander_1.Command("setup")
|
|
|
142
142
|
const tierChanged = existingConfig.modelTier !== modelTier;
|
|
143
143
|
const modeChanged = existingConfig.embedMode !== embedMode;
|
|
144
144
|
if (tierChanged) {
|
|
145
|
-
p.log.warn(`Model tier changed (${(_d = existingConfig.vectorDim) !== null && _d !== void 0 ? _d : 384}d → ${selectedTier.vectorDim}d). Existing indexes
|
|
145
|
+
p.log.warn(`Model tier changed (${(_d = existingConfig.vectorDim) !== null && _d !== void 0 ? _d : 384}d → ${selectedTier.vectorDim}d). Existing indexes are preserved and search is blocked until you run 'gmax repair --rebuild'.`);
|
|
146
146
|
}
|
|
147
147
|
else if (modeChanged) {
|
|
148
|
-
p.log.warn("Embedding mode changed.
|
|
148
|
+
p.log.warn("Embedding mode changed. Restart the gmax daemon to apply the new settings.");
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
// Step 8: Install plugins for detected clients
|
package/dist/commands/similar.js
CHANGED
|
@@ -43,13 +43,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
43
43
|
};
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
exports.similar = void 0;
|
|
46
|
-
const path = __importStar(require("node:path"));
|
|
47
46
|
const commander_1 = require("commander");
|
|
48
47
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
49
48
|
const agent_errors_1 = require("../lib/utils/agent-errors");
|
|
50
49
|
const arrow_1 = require("../lib/utils/arrow");
|
|
51
50
|
const exit_1 = require("../lib/utils/exit");
|
|
52
51
|
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
52
|
+
const path_containment_1 = require("../lib/utils/path-containment");
|
|
53
53
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
54
54
|
const project_root_1 = require("../lib/utils/project-root");
|
|
55
55
|
const stale_hint_1 = require("../lib/utils/stale-hint");
|
|
@@ -81,9 +81,9 @@ exports.similar = new commander_1.Command("similar")
|
|
|
81
81
|
// Look up the source chunk's vector
|
|
82
82
|
let sourceRows;
|
|
83
83
|
if (isFile) {
|
|
84
|
-
const absPath =
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
const absPath = (0, path_containment_1.resolveContainedPath)(projectRoot, target, {
|
|
85
|
+
verifyExistingTarget: true,
|
|
86
|
+
});
|
|
87
87
|
sourceRows = yield table
|
|
88
88
|
.query()
|
|
89
89
|
.select(["vector", "path", "defined_symbols", "start_line"])
|
|
@@ -95,7 +95,7 @@ exports.similar = new commander_1.Command("similar")
|
|
|
95
95
|
sourceRows = yield table
|
|
96
96
|
.query()
|
|
97
97
|
.select(["vector", "path", "defined_symbols", "start_line"])
|
|
98
|
-
.where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(target)}')`)
|
|
98
|
+
.where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(target)}') AND ${(0, filter_builder_1.pathStartsWith)(`${projectRoot}/`)}`)
|
|
99
99
|
.limit(1)
|
|
100
100
|
.toArray();
|
|
101
101
|
}
|
|
@@ -52,8 +52,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
52
52
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
53
|
exports.skeleton = void 0;
|
|
54
54
|
const fs = __importStar(require("node:fs"));
|
|
55
|
-
const path = __importStar(require("node:path"));
|
|
56
55
|
const commander_1 = require("commander");
|
|
56
|
+
const config_1 = require("../config");
|
|
57
57
|
const sync_helpers_1 = require("../lib/index/sync-helpers");
|
|
58
58
|
const syncer_1 = require("../lib/index/syncer");
|
|
59
59
|
const searcher_1 = require("../lib/search/searcher");
|
|
@@ -62,20 +62,11 @@ const retriever_1 = require("../lib/skeleton/retriever");
|
|
|
62
62
|
const skeletonizer_1 = require("../lib/skeleton/skeletonizer");
|
|
63
63
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
64
64
|
const exit_1 = require("../lib/utils/exit");
|
|
65
|
+
const file_utils_1 = require("../lib/utils/file-utils");
|
|
66
|
+
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
67
|
+
const path_containment_1 = require("../lib/utils/path-containment");
|
|
65
68
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
66
69
|
const project_root_1 = require("../lib/utils/project-root");
|
|
67
|
-
/**
|
|
68
|
-
* Resolve a relative path across all indexed projects.
|
|
69
|
-
* Returns the first match found, or null.
|
|
70
|
-
*/
|
|
71
|
-
function resolveAcrossProjects(relativePath) {
|
|
72
|
-
for (const project of (0, project_registry_1.listProjects)()) {
|
|
73
|
-
const candidate = path.join(project.root, relativePath);
|
|
74
|
-
if (fs.existsSync(candidate))
|
|
75
|
-
return candidate;
|
|
76
|
-
}
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
70
|
/**
|
|
80
71
|
* Check if target looks like a file path.
|
|
81
72
|
*/
|
|
@@ -94,12 +85,16 @@ function isSymbolLike(target) {
|
|
|
94
85
|
/**
|
|
95
86
|
* Find a file by symbol name in the index.
|
|
96
87
|
*/
|
|
97
|
-
function findFileBySymbol(symbol, db) {
|
|
88
|
+
function findFileBySymbol(symbol, db, projectRoot) {
|
|
98
89
|
return __awaiter(this, void 0, void 0, function* () {
|
|
99
90
|
try {
|
|
100
91
|
const table = yield db.ensureTable();
|
|
101
92
|
// Search for files that define this symbol
|
|
102
|
-
const results = yield table
|
|
93
|
+
const results = yield table
|
|
94
|
+
.search(symbol)
|
|
95
|
+
.where((0, filter_builder_1.pathStartsWith)(`${projectRoot}/`))
|
|
96
|
+
.limit(10)
|
|
97
|
+
.toArray();
|
|
103
98
|
// Find a result where this symbol is defined
|
|
104
99
|
for (const result of results) {
|
|
105
100
|
const defined = result.defined_symbols;
|
|
@@ -144,8 +139,26 @@ Examples:
|
|
|
144
139
|
// Sync if requested
|
|
145
140
|
if (options.sync) {
|
|
146
141
|
const { spinner, onProgress } = (0, sync_helpers_1.createIndexingSpinner)(projectRoot, "Syncing...", { verbose: false });
|
|
147
|
-
yield (0, syncer_1.initialSync)({ projectRoot, onProgress });
|
|
148
|
-
|
|
142
|
+
const result = yield (0, syncer_1.initialSync)({ projectRoot, onProgress });
|
|
143
|
+
if (result.degraded) {
|
|
144
|
+
spinner.warn(`Sync incomplete: ${result.scanErrors} scan error(s), ${result.failedFiles} file failure(s)`);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
const prefix = projectRoot.endsWith("/")
|
|
148
|
+
? projectRoot
|
|
149
|
+
: `${projectRoot}/`;
|
|
150
|
+
const chunkCount = yield vectorDb.countRowsForPath(prefix);
|
|
151
|
+
(0, project_registry_1.stampProjectFullSync)({
|
|
152
|
+
root: projectRoot,
|
|
153
|
+
generation: result.generation,
|
|
154
|
+
embedMode: result.embedMode,
|
|
155
|
+
chunkCount,
|
|
156
|
+
chunkerVersion: config_1.CONFIG.CHUNKER_VERSION,
|
|
157
|
+
expectedFingerprint: result.registryExpectation.embeddingFingerprint,
|
|
158
|
+
expectedRebuildId: result.registryExpectation.rebuildId,
|
|
159
|
+
});
|
|
160
|
+
spinner.succeed("Sync complete");
|
|
161
|
+
}
|
|
149
162
|
}
|
|
150
163
|
// Initialize skeletonizer
|
|
151
164
|
const skeletonizer = new skeletonizer_1.Skeletonizer();
|
|
@@ -154,7 +167,9 @@ Examples:
|
|
|
154
167
|
includeSummary: !options.noSummary,
|
|
155
168
|
};
|
|
156
169
|
// Determine mode based on target
|
|
157
|
-
const resolvedTarget =
|
|
170
|
+
const resolvedTarget = (0, path_containment_1.resolveContainedPath)(projectRoot, target, {
|
|
171
|
+
verifyExistingTarget: true,
|
|
172
|
+
});
|
|
158
173
|
// Directory mode is unsupported. Auto-picking files from a directory
|
|
159
174
|
// was confusingly magical (and on '.' it fell through to the resolver
|
|
160
175
|
// path and skeletonized .gitignore). Refuse and point at the file form.
|
|
@@ -176,12 +191,14 @@ Examples:
|
|
|
176
191
|
.map((t) => t.trim())
|
|
177
192
|
.filter(Boolean);
|
|
178
193
|
for (const t of targets) {
|
|
179
|
-
const filePath =
|
|
194
|
+
const filePath = (0, path_containment_1.resolveContainedPath)(projectRoot, t, {
|
|
195
|
+
verifyExistingTarget: true,
|
|
196
|
+
});
|
|
180
197
|
if (!fs.existsSync(filePath)) {
|
|
181
198
|
console.error(`Not found: ${t}`);
|
|
182
199
|
continue;
|
|
183
200
|
}
|
|
184
|
-
const content =
|
|
201
|
+
const content = (0, file_utils_1.readContainedTextFileSync)(projectRoot, filePath);
|
|
185
202
|
const result = yield skeletonizer.skeletonizeFile(filePath, content, skeletonOpts);
|
|
186
203
|
outputResult(result, options);
|
|
187
204
|
}
|
|
@@ -189,16 +206,13 @@ Examples:
|
|
|
189
206
|
}
|
|
190
207
|
if (isFilePath(target)) {
|
|
191
208
|
// === FILE MODE ===
|
|
192
|
-
|
|
209
|
+
const filePath = (0, path_containment_1.resolveContainedPath)(projectRoot, target, {
|
|
210
|
+
verifyExistingTarget: true,
|
|
211
|
+
});
|
|
193
212
|
if (!fs.existsSync(filePath)) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
console.error(`File not found: ${filePath}`);
|
|
198
|
-
process.exitCode = 1;
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
filePath = found;
|
|
213
|
+
console.error(`File not found: ${filePath}`);
|
|
214
|
+
process.exitCode = 1;
|
|
215
|
+
return;
|
|
202
216
|
}
|
|
203
217
|
if (vectorDb) {
|
|
204
218
|
// Use absolute path for DB lookup (centralized index stores absolute paths)
|
|
@@ -212,13 +226,13 @@ Examples:
|
|
|
212
226
|
return;
|
|
213
227
|
}
|
|
214
228
|
}
|
|
215
|
-
const content =
|
|
229
|
+
const content = (0, file_utils_1.readContainedTextFileSync)(projectRoot, filePath);
|
|
216
230
|
const result = yield skeletonizer.skeletonizeFile(filePath, content, skeletonOpts);
|
|
217
231
|
outputResult(result, options);
|
|
218
232
|
}
|
|
219
233
|
else if (isSymbolLike(target) && !target.includes(" ")) {
|
|
220
234
|
// === SYMBOL MODE ===
|
|
221
|
-
const filePath = yield findFileBySymbol(target, vectorDb);
|
|
235
|
+
const filePath = yield findFileBySymbol(target, vectorDb, projectRoot);
|
|
222
236
|
if (!filePath) {
|
|
223
237
|
console.error(`Symbol not found in index: ${target}`);
|
|
224
238
|
console.error("Try running 'gmax index' first or use a search query.");
|
|
@@ -226,9 +240,9 @@ Examples:
|
|
|
226
240
|
return;
|
|
227
241
|
}
|
|
228
242
|
// filePath from DB is absolute (centralized index)
|
|
229
|
-
const absolutePath =
|
|
230
|
-
|
|
231
|
-
|
|
243
|
+
const absolutePath = (0, path_containment_1.resolveContainedPath)(projectRoot, filePath, {
|
|
244
|
+
verifyExistingTarget: true,
|
|
245
|
+
});
|
|
232
246
|
if (!fs.existsSync(absolutePath)) {
|
|
233
247
|
console.error(`File not found: ${absolutePath}`);
|
|
234
248
|
process.exitCode = 1;
|
|
@@ -243,7 +257,7 @@ Examples:
|
|
|
243
257
|
}, options);
|
|
244
258
|
return;
|
|
245
259
|
}
|
|
246
|
-
const content =
|
|
260
|
+
const content = (0, file_utils_1.readContainedTextFileSync)(projectRoot, absolutePath);
|
|
247
261
|
const result = yield skeletonizer.skeletonizeFile(absolutePath, content, skeletonOpts);
|
|
248
262
|
outputResult(result, options);
|
|
249
263
|
}
|
|
@@ -251,7 +265,7 @@ Examples:
|
|
|
251
265
|
// === QUERY MODE ===
|
|
252
266
|
const searcher = new searcher_1.Searcher(vectorDb);
|
|
253
267
|
const limit = Math.min(Number.parseInt(options.limit, 10) || 3, 10);
|
|
254
|
-
const searchResults = yield searcher.search(target, limit);
|
|
268
|
+
const searchResults = yield searcher.search(target, limit, {}, {}, `${projectRoot}/`);
|
|
255
269
|
if (!searchResults.data || searchResults.data.length === 0) {
|
|
256
270
|
console.error(`No results found for: ${target}`);
|
|
257
271
|
process.exitCode = 1;
|
|
@@ -273,9 +287,21 @@ Examples:
|
|
|
273
287
|
const results = [];
|
|
274
288
|
for (const filePath of filePaths) {
|
|
275
289
|
// Paths from search results are absolute (centralized index)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
290
|
+
let absolutePath;
|
|
291
|
+
try {
|
|
292
|
+
absolutePath = (0, path_containment_1.resolveContainedPath)(projectRoot, filePath, {
|
|
293
|
+
verifyExistingTarget: true,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
catch (_c) {
|
|
297
|
+
results.push({
|
|
298
|
+
file: filePath,
|
|
299
|
+
skeleton: `// File outside selected project: ${filePath}`,
|
|
300
|
+
tokens: 0,
|
|
301
|
+
error: "File outside selected project",
|
|
302
|
+
});
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
279
305
|
if (!fs.existsSync(absolutePath)) {
|
|
280
306
|
results.push({
|
|
281
307
|
file: filePath,
|
|
@@ -295,7 +321,7 @@ Examples:
|
|
|
295
321
|
});
|
|
296
322
|
continue;
|
|
297
323
|
}
|
|
298
|
-
const content =
|
|
324
|
+
const content = (0, file_utils_1.readContainedTextFileSync)(projectRoot, absolutePath);
|
|
299
325
|
const result = yield skeletonizer.skeletonizeFile(absolutePath, content, skeletonOpts);
|
|
300
326
|
results.push({
|
|
301
327
|
file: filePath,
|
|
@@ -326,7 +352,7 @@ Examples:
|
|
|
326
352
|
try {
|
|
327
353
|
yield vectorDb.close();
|
|
328
354
|
}
|
|
329
|
-
catch (
|
|
355
|
+
catch (_d) {
|
|
330
356
|
// Ignore close errors
|
|
331
357
|
}
|
|
332
358
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -46,6 +46,7 @@ exports.status = void 0;
|
|
|
46
46
|
const os = __importStar(require("node:os"));
|
|
47
47
|
const commander_1 = require("commander");
|
|
48
48
|
const config_1 = require("../config");
|
|
49
|
+
const embedding_status_1 = require("../lib/index/embedding-status");
|
|
49
50
|
const index_config_1 = require("../lib/index/index-config");
|
|
50
51
|
const exit_1 = require("../lib/utils/exit");
|
|
51
52
|
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
@@ -157,7 +158,8 @@ Examples:
|
|
|
157
158
|
st = "idle";
|
|
158
159
|
const isCurrent = project.root === currentRoot;
|
|
159
160
|
const count = (_b = chunkCounts.get(project.root)) !== null && _b !== void 0 ? _b : project.chunkCount;
|
|
160
|
-
|
|
161
|
+
const identity = (0, embedding_status_1.projectEmbeddingStatus)(project, globalConfig);
|
|
162
|
+
console.log(`${project.name}\t${formatChunks(count)}\t${formatAge(project.lastIndexed)}\t${st}\tembedding=${identity.state}${isCurrent ? "\tcurrent" : ""}`);
|
|
161
163
|
}
|
|
162
164
|
yield (0, exit_1.gracefulExit)();
|
|
163
165
|
return;
|
|
@@ -191,10 +193,11 @@ Examples:
|
|
|
191
193
|
const chunks = `${formatChunks(count)} chunks`;
|
|
192
194
|
// Age column
|
|
193
195
|
const age = formatAge(project.lastIndexed);
|
|
196
|
+
const embedding = (0, embedding_status_1.projectEmbeddingStatus)(project, globalConfig).state;
|
|
194
197
|
// Current marker
|
|
195
198
|
const marker = isCurrent ? style.cyan(" ←") : "";
|
|
196
199
|
const name = project.name.padEnd(nameWidth);
|
|
197
|
-
console.log(` ${name} ${chunks.padEnd(12)} ${age.padEnd(10)} ${statusStr}${marker}`);
|
|
200
|
+
console.log(` ${name} ${chunks.padEnd(12)} ${age.padEnd(10)} ${statusStr} embedding:${embedding}${marker}`);
|
|
198
201
|
}
|
|
199
202
|
if (currentRoot) {
|
|
200
203
|
console.log(`\n${style.dim("Current")}: ${shortenPath(currentRoot)}`);
|
|
@@ -50,6 +50,12 @@ const syncer_1 = require("../lib/index/syncer");
|
|
|
50
50
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
51
51
|
const exit_1 = require("../lib/utils/exit");
|
|
52
52
|
const project_root_1 = require("../lib/utils/project-root");
|
|
53
|
+
// ⛔ HARD STOP FOR AI AGENTS: The summarizer is decommissioned on purpose.
|
|
54
|
+
// `generateSummaries` (src/lib/index/syncer.ts) is a no-op stub, so this
|
|
55
|
+
// command intentionally does nothing. DO NOT re-enable it, and DO NOT start
|
|
56
|
+
// the summarizer server (mlx-embed-server/summarizer.py, port 8101) or load its
|
|
57
|
+
// ~16GB model to "measure"/"benchmark" — doing so has crashed the machine.
|
|
58
|
+
// Stop and get the user's explicit authorization first. See CLAUDE.md.
|
|
53
59
|
exports.summarize = new commander_1.Command("summarize")
|
|
54
60
|
.description("Generate LLM summaries for indexed chunks without re-indexing")
|
|
55
61
|
.option("-p, --path <dir>", "Only summarize chunks under this directory")
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.surprises = void 0;
|
|
13
|
+
const commander_1 = require("commander");
|
|
14
|
+
const surprising_connections_1 = require("../lib/analysis/surprising-connections");
|
|
15
|
+
const vector_db_1 = require("../lib/store/vector-db");
|
|
16
|
+
const exit_1 = require("../lib/utils/exit");
|
|
17
|
+
const project_registry_1 = require("../lib/utils/project-registry");
|
|
18
|
+
const project_root_1 = require("../lib/utils/project-root");
|
|
19
|
+
const stale_hint_1 = require("../lib/utils/stale-hint");
|
|
20
|
+
const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
21
|
+
const style = {
|
|
22
|
+
bold: (s) => (useColors ? `\x1b[1m${s}\x1b[22m` : s),
|
|
23
|
+
dim: (s) => (useColors ? `\x1b[2m${s}\x1b[39m` : s),
|
|
24
|
+
cyan: (s) => (useColors ? `\x1b[36m${s}\x1b[39m` : s),
|
|
25
|
+
};
|
|
26
|
+
function parseIntOption(value, fallback, max = 10000) {
|
|
27
|
+
const parsed = Number.parseInt(String(value !== null && value !== void 0 ? value : ""), 10);
|
|
28
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
29
|
+
return fallback;
|
|
30
|
+
return Math.min(parsed, max);
|
|
31
|
+
}
|
|
32
|
+
function parseFloatOption(value, fallback) {
|
|
33
|
+
const parsed = Number.parseFloat(String(value !== null && value !== void 0 ? value : ""));
|
|
34
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
35
|
+
}
|
|
36
|
+
function formatAgent(result, top) {
|
|
37
|
+
const lines = [];
|
|
38
|
+
const { summary, findings } = result;
|
|
39
|
+
lines.push(`summary\t${summary.sampledAnchors}\t${summary.codeRows}\t${summary.acceptedPairs}\t${summary.acceptedFilePairs}`);
|
|
40
|
+
for (const finding of findings.slice(0, top)) {
|
|
41
|
+
const pair = finding.representative;
|
|
42
|
+
lines.push([
|
|
43
|
+
"surprise",
|
|
44
|
+
finding.score.toFixed(3),
|
|
45
|
+
finding.maxSimilarity.toFixed(3),
|
|
46
|
+
String(finding.pairCount),
|
|
47
|
+
finding.fileA,
|
|
48
|
+
finding.fileB,
|
|
49
|
+
(0, surprising_connections_1.lineLabel)(pair.source),
|
|
50
|
+
(0, surprising_connections_1.lineLabel)(pair.target),
|
|
51
|
+
finding.reasons.join(","),
|
|
52
|
+
`buckets=${(0, surprising_connections_1.findingBucketLabel)(finding, summary.options.dirDepth)}`,
|
|
53
|
+
`top_sims=${finding.topSimilarities.join(",")}`,
|
|
54
|
+
`penalties=${(0, surprising_connections_1.formatPenaltySummary)(pair.scoreParts)}`,
|
|
55
|
+
`next=${(0, surprising_connections_1.skeletonHint)(finding)}`,
|
|
56
|
+
].join("\t"));
|
|
57
|
+
}
|
|
58
|
+
return lines.join("\n");
|
|
59
|
+
}
|
|
60
|
+
function formatHuman(result, top) {
|
|
61
|
+
const { summary, findings } = result;
|
|
62
|
+
const out = [];
|
|
63
|
+
out.push(`${style.bold("Surprising connections")} ${style.dim("(experimental, embedding-similar but graph-disconnected file pairs)")}`);
|
|
64
|
+
out.push(style.dim(` sampled ${summary.sampledAnchors}/${summary.codeRows} code chunks; accepted ${summary.acceptedPairs} chunk pairs across ${summary.acceptedFilePairs} file pairs`));
|
|
65
|
+
out.push(style.dim(` score p50/p90/max ${summary.actionabilityScore.p50}/${summary.actionabilityScore.p90}/${summary.actionabilityScore.max}; graph file edges filtered ${summary.filters.graphEdge}`));
|
|
66
|
+
out.push("");
|
|
67
|
+
if (findings.length === 0) {
|
|
68
|
+
out.push(style.dim(" none"));
|
|
69
|
+
return out.join("\n");
|
|
70
|
+
}
|
|
71
|
+
for (const finding of findings.slice(0, top)) {
|
|
72
|
+
const pair = finding.representative;
|
|
73
|
+
out.push(` ${style.cyan(`score=${finding.score.toFixed(3)}`)} sim=${finding.maxSimilarity.toFixed(3)} pairs=${finding.pairCount} ${finding.fileA}`);
|
|
74
|
+
out.push(` <-> ${finding.fileB}`);
|
|
75
|
+
out.push(` best: ${(0, surprising_connections_1.lineLabel)(pair.source)} <-> ${(0, surprising_connections_1.lineLabel)(pair.target)}`);
|
|
76
|
+
out.push(` detail: no static file edge; buckets=${(0, surprising_connections_1.findingBucketLabel)(finding, summary.options.dirDepth)}; top_sims=${finding.topSimilarities.join(",")}`);
|
|
77
|
+
out.push(` reasons: ${finding.reasons.join(", ") || "none"}; penalties=${(0, surprising_connections_1.formatPenaltySummary)(pair.scoreParts)}`);
|
|
78
|
+
const examples = (0, surprising_connections_1.findingExamples)(finding, 2);
|
|
79
|
+
if (examples.length > 1) {
|
|
80
|
+
out.push(` examples: ${examples
|
|
81
|
+
.map((example) => `${(0, surprising_connections_1.lineLabel)(example.source)} <-> ${(0, surprising_connections_1.lineLabel)(example.target)}`)
|
|
82
|
+
.join("; ")}`);
|
|
83
|
+
}
|
|
84
|
+
out.push(` next: ${(0, surprising_connections_1.skeletonHint)(finding)}`);
|
|
85
|
+
}
|
|
86
|
+
return out.join("\n");
|
|
87
|
+
}
|
|
88
|
+
exports.surprises = new commander_1.Command("surprises")
|
|
89
|
+
.description("Experimental: find embedding-similar cross-directory file pairs not already connected by the static graph")
|
|
90
|
+
.option("--experimental", "Required acknowledgement for this experimental signal", false)
|
|
91
|
+
.option("--root <dir>", "Project root directory")
|
|
92
|
+
.option("--sample <n>", "How many indexed code chunks to sample", String(surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.sample))
|
|
93
|
+
.option("--neighbors <n>", "Nearest neighbors to inspect per sampled chunk", String(surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.neighbors))
|
|
94
|
+
.option("--top <n>", "How many grouped findings to show", "20")
|
|
95
|
+
.option("--dir-depth <n>", "Directory bucket depth considered unsurprising", String(surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.dirDepth))
|
|
96
|
+
.option("--min-sim <n>", "Minimum similarity 0-1", String(surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.minSimilarity))
|
|
97
|
+
.option("--max-rows <n>", `Maximum indexed rows to scan (capped at ${surprising_connections_1.MAX_SURPRISE_ROWS})`, String(surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.maxRows))
|
|
98
|
+
.option("--include-tests", "Include test files", false)
|
|
99
|
+
.option("--include-eval", "Include eval/experiment/script files", false)
|
|
100
|
+
.option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
|
|
101
|
+
.option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
|
|
102
|
+
.option("--agent", "Compact TSV output for AI agents", false)
|
|
103
|
+
.action((opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
104
|
+
var _a;
|
|
105
|
+
if (!opts.experimental) {
|
|
106
|
+
const msg = "`gmax surprises` is experimental; rerun with --experimental.";
|
|
107
|
+
console.error(opts.agent ? `error\texperimental_required\t${msg}` : msg);
|
|
108
|
+
process.exitCode = 1;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
let vectorDb = null;
|
|
112
|
+
try {
|
|
113
|
+
const root = (0, project_registry_1.resolveRootOrExit)(opts.root);
|
|
114
|
+
if (root === null)
|
|
115
|
+
return;
|
|
116
|
+
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
|
|
117
|
+
(0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
|
|
118
|
+
(0, stale_hint_1.maybeWarnStaleEmbedding)(projectRoot, { agent: opts.agent });
|
|
119
|
+
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
120
|
+
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
121
|
+
const table = yield vectorDb.ensureTable();
|
|
122
|
+
const top = parseIntOption(opts.top, 20, 100);
|
|
123
|
+
const result = yield (0, surprising_connections_1.analyzeSurprisingConnections)(table, projectRoot, {
|
|
124
|
+
sample: parseIntOption(opts.sample, surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.sample),
|
|
125
|
+
neighbors: parseIntOption(opts.neighbors, surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.neighbors, 200),
|
|
126
|
+
dirDepth: parseIntOption(opts.dirDepth, surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.dirDepth, 20),
|
|
127
|
+
minSimilarity: parseFloatOption(opts.minSim, surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.minSimilarity),
|
|
128
|
+
maxRows: parseIntOption(opts.maxRows, surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.maxRows, surprising_connections_1.MAX_SURPRISE_ROWS),
|
|
129
|
+
includeTests: Boolean(opts.includeTests),
|
|
130
|
+
includeEval: Boolean(opts.includeEval),
|
|
131
|
+
in: opts.in,
|
|
132
|
+
exclude: opts.exclude,
|
|
133
|
+
});
|
|
134
|
+
console.log(opts.agent ? formatAgent(result, top) : formatHuman(result, top));
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
138
|
+
console.error("Surprises failed:", msg);
|
|
139
|
+
process.exitCode = 1;
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
if (vectorDb) {
|
|
143
|
+
try {
|
|
144
|
+
yield vectorDb.close();
|
|
145
|
+
}
|
|
146
|
+
catch (_b) { }
|
|
147
|
+
}
|
|
148
|
+
yield (0, exit_1.gracefulExit)();
|
|
149
|
+
}
|
|
150
|
+
}));
|