grepmax 0.24.0 → 0.25.1

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 (75) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +15 -6
  3. package/dist/commands/add.js +38 -31
  4. package/dist/commands/config.js +22 -15
  5. package/dist/commands/context.js +38 -13
  6. package/dist/commands/doctor.js +86 -83
  7. package/dist/commands/extract.js +12 -1
  8. package/dist/commands/index.js +23 -24
  9. package/dist/commands/list.js +31 -14
  10. package/dist/commands/mcp.js +278 -143
  11. package/dist/commands/peek.js +16 -5
  12. package/dist/commands/repair.js +54 -120
  13. package/dist/commands/search-output.js +30 -9
  14. package/dist/commands/search-run.js +75 -47
  15. package/dist/commands/search-skeletons.js +28 -18
  16. package/dist/commands/search.js +45 -49
  17. package/dist/commands/serve.js +415 -373
  18. package/dist/commands/setup.js +2 -2
  19. package/dist/commands/similar.js +5 -5
  20. package/dist/commands/skeleton.js +67 -41
  21. package/dist/commands/status.js +11 -2
  22. package/dist/commands/summarize.js +6 -0
  23. package/dist/commands/watch.js +102 -38
  24. package/dist/config.js +3 -0
  25. package/dist/lib/daemon/daemon.js +1108 -379
  26. package/dist/lib/daemon/ipc-handler.js +122 -13
  27. package/dist/lib/daemon/mlx-server-manager.js +268 -125
  28. package/dist/lib/daemon/process-manager.js +7 -4
  29. package/dist/lib/daemon/search-handler.js +23 -9
  30. package/dist/lib/daemon/watcher-manager.js +353 -110
  31. package/dist/lib/index/batch-processor.js +231 -67
  32. package/dist/lib/index/embedding-generation.js +109 -0
  33. package/dist/lib/index/embedding-status.js +37 -0
  34. package/dist/lib/index/file-policy.js +273 -0
  35. package/dist/lib/index/ignore-patterns.js +4 -0
  36. package/dist/lib/index/index-config.js +18 -4
  37. package/dist/lib/index/syncer.js +256 -79
  38. package/dist/lib/index/walker.js +66 -86
  39. package/dist/lib/index/watcher-batch.js +9 -0
  40. package/dist/lib/index/watcher.js +129 -3
  41. package/dist/lib/llm/server.js +6 -0
  42. package/dist/lib/search/searcher.js +30 -19
  43. package/dist/lib/store/store-lease.js +473 -0
  44. package/dist/lib/store/vector-db.js +302 -57
  45. package/dist/lib/utils/blocked-roots.js +63 -0
  46. package/dist/lib/utils/cross-project.js +7 -3
  47. package/dist/lib/utils/daemon-client.js +24 -1
  48. package/dist/lib/utils/daemon-launcher.js +38 -13
  49. package/dist/lib/utils/doctor-status.js +76 -0
  50. package/dist/lib/utils/file-utils.js +74 -4
  51. package/dist/lib/utils/keyed-mutex.js +101 -0
  52. package/dist/lib/utils/logger.js +57 -1
  53. package/dist/lib/utils/mlx-hf-cache.js +114 -0
  54. package/dist/lib/utils/operation-coordinator.js +146 -0
  55. package/dist/lib/utils/path-containment.js +106 -0
  56. package/dist/lib/utils/process.js +44 -0
  57. package/dist/lib/utils/project-registry.js +351 -3
  58. package/dist/lib/utils/scope-filter.js +3 -9
  59. package/dist/lib/utils/stale-hint.js +12 -19
  60. package/dist/lib/utils/watcher-launcher.js +5 -1
  61. package/dist/lib/utils/watcher-store.js +2 -2
  62. package/dist/lib/workers/colbert-math.js +15 -12
  63. package/dist/lib/workers/embeddings/colbert.js +3 -2
  64. package/dist/lib/workers/embeddings/granite.js +4 -3
  65. package/dist/lib/workers/embeddings/mlx-client.js +59 -16
  66. package/dist/lib/workers/orchestrator.js +39 -8
  67. package/dist/lib/workers/pool.js +146 -82
  68. package/dist/lib/workers/process-child.js +11 -2
  69. package/dist/lib/workers/serialized-handler.js +10 -0
  70. package/dist/lib/workers/worker.js +13 -4
  71. package/mlx-embed-server/server.py +21 -3
  72. package/mlx-embed-server/summarizer.py +8 -0
  73. package/package.json +4 -3
  74. package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
  75. package/plugins/grepmax/hooks/start.js +3 -170
