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.
Files changed (85) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +25 -6
  3. package/dist/commands/add.js +38 -31
  4. package/dist/commands/config.js +16 -15
  5. package/dist/commands/context.js +38 -13
  6. package/dist/commands/doctor.js +76 -83
  7. package/dist/commands/extract.js +12 -1
  8. package/dist/commands/impact.js +33 -1
  9. package/dist/commands/index.js +23 -24
  10. package/dist/commands/list.js +23 -14
  11. package/dist/commands/mcp.js +494 -162
  12. package/dist/commands/peek.js +16 -5
  13. package/dist/commands/repair.js +54 -120
  14. package/dist/commands/search-output.js +30 -9
  15. package/dist/commands/search-run.js +75 -47
  16. package/dist/commands/search-skeletons.js +28 -18
  17. package/dist/commands/search.js +45 -49
  18. package/dist/commands/serve.js +415 -373
  19. package/dist/commands/setup.js +2 -2
  20. package/dist/commands/similar.js +5 -5
  21. package/dist/commands/skeleton.js +67 -41
  22. package/dist/commands/status.js +5 -2
  23. package/dist/commands/summarize.js +6 -0
  24. package/dist/commands/surprises.js +150 -0
  25. package/dist/commands/watch.js +102 -38
  26. package/dist/config.js +3 -0
  27. package/dist/eval-surprising-connections.js +191 -0
  28. package/dist/index.js +2 -0
  29. package/dist/lib/analysis/surprising-connections.js +600 -0
  30. package/dist/lib/daemon/daemon.js +1101 -379
  31. package/dist/lib/daemon/ipc-handler.js +122 -13
  32. package/dist/lib/daemon/mlx-server-manager.js +268 -125
  33. package/dist/lib/daemon/process-manager.js +7 -4
  34. package/dist/lib/daemon/search-handler.js +23 -9
  35. package/dist/lib/daemon/watcher-manager.js +353 -110
  36. package/dist/lib/graph/impact-rollup.js +202 -0
  37. package/dist/lib/graph/impact.js +15 -1
  38. package/dist/lib/help/agent-cheatsheet.js +1 -0
  39. package/dist/lib/index/batch-processor.js +231 -67
  40. package/dist/lib/index/embedding-generation.js +109 -0
  41. package/dist/lib/index/embedding-status.js +23 -0
  42. package/dist/lib/index/file-policy.js +273 -0
  43. package/dist/lib/index/ignore-patterns.js +4 -0
  44. package/dist/lib/index/index-config.js +18 -4
  45. package/dist/lib/index/syncer.js +256 -79
  46. package/dist/lib/index/walker.js +66 -86
  47. package/dist/lib/index/watcher-batch.js +9 -0
  48. package/dist/lib/index/watcher.js +129 -3
  49. package/dist/lib/llm/server.js +6 -0
  50. package/dist/lib/search/searcher.js +30 -19
  51. package/dist/lib/skeleton/skeletonizer.js +118 -0
  52. package/dist/lib/skeleton/summary-formatter.js +8 -1
  53. package/dist/lib/store/store-lease.js +473 -0
  54. package/dist/lib/store/vector-db.js +302 -57
  55. package/dist/lib/utils/blocked-roots.js +63 -0
  56. package/dist/lib/utils/cross-project.js +7 -3
  57. package/dist/lib/utils/daemon-client.js +24 -1
  58. package/dist/lib/utils/daemon-launcher.js +38 -13
  59. package/dist/lib/utils/doctor-status.js +76 -0
  60. package/dist/lib/utils/file-utils.js +74 -4
  61. package/dist/lib/utils/keyed-mutex.js +101 -0
  62. package/dist/lib/utils/logger.js +57 -1
  63. package/dist/lib/utils/mlx-hf-cache.js +114 -0
  64. package/dist/lib/utils/operation-coordinator.js +146 -0
  65. package/dist/lib/utils/path-containment.js +106 -0
  66. package/dist/lib/utils/process.js +44 -0
  67. package/dist/lib/utils/project-registry.js +351 -3
  68. package/dist/lib/utils/scope-filter.js +3 -9
  69. package/dist/lib/utils/stale-hint.js +12 -19
  70. package/dist/lib/utils/watcher-launcher.js +5 -1
  71. package/dist/lib/utils/watcher-store.js +2 -2
  72. package/dist/lib/workers/colbert-math.js +15 -12
  73. package/dist/lib/workers/embeddings/colbert.js +3 -2
  74. package/dist/lib/workers/embeddings/granite.js +4 -3
  75. package/dist/lib/workers/embeddings/mlx-client.js +59 -16
  76. package/dist/lib/workers/orchestrator.js +39 -8
  77. package/dist/lib/workers/pool.js +150 -83
  78. package/dist/lib/workers/process-child.js +11 -2
  79. package/dist/lib/workers/serialized-handler.js +10 -0
  80. package/dist/lib/workers/worker.js +13 -4
  81. package/mlx-embed-server/server.py +21 -3
  82. package/mlx-embed-server/summarizer.py +8 -0
  83. package/package.json +6 -3
  84. package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
  85. package/plugins/grepmax/hooks/start.js +3 -170
