grepmax 0.17.20 → 0.17.22

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.
@@ -50,6 +50,7 @@ const searcher_1 = require("../lib/search/searcher");
50
50
  const skeleton_1 = require("../lib/skeleton");
51
51
  const vector_db_1 = require("../lib/store/vector-db");
52
52
  const arrow_1 = require("../lib/utils/arrow");
53
+ const budget_pack_1 = require("../lib/utils/budget-pack");
53
54
  const exit_1 = require("../lib/utils/exit");
54
55
  const filter_builder_1 = require("../lib/utils/filter-builder");
55
56
  const project_registry_1 = require("../lib/utils/project-registry");
@@ -157,7 +158,7 @@ exports.context = new commander_1.Command("context")
157
158
  .option("--root <dir>", "Project root directory")
158
159
  .option("--agent", "Compact output for AI agents", false)
159
160
  .action((topic, opts) => __awaiter(void 0, void 0, void 0, function* () {
160
- var _a, _b, _c, _d, _e;
161
+ var _a, _b, _c;
161
162
  const budget = Number.parseInt(opts.budget || "4000", 10) || 4000;
162
163
  const maxResults = Number.parseInt(opts.maxResults || "10", 10) || 10;
163
164
  let vectorDb = null;
@@ -204,33 +205,45 @@ exports.context = new commander_1.Command("context")
204
205
  sections.push(epText);
205
206
  tokensUsed += estimateTokens(epText);
206
207
  }
207
- // Phase 3: Key function bodies (top 2-3 results)
208
+ // Phase 3: Key function bodies (top 2-3 results). Token-aware packing
209
+ // (knapsack-continue): an oversized body is skipped so a smaller, still-
210
+ // relevant one can fill the remaining budget instead of aborting the rest.
208
211
  const topChunks = entryPoints.slice(0, 3);
209
- const bodySection = ["\n## Key Functions"];
210
- for (const r of topChunks) {
212
+ const bodyBlobs = topChunks.map((r) => {
213
+ var _a, _b;
211
214
  const absP = chunkPath(r);
212
215
  const startLine = chunkStartLine(r);
213
216
  const endLine = chunkEndLine(r);
214
- const sym = (_e = (_d = (0, arrow_1.toArr)(r.defined_symbols)) === null || _d === void 0 ? void 0 : _d[0]) !== null && _e !== void 0 ? _e : "";
217
+ const sym = (_b = (_a = (0, arrow_1.toArr)(r.defined_symbols)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : "";
215
218
  try {
216
219
  const content = fs.readFileSync(absP, "utf-8");
217
220
  const allLines = content.split("\n");
218
221
  const body = allLines
219
222
  .slice(startLine, Math.min(endLine + 1, allLines.length))
220
223
  .join("\n");
221
- const blob = `\n--- ${relPath(projectRoot, absP)}:${startLine + 1} ${sym} ---\n${body}`;
222
- const blobTokens = estimateTokens(blob);
223
- if (tokensUsed + blobTokens > budget)
224
- break;
225
- bodySection.push(blob);
226
- tokensUsed += blobTokens;
224
+ return `\n--- ${relPath(projectRoot, absP)}:${startLine + 1} ${sym} ---\n${body}`;
227
225
  }
228
- catch (_f) {
229
- // File not readable — skip
226
+ catch (_c) {
227
+ return null; // File not readable — drop
230
228
  }
229
+ });
230
+ const bodyCandidates = bodyBlobs.map((blob, idx) => ({
231
+ tokens: blob ? estimateTokens(blob) : Number.POSITIVE_INFINITY,
232
+ score: topChunks.length - idx, // preserve relevance order
233
+ }));
234
+ const bodyPack = (0, budget_pack_1.packByBudget)(bodyCandidates, budget - tokensUsed, {
235
+ atLeastOne: false,
236
+ });
237
+ const bodySection = ["\n## Key Functions"];
238
+ for (const i of bodyPack.selected) {
239
+ const blob = bodyBlobs[i];
240
+ if (!blob)
241
+ continue;
242
+ bodySection.push(blob);
231
243
  }
232
244
  if (bodySection.length > 1) {
233
245
  sections.push(bodySection.join(""));
246
+ tokensUsed += bodyPack.tokensUsed;
234
247
  }
235
248
  // Phase 4: File skeletons for unique files
236
249
  const uniqueFiles = [
@@ -249,12 +262,14 @@ exports.context = new commander_1.Command("context")
249
262
  continue;
250
263
  const blob = `\n--- ${relPath(projectRoot, absP)} (skeleton, ~${result.tokenEstimate} tokens) ---\n${result.skeleton}`;
251
264
  const blobTokens = estimateTokens(blob);
265
+ // Skip an oversized skeleton but keep trying smaller ones (a verbose
266
+ // file shouldn't starve the rest of the budget).
252
267
  if (tokensUsed + blobTokens > budget)
253
- break;
268
+ continue;
254
269
  skelSection.push(blob);
255
270
  tokensUsed += blobTokens;
256
271
  }
257
- catch (_g) {
272
+ catch (_d) {
258
273
  // Skip unreadable files
259
274
  }
260
275
  }
@@ -315,7 +330,7 @@ exports.context = new commander_1.Command("context")
315
330
  try {
316
331
  yield vectorDb.close();
317
332
  }
318
- catch (_h) { }
333
+ catch (_e) { }
319
334
  }
320
335
  yield (0, exit_1.gracefulExit)();
321
336
  }
@@ -58,10 +58,13 @@ exports.impact = new commander_1.Command("impact")
58
58
  .option("--root <dir>", "Project root directory")
59
59
  .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
60
60
  .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
61
+ .option("--no-tests", "Skip affected-test analysis; show production blast radius only")
61
62
  .option("--agent", "Compact output for AI agents", false)
62
63
  .action((target, opts) => __awaiter(void 0, void 0, void 0, function* () {
63
64
  var _a;
64
65
  const depth = Math.min(Math.max(Number.parseInt(opts.depth || "1", 10), 1), 3);
66
+ // commander maps --no-tests → opts.tests === false (defaults true).
67
+ const includeTests = opts.tests !== false;
65
68
  let vectorDb = null;
66
69
  try {
67
70
  const root = (0, project_registry_1.resolveRootOrExit)(opts.root);
@@ -96,10 +99,14 @@ exports.impact = new commander_1.Command("impact")
96
99
  const queryRoot = opts.in && opts.in.length > 0
97
100
  ? scope.pathPrefix.replace(/\/$/, "")
98
101
  : projectRoot;
99
- // Run dependents and tests in parallel
102
+ // Run dependents and tests in parallel. --no-tests skips the test
103
+ // traversal entirely so the affected-tests section is omitted (not just
104
+ // empty) below.
100
105
  const [dependents, tests] = yield Promise.all([
101
106
  (0, impact_1.findDependents)(symbols, vectorDb, queryRoot, excludePaths, undefined, scope.excludePrefixes),
102
- (0, impact_1.findTests)(symbols, vectorDb, queryRoot, depth, scope.excludePrefixes),
107
+ includeTests
108
+ ? (0, impact_1.findTests)(symbols, vectorDb, queryRoot, depth, scope.excludePrefixes)
109
+ : Promise.resolve([]),
103
110
  ]);
104
111
  // Separate test files from non-test dependents
105
112
  const nonTestDeps = dependents.filter((d) => !(0, impact_1.isTestPath)(d.file));
@@ -127,16 +134,18 @@ exports.impact = new commander_1.Command("impact")
127
134
  else {
128
135
  console.log("Direct dependents: none found");
129
136
  }
130
- console.log("");
131
- if (tests.length > 0) {
132
- console.log(`Affected tests (${tests.length}):`);
133
- for (const t of tests) {
134
- const hopLabel = t.hops === 0 ? "calls directly" : `${t.hops} hop${t.hops > 1 ? "s" : ""} away`;
135
- console.log(` ${rel(t.file)}:${t.line + 1} ${t.symbol} (${hopLabel})`);
137
+ if (includeTests) {
138
+ console.log("");
139
+ if (tests.length > 0) {
140
+ console.log(`Affected tests (${tests.length}):`);
141
+ for (const t of tests) {
142
+ const hopLabel = t.hops === 0 ? "calls directly" : `${t.hops} hop${t.hops > 1 ? "s" : ""} away`;
143
+ console.log(` ${rel(t.file)}:${t.line + 1} ${t.symbol} (${hopLabel})`);
144
+ }
145
+ }
146
+ else {
147
+ console.log("Affected tests: none found");
136
148
  }
137
- }
138
- else {
139
- console.log("Affected tests: none found");
140
149
  }
141
150
  }
142
151
  }
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
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
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.runSearch = runSearch;
46
+ const path = __importStar(require("node:path"));
47
+ const grammar_loader_1 = require("../lib/index/grammar-loader");
48
+ const sync_helpers_1 = require("../lib/index/sync-helpers");
49
+ const syncer_1 = require("../lib/index/syncer");
50
+ const searcher_1 = require("../lib/search/searcher");
51
+ const vector_db_1 = require("../lib/store/vector-db");
52
+ const lock_1 = require("../lib/utils/lock");
53
+ /**
54
+ * Acquire search results, picking the path: daemon-mediated first (ships the
55
+ * query over IPC to the already-warm daemon), in-process fallback otherwise
56
+ * (opens VectorDB, ensures the index, runs Searcher). The presentation/render
57
+ * stage stays in the command action.
58
+ *
59
+ * On success/dry-run the opened VectorDB (if any) is returned for the caller to
60
+ * close after rendering; if indexing throws, it is closed here before rethrow.
61
+ */
62
+ function runSearch(params) {
63
+ return __awaiter(this, void 0, void 0, function* () {
64
+ var _a, _b;
65
+ const { pattern, options, projectRoot, effectiveRoot, paths, projectStatus, searchFilters, pathFilter, seeds, } = params;
66
+ // Tracks a DB opened by the in-process path so it can be (a) returned to the
67
+ // caller for closing after render, or (b) closed here if indexing throws.
68
+ let openedDb = null;
69
+ try {
70
+ // Daemon-mediated search: ships query+args over IPC, daemon runs the
71
+ // hybrid+rerank against its already-warm VectorDB and worker pool.
72
+ // Drops cold-start cost (~17s wall, 6GB RAM in the CLI) to <1s. Falls
73
+ // back to in-process on any failure.
74
+ let searchResult = null;
75
+ let precomputedSkeletons;
76
+ let precomputedGraph;
77
+ let indexState;
78
+ if (!options.sync && !options.dryRun) {
79
+ try {
80
+ const { isDaemonRunning, sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
81
+ if (yield isDaemonRunning()) {
82
+ const resp = yield sendDaemonCommand({
83
+ cmd: "search",
84
+ projectRoot: effectiveRoot,
85
+ query: pattern,
86
+ limit: parseInt(options.m, 10),
87
+ filters: Object.keys(searchFilters).length > 0
88
+ ? searchFilters
89
+ : undefined,
90
+ pathPrefix: pathFilter,
91
+ rerank: process.env.GMAX_RERANK === "1",
92
+ explain: options.explain,
93
+ seeds,
94
+ includeSkeletons: options.skeleton,
95
+ includeGraph: options.symbol,
96
+ }, { timeoutMs: 60000 });
97
+ if (resp.ok) {
98
+ searchResult = {
99
+ data: resp.data,
100
+ warnings: resp.warnings,
101
+ };
102
+ precomputedSkeletons = resp.skeletons;
103
+ precomputedGraph = resp.graph;
104
+ indexState = resp.indexState;
105
+ }
106
+ else if (process.env.GMAX_DEBUG === "1") {
107
+ console.error(`[search] daemon path unavailable: ${(_a = resp.error) !== null && _a !== void 0 ? _a : "unknown"}`);
108
+ }
109
+ }
110
+ }
111
+ catch (err) {
112
+ if (process.env.GMAX_DEBUG === "1") {
113
+ console.error("[search] daemon attempt threw:", err);
114
+ }
115
+ }
116
+ }
117
+ // In-process fallback: open VectorDB, ensure index, run Searcher.
118
+ // Only entered when the daemon path didn't produce results.
119
+ if (!searchResult) {
120
+ const vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
121
+ openedDb = vectorDb;
122
+ // Check for active indexing lock and warn if present
123
+ const locked = (0, lock_1.isLocked)(paths.dataDir);
124
+ if (!options.agent && locked) {
125
+ console.warn("⚠️ Warning: Indexing in progress... search results may be incomplete.");
126
+ }
127
+ // No daemon here, so no precise pending count — surface the coarse
128
+ // signal (active lock or initial index not yet complete) so agent mode
129
+ // still gets a partial-index footer.
130
+ if (!indexState && (locked || projectStatus === "pending")) {
131
+ indexState = { indexing: true, pendingFiles: 0 };
132
+ }
133
+ const hasRows = yield vectorDb.hasAnyRows();
134
+ const needsSync = options.sync || !hasRows;
135
+ if (needsSync) {
136
+ const isTTY = process.stdout.isTTY;
137
+ let abortController;
138
+ let signal;
139
+ if (!isTTY) {
140
+ abortController = new AbortController();
141
+ signal = abortController.signal;
142
+ setTimeout(() => {
143
+ abortController === null || abortController === void 0 ? void 0 : abortController.abort();
144
+ }, 60000); // 60 seconds timeout for non-TTY auto-indexing
145
+ }
146
+ const { spinner, onProgress } = (0, sync_helpers_1.createIndexingSpinner)(projectRoot, options.sync ? "Indexing..." : "Indexing repository (first run)...");
147
+ try {
148
+ yield (0, grammar_loader_1.ensureGrammars)(console.log, { silent: true });
149
+ const result = yield (0, syncer_1.initialSync)({
150
+ projectRoot,
151
+ dryRun: options.dryRun,
152
+ onProgress,
153
+ signal,
154
+ });
155
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
156
+ spinner.warn(`Indexing timed out (${result.processed}/${result.total}). Results may be partial.`);
157
+ }
158
+ if (options.dryRun) {
159
+ spinner.succeed(`Dry run complete (${result.processed}/${result.total}) • would have indexed ${result.indexed}`);
160
+ console.log((0, sync_helpers_1.formatDryRunSummary)(result, {
161
+ actionDescription: "would have indexed",
162
+ includeTotal: true,
163
+ }));
164
+ return { kind: "dry-run", vectorDb: openedDb };
165
+ }
166
+ yield vectorDb.createFTSIndex();
167
+ // Update registry after sync
168
+ const { readGlobalConfig } = yield Promise.resolve().then(() => __importStar(require("../lib/index/index-config")));
169
+ const { registerProject } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/project-registry")));
170
+ const gc = readGlobalConfig();
171
+ registerProject({
172
+ root: projectRoot,
173
+ name: path.basename(projectRoot),
174
+ vectorDim: gc.vectorDim,
175
+ modelTier: gc.modelTier,
176
+ embedMode: gc.embedMode,
177
+ lastIndexed: new Date().toISOString(),
178
+ chunkCount: result.indexed,
179
+ status: "indexed",
180
+ });
181
+ const failedSuffix = result.failedFiles > 0 ? ` • ${result.failedFiles} failed` : "";
182
+ spinner.succeed(`${options.sync ? "Indexing" : "Initial indexing"} complete (${result.processed}/${result.total}) • indexed ${result.indexed}${failedSuffix}`);
183
+ }
184
+ catch (e) {
185
+ spinner.fail("Indexing failed");
186
+ throw e;
187
+ }
188
+ }
189
+ // Ensure a watcher is running for live reindexing
190
+ if (!process.env.VITEST && !((_b = process.env.NODE_ENV) === null || _b === void 0 ? void 0 : _b.includes("test"))) {
191
+ const { launchWatcher } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/watcher-launcher")));
192
+ const launched = yield launchWatcher(projectRoot);
193
+ if (!launched.ok && launched.reason === "spawn-failed") {
194
+ console.warn(`[search] ${launched.message}`);
195
+ }
196
+ }
197
+ const searcher = new searcher_1.Searcher(vectorDb);
198
+ searchResult = yield searcher.search(pattern, parseInt(options.m, 10), {
199
+ rerank: process.env.GMAX_RERANK === "1",
200
+ explain: options.explain,
201
+ seeds,
202
+ }, Object.keys(searchFilters).length > 0
203
+ ? searchFilters
204
+ : undefined, pathFilter);
205
+ } // end if (!searchResult) — in-process fallback
206
+ return {
207
+ kind: "result",
208
+ vectorDb: openedDb,
209
+ searchResult,
210
+ precomputedSkeletons,
211
+ precomputedGraph,
212
+ indexState,
213
+ };
214
+ }
215
+ catch (e) {
216
+ // Mirror the original action's finally: close a DB opened mid-flight when
217
+ // indexing throws, before the error propagates to the caller.
218
+ if (openedDb) {
219
+ try {
220
+ yield openedDb.close();
221
+ }
222
+ catch (_c) { }
223
+ }
224
+ throw e;
225
+ }
226
+ });
227
+ }
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
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
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.outputSkeletons = outputSkeletons;
46
+ const fs = __importStar(require("node:fs"));
47
+ const path = __importStar(require("node:path"));
48
+ const skeleton_1 = require("../lib/skeleton");
49
+ const retriever_1 = require("../lib/skeleton/retriever");
50
+ // Reuse Skeletonizer instance
51
+ let globalSkeletonizer = null;
52
+ function outputSkeletons(results, projectRoot, limit, db, precomputed) {
53
+ return __awaiter(this, void 0, void 0, function* () {
54
+ var _a, _b;
55
+ const seenPaths = new Set();
56
+ const filesToProcess = [];
57
+ for (const result of results) {
58
+ const p = (_a = result.metadata) === null || _a === void 0 ? void 0 : _a.path;
59
+ if (typeof p === "string" && !seenPaths.has(p)) {
60
+ seenPaths.add(p);
61
+ filesToProcess.push(p);
62
+ if (filesToProcess.length >= limit)
63
+ break;
64
+ }
65
+ }
66
+ if (filesToProcess.length === 0) {
67
+ console.log("No skeleton matches found.");
68
+ console.log("\nTry: broaden your query, or use `gmax skeleton <path>` to view a specific file's structure.");
69
+ process.exitCode = 1;
70
+ return;
71
+ }
72
+ // Reuse or init skeletonizer for fallbacks
73
+ if (!globalSkeletonizer) {
74
+ globalSkeletonizer = new skeleton_1.Skeletonizer();
75
+ // Lazy init only if we actually fallback
76
+ }
77
+ const skeletonOpts = { includeSummary: true };
78
+ const skeletonResults = [];
79
+ for (const filePath of filesToProcess) {
80
+ // Paths from search results are now absolute (centralized index)
81
+ const absPath = path.isAbsolute(filePath)
82
+ ? filePath
83
+ : path.resolve(projectRoot, filePath);
84
+ // 0. Daemon-supplied (preferred — already-warm DB lookup, no cold open)
85
+ const fromDaemon = (_b = precomputed === null || precomputed === void 0 ? void 0 : precomputed[absPath]) !== null && _b !== void 0 ? _b : precomputed === null || precomputed === void 0 ? void 0 : precomputed[filePath];
86
+ if (fromDaemon) {
87
+ skeletonResults.push({
88
+ file: filePath,
89
+ skeleton: fromDaemon,
90
+ tokens: Math.ceil(fromDaemon.length / 4),
91
+ });
92
+ continue;
93
+ }
94
+ // 1. Try DB cache
95
+ if (db) {
96
+ const cached = yield (0, retriever_1.getStoredSkeleton)(db, absPath);
97
+ if (cached) {
98
+ skeletonResults.push({
99
+ file: filePath,
100
+ skeleton: cached,
101
+ tokens: Math.ceil(cached.length / 4), // Rough estimate
102
+ });
103
+ continue;
104
+ }
105
+ }
106
+ // 2. Fallback to fresh generation
107
+ yield globalSkeletonizer.init();
108
+ if (!fs.existsSync(absPath)) {
109
+ skeletonResults.push({
110
+ file: filePath,
111
+ skeleton: `// File not found: ${filePath}`,
112
+ tokens: 0,
113
+ error: "File not found",
114
+ });
115
+ continue;
116
+ }
117
+ const content = fs.readFileSync(absPath, "utf-8");
118
+ const res = yield globalSkeletonizer.skeletonizeFile(absPath, content, skeletonOpts);
119
+ skeletonResults.push({
120
+ file: filePath,
121
+ skeleton: res.skeleton,
122
+ tokens: res.tokenEstimate,
123
+ error: res.error,
124
+ });
125
+ }
126
+ // Since search doesn't support --json explicitly yet, we just print text.
127
+ // But if we ever add it, we have the structure.
128
+ for (const res of skeletonResults) {
129
+ console.log(res.skeleton);
130
+ console.log(""); // Separator
131
+ }
132
+ });
133
+ }