@@ -10,7 +10,7 @@
10
10
  "name": "grepmax",
11
11
  "source": "./plugins/grepmax",
12
12
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
13
- "version": "0.24.0",
13
+ "version": "0.25.1",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
package/README.md CHANGED
@@ -332,16 +332,25 @@ worker RAM, and dense-vector storage while *lowering* recall on code search.
332
332
  Unless you have a measured reason to switch (e.g. a recall complaint on a very
333
333
  large repo — benchmark it first), stay on 384d.
334
334
 
335
- To change tiers:
335
+ The model tier is part of the persisted embedding generation. You can change the desired
336
+ configuration, but existing indexes remain on their built generation until you explicitly run the
337
+ guarded whole-corpus rebuild:
336
338
 
337
339
  ```bash
338
- gmax config --model-tier standard # a dim change needs a full re-embed
339
- gmax repair --rebuild # drops the shared table, re-embeds every project at the new dim
340
+ gmax config --model-tier standard # changes desired configuration only
341
+ gmax config # shows configured vs built identity
342
+ gmax status # reports current / legacy / stale / unbuilt per project
343
+ gmax repair --rebuild # guarded whole-corpus rebuild through the daemon
340
344
  ```
341
345
 
342
- A vector-dimension change can't be applied by a per-project `gmax index --reset`
343
- the shared `chunks` table is fixed-width, so `gmax repair --rebuild` is the
344
- sanctioned path.
346
+ `legacy` means the existing index is compatible but its exact model fingerprint was inferred from
347
+ the previous registry shape. Run `gmax index` in that project to persist exact identity; no reset or
348
+ re-embedding is required when the cached files are unchanged.
349
+
350
+ A model change cannot be applied by a per-project `gmax index --reset`, even when vector widths
351
+ match: one query embedding cannot safely search rows from another embedding space. Stale-generation
352
+ search and sync fail before mutation, preserving the existing index. Run `gmax repair --rebuild` to
353
+ replace the whole corpus, or restore the prior model tier as the non-destructive alternative.
345
354
 
346
355
  ### Ignoring Files
347
356
 
@@ -54,30 +54,12 @@ const sync_helpers_1 = require("../lib/index/sync-helpers");
54
54
  const syncer_1 = require("../lib/index/syncer");
55
55
  const setup_helpers_1 = require("../lib/setup/setup-helpers");
56
56
  const vector_db_1 = require("../lib/store/vector-db");
57
+ const blocked_roots_1 = require("../lib/utils/blocked-roots");
57
58
  const exit_1 = require("../lib/utils/exit");
58
59
  const project_marker_1 = require("../lib/utils/project-marker");
59
60
  const project_registry_1 = require("../lib/utils/project-registry");
60
61
  const project_root_1 = require("../lib/utils/project-root");
61
62
  const watcher_launcher_1 = require("../lib/utils/watcher-launcher");