@@ -48,7 +48,6 @@ const fs = __importStar(require("node:fs"));
48
48
  const path = __importStar(require("node:path"));
49
49
  const commander_1 = require("commander");
50
50
  const config_1 = require("../config");
51
- const index_config_1 = require("../lib/index/index-config");
52
51
  const syncer_1 = require("../lib/index/syncer");
53
52
  const watcher_1 = require("../lib/index/watcher");
54
53
  const meta_cache_1 = require("../lib/store/meta-cache");
@@ -122,19 +121,51 @@ exports.watch = new commander_1.Command("watch")
122
121
  }
123
122
  const logFile = path.join(config_1.PATHS.logsDir, "daemon.log");
124
123
  const out = (0, log_rotate_1.openRotatedLog)(logFile);
125
- const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], "watch", "--daemon"], {
126
- detached: true,
127
- stdio: ["ignore", out, out],
128
- cwd: process.cwd(),
129
- env: Object.assign(Object.assign({}, process.env), { GMAX_BACKGROUND: "true" }),
130
- });
124
+ let child;
125
+ try {
126
+ child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], "watch", "--daemon"], {
127
+ detached: true,
128
+ stdio: ["ignore", out, out],
129
+ cwd: process.cwd(),
130
+ env: Object.assign(Object.assign({}, process.env), { GMAX_BACKGROUND: "true" }),
131
+ });
132
+ yield new Promise((resolve, reject) => {
133
+ child.once("spawn", resolve);
134
+ child.once("error", reject);
135
+ });
136
+ }
137
+ catch (error) {
138
+ const message = error instanceof Error ? error.message : String(error);
139
+ console.error(`Failed to start daemon: ${message}`);
140
+ process.exitCode = 1;
141
+ return;
142
+ }
143
+ finally {
144
+ try {
145
+ fs.closeSync(out);
146
+ }
147
+ catch (_c) { }
148
+ }
131
149
  child.unref();
132
150
  console.log(`Daemon started (PID: ${child.pid}, log: ${logFile})`);
133
151
  process.exit(0);
134
152
  }
135
153
  // Daemon foreground
154
+ // Stamp every daemon.log line so incidents can be correlated with the
155
+ // timestamped MLX/LLM server logs. Workers inherit via env flag.
156
+ const { installTimestampedOutput } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/logger")));
157
+ installTimestampedOutput();
136
158
  const { Daemon } = yield Promise.resolve().then(() => __importStar(require("../lib/daemon/daemon")));
137
159
  const daemon = new Daemon();
160
+ process.on("SIGINT", () => daemon.shutdown().then(() => (0, exit_1.gracefulExit)()));
161
+ process.on("SIGTERM", () => daemon.shutdown().then(() => (0, exit_1.gracefulExit)()));
162
+ process.on("uncaughtException", (err) => {
163
+ console.error("[daemon] uncaughtException:", err);
164
+ daemon.shutdown().then(() => (0, exit_1.gracefulExit)(1));
165
+ });
166
+ process.on("unhandledRejection", (reason) => {
167
+ console.error("[daemon] unhandledRejection:", reason);
168
+ });
138
169
  try {
139
170
  yield daemon.start();
140
171
  }
@@ -151,19 +182,10 @@ exports.watch = new commander_1.Command("watch")
151
182
  try {
152
183
  yield daemon.shutdown();
153
184
  }
154
- catch (_c) { }
185
+ catch (_d) { }
155
186
  process.exitCode = 1;
156
187
  return;
157
188
  }
