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
@@ -32,16 +32,6 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
- return new (P || (P = Promise))(function (resolve, reject) {
38
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
- step((generator = generator.apply(thisArg, _arguments || [])).next());
42
- });
43
- };
44
- var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
45
35
  var __asyncValues = (this && this.__asyncValues) || function (o) {
46
36
  if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
47
37
  var m = o[Symbol.asyncIterator], i;
@@ -49,6 +39,7 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
49
39
  function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
50
40
  function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
51
41
  };
42
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
52
43
  var __asyncDelegator = (this && this.__asyncDelegator) || function (o) {
53
44
  var i, p;
54
45
  return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i;
@@ -66,103 +57,92 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar
66
57
  function reject(value) { resume("throw", value); }
67
58
  function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
68
59
  };
69
- var __importDefault = (this && this.__importDefault) || function (mod) {
70
- return (mod && mod.__esModule) ? mod : { "default": mod };
71
- };
72
60
  Object.defineProperty(exports, "__esModule", { value: true });
61
+ exports.createWalkState = createWalkState;
62
+ exports.isPathProtectedByWalkState = isPathProtectedByWalkState;
73
63
  exports.walk = walk;
74
64
  const fs = __importStar(require("node:fs/promises"));
75
65
  const path = __importStar(require("node:path"));