62
- function getBlockedRoots() {
63
- const home = os.homedir();
64
- return new Set([
65
- home,
66
- path.dirname(home),
67
- "/",
68
- "/tmp",
69
- "/private",
70
- "/private/tmp",
71
- "/private/var",
72
- "/var",
73
- "/usr",
74
- "/opt",
75
- "/etc",
76
- "/System",
77
- "/Library",
78
- "/Applications",
79
- ].map((p) => path.resolve(p)));
80
- }
81
63
  function logBlockedAttempt(reason, attempted, extra) {
82
64
  var _a, _b;
83
65
  try {
@@ -101,17 +83,16 @@ Examples:
101
83
  gmax add . --no-index Register only, index later with gmax index
102
84
  `)
103
85
  .action((dir, opts) => __awaiter(void 0, void 0, void 0, function* () {
104
- var _a, _b, _c, _d, _e;
86
+ var _a, _b, _c, _d, _e, _f;
105
87
  let vectorDb = null;
106
88
  try {
107
89
  const targetDir = dir ? path.resolve(dir) : process.cwd();
108
90
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(targetDir)) !== null && _a !== void 0 ? _a : targetDir;
109
91
  const projectName = path.basename(projectRoot);
110
- const blocked = getBlockedRoots();
111
- if (blocked.has(path.resolve(projectRoot))) {
92
+ if ((0, blocked_roots_1.isBlockedProjectRoot)(projectRoot)) {
112
93
  logBlockedAttempt("blocked_root", projectRoot);
113
94
  console.error(`Refusing to add ${projectRoot}: this path is blocked from indexing.\n` +
114
- `(Blocked: home, /, /Users, /tmp, /private, /var, /usr, /opt, /etc, /System, /Library, /Applications.)\n` +
95
+ `(Blocked: ${blocked_roots_1.BLOCKED_ROOTS_DESCRIPTION}.)\n` +
115
96
  `Pick a specific project subdirectory instead.\n` +
116
97
  `Diagnostic logged to ~/.gmax/logs/blocked-add.log (cwd=${process.cwd()} ppid=${process.ppid}).`);
117
98
  process.exitCode = 1;
@@ -221,14 +202,18 @@ Examples:
221
202
  if (!done.ok) {
222
203
  throw new Error((_c = done.error) !== null && _c !== void 0 ? _c : "daemon add failed");
223
204
  }
224
- (0, project_registry_1.registerProject)(Object.assign(Object.assign({}, pendingEntry), { lastIndexed: new Date().toISOString(), chunkCount: (_d = done.indexed) !== null && _d !== void 0 ? _d : 0, status: "indexed", chunkerVersion: config_1.CONFIG.CHUNKER_VERSION }));
225
- const failedFiles = (_e = done.failedFiles) !== null && _e !== void 0 ? _e : 0;
205
+ if (done.degraded === true) {
206
+ (0, project_registry_1.registerProject)(Object.assign(Object.assign({}, pendingEntry), { status: "pending" }));
207
+ spinner.warn(`Added ${projectName}, but indexing is incomplete (${(_d = done.scanErrors) !== null && _d !== void 0 ? _d : 0} scan errors, ${(_e = done.failedFiles) !== null && _e !== void 0 ? _e : 0} failed)`);
208
+ process.exitCode = 1;
209
+ return;
210
+ }
211
+ const failedFiles = (_f = done.failedFiles) !== null && _f !== void 0 ? _f : 0;
226
212
  const failedSuffix = failedFiles > 0 ? ` · ${failedFiles} failed` : "";
227
213
  spinner.succeed(`Added ${projectName} (${done.total} files, ${done.indexed} chunks${failedSuffix})`);
228
214
  // Watcher already started by daemon's addProject
229
215
  }
230
216
  catch (e) {
231
- (0, project_registry_1.registerProject)(pendingEntry);
232
217
  spinner.fail(`Failed to index ${projectName}`);
233
218
  throw e;
234
219
  }
@@ -242,12 +227,34 @@ Examples:
242
227
  projectRoot,
243
228
  onProgress,
244
229
  });
245
- (0, project_registry_1.registerProject)(Object.assign(Object.assign({}, pendingEntry), { lastIndexed: new Date().toISOString(), chunkCount: result.indexed, status: "indexed", chunkerVersion: config_1.CONFIG.CHUNKER_VERSION }));
246
- const failedSuffix = result.failedFiles > 0 ? ` · ${result.failedFiles} failed` : "";
247
- spinner.succeed(`Added ${projectName} (${result.total} files, ${result.indexed} chunks${failedSuffix})`);
230
+ if (result.degraded) {
231
+ (0, project_registry_1.registerProject)(Object.assign(Object.assign({}, pendingEntry), { status: "pending" }));
232
+ spinner.warn(`Added ${projectName}, but indexing is incomplete (${result.scanErrors} scan errors, ${result.failedFiles} failed)`);
233
+ process.exitCode = 1;
234
+ }
235
+ else {
236
+ const prefix = projectRoot.endsWith("/")
237
+ ? projectRoot
238
+ : `${projectRoot}/`;
239
+ const chunkCount = yield vectorDb.countRowsForPath(prefix);
240
+ (0, project_registry_1.stampProjectFullSync)({
241
+ root: projectRoot,
242
+ name: projectName,
243
+ generation: result.generation,
244
+ embedMode: result.embedMode,
245
+ chunkCount,
246
+ chunkerVersion: config_1.CONFIG.CHUNKER_VERSION,
247
+ expectedFingerprint: result.registryExpectation.embeddingFingerprint,
248
+ expectedRebuildId: result.registryExpectation.rebuildId,
249
+ });
250
+ const failedSuffix = result.failedFiles > 0 ? ` · ${result.failedFiles} failed` : "";
251
+ spinner.succeed(`Added ${projectName} (${result.total} files, ${result.indexed} chunks${failedSuffix})`);
252
+ }
248
253
  }
249
254
  catch (e) {
250
- (0, project_registry_1.registerProject)(pendingEntry);
255
+ if (!(e instanceof project_registry_1.ProjectRegistryConflictError)) {
256
+ (0, project_registry_1.registerProject)(pendingEntry);
257
+ }
251
258
  spinner.fail(`Failed to index ${projectName}`);
252
259
  throw e;
253
260
  }
@@ -271,7 +278,7 @@ Examples:
271
278
  try {
272
279
  yield vectorDb.close();
273
280
  }
274
- catch (_f) { }
281
+ catch (_g) { }
275
282
  }
276
283
  yield (0, exit_1.gracefulExit)();
277
284
  }
@@ -12,8 +12,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.config = void 0;
13
13
  const commander_1 = require("commander");
14
14
  const config_1 = require("../config");
15
+ const embedding_status_1 = require("../lib/index/embedding-status");
15
16
  const index_config_1 = require("../lib/index/index-config");
16
17
  const exit_1 = require("../lib/utils/exit");
18
+ const project_registry_1 = require("../lib/utils/project-registry");
17
19
  const project_root_1 = require("../lib/utils/project-root");
18
20
  exports.config = new commander_1.Command("config")
19
21
  .description("View or update gmax configuration")
@@ -28,24 +30,35 @@ Examples:
28
30
  gmax config --query-log on Enable query logging
29
31
  `)
30
32
  .action((_opts, cmd) => __awaiter(void 0, void 0, void 0, function* () {
31
- var _a, _b, _c, _d;
33
+ var _a, _b, _c, _d, _e;
32
34
  const options = cmd.optsWithGlobals();
33
35
  const globalConfig = (0, index_config_1.readGlobalConfig)();
34
36
  const paths = (0, project_root_1.ensureProjectPaths)(process.cwd());
35
- const indexConfig = (0, index_config_1.readIndexConfig)(paths.configPath);
37
+ const project = (0, project_registry_1.getProject)((_a = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _a !== void 0 ? _a : process.cwd());
36
38
  const hasUpdates = options.embedMode !== undefined ||
37
39
  options.modelTier !== undefined ||
38
40
  options.queryLog !== undefined;
39
41
  if (!hasUpdates) {
40
42
  // Show current config
41
- const tier = (_a = config_1.MODEL_TIERS[globalConfig.modelTier]) !== null && _a !== void 0 ? _a : config_1.MODEL_TIERS.small;
43
+ const tier = (_b = config_1.MODEL_TIERS[globalConfig.modelTier]) !== null && _b !== void 0 ? _b : config_1.MODEL_TIERS.small;
42
44
  console.log("gmax configuration (~/.gmax/config.json)\n");
43
45
  console.log(` Model tier: ${globalConfig.modelTier} (${tier.vectorDim}d, ${tier.params})`);
44
46
  console.log(` Embed mode: ${globalConfig.embedMode}`);
45
47
  console.log(` Embed model: ${globalConfig.embedMode === "gpu" ? tier.mlxModel : tier.onnxModel}`);
48
+ const identity = (0, embedding_status_1.projectEmbeddingStatus)(project, globalConfig);
49
+ console.log(` Configured: ${identity.configured.tier} ${identity.configured.vectorDim}d [${(0, embedding_status_1.embeddingFingerprintLabel)(identity.configured.fingerprint)}]`);
50
+ if (identity.built) {
51
+ const builtLabel = identity.state === "legacy" ? "Built (inferred):" : "Built:";
52
+ console.log(` ${builtLabel.padEnd(18)} ${identity.built.tier} ${identity.built.vectorDim}d [${(0, embedding_status_1.embeddingFingerprintLabel)(identity.built.fingerprint)}] (${identity.state})`);
53
+ if (identity.state === "legacy") {
54
+ const notice = (0, embedding_status_1.formatLegacyEmbeddingNotice)(1);
55
+ if (notice)
56
+ console.log(` ${notice}`);
57
+ }
58
+ }
46
59
  console.log(` Query log: ${globalConfig.queryLog ? "on" : "off"}`);
47
- if (indexConfig === null || indexConfig === void 0 ? void 0 : indexConfig.indexedAt) {
48
- console.log(` Last indexed: ${indexConfig.indexedAt}`);
60
+ if (project === null || project === void 0 ? void 0 : project.lastIndexed) {
61
+ console.log(` Last indexed: ${project.lastIndexed}`);
49
62
  }
50
63
  console.log(`\nTo change: gmax config --embed-mode <cpu|gpu> --model-tier <small|standard> --query-log <on|off>`);
51
64
  yield (0, exit_1.gracefulExit)();
@@ -80,11 +93,10 @@ Examples:
80
93
  // Reload config after queryLog write
81
94
  Object.assign(globalConfig, (0, index_config_1.readGlobalConfig)());
82
95
  }
83
- const newTier = (_b = options.modelTier) !== null && _b !== void 0 ? _b : globalConfig.modelTier;
84
- const newMode = (_c = options.embedMode) !== null && _c !== void 0 ? _c : globalConfig.embedMode;
85
- const tier = (_d = config_1.MODEL_TIERS[newTier]) !== null && _d !== void 0 ? _d : config_1.MODEL_TIERS.small;
96
+ const newTier = (_c = options.modelTier) !== null && _c !== void 0 ? _c : globalConfig.modelTier;
97
+ const newMode = (_d = options.embedMode) !== null && _d !== void 0 ? _d : globalConfig.embedMode;
98
+ const tier = (_e = config_1.MODEL_TIERS[newTier]) !== null && _e !== void 0 ? _e : config_1.MODEL_TIERS.small;
86
99
  const tierChanged = newTier !== globalConfig.modelTier;
87
- const dimChanged = tier.vectorDim !== globalConfig.vectorDim;
88
100
  (0, index_config_1.writeGlobalConfig)({
89
101
  modelTier: newTier,
90
102
  vectorDim: tier.vectorDim,
@@ -99,12 +111,7 @@ Examples:
99
111
  });
100
112
  console.log(`Updated: embed-mode=${newMode}, model-tier=${newTier} (${tier.vectorDim}d)`);
101
113
  if (tierChanged) {
102
- // A dimension change can't be fixed by a per-project `gmax index --reset`:
103
- // the shared `chunks` table is fixed-width, so it must be dropped and
104
- // rebuilt. A same-dim model swap (future tiers) can use a per-project reset.
105
- console.log(dimChanged
106
- ? `⚠️ Model tier changed (${globalConfig.vectorDim}d → ${tier.vectorDim}d). The shared vector table is fixed-width — run \`${config_1.REBUILD_COMMAND}\` to drop it and re-embed every project at the new dim.`
107
- : "⚠️ Model tier changed — run `gmax index --reset` per project to re-embed with the new model.");
114
+ console.log(`Model tier changed (${globalConfig.vectorDim}d ${tier.vectorDim}d). Existing indexes are preserved and search is blocked until you run 'gmax repair --rebuild'.`);
108
115
  }
109
116
  yield (0, exit_1.gracefulExit)();
110
117
  }));
@@ -53,7 +53,9 @@ const vector_db_1 = require("../lib/store/vector-db");
53
53
  const arrow_1 = require("../lib/utils/arrow");
54
54
  const budget_pack_1 = require("../lib/utils/budget-pack");
55
55
  const exit_1 = require("../lib/utils/exit");
56
+ const file_utils_1 = require("../lib/utils/file-utils");
56
57
  const filter_builder_1 = require("../lib/utils/filter-builder");
58
+ const path_containment_1 = require("../lib/utils/path-containment");
57
59
  const project_registry_1 = require("../lib/utils/project-registry");
58
60
  const project_root_1 = require("../lib/utils/project-root");
59
61
  function estimateTokens(text) {
@@ -106,8 +108,11 @@ function resolveExistingPath(target, root, projectRoot) {
106
108
  path.resolve(projectRoot, target),
107
109
  ];
108
110
  for (const candidate of candidates) {
109
- if (fs.existsSync(candidate))
110
- return candidate;
111
+ if (!fs.existsSync(candidate))
112
+ continue;
113
+ return (0, path_containment_1.resolveContainedPath)(projectRoot, candidate, {
114
+ verifyExistingTarget: true,
115
+ });
111
116
  }
112
117
  return null;
113
118
  }
@@ -137,7 +142,7 @@ function renderPathContext(target, absPath, projectRoot, budget) {
137
142
  }
138
143
  return state;
139
144
  }
140
- const content = fs.readFileSync(absPath, "utf-8");
145
+ const content = (0, file_utils_1.readContainedTextFileSync)(projectRoot, absPath);
141
146
  const skeletonizer = new skeleton_1.Skeletonizer();
142
147
  yield skeletonizer.init();
143
148
  if (skeletonizer.isSupported(absPath).supported) {
@@ -196,8 +201,9 @@ exports.context = new commander_1.Command("context")
196
201
  }
197
202
  const searcher = new searcher_1.Searcher(vectorDb);
198
203
  // Phase 1: Semantic search
199
- const response = yield searcher.search(topic, maxResults, { rerank: true }, {}, projectRoot);
200
- if (response.data.length === 0) {
204
+ const response = yield searcher.search(topic, maxResults, { rerank: true }, {}, `${projectRoot}/`);
205
+ const scopedData = response.data.filter((result) => (0, path_containment_1.isPathWithin)(projectRoot, chunkPath(result)));
206
+ if (scopedData.length === 0) {
201
207
  console.log(`No results found for "${topic}".`);
202
208
  return;
203
209
  }
@@ -208,8 +214,8 @@ exports.context = new commander_1.Command("context")
208
214
  sections.push(header);
209
215
  tokensUsed += estimateTokens(header);
210
216
  // Phase 2: Entry points (ORCHESTRATION role results)
211
- const orchestrators = response.data.filter((r) => r.role === "ORCHESTRATION");
212
- const entryPoints = orchestrators.length > 0 ? orchestrators : response.data.slice(0, 3);
217
+ const orchestrators = scopedData.filter((r) => r.role === "ORCHESTRATION");
218
+ const entryPoints = orchestrators.length > 0 ? orchestrators : scopedData.slice(0, 3);
213
219
  const epSection = ["\n## Entry Points"];
214
220
  for (const r of entryPoints.slice(0, 5)) {
215
221
  const p = chunkPath(r);
@@ -230,13 +236,21 @@ exports.context = new commander_1.Command("context")
230
236
  const topChunks = entryPoints.slice(0, 3);
231
237
  const bodyBlobs = topChunks.map((r) => {
232
238
  var _a, _b;
233
- const absP = chunkPath(r);
239
+ let absP;
240
+ try {
241
+ absP = (0, path_containment_1.resolveContainedPath)(projectRoot, chunkPath(r), {
242
+ verifyExistingTarget: true,
243
+ });
244
+ }
245
+ catch (_c) {
246
+ return null;
247
+ }
234
248
  const startLine = chunkStartLine(r);
235
249
  const endLine = chunkEndLine(r);
236
250
  const sym = (_b = (_a = (0, arrow_1.toArr)(r.defined_symbols)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : "";
237
251
  const parentSym = String(r.parent_symbol || "");
238
252
  try {
239
- const content = fs.readFileSync(absP, "utf-8");
253
+ const content = (0, file_utils_1.readContainedTextFileSync)(projectRoot, absP);
240
254
  const allLines = content.split("\n");
241
255
  const body = allLines
242
256
  .slice(startLine, Math.min(endLine + 1, allLines.length))
@@ -255,7 +269,7 @@ exports.context = new commander_1.Command("context")
255
269
  }
256
270
  return `\n--- ${relPath(projectRoot, absP)}:${startLine + 1} ${label} ---\n${enclosing}${body}`;
257
271
  }
258
- catch (_c) {
272
+ catch (_d) {
259
273
  return null; // File not readable — drop
260
274
  }
261
275
  });
@@ -279,7 +293,18 @@ exports.context = new commander_1.Command("context")
279
293
  }
280
294
  // Phase 4: File skeletons for unique files
281
295
  const uniqueFiles = [
282
- ...new Set(response.data.map((r) => chunkPath(r)).filter(Boolean)),
296
+ ...new Set(scopedData
297
+ .map((r) => {
298
+ try {
299
+ return (0, path_containment_1.resolveContainedPath)(projectRoot, chunkPath(r), {
300
+ verifyExistingTarget: true,
301
+ });
302
+ }
303
+ catch (_a) {
304
+ return "";
305
+ }
306
+ })
307
+ .filter(Boolean)),
283
308
  ].slice(0, 5);
284
309
  const skelSection = ["\n## File Structure"];
285
310
  const skeletonizer = new skeleton_1.Skeletonizer();
@@ -288,7 +313,7 @@ exports.context = new commander_1.Command("context")
288
313
  if (!skeletonizer.isSupported(absP).supported)
289
314
  continue;
290
315
  try {
291
- const content = fs.readFileSync(absP, "utf-8");
316
+ const content = (0, file_utils_1.readContainedTextFileSync)(projectRoot, absP);
292
317
  const result = yield skeletonizer.skeletonizeFile(absP, content);
293
318
  if (!result.success)
294
319
  continue;
@@ -311,7 +336,7 @@ exports.context = new commander_1.Command("context")
311
336
  // Phase 5: Related files summary
312
337
  const table = yield vectorDb.ensureTable();
313
338
  const allSymbols = new Set();
314
- for (const r of response.data) {
339
+ for (const r of scopedData) {
315
340
  for (const s of (0, arrow_1.toArr)(r.defined_symbols))
316
341
  allSymbols.add(s);
317
342
  }