dsh-gitnexus-plus 0.1.1 → 0.1.2

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 (54) hide show
  1. package/README.md +197 -49
  2. package/lib/bin/search-helper.d.ts +22 -0
  3. package/lib/bin/search-helper.d.ts.map +1 -0
  4. package/lib/bin/search-helper.js +57 -0
  5. package/lib/bin/search-helper.js.map +1 -0
  6. package/lib/client.js +576 -87
  7. package/lib/gitnexus/analyze.d.ts +27 -2
  8. package/lib/gitnexus/analyze.d.ts.map +1 -1
  9. package/lib/gitnexus/analyze.js +31 -6
  10. package/lib/gitnexus/analyze.js.map +1 -1
  11. package/lib/gitnexus/commands.d.ts +41 -8
  12. package/lib/gitnexus/commands.d.ts.map +1 -1
  13. package/lib/gitnexus/commands.js +62 -9
  14. package/lib/gitnexus/commands.js.map +1 -1
  15. package/lib/gitnexus/coverage.d.ts.map +1 -1
  16. package/lib/gitnexus/coverage.js +3 -1
  17. package/lib/gitnexus/coverage.js.map +1 -1
  18. package/lib/gitnexus/index-meta.d.ts +15 -5
  19. package/lib/gitnexus/index-meta.d.ts.map +1 -1
  20. package/lib/gitnexus/index-meta.js +25 -5
  21. package/lib/gitnexus/index-meta.js.map +1 -1
  22. package/lib/gitnexus/languages.d.ts +7 -0
  23. package/lib/gitnexus/languages.d.ts.map +1 -1
  24. package/lib/gitnexus/languages.js +82 -2
  25. package/lib/gitnexus/languages.js.map +1 -1
  26. package/lib/gitnexus/root.d.ts +6 -0
  27. package/lib/gitnexus/root.d.ts.map +1 -1
  28. package/lib/gitnexus/root.js +6 -0
  29. package/lib/gitnexus/root.js.map +1 -1
  30. package/lib/gitnexus/scan.d.ts +1 -0
  31. package/lib/gitnexus/scan.d.ts.map +1 -1
  32. package/lib/gitnexus/scan.js +3 -1
  33. package/lib/gitnexus/scan.js.map +1 -1
  34. package/lib/gitnexus/search.d.ts +167 -0
  35. package/lib/gitnexus/search.d.ts.map +1 -0
  36. package/lib/gitnexus/search.js +517 -0
  37. package/lib/gitnexus/search.js.map +1 -0
  38. package/lib/index.d.ts +32 -13
  39. package/lib/index.d.ts.map +1 -1
  40. package/lib/index.js +62 -21
  41. package/lib/index.js.map +1 -1
  42. package/lib/routes/analyze.d.ts +8 -2
  43. package/lib/routes/analyze.d.ts.map +1 -1
  44. package/lib/routes/analyze.js +5 -1
  45. package/lib/routes/analyze.js.map +1 -1
  46. package/lib/routes/coverage.d.ts +13 -5
  47. package/lib/routes/coverage.d.ts.map +1 -1
  48. package/lib/routes/coverage.js +24 -11
  49. package/lib/routes/coverage.js.map +1 -1
  50. package/lib/routes/search.d.ts +27 -0
  51. package/lib/routes/search.d.ts.map +1 -0
  52. package/lib/routes/search.js +77 -0
  53. package/lib/routes/search.js.map +1 -0
  54. package/package.json +2 -2