158
- process.on("SIGINT", () => daemon.shutdown().then(() => (0, exit_1.gracefulExit)()));
159
- process.on("SIGTERM", () => daemon.shutdown().then(() => (0, exit_1.gracefulExit)()));
160
- process.on("uncaughtException", (err) => {
161
- console.error("[daemon] uncaughtException:", err);
162
- daemon.shutdown().then(() => (0, exit_1.gracefulExit)(1));
163
- });
164
- process.on("unhandledRejection", (reason) => {
165
- console.error("[daemon] unhandledRejection:", reason);
166
- });
167
189
  return;
168
190
  }
169
191
  // --- Per-project mode ---
@@ -185,12 +207,31 @@ exports.watch = new commander_1.Command("watch")
185
207
  const safeName = projectName.replace(/[^a-zA-Z0-9._-]/g, "_");
186
208
  const logFile = path.join(config_1.PATHS.logsDir, `watch-${safeName}.log`);
187
209
  const out = (0, log_rotate_1.openRotatedLog)(logFile);
188
- const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], ...args], {
189
- detached: true,
190
- stdio: ["ignore", out, out],
191
- cwd: process.cwd(),
192
- env: Object.assign(Object.assign({}, process.env), { GMAX_BACKGROUND: "true" }),
193
- });
210
+ let child;
211
+ try {
212
+ child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], ...args], {
213
+ detached: true,
214
+ stdio: ["ignore", out, out],
215
+ cwd: process.cwd(),
216
+ env: Object.assign(Object.assign({}, process.env), { GMAX_BACKGROUND: "true" }),
217
+ });
218
+ yield new Promise((resolve, reject) => {
219
+ child.once("spawn", resolve);
220
+ child.once("error", reject);
221
+ });
222
+ }
223
+ catch (error) {
224
+ const message = error instanceof Error ? error.message : String(error);
225
+ console.error(`Failed to start watcher for ${projectName}: ${message}`);
226
+ process.exitCode = 1;
227
+ return;
228
+ }
229
+ finally {
230
+ try {
231
+ fs.closeSync(out);
232
+ }
233
+ catch (_e) { }
234
+ }
194
235
  child.unref();
195
236
  console.log(`Watcher started for ${projectName} (PID: ${child.pid}, log: ${logFile})`);
196
237
  process.exit(0);
@@ -225,24 +266,36 @@ exports.watch = new commander_1.Command("watch")
225
266
  .where((0, filter_builder_1.pathStartsWith)(prefix))
226
267
  .limit(1)
227
268
  .toArray();
269
+ let degraded = false;
270
+ let degradedErrors = 0;
271
+ let initialScanErrors = 0;
272
+ let initialFailedFiles = 0;
228
273
  if (indexed.length === 0) {
229
274
  console.log(`[watch:${projectName}] No index found for ${projectRoot}, running initial sync...`);
230
275
  const syncResult = yield (0, syncer_1.initialSync)({ projectRoot });
231
- // Update registry after sync
232
- const globalConfig = (0, index_config_1.readGlobalConfig)();
233
- (0, project_registry_1.registerProject)({
234
- root: projectRoot,
235
- name: projectName,
236
- vectorDim: globalConfig.vectorDim,
237
- modelTier: globalConfig.modelTier,
238
- embedMode: globalConfig.embedMode,
239
- lastIndexed: new Date().toISOString(),
240
- chunkCount: syncResult.indexed,
241
- status: "indexed",
242
- });
243
- console.log(`[watch:${projectName}] Initial sync complete.`);
276
+ if (syncResult.degraded) {
277
+ degraded = true;
278
+ degradedErrors = syncResult.scanErrors + syncResult.failedFiles;
279
+ initialScanErrors = syncResult.scanErrors;
280
+ initialFailedFiles = syncResult.failedFiles;
281
+ console.warn(`[watch:${projectName}] Initial sync incomplete: ${syncResult.scanErrors} scan error(s), ${syncResult.failedFiles} file failure(s). Preserving pending registry state.`);
282
+ }
283
+ else {
284
+ const chunkCount = yield vectorDb.countRowsForPath(prefix);
285
+ (0, project_registry_1.stampProjectFullSync)({
286
+ root: projectRoot,
287
+ name: projectName,
288
+ generation: syncResult.generation,
289
+ embedMode: syncResult.embedMode,
290
+ chunkCount,
291
+ chunkerVersion: config_1.CONFIG.CHUNKER_VERSION,
292
+ expectedFingerprint: syncResult.registryExpectation.embeddingFingerprint,
293
+ expectedRebuildId: syncResult.registryExpectation.rebuildId,
294
+ });
295
+ console.log(`[watch:${projectName}] Initial sync complete.`);
296
+ }
244
297
  }