76
- const ignore_1 = __importDefault(require("ignore"));
77
- const ignore_patterns_1 = require("./ignore-patterns");
78
- function getIgnoreFilter(dir, ignoreFiles) {
79
- return __awaiter(this, void 0, void 0, function* () {
80
- let filter = null;
81
- for (const fileName of ignoreFiles) {
82
- const ignorePath = path.join(dir, fileName);
83
- try {
84
- const content = yield fs.readFile(ignorePath, "utf-8");
85
- if (!filter)
86
- filter = (0, ignore_1.default)();
87
- filter.add(content);
88
- }
89
- catch (_err) {
90
- // Ignore missing files
91
- }
92
- }
93
- return filter;
94
- });
66
+ const path_containment_1 = require("../utils/path-containment");
67
+ const file_policy_1 = require("./file-policy");
68
+ function createWalkState() {
69
+ return { rootComplete: true, protectedPaths: new Set(), errors: [] };
70
+ }
71
+ function isPathProtectedByWalkState(candidate, state) {
72
+ if (!state.rootComplete)
73
+ return true;
74
+ return Array.from(state.protectedPaths).some((protectedPath) => (0, path_containment_1.isPathWithin)(protectedPath, candidate));
95
75
  }
96
76
  function walk(rootDir_1) {
97
77
  return __asyncGenerator(this, arguments, function* walk_1(rootDir, options = {}) {
98
- const ignoreFiles = options.ignoreFiles || [".gitignore", ".gmaxignore"];
99
- const rootParams = (0, ignore_1.default)().add(ignore_patterns_1.DEFAULT_IGNORE_PATTERNS);
100
- if (options.additionalPatterns) {
101
- rootParams.add(options.additionalPatterns);
102
- }
103
- // Initial scope for root
104
- const rootScope = {
105
- filter: rootParams,
106
- dir: rootDir,
107
- };
108
- // Stack of scopes.
109
- // We check against ALL scopes in the stack.
110
- // This implements the "additive" ignore behavior (files ignored by parent are ignored by child).
111
- // Note: This does not strictly support "un-ignoring" a parent rule via a child .gitignore
112
- // (because we check parent independently), but it is the safest robust implementation for "hiding" nested files.
113
- const stack = [rootScope];
114
- // We also try to load root .gitignore immediately to add to the stack
115
- const rootGitIgnore = yield __await(getIgnoreFilter(rootDir, ignoreFiles));
116
- if (rootGitIgnore) {
117
- stack.push({ filter: rootGitIgnore, dir: rootDir });
118
- }
119
- yield __await(yield* __asyncDelegator(__asyncValues(_walk(rootDir, rootDir, stack, ignoreFiles))));
78
+ var _a, _b;
79
+ const root = path.resolve(rootDir);
80
+ const policy = (_a = options.policy) !== null && _a !== void 0 ? _a : new file_policy_1.ProjectFilePolicy(root, {
81
+ ignoreFiles: options.ignoreFiles,
82
+ additionalPatterns: options.additionalPatterns,
83
+ });
84
+ const state = (_b = options.state) !== null && _b !== void 0 ? _b : createWalkState();
85
+ yield __await(yield* __asyncDelegator(__asyncValues(walkDirectory(root, root, policy, state, true))));
120
86
  });
121
87
  }
122
- function _walk(currentDir, rootDir, stack, ignoreFiles) {
123
- return __asyncGenerator(this, arguments, function* _walk_1() {
88
+ function walkDirectory(currentDir, rootDir, policy, state, isRoot) {
89
+ return __asyncGenerator(this, arguments, function* walkDirectory_1() {
90
+ const directory = yield __await(policy.classifyDirectory(currentDir));
91
+ if (directory.status === "error") {
92
+ state.protectedPaths.add(directory.protectedPath);
93
+ state.errors.push({
94
+ path: directory.protectedPath,
95
+ error: directory.error,
96
+ });
97
+ if (isRoot)
98
+ state.rootComplete = false;
99
+ return yield __await(void 0);
100
+ }
101
+ if (directory.status !== "traverse") {
102
+ if (isRoot)
103
+ state.rootComplete = false;
104
+ return yield __await(void 0);
105
+ }
124
106
  let entries;
125
107
  try {
126
108
  entries = yield __await(fs.readdir(currentDir, { withFileTypes: true }));
127
109
  }
128
- catch (_err) {
110
+ catch (error) {
111
+ state.protectedPaths.add(currentDir);
112
+ state.errors.push({ path: currentDir, error });
113
+ if (isRoot)
114
+ state.rootComplete = false;
129
115
  return yield __await(void 0);
130
116
  }
131
117
  for (const entry of entries) {
132
118
  const absPath = path.join(currentDir, entry.name);
133
- const relPathToRoot = path.relative(rootDir, absPath);
134
- // 1. Check if ignored by any scope in the stack
135
- const isDir = entry.isDirectory();
136
- let isIgnored = false;
137
- for (const scope of stack) {
138
- const relToScope = path.relative(scope.dir, absPath);
139
- if (!relToScope)
140
- continue;
141
- // Append trailing slash for directories so patterns like "dist/" match
142
- const testPath = isDir ? `${relToScope}/` : relToScope;
143
- if (scope.filter.ignores(testPath)) {
144
- isIgnored = true;
145
- break;
146
- }
147
- }
148
- if (isIgnored)
149
- continue;
150
119
  if (entry.isDirectory()) {
151
- // 2. Prepare scope for the new directory
152
- const childIgnore = yield __await(getIgnoreFilter(absPath, ignoreFiles));
153
- if (childIgnore) {
154
- // Push new scope
155
- stack.push({ filter: childIgnore, dir: absPath });
156
- yield __await(yield* __asyncDelegator(__asyncValues(_walk(absPath, rootDir, stack, ignoreFiles))));
157
- stack.pop();
120
+ yield __await(yield* __asyncDelegator(__asyncValues(walkDirectory(absPath, rootDir, policy, state, false))));
121
+ continue;
122
+ }
123
+ if (!entry.isFile()) {
124
+ try {
125
+ const stat = yield __await(fs.lstat(absPath));
126
+ if (stat.isDirectory()) {
127
+ yield __await(yield* __asyncDelegator(__asyncValues(walkDirectory(absPath, rootDir, policy, state, false))));
128
+ continue;
129
+ }
130
+ if (!stat.isFile())
131
+ continue;
158
132
  }
159
- else {
160
- // Just recurse with same stack
161
- yield __await(yield* __asyncDelegator(__asyncValues(_walk(absPath, rootDir, stack, ignoreFiles))));
133
+ catch (error) {
134
+ state.protectedPaths.add(absPath);
135
+ state.errors.push({ path: absPath, error });
136
+ continue;
162
137
  }
163
138
  }
164
- else {
165
- yield yield __await(relPathToRoot);
139
+ const file = yield __await(policy.classifyFile(absPath));
140
+ if (file.status === "indexable") {
141
+ yield yield __await(path.relative(rootDir, absPath));
142
+ }
143
+ else if (file.status === "error") {
144
+ state.protectedPaths.add(file.protectedPath);
145
+ state.errors.push({ path: file.protectedPath, error: file.error });
166
146
  }
167
147
  }
168
148
  });
@@ -44,6 +44,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.processBatchCore = processBatchCore;
46
46
  exports.flushBatchToDb = flushBatchToDb;
47
+ exports.computePathRetry = computePathRetry;
47
48
  exports.computeRetryAction = computeRetryAction;
48
49
  const fs = __importStar(require("node:fs"));
49
50
  const cache_check_1 = require("../utils/cache-check");
@@ -139,6 +140,14 @@ function flushBatchToDb(result, vectorDb) {
139
140
  }
140
141
  });
141
142
  }
143
+ function computePathRetry(previousFailures, maxFailures, baseDelayMs) {
144
+ const failures = previousFailures + 1;
145
+ return {
146
+ retry: failures < maxFailures,
147
+ failures,
148
+ backoffMs: Math.min(baseDelayMs * Math.pow(2, failures), 30000),
149
+ };
150
+ }
142
151
  function computeRetryAction(batch, retryCount, maxRetries, isLockError, consecutiveLockFailures, debounceMs) {
143
152
  var _a;
144
153
  const requeued = new Map();
@@ -41,11 +41,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
41
41
  step((generator = generator.apply(thisArg, _arguments || [])).next());
42
42
  });
43
43
  };
44
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
45
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
46
+ var m = o[Symbol.asyncIterator], i;
47
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
48
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
49
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
50
+ };
44
51
  Object.defineProperty(exports, "__esModule", { value: true });
45
52
  exports.WATCHER_IGNORE_GLOBS = void 0;
46
53
  exports.startWatcher = startWatcher;
54
+ const path = __importStar(require("node:path"));
47
55
  const watcher = __importStar(require("@parcel/watcher"));
48
56
  const batch_processor_1 = require("./batch-processor");
57
+ const file_policy_1 = require("./file-policy");
58
+ const walker_1 = require("./walker");
49
59
  // Ignore patterns for @parcel/watcher (micromatch globs + directory names).
50
60
  // Directory names are matched at any depth automatically.
51
61
  exports.WATCHER_IGNORE_GLOBS = [
@@ -70,15 +80,125 @@ exports.WATCHER_IGNORE_GLOBS = [
70
80
  ".m2",
71
81
  "vendor",
72
82
  "lancedb",
73
- ".*", // dotfiles
74
83
  "**/*.tmp.*", // editor atomic save artifacts
75
84
  "**/*.sb-*", // Xcode swap files
76
85
  ];
77
86
  function startWatcher(opts) {
78
87
  return __awaiter(this, void 0, void 0, function* () {
88
+ var _a, _b;
79
89
  const { projectRoot } = opts;
80
90
  const wtag = `watch:${projectRoot.split("/").pop()}`;
81
- const processor = new batch_processor_1.ProjectBatchProcessor(opts);
91
+ const filePolicy = new file_policy_1.ProjectFilePolicy(projectRoot);
92
+ let reconciliation = null;
93
+ let reconcileRequested = false;
94
+ let closing = false;
95
+ let scanHealthy = ((_a = opts.initialScanErrors) !== null && _a !== void 0 ? _a : 0) === 0;
96
+ let ingestionDegraded = ((_b = opts.initialFailedFiles) !== null && _b !== void 0 ? _b : 0) > 0;
97
+ let degradedRepairQueued = false;
98
+ const terminalFailures = new Set();
99
+ let processor;
100
+ const reportHealthyIfSettled = () => {
101
+ var _a;
102
+ if (scanHealthy &&
103
+ !ingestionDegraded &&
104
+ terminalFailures.size === 0 &&
105
+ processor.progress.pendingFiles === 0) {
106
+ (_a = opts.onHealthChange) === null || _a === void 0 ? void 0 : _a.call(opts, true, 0);
107
+ }
108
+ };
109
+ const reconcile = () => {
110
+ if (closing)
111
+ return;
112
+ if (reconciliation) {
113
+ reconcileRequested = true;
114
+ return;
115
+ }
116
+ reconciliation = (() => __awaiter(this, void 0, void 0, function* () {
117
+ var _a, e_1, _b, _c;
118
+ var _d;
119
+ let incompleteRetries = 0;
120
+ do {
121
+ reconcileRequested = false;
122
+ filePolicy.invalidateIgnoreCache();
123
+ const rootPrefix = projectRoot.endsWith("/")
124
+ ? projectRoot
125
+ : `${projectRoot}/`;
126
+ const cached = yield opts.metaCache.getKeysWithPrefix(rootPrefix);
127
+ const seen = new Set();
128
+ const state = (0, walker_1.createWalkState)();
129
+ try {
130
+ for (var _e = true, _f = (e_1 = void 0, __asyncValues((0, walker_1.walk)(projectRoot, {
131
+ policy: filePolicy,
132
+ state,
133
+ }))), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) {
134
+ _c = _g.value;
135
+ _e = false;
136
+ const relative = _c;
137
+ if (closing)
138
+ return;
139
+ const absolute = path.join(projectRoot, relative);
140
+ seen.add(absolute);
141
+ processor.handleFileEvent("change", absolute);
142
+ }
143
+ }
144
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
145
+ finally {
146
+ try {
147
+ if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
148
+ }
149
+ finally { if (e_1) throw e_1.error; }
150
+ }
151
+ for (const cachedPath of cached) {
152
+ if (closing)
153
+ return;
154
+ if (!seen.has(cachedPath) &&
155
+ !(0, walker_1.isPathProtectedByWalkState)(cachedPath, state)) {
156
+ processor.handleFileEvent("unlink", cachedPath);
157
+ }
158
+ }
159
+ if (!state.rootComplete || state.errors.length > 0) {
160
+ scanHealthy = false;
161
+ (_d = opts.onHealthChange) === null || _d === void 0 ? void 0 : _d.call(opts, false, state.errors.length);
162
+ console.error(`[${wtag}] Reconciliation incomplete at ${state.errors.length} path(s)`);
163
+ if (incompleteRetries < 3) {
164
+ incompleteRetries++;
165
+ reconcileRequested = true;
166
+ yield new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, (incompleteRetries - 1))));
167
+ }
168
+ }
169
+ else {
170
+ scanHealthy = true;
171
+ if (ingestionDegraded && processor.progress.pendingFiles > 0) {
172
+ degradedRepairQueued = true;
173
+ }
174
+ reportHealthyIfSettled();
175
+ incompleteRetries = 0;
176
+ }
177
+ } while (reconcileRequested && !closing);
178
+ }))()
179
+ .catch((err) => console.error(`[${wtag}] Reconciliation failed:`, err))
180
+ .finally(() => {
181
+ reconciliation = null;
182
+ });
183
+ };
184
+ processor = new batch_processor_1.ProjectBatchProcessor(Object.assign(Object.assign({}, opts), { filePolicy, onPolicyChange: reconcile, onReindex: (files, durationMs) => {
185
+ var _a;
186
+ (_a = opts.onReindex) === null || _a === void 0 ? void 0 : _a.call(opts, files, durationMs);
187
+ reportHealthyIfSettled();
188
+ }, onTerminalFailure: (absPath) => {
189
+ var _a;
190
+ terminalFailures.add(absPath);
191
+ (_a = opts.onHealthChange) === null || _a === void 0 ? void 0 : _a.call(opts, false, terminalFailures.size);
192
+ }, onPathSuccess: (absPath) => {
193
+ terminalFailures.delete(absPath);
194
+ if (degradedRepairQueued &&
195
+ terminalFailures.size === 0 &&
196
+ processor.progress.pendingFiles === 0) {
197
+ ingestionDegraded = false;
198
+ degradedRepairQueued = false;
199
+ }
200
+ reportHealthyIfSettled();
201
+ } }));
82
202
  const subscription = yield watcher.subscribe(projectRoot, (err, events) => {
83
203
  if (err) {
84
204
  console.error(`[${wtag}] Watcher error:`, err);
@@ -88,10 +208,16 @@ function startWatcher(opts) {
88
208
  processor.handleFileEvent(event.type === "delete" ? "unlink" : "change", event.path);
89
209
  }
90
210
  }, { ignore: exports.WATCHER_IGNORE_GLOBS });
211
+ reconcile();
91
212
  return {
213
+ get progress() {
214
+ return processor.progress;
215
+ },
92
216
  close: () => __awaiter(this, void 0, void 0, function* () {
93
- yield processor.close();
217
+ closing = true;
94
218
  yield subscription.unsubscribe();
219
+ yield reconciliation;
220
+ yield processor.close();
95
221
  }),
96
222
  };
97
223
  });
@@ -51,6 +51,12 @@ const config_1 = require("../../config");
51
51
  const index_config_1 = require("../index/index-config");
52
52
  const log_rotate_1 = require("../utils/log-rotate");
53
53
  const config_2 = require("./config");
54
+ // ⛔ HARD STOP FOR AI AGENTS: This starts llama-server with a multi-GB local
55
+ // LLM (Qwen3.5-35B GGUF ≈21GB by default). DO NOT start it — via start(),
56
+ // llm-start, reviewCommit, investigate, or a benchmark — without the user's
57
+ // explicit, in-session authorization. Loading models this size has crashed the
58
+ // machine. Stop and ask first. See the "HARD STOP" section at the top of
59
+ // CLAUDE.md.
54
60
  const HEALTH_TIMEOUT_MS = 2000;
55
61
  const POLL_INTERVAL_MS = 500;
56
62
  const STOP_GRACE_MS = 5000;
@@ -101,14 +101,30 @@ function buildWhereClause(pathPrefix, filters, searchIntent) {
101
101
  if (typeof roleFilter === "string" && roleFilter) {
102
102
  parts.push(`role = '${(0, filter_builder_1.escapeSqlString)(roleFilter)}'`);
103
103
  }
104
- const projectRoots = filters === null || filters === void 0 ? void 0 : filters.project_roots;
105
- if (typeof projectRoots === "string" && projectRoots) {
106
- const roots = projectRoots.split(",");
107
- const clauses = roots.map((r) => {
108
- const prefix = r.endsWith("/") ? r : `${r}/`;
109
- return (0, filter_builder_1.pathStartsWith)(prefix);
110
- });
111
- parts.push(`(${clauses.join(" OR ")})`);
104
+ const projectRoots = filters === null || filters === void 0 ? void 0 : filters.projectRoots;
105
+ if (Array.isArray(projectRoots)) {
106
+ const roots = projectRoots.filter((root) => typeof root === "string" && root.length > 0);
107
+ if (roots.length === 0) {
108
+ parts.push("1 = 0");
109
+ }
110
+ else {
111
+ const clauses = roots.map((root) => {
112
+ const prefix = root.endsWith("/") ? root : `${root}/`;
113
+ return (0, filter_builder_1.pathStartsWith)(prefix);
114
+ });
115
+ parts.push(`(${clauses.join(" OR ")})`);
116
+ }
117
+ }
118
+ else {
119
+ const legacyProjectRoots = filters === null || filters === void 0 ? void 0 : filters.project_roots;
120
+ if (typeof legacyProjectRoots === "string" && legacyProjectRoots) {
121
+ const roots = legacyProjectRoots.split(",");
122
+ const clauses = roots.map((r) => {
123
+ const prefix = r.endsWith("/") ? r : `${r}/`;
124
+ return (0, filter_builder_1.pathStartsWith)(prefix);
125
+ });
126
+ parts.push(`(${clauses.join(" OR ")})`);
127
+ }
112
128
  }
113
129
  const excludeRoots = filters === null || filters === void 0 ? void 0 : filters.exclude_project_roots;
114
130
  if (typeof excludeRoots === "string" && excludeRoots) {
@@ -132,8 +148,9 @@ function buildWhereClause(pathPrefix, filters, searchIntent) {
132
148
  return parts.length > 0 ? parts.join(" AND ") : undefined;
133
149
  }
134
150
  class Searcher {
135
- constructor(db) {
151
+ constructor(db, workerPool = (0, pool_1.getWorkerPool)()) {
136
152
  this.db = db;
153
+ this.workerPool = workerPool;
137
154
  this.ftsIndexChecked = false;
138
155
  this.ftsAvailable = false;
139
156
  this.ftsLastCheckedAt = 0;
@@ -389,7 +406,7 @@ class Searcher {
389
406
  const searchIntent = intent || (0, intent_1.detectIntent)(query);
390
407
  // Bare-identifier queries get symbol-definition promotion (see below).
391
408
  const symbolQuery = asSymbolQuery(query);
392
- const pool = (0, pool_1.getWorkerPool)();
409
+ const pool = this.workerPool;
393
410
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
394
411
  const err = new Error("Aborted");
395
412
  err.name = "AbortError";
@@ -409,13 +426,7 @@ class Searcher {
409
426
  const PRE_RERANK_K = Number.isFinite(envPreK) && envPreK > 0
410
427
  ? envPreK
411
428
  : Math.max(finalLimit * 5, 500);
412
- let table;
413
- try {
414
- table = yield this.db.ensureTable();
415
- }
416
- catch (_r) {
417
- return { data: [] };
418
- }
429
+ const table = yield this.db.ensureTable();
419
430
  // Ensure FTS index exists (lazy init, retry periodically on failure)
420
431
  const now = Date.now();
421
432
  if (!this.ftsIndexChecked ||
@@ -496,7 +507,7 @@ class Searcher {
496
507
  this.ftsAvailable = true;
497
508
  console.warn("[Searcher] Rebuilt FTS index with position support — retry search");
498
509
  }
499
- catch (_s) { }
510
+ catch (_r) { }
500
511
  }
501
512
  else {
502
513
  console.warn(`[Searcher] FTS search failed (will retry later): ${msg}`);
@@ -806,7 +817,7 @@ class Searcher {
806
817
  defs = arr.filter((v) => typeof v === "string");
807
818
  }
808
819
  }
809
- catch (_t) { }
820
+ catch (_s) { }
810
821
  }
811
822
  const norm = (0, pagerank_1.pageRankBoostForSymbols)(defs, prScores, prMax);
812
823
  item.score += PR_WEIGHT * norm;
@@ -69,6 +69,8 @@ const DEFAULT_OPTIONS = {
69
69
  includeSummary: true,
70
70
  maxCallsInSummary: 4,
71
71
  };
72
+ const SQL_TEMPLATE_LANGS = new Set(["typescript", "tsx", "javascript"]);
73
+ const SQL_IDENTIFIER_PATTERN = '(?:"[^"]+"|\\[[^\\]]+\\]|`[^`]+`|[A-Za-z_][\\w$]*)(?:\\s*\\.\\s*(?:"[^"]+"|\\[[^\\]]+\\]|`[^`]+`|[A-Za-z_][\\w$]*)){0,2}';
72
74
  // WASM locator (same as chunker.ts)
73
75
  function resolveTreeSitterWasmLocator() {
74
76
  try {
@@ -238,10 +240,12 @@ class Skeletonizer {
238
240
  }
239
241
  // Extract metadata from the body
240
242
  const referencedSymbols = this.extractReferencedSymbols(bodyNode);
243
+ const sqlTemplates = this.extractSqlTemplateSummaries(bodyNode, langId);
241
244
  const complexity = this.calculateComplexity(bodyNode);
242
245
  const role = this.classifyRole(complexity, referencedSymbols.length);
243
246
  const metadata = {
244
247
  referencedSymbols,
248
+ sqlTemplates,
245
249
  complexity,
246
250
  role,
247
251
  };
@@ -450,6 +454,120 @@ class Skeletonizer {
450
454
  extract(node);
451
455
  return refs;
452
456
  }
457
+ /**
458
+ * Surface SQL tagged templates hidden inside elided JS/TS function bodies.
459
+ * This is intentionally a lightweight skeleton hint, not a SQL parser.
460
+ */
461
+ extractSqlTemplateSummaries(node, langId) {
462
+ if (!SQL_TEMPLATE_LANGS.has(langId))
463
+ return [];
464
+ const summaries = [];
465
+ const seen = new Set();
466
+ const extract = (n) => {
467
+ const template = (n.namedChildren || []).find((child) => child.type === "template_string");
468
+ if (template && this.isSqlTaggedTemplate(n, template)) {
469
+ const summary = this.summarizeSqlTemplate(template);
470
+ if (summary && !seen.has(summary)) {
471
+ seen.add(summary);
472
+ summaries.push(summary);
473
+ }
474
+ return;
475
+ }
476
+ for (const child of n.namedChildren || []) {
477
+ extract(child);
478
+ }
479
+ };
480
+ extract(node);
481
+ return summaries;
482
+ }
483
+ isSqlTaggedTemplate(node, template) {
484
+ const tagText = node.text
485
+ .slice(0, template.startIndex - node.startIndex)
486
+ .trim();
487
+ return /(?:^|[.\s])sql(?:$|[.<(])/.test(tagText);
488
+ }
489
+ summarizeSqlTemplate(template) {
490
+ var _a;
491
+ const sqlText = this.extractTemplateText(template)
492
+ .replace(/--[^\n]*/g, " ")
493
+ .replace(/\/\*[\s\S]*?\*\//g, " ")
494
+ .replace(/\s+/g, " ")
495
+ .trim();
496
+ if (!sqlText)
497
+ return null;
498
+ const op = (_a = sqlText.match(/\b(select|insert|update|delete|merge|create|alter|drop|truncate)\b/i)) === null || _a === void 0 ? void 0 : _a[1];
499
+ if (!op)
500
+ return "SQL";
501
+ const operation = op.toUpperCase();
502
+ const tables = this.extractSqlTables(sqlText, operation);
503
+ if (tables.length === 0)
504
+ return operation;
505
+ const tableList = tables.slice(0, 2);
506
+ if (tables.length > tableList.length)
507
+ tableList.push("...");
508
+ return `${operation} ${tableList.join(", ")}`;
509
+ }
510
+ extractTemplateText(template) {
511
+ const parts = [];
512
+ for (const child of template.namedChildren || []) {
513
+ if (child.type === "string_fragment") {
514
+ parts.push(child.text);
515
+ }
516
+ else if (child.type === "template_substitution") {
517
+ parts.push(" ? ");
518
+ }
519
+ }
520
+ if (parts.length > 0)
521
+ return parts.join("");
522
+ return template.text.slice(1, -1).replace(/\$\{[^}]*\}/g, " ? ");
523
+ }
524
+ extractSqlTables(sqlText, operation) {
525
+ const tables = [];
526
+ const seen = new Set();
527
+ const addMatches = (pattern) => {
528
+ for (const match of sqlText.matchAll(pattern)) {
529
+ const table = this.normalizeSqlIdentifier(match[1]);
530
+ if (table && !seen.has(table)) {
531
+ seen.add(table);
532
+ tables.push(table);
533
+ }
534
+ }
535
+ };
536
+ switch (operation) {
537
+ case "SELECT":
538
+ addMatches(new RegExp(`\\b(?:from|join)\\s+(${SQL_IDENTIFIER_PATTERN})`, "gi"));
539
+ break;
540
+ case "INSERT":
541
+ addMatches(new RegExp(`\\binsert\\s+into\\s+(${SQL_IDENTIFIER_PATTERN})`, "gi"));
542
+ break;
543
+ case "UPDATE":
544
+ addMatches(new RegExp(`\\bupdate\\s+(${SQL_IDENTIFIER_PATTERN})`, "gi"));
545
+ break;
546
+ case "DELETE":
547
+ addMatches(new RegExp(`\\bdelete\\s+from\\s+(${SQL_IDENTIFIER_PATTERN})`, "gi"));
548
+ break;
549
+ case "MERGE":
550
+ addMatches(new RegExp(`\\bmerge\\s+into\\s+(${SQL_IDENTIFIER_PATTERN})`, "gi"));
551
+ break;
552
+ case "CREATE":
553
+ addMatches(new RegExp(`\\bcreate\\s+(?:or\\s+replace\\s+)?(?:table|view|materialized\\s+view|index)\\s+(?:if\\s+not\\s+exists\\s+)?(${SQL_IDENTIFIER_PATTERN})`, "gi"));
554
+ break;
555
+ case "ALTER":
556
+ case "DROP":
557
+ addMatches(new RegExp(`\\b${operation.toLowerCase()}\\s+(?:table|view|materialized\\s+view|index)\\s+(?:if\\s+(?:exists|not\\s+exists)\\s+)?(${SQL_IDENTIFIER_PATTERN})`, "gi"));
558
+ break;
559
+ case "TRUNCATE":
560
+ addMatches(new RegExp(`\\btruncate\\s+(?:table\\s+)?(${SQL_IDENTIFIER_PATTERN})`, "gi"));
561
+ break;
562
+ }
563
+ return tables;
564
+ }
565
+ normalizeSqlIdentifier(identifier) {
566
+ const normalized = identifier === null || identifier === void 0 ? void 0 : identifier.replace(/\s*\.\s*/g, ".").replace(/["`[\]]/g, "").trim();
567
+ if (!normalized || normalized === "?")
568
+ return null;
569
+ return normalized;
570
+ }
453
571
  /**
454
572
  * Calculate cyclomatic complexity of a node.
455
573
  */
@@ -25,7 +25,7 @@ const DEFAULT_OPTIONS = {
25
25
  * // Output: "// → findByEmail, compare, sign | C:8 | ORCH"
26
26
  */
27
27
  function formatSummary(metadata, options = {}) {
28
- var _a;
28
+ var _a, _b;
29
29
  const opts = Object.assign(Object.assign({}, DEFAULT_OPTIONS), options);
30
30
  const parts = [];
31
31
  // Calls (referenced symbols)
@@ -36,6 +36,13 @@ function formatSummary(metadata, options = {}) {
36
36
  }
37
37
  parts.push(`→ ${calls.join(", ")}`);
38
38
  }
39
+ if ((_b = metadata.sqlTemplates) === null || _b === void 0 ? void 0 : _b.length) {
40
+ const templates = metadata.sqlTemplates.slice(0, 3);
41
+ if (metadata.sqlTemplates.length > templates.length) {
42
+ templates.push("...");
43
+ }
44
+ parts.push(`SQL: ${templates.join("; ")}`);
45
+ }
39
46
  // Complexity (only if > 1, trivial functions don't need it)
40
47
  if (opts.showComplexity && metadata.complexity && metadata.complexity > 1) {
41
48
  parts.push(`C:${metadata.complexity}`);