@@ -0,0 +1,517 @@
1
+ /**
2
+ * Keyword search over the graph GitNexus already indexed.
3
+ *
4
+ * WHY THIS DOES NOT USE FTS
5
+ * -------------------------
6
+ * GitNexus's keyword surface is its BM25/FTS extension. On this machine that
7
+ * extension fails to load (`libfts.lbug_extension` is missing), and the existing
8
+ * index was built with `--skip-fts`, so there are no FTS tables to query even if the
9
+ * extension were present. Reporting "search unavailable" would be honest, but it
10
+ * would also be the end of the feature for the state most users are in.
11
+ *
12
+ * The graph itself still contains every symbol's name and file path, and a Cypher
13
+ * `CONTAINS` over those columns answers the question the user is actually asking
14
+ * when they type a symbol name. This module therefore searches **names and paths**,
15
+ * not source text or descriptions. That distinction is deliberate and surfaced to
16
+ * the panel: an FTS repair can add content search later without this module
17
+ * pretending it already has it.
18
+ *
19
+ * READ-ONLY, AND REUSING GITNEXUS'S OWN ADAPTER
20
+ * ---------------------------------------------
21
+ * The database is opened through GitNexus's `withLbugDb(..., { readOnly: true,
22
+ * skipFts: true })`, so it takes no write lock, cannot mutate the index, and does not
23
+ * attempt the FTS load that is known to fail here. The adapter is deep-imported from
24
+ * the installed package, matching the plugin's existing "reuse, don't reimplement"
25
+ * stance; if the package or its native engine is unavailable, search reports that
26
+ * state rather than falling back to a filesystem scan with different semantics.
27
+ */
28
+ import { existsSync } from 'node:fs';
29
+ import fs from 'node:fs/promises';
30
+ import path from 'node:path';
31
+ import { pathToFileURL } from 'node:url';
32
+ import { findGitnexusRoot } from './root.js';
33
+ import { isProjectRoot, readIndexMeta } from './index-meta.js';
34
+ import { readCapabilities } from './coverage.js';
35
+ export class SearchError extends Error {
36
+ code;
37
+ status;
38
+ constructor(code, status, message) {
39
+ super(message);
40
+ this.name = 'SearchError';
41
+ this.code = code;
42
+ this.status = status;
43
+ }
44
+ }
45
+ const adapters = new Map();
46
+ /**
47
+ * Load GitNexus's LadybugDB adapter from the installed package.
48
+ *
49
+ * A missing package, a missing native engine, or a reshaped adapter all become the
50
+ * same reported state: search is unavailable here, and the reason is carried in the
51
+ * message rather than hidden behind an empty result list.
52
+ */
53
+ async function loadAdapter(options) {
54
+ const root = findGitnexusRoot({ env: options.env, managedPrefix: options.managedPrefix });
55
+ if (root === null) {
56
+ throw new SearchError('search-unavailable', 503, '找不到 GitNexus 包,无法打开图数据库。安装或指向 GitNexus 后重试。');
57
+ }
58
+ const cached = adapters.get(root);
59
+ if (cached !== undefined)
60
+ return cached;
61
+ try {
62
+ const mod = (await import(pathToFileURL(path.join(root, 'dist', 'core', 'lbug', 'lbug-adapter.js')).href));
63
+ if (typeof mod.withLbugDb !== 'function' || typeof mod.executePrepared !== 'function') {
64
+ throw new Error('adapter does not export withLbugDb/executePrepared');
65
+ }
66
+ const adapter = {
67
+ withLbugDb: mod.withLbugDb,
68
+ executePrepared: mod.executePrepared,
69
+ };
70
+ adapters.set(root, adapter);
71
+ return adapter;
72
+ }
73
+ catch (cause) {
74
+ throw new SearchError('search-unavailable', 503, `GitNexus 的图数据库适配器不可用:${cause instanceof Error ? cause.message : String(cause)}`);
75
+ }
76
+ }
77
+ /**
78
+ * Normalize a user keyword into a safe Cypher string literal body.
79
+ *
80
+ * The query is interpolated as a single-quoted literal because LadybugDB's prepare
81
+ * path does not accept a bound parameter in this `CONTAINS` position (measured:
82
+ * `lower(n.name) CONTAINS $q` fails at parse time, while the same query with a
83
+ * literal works). Safety therefore comes from **removing** every character that
84
+ * could terminate or escape the literal — quotes, backslashes and control
85
+ * characters — rather than from escaping them. The allowed set is what a code
86
+ * identifier or path search needs; everything else becomes a space.
87
+ *
88
+ * Case is lowered here to match `lower(n.name)` on the other side. The length cap
89
+ * keeps a pathological paste from turning into an expensive graph scan.
90
+ */
91
+ export function sanitizeSearchQuery(raw) {
92
+ return raw
93
+ .normalize('NFC')
94
+ .toLowerCase()
95
+ .replace(/[^\p{L}\p{N}\s_./:@#+-]/gu, ' ')
96
+ .replace(/\s+/g, ' ')
97
+ .trim()
98
+ .slice(0, 80);
99
+ }
100
+ function clampLimit(value) {
101
+ const n = value === undefined || !Number.isFinite(value) ? 20 : Math.floor(value);
102
+ return Math.max(1, Math.min(50, n));
103
+ }
104
+ function toLine(value) {
105
+ if (typeof value === 'number' && Number.isFinite(value))
106
+ return Math.trunc(value);
107
+ if (typeof value === 'bigint')
108
+ return Number(value);
109
+ return null;
110
+ }
111
+ function buildCypher(query, limit) {
112
+ // `query` has already been reduced to the safe literal alphabet by
113
+ // sanitizeSearchQuery; the limit is a clamped integer. Neither is user-arbitrary
114
+ // syntax at this point.
115
+ return `
116
+ MATCH (n)
117
+ WHERE (lower(n.name) CONTAINS '${query}'
118
+ OR (label(n) = 'File' AND lower(n.filePath) CONTAINS '${query}'))
119
+ RETURN label(n) AS kind, n.name AS name, n.filePath AS filePath,
120
+ n.startLine AS startLine, n.endLine AS endLine,
121
+ CASE WHEN lower(n.name) = '${query}' THEN 0
122
+ WHEN lower(n.name) STARTS WITH '${query}' THEN 1
123
+ WHEN label(n) = 'File' THEN 2
124
+ ELSE 3 END AS rank
125
+ ORDER BY rank, name
126
+ LIMIT ${limit}
127
+ `;
128
+ }
129
+ function mapRows(rows) {
130
+ const results = [];
131
+ for (const value of rows) {
132
+ if (value === null || typeof value !== 'object')
133
+ continue;
134
+ const row = value;
135
+ const name = typeof row.name === 'string' ? row.name : '';
136
+ const filePath = typeof row.filePath === 'string' ? row.filePath : '';
137
+ if (name === '' || filePath === '')
138
+ continue;
139
+ results.push({
140
+ kind: typeof row.kind === 'string' && row.kind !== '' ? row.kind : 'Unknown',
141
+ name,
142
+ filePath,
143
+ startLine: toLine(row.startLine),
144
+ endLine: toLine(row.endLine),
145
+ });
146
+ }
147
+ return results;
148
+ }
149
+ function isBusyMessage(message) {
150
+ return /busy|locked|lock held|another process|timeout/i.test(message);
151
+ }
152
+ /**
153
+ * Search symbol names and file paths in one repository's index.
154
+ *
155
+ * @throws {SearchError} for states the route should report as status + code.
156
+ */
157
+ export async function searchSymbols(repoPath, rawQuery, options = {}) {
158
+ const query = sanitizeSearchQuery(rawQuery);
159
+ if (query === '') {
160
+ throw new SearchError('invalid-query', 400, '搜索词不能为空,且至少包含一个字母或数字。');
161
+ }
162
+ const absRepo = path.resolve(repoPath);
163
+ if (!(await isProjectRoot(absRepo))) {
164
+ throw new SearchError('not-a-project', 404, '目录不是 GitNexus 索引,也不是 git 项目根。');
165
+ }
166
+ const meta = await readIndexMeta(absRepo);
167
+ if (meta === null) {
168
+ throw new SearchError('no-index', 409, '该项目还没有 GitNexus 索引;初始化后才能搜索。');
169
+ }
170
+ const lbugPath = path.join(absRepo, '.gitnexus', 'lbug');
171
+ if (!existsSync(lbugPath)) {
172
+ throw new SearchError('no-index-store', 409, '索引缺少 .gitnexus/lbug 图存储,无法搜索。');
173
+ }
174
+ const adapter = await loadAdapter(options);
175
+ const limit = clampLimit(options.limit);
176
+ const cypher = buildCypher(query, limit);
177
+ let rows;
178
+ try {
179
+ // `skipFts` avoids the extension load that is known to fail on this machine;
180
+ // `readOnly` keeps the search from taking a writer lock or creating anything.
181
+ rows = (await adapter.withLbugDb(lbugPath, async () => adapter.executePrepared(cypher, {}), { readOnly: true, skipFts: true }));
182
+ }
183
+ catch (cause) {
184
+ const message = cause instanceof Error ? cause.message : String(cause);
185
+ if (isBusyMessage(message)) {
186
+ throw new SearchError('index-busy', 503, '索引正被另一个 GitNexus 操作占用,稍后重试。');
187
+ }
188
+ throw new SearchError('search-failed', 500, `图查询失败:${message}`);
189
+ }
190
+ const results = mapRows(rows);
191
+ const capabilities = readCapabilities(meta.raw ?? null);
192
+ const ftsAvailable = capabilities.fts?.status === 'available';
193
+ return {
194
+ query,
195
+ mode: 'graph',
196
+ ftsAvailable,
197
+ results,
198
+ total: results.length,
199
+ truncated: results.length >= limit,
200
+ warning: ftsAvailable
201
+ ? null
202
+ : 'FTS 内容检索不可用;本次按符号名与文件路径匹配,不搜索正文。',
203
+ };
204
+ }
205
+ function quoteFor(value) {
206
+ return process.platform === 'win32'
207
+ ? `'${value.replace(/'/g, "''")}'`
208
+ : `'${value.replace(/'/g, "'\\''")}'`;
209
+ }
210
+ /**
211
+ * Parse the helper's one-object stdout without assuming it is the very first byte.
212
+ *
213
+ * GitNexus or Node can write a warning to stdout before our payload. Taking the
214
+ * first `{` to the last `}` is deliberately forgiving of that, while a missing
215
+ * object still becomes a typed failure rather than an empty result.
216
+ */
217
+ function parseHelperOutput(text) {
218
+ const start = text.indexOf('{');
219
+ const end = text.lastIndexOf('}');
220
+ if (start < 0 || end <= start)
221
+ return null;
222
+ try {
223
+ const parsed = JSON.parse(text.slice(start, end + 1));
224
+ return parsed !== null && typeof parsed === 'object' ? parsed : null;
225
+ }
226
+ catch {
227
+ return null;
228
+ }
229
+ }
230
+ function searchErrorCode(value) {
231
+ switch (value) {
232
+ case 'invalid-query':
233
+ case 'not-a-project':
234
+ case 'no-index':
235
+ case 'no-index-store':
236
+ case 'search-unavailable':
237
+ case 'index-busy':
238
+ case 'search-failed':
239
+ return value;
240
+ default:
241
+ return 'search-failed';
242
+ }
243
+ }
244
+ function asReport(value) {
245
+ if (value === null || typeof value !== 'object')
246
+ return null;
247
+ const raw = value;
248
+ if (!Array.isArray(raw.results))
249
+ return null;
250
+ const results = [];
251
+ for (const item of raw.results) {
252
+ if (item === null || typeof item !== 'object')
253
+ continue;
254
+ const row = item;
255
+ if (typeof row.name !== 'string' || typeof row.filePath !== 'string')
256
+ continue;
257
+ results.push({
258
+ kind: typeof row.kind === 'string' ? row.kind : 'Unknown',
259
+ name: row.name,
260
+ filePath: row.filePath,
261
+ startLine: toLine(row.startLine),
262
+ endLine: toLine(row.endLine),
263
+ });
264
+ }
265
+ return {
266
+ query: typeof raw.query === 'string' ? raw.query : '',
267
+ mode: raw.mode === 'text' ? 'text' : 'graph',
268
+ ftsAvailable: raw.ftsAvailable === true,
269
+ results,
270
+ total: typeof raw.total === 'number' ? raw.total : results.length,
271
+ truncated: raw.truncated === true,
272
+ warning: typeof raw.warning === 'string' ? raw.warning : null,
273
+ };
274
+ }
275
+ /**
276
+ * Run the search in a child Node process.
277
+ *
278
+ * WHY A SUBPROCESS IS NOT OPTIONAL
279
+ * --------------------------------
280
+ * The DSH host runs on Electron's Node, and GitNexus's LadybugDB addon is built for
281
+ * the system Node ABI. Loading it in the host fails with
282
+ *
283
+ * A dynamic link library (DLL) initialization routine failed. …\lbugjs.node
284
+ *
285
+ * while the very same addon loads under the system Node 24 that the CLI uses. The
286
+ * host therefore cannot own the native connection; it starts `node <helper>` and
287
+ * parses one JSON object. That is the same execution boundary the analyze runner
288
+ * already uses, and it keeps the panel process free of native state it cannot load.
289
+ *
290
+ * The command has no environment prefix: a short-lived helper does not need the
291
+ * heap respawn guard, and avoiding `$env:` syntax keeps the command portable across
292
+ * PowerShell, Git Bash and POSIX shells.
293
+ */
294
+ export async function searchViaHelper(repoPath, rawQuery, options) {
295
+ const query = sanitizeSearchQuery(rawQuery);
296
+ if (query === '') {
297
+ throw new SearchError('invalid-query', 400, '搜索词不能为空,且至少包含一个字母或数字。');
298
+ }
299
+ const absRepo = path.resolve(repoPath);
300
+ if (!(await isProjectRoot(absRepo))) {
301
+ throw new SearchError('not-a-project', 404, '目录不是 GitNexus 索引,也不是 git 项目根。');
302
+ }
303
+ const meta = await readIndexMeta(absRepo);
304
+ if (meta === null) {
305
+ throw new SearchError('no-index', 409, '该项目还没有 GitNexus 索引;初始化后才能搜索。');
306
+ }
307
+ if (!existsSync(path.join(absRepo, '.gitnexus', 'lbug'))) {
308
+ throw new SearchError('no-index-store', 409, '索引缺少 .gitnexus/lbug 图存储,无法搜索。');
309
+ }
310
+ if (options.shell === null) {
311
+ throw new SearchError('search-unavailable', 503, '当前部署没有 shell 执行器,无法启动搜索子进程;搜索需要系统 Node 24 加载 GitNexus 原生模块。');
312
+ }
313
+ const limit = clampLimit(options.limit);
314
+ const args = [options.helperPath, absRepo, query, String(limit), options.managedPrefix ?? '']
315
+ .map(quoteFor)
316
+ .join(' ');
317
+ const command = `node ${args}`;
318
+ let result;
319
+ try {
320
+ const spec = options.shell.resolve({
321
+ command,
322
+ workdir: absRepo,
323
+ timeoutMs: 20_000,
324
+ onExpiry: 'kill',
325
+ stdoutMaxBytes: 2 * 1024 * 1024,
326
+ });
327
+ const execution = await options.shell.execute(spec);
328
+ result = await execution.result();
329
+ }
330
+ catch (cause) {
331
+ throw new SearchError('search-unavailable', 503, `无法启动搜索子进程:${cause instanceof Error ? cause.message : String(cause)}`);
332
+ }
333
+ if (result.timedOut === true) {
334
+ throw new SearchError('search-failed', 504, '搜索子进程超时(20 秒)。');
335
+ }
336
+ const stdout = typeof result.stdout?.text === 'string' ? result.stdout.text : '';
337
+ const stderr = typeof result.stderr?.text === 'string' ? result.stderr.text : '';
338
+ const parsed = parseHelperOutput(stdout);
339
+ if (parsed === null) {
340
+ const tail = stderr.trim().slice(-600);
341
+ throw new SearchError('search-failed', 500, `搜索子进程没有返回可解析结果${result.exitCode === undefined ? '' : ` (exit ${String(result.exitCode)})`}` + (tail === '' ? '' : `:${tail}`));
342
+ }
343
+ if (parsed.ok !== true) {
344
+ const status = typeof parsed.status === 'number' ? parsed.status : 500;
345
+ const message = typeof parsed.message === 'string' ? parsed.message : '搜索失败。';
346
+ throw new SearchError(searchErrorCode(parsed.code), status, message);
347
+ }
348
+ const report = asReport(parsed.report);
349
+ if (report === null) {
350
+ throw new SearchError('search-failed', 500, '搜索子进程返回了无法识别的结果形状。');
351
+ }
352
+ return report;
353
+ }
354
+ const DEFAULT_TEXT_MAX_FILES = 2_000;
355
+ const DEFAULT_TEXT_MAX_BYTES = 16 * 1024 * 1024;
356
+ const DEFAULT_TEXT_MAX_FILE_BYTES = 1024 * 1024;
357
+ /**
358
+ * Bounded keyword search over the source files listed by the index.
359
+ *
360
+ * This exists for one concrete state: the host cannot start the native query path
361
+ * (Electron ABI, no usable shell sandbox, busy database). In that state the index
362
+ * manifest still names every file the index covers, and reading those files is
363
+ * ordinary filesystem access the host already has. It is deliberately **not** a
364
+ * hidden replacement for the graph search:
365
+ *
366
+ * - it searches line text, so `mode` is `text` and the panel says so;
367
+ * - it only looks at files the index lists, so a brand-new untracked file is
368
+ * still absent until the index is refreshed;
369
+ * - every dimension is capped and `truncated` reports when a cap bit, because a
370
+ * silent partial result is the failure mode this whole project exists to avoid.
371
+ *
372
+ * It performs no spawn, no native load and no write.
373
+ */
374
+ export async function searchText(repoPath, rawQuery, options = {}) {
375
+ const query = rawQuery.normalize('NFC').trim().slice(0, 200);
376
+ if (query === '') {
377
+ throw new SearchError('invalid-query', 400, '搜索词不能为空。');
378
+ }
379
+ const absRepo = path.resolve(repoPath);
380
+ if (!(await isProjectRoot(absRepo))) {
381
+ throw new SearchError('not-a-project', 404, '目录不是 GitNexus 索引,也不是 git 项目根。');
382
+ }
383
+ const meta = await readIndexMeta(absRepo);
384
+ if (meta === null) {
385
+ throw new SearchError('no-index', 409, '该项目还没有 GitNexus 索引;初始化后才能搜索。');
386
+ }
387
+ const limit = clampLimit(options.limit);
388
+ const maxFiles = Math.max(1, Math.min(5_000, Math.floor(options.maxFiles ?? DEFAULT_TEXT_MAX_FILES)));
389
+ const maxBytes = Math.max(64 * 1024, Math.min(256 * 1024 * 1024, Math.floor(options.maxBytes ?? DEFAULT_TEXT_MAX_BYTES)));
390
+ const maxFileBytes = Math.max(8 * 1024, Math.min(16 * 1024 * 1024, Math.floor(options.maxFileBytes ?? DEFAULT_TEXT_MAX_FILE_BYTES)));
391
+ const requested = Object.keys(meta.fileHashes ?? {});
392
+ const warning = `图查询不可用${options.reason === undefined || options.reason.trim() === ''
393
+ ? ''
394
+ : `(${options.reason.trim().slice(0, 120)})`};已回退到索引文件的源码关键词搜索。结果来自文本,不是图谱。`;
395
+ if (requested.length === 0) {
396
+ return {
397
+ query,
398
+ mode: 'text',
399
+ ftsAvailable: false,
400
+ results: [],
401
+ total: 0,
402
+ truncated: false,
403
+ warning: `图查询不可用;索引清单没有文件列表,无法回退到源码搜索。`,
404
+ };
405
+ }
406
+ const lower = query.toLowerCase();
407
+ const results = [];
408
+ let bytesRead = 0;
409
+ let truncated = requested.length > maxFiles;
410
+ const prefix = absRepo.endsWith(path.sep) ? absRepo : `${absRepo}${path.sep}`;
411
+ // A keyword in a README should not fill the result limit before the source file
412
+ // that defines it. Code-bearing paths go first; path order breaks the tie.
413
+ const codePath = /\.(?:ts|tsx|js|jsx|mjs|cjs|py|java|go|rs|c|cc|cpp|h|hpp|cs|rb|php|kt|swift|dart|vue|sh|ps1|sql)$/iu;
414
+ const ordered = [...requested].sort((a, b) => {
415
+ const aCode = codePath.test(a) ? 1 : 0;
416
+ const bCode = codePath.test(b) ? 1 : 0;
417
+ return bCode - aCode || a.localeCompare(b);
418
+ });
419
+ for (const relative of ordered.slice(0, maxFiles)) {
420
+ if (results.length >= limit) {
421
+ truncated = true;
422
+ break;
423
+ }
424
+ if (relative.includes('\0'))
425
+ continue;
426
+ const absolute = path.resolve(absRepo, relative);
427
+ // The manifest is written by GitNexus, but it is still data at a boundary: do
428
+ // not let a `../` entry turn the fallback into a read outside the project.
429
+ if (!absolute.startsWith(prefix))
430
+ continue;
431
+ let size;
432
+ try {
433
+ size = (await fs.stat(absolute)).size;
434
+ }
435
+ catch {
436
+ continue;
437
+ }
438
+ if (size > maxFileBytes) {
439
+ truncated = true;
440
+ continue;
441
+ }
442
+ if (bytesRead + size > maxBytes) {
443
+ truncated = true;
444
+ break;
445
+ }
446
+ let buffer;
447
+ try {
448
+ buffer = await fs.readFile(absolute);
449
+ }
450
+ catch {
451
+ continue;
452
+ }
453
+ bytesRead += buffer.length;
454
+ // Binary files have no meaningful line text; git's own NUL-byte rule is enough.
455
+ if (buffer.subarray(0, 8192).includes(0))
456
+ continue;
457
+ const text = buffer.toString('utf8');
458
+ if (!text.toLowerCase().includes(lower))
459
+ continue;
460
+ const lines = text.split(/\r?\n/);
461
+ for (let index = 0; index < lines.length; index += 1) {
462
+ if (results.length >= limit) {
463
+ truncated = true;
464
+ break;
465
+ }
466
+ const line = lines[index];
467
+ if (!line.toLowerCase().includes(lower))
468
+ continue;
469
+ results.push({
470
+ kind: 'Text',
471
+ name: line.trim().slice(0, 160) || query,
472
+ filePath: relative.replace(/\\/g, '/'),
473
+ startLine: index + 1,
474
+ endLine: index + 1,
475
+ });
476
+ }
477
+ }
478
+ return {
479
+ query,
480
+ mode: 'text',
481
+ ftsAvailable: false,
482
+ results,
483
+ total: results.length,
484
+ truncated,
485
+ warning,
486
+ };
487
+ }
488
+ /** Failures for which the source fallback is more useful than an error card. */
489
+ const FALLBACK_CODES = new Set([
490
+ 'search-unavailable',
491
+ 'search-failed',
492
+ 'index-busy',
493
+ 'no-index-store',
494
+ ]);
495
+ /**
496
+ * Graph first, then the bounded source fallback when the native path cannot run.
497
+ *
498
+ * The fallback is not attempted for an empty query, an unidentified project, or a
499
+ * project with no index: in all three there is either nothing to search or no honest
500
+ * denominator. Every other failure gets the text search, and its report carries
501
+ * `mode: "text"` plus the graph failure in `warning`.
502
+ */
503
+ export async function searchWithFallback(repoPath, rawQuery, options) {
504
+ try {
505
+ return await searchViaHelper(repoPath, rawQuery, options);
506
+ }
507
+ catch (error) {
508
+ if (error instanceof SearchError && FALLBACK_CODES.has(error.code)) {
509
+ return searchText(repoPath, rawQuery, {
510
+ limit: options.limit,
511
+ reason: error.message,
512
+ });
513
+ }
514
+ throw error;
515
+ }
516
+ }
517
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/gitnexus/search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAyDjD,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,IAAI,CAAkB;IACtB,MAAM,CAAS;IAExB,YAAY,IAAqB,EAAE,MAAc,EAAE,OAAe;QAChE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAYD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;AAEhD;;;;;;GAMG;AACH,KAAK,UAAU,WAAW,CAAC,OAAsB;IAC/C,MAAM,IAAI,GAAG,gBAAgB,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1F,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,MAAM,IAAI,WAAW,CACnB,oBAAoB,EACpB,GAAG,EACH,6CAA6C,CAC9C,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IAExC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CACvB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAC/E,CAGA,CAAC;QACF,IAAI,OAAO,GAAG,CAAC,UAAU,KAAK,UAAU,IAAI,OAAO,GAAG,CAAC,eAAe,KAAK,UAAU,EAAE,CAAC;YACtF,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;QACxE,CAAC;QACD,MAAM,OAAO,GAAgB;YAC3B,UAAU,EAAE,GAAG,CAAC,UAAuC;YACvD,eAAe,EAAE,GAAG,CAAC,eAAiD;SACvE,CAAC;QACF,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC5B,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,WAAW,CACnB,oBAAoB,EACpB,GAAG,EACH,wBAAwB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACjF,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC7C,OAAO,GAAG;SACP,SAAS,CAAC,KAAK,CAAC;SAChB,WAAW,EAAE;SACb,OAAO,CAAC,2BAA2B,EAAE,GAAG,CAAC;SACzC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE;SACN,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAClB,CAAC;AAED,SAAS,UAAU,CAAC,KAAyB;IAC3C,MAAM,CAAC,GAAG,KAAK,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAClF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,MAAM,CAAC,KAAc;IAC5B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAClF,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACpD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,WAAW,CAAC,KAAa,EAAE,KAAa;IAC/C,mEAAmE;IACnE,iFAAiF;IACjF,wBAAwB;IACxB,OAAO;;iCAEwB,KAAK;+DACyB,KAAK;;;oCAGhC,KAAK;8CACK,KAAK;;;;QAI3C,KAAK;CACZ,CAAC;AACF,CAAC;AAED,SAAS,OAAO,CAAC,IAAe;IAC9B,MAAM,OAAO,GAAgB,EAAE,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;QACzB,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,SAAS;QAC1D,MAAM,GAAG,GAAG,KAAgC,CAAC;QAC7C,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,MAAM,QAAQ,GAAG,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QACtE,IAAI,IAAI,KAAK,EAAE,IAAI,QAAQ,KAAK,EAAE;YAAE,SAAS;QAC7C,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;YAC5E,IAAI;YACJ,QAAQ;YACR,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;YAChC,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;SAC7B,CAAC,CAAC;IACL,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,aAAa,CAAC,OAAe;IACpC,OAAO,gDAAgD,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACxE,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAgB,EAChB,QAAgB,EAChB,UAAyB,EAAE;IAE3B,MAAM,KAAK,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAC5C,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,WAAW,CAAC,eAAe,EAAE,GAAG,EAAE,uBAAuB,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvC,IAAI,CAAC,CAAC,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,WAAW,CAAC,eAAe,EAAE,GAAG,EAAE,+BAA+B,CAAC,CAAC;IAC/E,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,MAAM,IAAI,WAAW,CAAC,UAAU,EAAE,GAAG,EAAE,8BAA8B,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;IACzD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,WAAW,CAAC,gBAAgB,EAAE,GAAG,EAAE,+BAA+B,CAAC,CAAC;IAChF,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAEzC,IAAI,IAAe,CAAC;IACpB,IAAI,CAAC;QACH,6EAA6E;QAC7E,8EAA8E;QAC9E,IAAI,GAAG,CAAC,MAAM,OAAO,CAAC,UAAU,CAC9B,QAAQ,EACR,KAAK,IAAI,EAAE,CAAC,OAAO,CAAC,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC,EAC/C,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAClC,CAAc,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,IAAI,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,WAAW,CAAC,YAAY,EAAE,GAAG,EAAE,6BAA6B,CAAC,CAAC;QAC1E,CAAC;QACD,MAAM,IAAI,WAAW,CAAC,eAAe,EAAE,GAAG,EAAE,SAAS,OAAO,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,YAAY,GAAG,gBAAgB,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;IACxD,MAAM,YAAY,GAAG,YAAY,CAAC,GAAG,EAAE,MAAM,KAAK,WAAW,CAAC;IAC9D,OAAO;QACL,KAAK;QACL,IAAI,EAAE,OAAO;QACb,YAAY;QACZ,OAAO;QACP,KAAK,EAAE,OAAO,CAAC,MAAM;QACrB,SAAS,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;QAClC,OAAO,EAAE,YAAY;YACnB,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,kCAAkC;KACvC,CAAC;AACJ,CAAC;AAaD,SAAS,QAAQ,CAAC,KAAa;IAC7B,OAAO,OAAO,CAAC,QAAQ,KAAK,OAAO;QACjC,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG;QAClC,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AAC1C,CAAC;AAED;;;;;;GAMG;AACH,SAAS,iBAAiB,CAAC,IAAY;IACrC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,KAAK;QAAE,OAAO,IAAI,CAAC;IAC3C,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QAC/D,OAAO,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAE,MAAkC,CAAC,CAAC,CAAC,IAAI,CAAC;IACpG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,KAAc;IACrC,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,eAAe,CAAC;QACrB,KAAK,eAAe,CAAC;QACrB,KAAK,UAAU,CAAC;QAChB,KAAK,gBAAgB,CAAC;QACtB,KAAK,oBAAoB,CAAC;QAC1B,KAAK,YAAY,CAAC;QAClB,KAAK,eAAe;YAClB,OAAO,KAAK,CAAC;QACf;YACE,OAAO,eAAe,CAAC;IAC3B,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC7D,MAAM,GAAG,GAAG,KAAgC,CAAC;IAC7C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,OAAO,GAAgB,EAAE,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;QAC/B,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,SAAS;QACxD,MAAM,GAAG,GAAG,IAA+B,CAAC;QAC5C,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ;YAAE,SAAS;QAC/E,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;YACzD,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;YAChC,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;SAC7B,CAAC,CAAC;IACL,CAAC;IACD,OAAO;QACL,KAAK,EAAE,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QACrD,IAAI,EAAE,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;QAC5C,YAAY,EAAE,GAAG,CAAC,YAAY,KAAK,IAAI;QACvC,OAAO;QACP,KAAK,EAAE,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM;QACjE,SAAS,EAAE,GAAG,CAAC,SAAS,KAAK,IAAI;QACjC,OAAO,EAAE,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;KAC9D,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAgB,EAChB,QAAgB,EAChB,OAA4B;IAE5B,MAAM,KAAK,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAC5C,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,WAAW,CAAC,eAAe,EAAE,GAAG,EAAE,uBAAuB,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvC,IAAI,CAAC,CAAC,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,WAAW,CAAC,eAAe,EAAE,GAAG,EAAE,+BAA+B,CAAC,CAAC;IAC/E,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,MAAM,IAAI,WAAW,CAAC,UAAU,EAAE,GAAG,EAAE,8BAA8B,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,WAAW,CAAC,gBAAgB,EAAE,GAAG,EAAE,+BAA+B,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;QAC3B,MAAM,IAAI,WAAW,CACnB,oBAAoB,EACpB,GAAG,EACH,6DAA6D,CAC9D,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC;SAC1F,GAAG,CAAC,QAAQ,CAAC;SACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACb,MAAM,OAAO,GAAG,QAAQ,IAAI,EAAE,CAAC;IAE/B,IAAI,MAKH,CAAC;IACF,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;YACjC,OAAO;YACP,OAAO,EAAE,OAAO;YAChB,SAAS,EAAE,MAAM;YACjB,QAAQ,EAAE,MAAM;YAChB,cAAc,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;SAChC,CAAC,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACpD,MAAM,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,CAAC;IACpC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,WAAW,CACnB,oBAAoB,EACpB,GAAG,EACH,aAAa,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACtE,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;QAC7B,MAAM,IAAI,WAAW,CAAC,eAAe,EAAE,GAAG,EAAE,gBAAgB,CAAC,CAAC;IAChE,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,MAAM,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACjF,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,MAAM,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACjF,MAAM,MAAM,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC;QACvC,MAAM,IAAI,WAAW,CACnB,eAAe,EACf,GAAG,EACH,iBACE,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GACxE,EAAE,GAAG,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CACrC,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC;QACvE,MAAM,OAAO,GAAG,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QAC9E,MAAM,IAAI,WAAW,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,IAAI,WAAW,CAAC,eAAe,EAAE,GAAG,EAAE,oBAAoB,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAeD,MAAM,sBAAsB,GAAG,KAAK,CAAC;AACrC,MAAM,sBAAsB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAChD,MAAM,2BAA2B,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,QAAgB,EAChB,QAAgB,EAChB,UAA6B,EAAE;IAE/B,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC7D,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,WAAW,CAAC,eAAe,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvC,IAAI,CAAC,CAAC,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,WAAW,CAAC,eAAe,EAAE,GAAG,EAAE,+BAA+B,CAAC,CAAC;IAC/E,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,MAAM,IAAI,WAAW,CAAC,UAAU,EAAE,GAAG,EAAE,8BAA8B,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CACvB,CAAC,EACD,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,IAAI,sBAAsB,CAAC,CAAC,CACxE,CAAC;IACF,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CACvB,EAAE,GAAG,IAAI,EACT,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,IAAI,sBAAsB,CAAC,CAAC,CACpF,CAAC;IACF,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAC3B,CAAC,GAAG,IAAI,EACR,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,IAAI,2BAA2B,CAAC,CAAC,CAC5F,CAAC;IAEF,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,SACd,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE;QAC1D,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAC7C,gCAAgC,CAAC;IACjC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO;YACL,KAAK;YACL,IAAI,EAAE,MAAM;YACZ,YAAY,EAAE,KAAK;YACnB,OAAO,EAAE,EAAE;YACX,KAAK,EAAE,CAAC;YACR,SAAS,EAAE,KAAK;YAChB,OAAO,EAAE,8BAA8B;SACxC,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IAClC,MAAM,OAAO,GAAgB,EAAE,CAAC;IAChC,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,SAAS,GAAG,SAAS,CAAC,MAAM,GAAG,QAAQ,CAAC;IAC5C,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC9E,gFAAgF;IAChF,2EAA2E;IAC3E,MAAM,QAAQ,GAAG,oGAAoG,CAAC;IACtH,MAAM,OAAO,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC3C,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvC,OAAO,KAAK,GAAG,KAAK,IAAI,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC;QAClD,IAAI,OAAO,CAAC,MAAM,IAAI,KAAK,EAAE,CAAC;YAC5B,SAAS,GAAG,IAAI,CAAC;YACjB,MAAM;QACR,CAAC;QACD,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,SAAS;QACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACjD,8EAA8E;QAC9E,2EAA2E;QAC3E,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,SAAS;QAE3C,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,IAAI,GAAG,YAAY,EAAE,CAAC;YACxB,SAAS,GAAG,IAAI,CAAC;YACjB,SAAS;QACX,CAAC;QACD,IAAI,SAAS,GAAG,IAAI,GAAG,QAAQ,EAAE,CAAC;YAChC,SAAS,GAAG,IAAI,CAAC;YACjB,MAAM;QACR,CAAC;QAED,IAAI,MAAc,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,SAAS,IAAI,MAAM,CAAC,MAAM,CAAC;QAC3B,gFAAgF;QAChF,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,SAAS;QAEnD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,SAAS;QAClD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAClC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;YACrD,IAAI,OAAO,CAAC,MAAM,IAAI,KAAK,EAAE,CAAC;gBAC5B,SAAS,GAAG,IAAI,CAAC;gBACjB,MAAM;YACR,CAAC;YACD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,SAAS;YAClD,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,KAAK;gBACxC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;gBACtC,SAAS,EAAE,KAAK,GAAG,CAAC;gBACpB,OAAO,EAAE,KAAK,GAAG,CAAC;aACnB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK;QACL,IAAI,EAAE,MAAM;QACZ,YAAY,EAAE,KAAK;QACnB,OAAO;QACP,KAAK,EAAE,OAAO,CAAC,MAAM;QACrB,SAAS;QACT,OAAO;KACR,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,MAAM,cAAc,GAAiC,IAAI,GAAG,CAAC;IAC3D,oBAAoB;IACpB,eAAe;IACf,YAAY;IACZ,gBAAgB;CACjB,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,QAAgB,EAChB,QAAgB,EAChB,OAA4B;IAE5B,IAAI,CAAC;QACH,OAAO,MAAM,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC5D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,WAAW,IAAI,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACnE,OAAO,UAAU,CAAC,QAAQ,EAAE,QAAQ,EAAE;gBACpC,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,MAAM,EAAE,KAAK,CAAC,OAAO;aACtB,CAAC,CAAC;QACL,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
package/lib/index.d.ts CHANGED
@@ -1,22 +1,23 @@
1
1
  /**
2
- * dsh-gitnexus-plus 鈥?host half.
2
+ * dsh-gitnexus-plus —host half.
3
3
  *
4
4
  * Registers one read-only tool and one loopback HTTP route over the coverage
5
- * engine in `./gitnexus/`. Both answer the same question 鈥?"what did this index
6
- * leave out?" 鈥?for the two callers that need it: the model, and a browser half
5
+ * engine in `./gitnexus/`. Both answer the same question —"what did this index
6
+ * leave out?" —for the two callers that need it: the model, and a browser half
7
7
  * that will render it.
8
8
  *
9
- * Deliberately read-only. Nothing here triggers `analyze`, writes to `.gitnexus/`,
10
- * or takes the index lock; driving updates is a separate concern with its own
11
- * safety story, and mixing the two would mean a status check could start a
12
- * 30-second rebuild.
9
+ * The read path is still never allowed to change anything: a status check must not
10
+ * start a 30-second rebuild. The two mutating capabilities are separate routes with
11
+ * their own guards, and both go through the host's shell executor rather than
12
+ * spawning from the read path. Keyword search is a third read-only route over the
13
+ * graph itself; the tool above stays read-only.
13
14
  */
14
15
  import type { Context } from '@deepseek-ai/cordis';
15
16
  import { type MaintenanceCommand } from './gitnexus/commands.js';
16
17
  export declare const name = "gitnexus-plus";
17
18
  /**
18
19
  * Only `tools` is required. `webServer` is reached through `ctx.inject` because a
19
- * headless profile has no HTTP carrier at all 鈥?naming it here would leave the
20
+ * headless profile has no HTTP carrier at all —naming it here would leave the
20
21
  * plugin permanently pending there, and the tool would never register.
21
22
  */
22
23
  export declare const inject: readonly ["tools"];
@@ -26,10 +27,16 @@ export interface PluginConfig {
26
27
  minifiedSampleLimit: number;
27
28
  serveRoutes: boolean;
28
29
  /**
29
- * Shapes the maintenance commands the panel hands to the operator. The defaults
30
- * are the values measured to work inside a confined DSH process; a deployment
31
- * that is not sandboxed can turn them off to get keyword search and a
32
- * self-managed heap.
30
+ * Shapes the maintenance commands the panel hands to the operator. `memoryOff`
31
+ * defaults on because the CLI otherwise re-spawns itself over piped stdio, which
32
+ * a confined process denies.
33
+ *
34
+ * `skipFts: false` (default) means **automatic**: the plugin skips FTS when the
35
+ * index does not have it, and omits the flag when it does. Both static values were
36
+ * measured wrong on a real machine: `true` forces a 21x full rebuild on an FTS
37
+ * index, while an unconditional `false` makes a refresh on a non-FTS index die
38
+ * with `spawn EPERM` when the extension tries to install out-of-process. `true`
39
+ * here forces the flag for an operator who needs it.
33
40
  */
34
41
  skipFts: boolean;
35
42
  memoryOff: boolean;
@@ -50,6 +57,12 @@ export interface PluginConfig {
50
57
  gitnexusInstallDir: string;
51
58
  /** Wall-clock ceiling for that install. Compiling native modules is slow. */
52
59
  installTimeoutMs: number;
60
+ /**
61
+ * Redirect for GitNexus's global registry write. Empty means
62
+ * `<project>/.gitnexus/plugin-home`, inside the workspace. The global
63
+ * `~/.gitnexus` is outside a confined host's writable scope, and a refresh that
64
+ * dies on the registry write leaves the index incomplete.
65
+ */
53
66
  gitnexusHome: string;
54
67
  }
55
68
  export declare const Config: import("@deepseek-ai/schemastery").SchemaLike<{
@@ -68,10 +81,16 @@ export declare const Config: import("@deepseek-ai/schemastery").SchemaLike<{
68
81
  export interface MaintenanceInfo {
69
82
  commands: MaintenanceCommand[];
70
83
  rationale: string[];
84
+ /**
85
+ * Whether the mounted analyze route understands `force=1`. The client uses it to
86
+ * avoid offering a full-rebuild button against an older host, where that flag was
87
+ * ignored and the button would silently run an incremental refresh instead.
88
+ */
89
+ supportsForce: true;
71
90
  }
72
91
  export declare function apply(ctx: Context, config: PluginConfig): void;
73
92
  /**
74
- * Human-readable rendering lives in `./gitnexus/format.js` 鈥?free of DSH imports
93
+ * Human-readable rendering lives in `./gitnexus/format.js` —free of DSH imports
75
94
  * so it can be tested without a host. Re-exported here for callers that already
76
95
  * hold the plugin module.
77
96
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAKnD,OAAO,EAAyC,KAAK,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAqBxG,eAAO,MAAM,IAAI,kBAAkB,CAAC;AAEpC;;;;GAIG;AACH,eAAO,MAAM,MAAM,oBAAqB,CAAC;AAEzC,2EAA2E;AAC3E,MAAM,WAAW,YAAY;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,6EAA6E;IAC7E,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,MAAM;;;;;;;;;;;EAWjB,CAAC;AAqBH,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAiBD,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAuJ9D;AAED;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAKnD,OAAO,EAKL,KAAK,kBAAkB,EACxB,MAAM,wBAAwB,CAAC;AAuBhC,eAAO,MAAM,IAAI,kBAAkB,CAAC;AAEpC;;;;GAIG;AACH,eAAO,MAAM,MAAM,oBAAqB,CAAC;AAEzC,2EAA2E;AAC3E,MAAM,WAAW,YAAY;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;OAWG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,6EAA6E;IAC7E,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,MAAM;;;;;;;;;;;EAWjB,CAAC;AAqBH,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB;;;;OAIG;IACH,aAAa,EAAE,IAAI,CAAC;CACrB;AAiBD,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CA+L9D;AAED;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC"}