245
- (0, watcher_store_1.updateWatcherStatus)(process.pid, "watching");
298
+ (0, watcher_store_1.updateWatcherStatus)(process.pid, degraded ? "degraded" : "watching", undefined, degraded ? `${degradedErrors} incomplete scan path(s)` : undefined);
246
299
  // Open resources for watcher
247
300
  const metaCache = new meta_cache_1.MetaCache(paths.lmdbPath);
248
301
  // Start watching
@@ -251,10 +304,17 @@ exports.watch = new commander_1.Command("watch")
251
304
  vectorDb,
252
305
  metaCache,
253
306
  dataDir: paths.dataDir,
307
+ initialScanErrors,
308
+ initialFailedFiles,
254
309
  onReindex: (files, ms) => {
255
310
  console.log(`[watch:${projectName}] Reindexed ${files} file${files !== 1 ? "s" : ""} (${(ms / 1000).toFixed(1)}s)`);
256
311
  lastActivity = Date.now();
257
- (0, watcher_store_1.updateWatcherStatus)(process.pid, "watching", Date.now());
312
+ (0, watcher_store_1.updateWatcherStatus)(process.pid, degraded ? "degraded" : "watching", Date.now(), degraded ? `${degradedErrors} incomplete scan path(s)` : undefined);
313
+ },
314
+ onHealthChange: (complete, errors) => {
315
+ degraded = !complete;
316
+ degradedErrors = errors;
317
+ (0, watcher_store_1.updateWatcherStatus)(process.pid, complete ? "watching" : "degraded", undefined, complete ? undefined : `${errors} incomplete scan path(s)`);
258
318
  },
259
319
  });
260
320
  console.log(`[watch:${projectName}] File watcher active`);
@@ -300,6 +360,10 @@ exports.watch
300
360
  const projects = resp.projects;
301
361
  const uptime = Math.floor(resp.uptime / 60);
302
362
  console.log(`Daemon (PID: ${resp.pid}, uptime: ${uptime}m):`);
363
+ const mlx = resp.mlx;
364
+ if (mlx) {
365
+ console.log(` MLX: ${mlx.state} (${mlx.model}${mlx.pid ? `, PID ${mlx.pid}` : ""})${mlx.error ? ` — ${mlx.error}` : ""}`);
366
+ }
303
367
  for (const p of projects) {
304
368
  console.log(` - ${p.root} [${p.status}]`);
305
369
  }
package/dist/config.js CHANGED
@@ -217,6 +217,9 @@ exports.PATHS = {
217
217
  globalRoot: GLOBAL_ROOT,
218
218
  models: path.join(GLOBAL_ROOT, "models"),
219
219
  grammars: path.join(GLOBAL_ROOT, "grammars"),
220
+ // HF cache for the MLX embed server, pinned to internal disk so the server
221
+ // survives an unmounted external volume behind the user's HF_HOME.
222
+ hfDir: path.join(GLOBAL_ROOT, "hf"),
220
223
  logsDir: path.join(GLOBAL_ROOT, "logs"),
221
224
  daemonSocket: path.join(GLOBAL_ROOT, "daemon.sock"),
222
225
  daemonPidFile: path.join(GLOBAL_ROOT, "daemon.pid"),
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ /**
3
+ * Measure-first prototype for Graphify Phase 3 "surprising connections".
4
+ *
5
+ * It samples indexed code chunks, asks LanceDB for each chunk's nearest vector
6
+ * neighbors, then filters out pairs that are already obvious. Output is grouped
7
+ * by file pair and scored for actionability. This is deliberately an eval
8
+ * harness; the product surface is `gmax surprises --experimental`.
9
+ *
10
+ * Run:
11
+ * pnpm bench:surprises
12
+ * pnpm bench:surprises -- --sample 300 --neighbors 25 --top 30
13
+ * pnpm bench:surprises:json
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
49
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
50
+ return new (P || (P = Promise))(function (resolve, reject) {
51
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
52
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
53
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
54
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
55
+ });
56
+ };
57
+ Object.defineProperty(exports, "__esModule", { value: true });
58
+ const path = __importStar(require("node:path"));
59
+ const surprising_connections_1 = require("./lib/analysis/surprising-connections");
60
+ const vector_db_1 = require("./lib/store/vector-db");
61
+ const exit_1 = require("./lib/utils/exit");
62
+ const project_root_1 = require("./lib/utils/project-root");
63
+ function argValue(name) {
64
+ const prefix = `${name}=`;
65
+ for (let i = 2; i < process.argv.length; i++) {
66
+ const arg = process.argv[i];
67
+ if (arg === name)
68
+ return process.argv[i + 1];
69
+ if (arg.startsWith(prefix))
70
+ return arg.slice(prefix.length);
71
+ }
72
+ return undefined;
73
+ }
74
+ function argValues(name) {
75
+ const values = [];
76
+ const prefix = `${name}=`;
77
+ for (let i = 2; i < process.argv.length; i++) {
78
+ const arg = process.argv[i];
79
+ if (arg === name && process.argv[i + 1]) {
80
+ values.push(process.argv[i + 1]);
81
+ i++;
82
+ }
83
+ else if (arg.startsWith(prefix)) {
84
+ values.push(arg.slice(prefix.length));
85
+ }
86
+ }
87
+ return values.length > 0 ? values : undefined;
88
+ }
89
+ function hasFlag(name) {
90
+ return process.argv.includes(name);
91
+ }
92
+ function intOpt(name, envName, fallback) {
93
+ var _a;
94
+ const raw = (_a = argValue(name)) !== null && _a !== void 0 ? _a : process.env[envName];
95
+ const parsed = Number.parseInt(raw !== null && raw !== void 0 ? raw : "", 10);
96
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
97
+ }
98
+ function floatOpt(name, envName, fallback) {
99
+ var _a;
100
+ const raw = (_a = argValue(name)) !== null && _a !== void 0 ? _a : process.env[envName];
101
+ const parsed = Number.parseFloat(raw !== null && raw !== void 0 ? raw : "");
102
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
103
+ }
104
+ function run() {
105
+ return __awaiter(this, void 0, void 0, function* () {
106
+ var _a, _b;
107
+ const root = path.resolve((_a = argValue("--root")) !== null && _a !== void 0 ? _a : process.cwd());
108
+ const projectRoot = (_b = (0, project_root_1.findProjectRoot)(root)) !== null && _b !== void 0 ? _b : root;
109
+ const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
110
+ const vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
111
+ const table = yield vectorDb.ensureTable();
112
+ const top = intOpt("--top", "GMAX_SURPRISE_TOP", 25);
113
+ const json = hasFlag("--json") || process.env.GMAX_EVAL_JSON === "1";
114
+ const log = json ? console.error : console.log;
115
+ const result = yield (0, surprising_connections_1.analyzeSurprisingConnections)(table, projectRoot, {
116
+ sample: intOpt("--sample", "GMAX_SURPRISE_SAMPLE", surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.sample),
117
+ neighbors: intOpt("--neighbors", "GMAX_SURPRISE_NEIGHBORS", surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.neighbors),
118
+ dirDepth: intOpt("--dir-depth", "GMAX_SURPRISE_DIR_DEPTH", surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.dirDepth),
119
+ minSimilarity: floatOpt("--min-sim", "GMAX_SURPRISE_MIN_SIM", surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.minSimilarity),
120
+ maxRows: intOpt("--max-rows", "GMAX_SURPRISE_MAX_ROWS", surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.maxRows),
121
+ includeTests: hasFlag("--include-tests"),
122
+ includeEval: hasFlag("--include-eval"),
123
+ in: argValues("--in"),
124
+ exclude: argValues("--exclude"),
125
+ });
126
+ const { summary, findings } = result;
127
+ const topFindings = findings.slice(0, top);
128
+ log(`Surprising-connections prototype: ${summary.sampledAnchors} sampled chunks from ${summary.codeRows} code chunks (${summary.rows} indexed rows)`);
129
+ if (json) {
130
+ process.stdout.write(`${JSON.stringify({
131
+ summary,
132
+ findings: topFindings.map((finding) => ({
133
+ score: finding.score,
134
+ maxSimilarity: finding.maxSimilarity,
135
+ medianSimilarity: finding.medianSimilarity,
136
+ pairCount: finding.pairCount,
137
+ files: [finding.fileA, finding.fileB],
138
+ reasons: finding.reasons,
139
+ topSimilarities: finding.topSimilarities,
140
+ representative: {
141
+ similarity: Number(finding.representative.similarity.toFixed(3)),
142
+ distance: Number(finding.representative.distance.toFixed(3)),
143
+ scoreParts: finding.representative.scoreParts,
144
+ source: {
145
+ file: finding.representative.source.relPath,
146
+ line: finding.representative.source.startLine + 1,
147
+ symbols: finding.representative.source.definedSymbols.slice(0, 4),
148
+ role: finding.representative.source.role,
149
+ },
150
+ target: {
151
+ file: finding.representative.target.relPath,
152
+ line: finding.representative.target.startLine + 1,
153
+ symbols: finding.representative.target.definedSymbols.slice(0, 4),
154
+ role: finding.representative.target.role,
155
+ },
156
+ },
157
+ })),
158
+ }, null, 2)}\n`);
159
+ }
160
+ else {
161
+ console.log("\nSummary");
162
+ console.log(` project: ${projectRoot}`);
163
+ console.log(` rows/code rows: ${summary.rows}/${summary.codeRows}`);
164
+ console.log(` sampled anchors: ${summary.sampledAnchors}`);
165
+ console.log(` graph file edges: ${summary.graphFileEdges}`);
166
+ console.log(` accepted pairs/file-pairs: ${summary.acceptedPairs}/${summary.acceptedFilePairs}`);
167
+ console.log(` similarity p50/p90/max: ${summary.similarity.p50}/${summary.similarity.p90}/${summary.similarity.max}`);
168
+ console.log(` score p50/p90/max: ${summary.actionabilityScore.p50}/${summary.actionabilityScore.p90}/${summary.actionabilityScore.max}`);
169
+ console.log(` filtered: same-file ${summary.filters.sameFile}, same-dir ${summary.filters.sameDirBucket}, graph-edge ${summary.filters.graphEdge}, tests ${summary.filters.tests}`);
170
+ console.log("\nTop grouped surprising connections");
171
+ if (topFindings.length === 0) {
172
+ console.log(" none");
173
+ }
174
+ else {
175
+ for (const finding of topFindings) {
176
+ const pair = finding.representative;
177
+ console.log(` score=${finding.score.toFixed(3)} sim=${finding.maxSimilarity.toFixed(3)} pairs=${finding.pairCount} ${finding.fileA}`);
178
+ console.log(` <-> ${finding.fileB}`);
179
+ console.log(` best: ${(0, surprising_connections_1.lineLabel)(pair.source)} <-> ${(0, surprising_connections_1.lineLabel)(pair.target)}`);
180
+ console.log(` reasons: ${finding.reasons.join(", ") || "none"}`);
181
+ }
182
+ }
183
+ }
184
+ yield vectorDb.close();
185
+ yield (0, exit_1.gracefulExit)(0);
186
+ });
187
+ }
188
+ run().catch((error) => __awaiter(void 0, void 0, void 0, function* () {
189
+ console.error(error instanceof Error ? error.stack || error.message : error);
190
+ yield (0, exit_1.gracefulExit)(1);
191
+ }));
package/dist/index.js CHANGED
@@ -73,6 +73,7 @@ const similar_1 = require("./commands/similar");
73
73
  const skeleton_1 = require("./commands/skeleton");
74
74
  const status_1 = require("./commands/status");
75
75
  const summarize_1 = require("./commands/summarize");
76
+ const surprises_1 = require("./commands/surprises");
76
77
  const symbols_1 = require("./commands/symbols");
77
78
  const test_find_1 = require("./commands/test-find");
78
79
  const trace_1 = require("./commands/trace");
@@ -129,6 +130,7 @@ commander_1.program.addCommand(diff_1.diff);
129
130
  commander_1.program.addCommand(test_find_1.testFind);
130
131
  commander_1.program.addCommand(impact_1.impact);
131
132
  commander_1.program.addCommand(similar_1.similar);
133
+ commander_1.program.addCommand(surprises_1.surprises);
132
134
  commander_1.program.addCommand(context_1.context);
133
135
  // Services
134
136
  commander_1.program.addCommand(serve_1.serve);