grepmax 0.16.0 → 0.16.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.
@@ -368,7 +368,8 @@ exports.search = new commander_1.Command("search")
368
368
  .option("--skeleton", "Show code skeleton for matching files instead of snippets", false)
369
369
  .option("--root <dir>", "Search a different project directory")
370
370
  .option("--file <name>", "Filter to files matching this name (e.g. 'syncer.ts')")
371
- .option("--exclude <prefix>", "Exclude files under this path prefix (e.g. 'tests/')")
371
+ .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable; comma-separated also accepted)", (value, prev) => (prev ? [...prev, value] : [value]))
372
+ .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable; e.g. 'tests/')", (value, prev) => (prev ? [...prev, value] : [value]))
372
373
  .option("--lang <ext>", "Filter by file extension (e.g. 'ts', 'py')")
373
374
  .option("--role <role>", "Filter by role: ORCHESTRATION, DEFINITION, IMPLEMENTATION")
374
375
  .option("--symbol", "Append call graph after search results", false)
@@ -388,7 +389,7 @@ Examples:
388
389
  gmax "handler" --name "handle.*" --exclude tests/
389
390
  `)
390
391
  .action((pattern, exec_path, _options, cmd) => __awaiter(void 0, void 0, void 0, function* () {
391
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10;
392
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9;
392
393
  const options = cmd.optsWithGlobals();
393
394
  const root = process.cwd();
394
395
  const minScore = Number.isFinite(Number.parseFloat(options.minScore))
@@ -496,9 +497,16 @@ Examples:
496
497
  // Propagate project root to worker processes
497
498
  process.env.GMAX_PROJECT_ROOT = projectRoot;
498
499
  // Check if project is registered
499
- const checkRoot = options.root
500
- ? (_c = (0, project_root_1.findProjectRoot)(path.resolve(options.root))) !== null && _c !== void 0 ? _c : path.resolve(options.root)
501
- : projectRoot;
500
+ let checkRoot;
501
+ if (options.root) {
502
+ const resolved = (0, project_registry_1.resolveRootOrExit)(options.root);
503
+ if (resolved === null)
504
+ return;
505
+ checkRoot = (_c = (0, project_root_1.findProjectRoot)(resolved)) !== null && _c !== void 0 ? _c : resolved;
506
+ }
507
+ else {
508
+ checkRoot = projectRoot;
509
+ }
502
510
  const project = (0, project_registry_1.getProject)(checkRoot);
503
511
  if (!project) {
504
512
  console.error(`This project hasn't been added to gmax yet.\n\nRun: gmax add ${checkRoot}\n`);
@@ -509,25 +517,40 @@ Examples:
509
517
  console.warn("This project is still being indexed. Results may be incomplete.\n");
510
518
  }
511
519
  // Compute effective paths + filters early — both the daemon-mediated
512
- // and in-process search paths need them.
513
- const effectiveRoot = options.root
514
- ? (_d = (0, project_root_1.findProjectRoot)(path.resolve(options.root))) !== null && _d !== void 0 ? _d : path.resolve(options.root)
515
- : projectRoot;
516
- const searchPathPrefix = exec_path
517
- ? path.resolve(exec_path)
518
- : effectiveRoot;
519
- const pathFilter = searchPathPrefix.endsWith("/")
520
- ? searchPathPrefix
521
- : `${searchPathPrefix}/`;
520
+ // and in-process search paths need them. Reuse the resolved checkRoot
521
+ // so --root <name> only resolves once per invocation.
522
+ const effectiveRoot = checkRoot;
523
+ // --in / --exclude / [path positional] composition. --in wins over the
524
+ // positional [path] when both are given (positional was the older
525
+ // shape; --in is canonical going forward).
526
+ if (exec_path && options.in && options.in.length > 0) {
527
+ console.warn("Warning: --in overrides positional [path]; using --in.");
528
+ }
529
+ const { resolveScope } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
530
+ const scope = resolveScope({
531
+ projectRoot: effectiveRoot,
532
+ in: options.in,
533
+ exclude: options.exclude,
534
+ });
535
+ const pathFilter = options.in && options.in.length > 0
536
+ ? scope.pathPrefix
537
+ : exec_path
538
+ ? (() => {
539
+ const p = path.resolve(exec_path);
540
+ return p.endsWith("/") ? p : `${p}/`;
541
+ })()
542
+ : scope.pathPrefix;
522
543
  const searchFilters = {};
523
544
  if (options.file)
524
545
  searchFilters.file = options.file;
525
- if (options.exclude)
526
- searchFilters.exclude = options.exclude;
527
546
  if (options.lang)
528
547
  searchFilters.language = options.lang;
529
548
  if (options.role)
530
549
  searchFilters.role = options.role;
550
+ if (scope.inPrefixes.length > 0)
551
+ searchFilters.inPrefixes = scope.inPrefixes;
552
+ if (scope.excludePrefixes.length > 0)
553
+ searchFilters.excludePrefixes = scope.excludePrefixes;
531
554
  // Daemon-mediated search: ships query+args over IPC, daemon runs the
532
555
  // hybrid+rerank against its already-warm VectorDB and worker pool.
533
556
  // Drops cold-start cost (~17s wall, 6GB RAM in the CLI) to <1s. Falls
@@ -562,7 +585,7 @@ Examples:
562
585
  precomputedGraph = resp.graph;
563
586
  }
564
587
  else if (process.env.GMAX_DEBUG === "1") {
565
- console.error(`[search] daemon path unavailable: ${(_e = resp.error) !== null && _e !== void 0 ? _e : "unknown"}`);
588
+ console.error(`[search] daemon path unavailable: ${(_d = resp.error) !== null && _d !== void 0 ? _d : "unknown"}`);
566
589
  }
567
590
  }
568
591
  }
@@ -637,7 +660,7 @@ Examples:
637
660
  }
638
661
  }
639
662
  // Ensure a watcher is running for live reindexing
640
- if (!process.env.VITEST && !((_f = process.env.NODE_ENV) === null || _f === void 0 ? void 0 : _f.includes("test"))) {
663
+ if (!process.env.VITEST && !((_e = process.env.NODE_ENV) === null || _e === void 0 ? void 0 : _e.includes("test"))) {
641
664
  const { launchWatcher } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/watcher-launcher")));
642
665
  const launched = yield launchWatcher(projectRoot);
643
666
  if (!launched.ok && launched.reason === "spawn-failed") {
@@ -645,9 +668,11 @@ Examples:
645
668
  }
646
669
  }
647
670
  const searcher = new searcher_1.Searcher(vectorDb);
648
- searchResult = yield searcher.search(pattern, parseInt(options.m, 10), { rerank: true, explain: options.explain }, Object.keys(searchFilters).length > 0 ? searchFilters : undefined, pathFilter);
671
+ searchResult = yield searcher.search(pattern, parseInt(options.m, 10), { rerank: true, explain: options.explain }, Object.keys(searchFilters).length > 0
672
+ ? searchFilters
673
+ : undefined, pathFilter);
649
674
  } // end if (!searchResult) — in-process fallback
650
- if (!options.agent && ((_g = searchResult.warnings) === null || _g === void 0 ? void 0 : _g.length)) {
675
+ if (!options.agent && ((_f = searchResult.warnings) === null || _f === void 0 ? void 0 : _f.length)) {
651
676
  for (const w of searchResult.warnings) {
652
677
  console.warn(`Warning: ${w}`);
653
678
  }
@@ -664,7 +689,7 @@ Examples:
664
689
  return defs.some((d) => regex.test(d));
665
690
  });
666
691
  }
667
- catch (_11) {
692
+ catch (_10) {
668
693
  // Invalid regex — skip
669
694
  }
670
695
  }
@@ -690,16 +715,16 @@ Examples:
690
715
  // In agent mode, print imports header per file
691
716
  const seenImportFiles = new Set();
692
717
  for (const r of filteredData) {
693
- const absP = (_k = (_h = r.path) !== null && _h !== void 0 ? _h : (_j = r.metadata) === null || _j === void 0 ? void 0 : _j.path) !== null && _k !== void 0 ? _k : "";
718
+ const absP = (_j = (_g = r.path) !== null && _g !== void 0 ? _g : (_h = r.metadata) === null || _h === void 0 ? void 0 : _h.path) !== null && _j !== void 0 ? _j : "";
694
719
  const relPath = absP.startsWith(effectiveRoot)
695
720
  ? absP.slice(effectiveRoot.length + 1)
696
721
  : absP;
697
- const startLine = Math.max(1, ((_p = (_m = (_l = r.startLine) !== null && _l !== void 0 ? _l : r.start_line) !== null && _m !== void 0 ? _m : (_o = r.generated_metadata) === null || _o === void 0 ? void 0 : _o.start_line) !== null && _p !== void 0 ? _p : 0) + 1);
722
+ const startLine = Math.max(1, ((_o = (_l = (_k = r.startLine) !== null && _k !== void 0 ? _k : r.start_line) !== null && _l !== void 0 ? _l : (_m = r.generated_metadata) === null || _m === void 0 ? void 0 : _m.start_line) !== null && _o !== void 0 ? _o : 0) + 1);
698
723
  const defs = Array.isArray(r.defined_symbols)
699
724
  ? r.defined_symbols
700
725
  : [];
701
726
  const symbol = defs[0] || "";
702
- const role = ((_q = r.role) !== null && _q !== void 0 ? _q : "")
727
+ const role = ((_p = r.role) !== null && _p !== void 0 ? _p : "")
703
728
  .slice(0, 4)
704
729
  .toUpperCase();
705
730
  let hint = "";
@@ -708,7 +733,7 @@ Examples:
708
733
  }
709
734
  else {
710
735
  // Extract first meaningful signature line from content
711
- const raw = (_s = (_r = r.content) !== null && _r !== void 0 ? _r : r.text) !== null && _s !== void 0 ? _s : "";
736
+ const raw = (_r = (_q = r.content) !== null && _q !== void 0 ? _q : r.text) !== null && _r !== void 0 ? _r : "";
712
737
  const lines = raw.split("\n");
713
738
  for (const line of lines) {
714
739
  const trimmed = line.trim();
@@ -743,10 +768,12 @@ Examples:
743
768
  }
744
769
  const sym = symbol ? ` ${symbol}` : "";
745
770
  const rl = role ? ` [${role}]` : "";
771
+ const score = r.score;
772
+ const scoreCol = typeof score === "number" ? `\ts=${score.toFixed(3)}` : "";
746
773
  const explainSuffix = options.explain && r.scoreBreakdown
747
774
  ? `\texplain:rerank=${r.scoreBreakdown.rerank.toFixed(3)},fused=${r.scoreBreakdown.fused.toFixed(3)},boost=${r.scoreBreakdown.boost.toFixed(2)}x,score=${r.scoreBreakdown.normalized.toFixed(3)}`
748
775
  : "";
749
- console.log(`${relPath}:${startLine}${sym}${rl}${hint}${explainSuffix}`);
776
+ console.log(`${relPath}:${startLine}${scoreCol}${sym}${rl}${hint}${explainSuffix}`);
750
777
  }
751
778
  }
752
779
  // Agent trace (compact)
@@ -778,7 +805,7 @@ Examples:
778
805
  }
779
806
  }
780
807
  }
781
- catch (_12) { }
808
+ catch (_11) { }
782
809
  }
783
810
  return;
784
811
  }
@@ -810,9 +837,9 @@ Examples:
810
837
  let shown = 0;
811
838
  console.log(resultCountHeader(filteredData, parseInt(options.m, 10)));
812
839
  for (const r of filteredData) {
813
- const absP = (_v = (_t = r.path) !== null && _t !== void 0 ? _t : (_u = r.metadata) === null || _u === void 0 ? void 0 : _u.path) !== null && _v !== void 0 ? _v : "";
814
- const startLine = (_z = (_x = (_w = r.startLine) !== null && _w !== void 0 ? _w : r.start_line) !== null && _x !== void 0 ? _x : (_y = r.generated_metadata) === null || _y === void 0 ? void 0 : _y.start_line) !== null && _z !== void 0 ? _z : 0;
815
- const endLine = (_3 = (_1 = (_0 = r.endLine) !== null && _0 !== void 0 ? _0 : r.end_line) !== null && _1 !== void 0 ? _1 : (_2 = r.generated_metadata) === null || _2 === void 0 ? void 0 : _2.end_line) !== null && _3 !== void 0 ? _3 : startLine;
840
+ const absP = (_u = (_s = r.path) !== null && _s !== void 0 ? _s : (_t = r.metadata) === null || _t === void 0 ? void 0 : _t.path) !== null && _u !== void 0 ? _u : "";
841
+ const startLine = (_y = (_w = (_v = r.startLine) !== null && _v !== void 0 ? _v : r.start_line) !== null && _w !== void 0 ? _w : (_x = r.generated_metadata) === null || _x === void 0 ? void 0 : _x.start_line) !== null && _y !== void 0 ? _y : 0;
842
+ const endLine = (_2 = (_0 = (_z = r.endLine) !== null && _z !== void 0 ? _z : r.end_line) !== null && _0 !== void 0 ? _0 : (_1 = r.generated_metadata) === null || _1 === void 0 ? void 0 : _1.end_line) !== null && _2 !== void 0 ? _2 : startLine;
816
843
  const relPath = absP.startsWith(projectRoot)
817
844
  ? absP.slice(projectRoot.length + 1)
818
845
  : absP;
@@ -845,7 +872,7 @@ Examples:
845
872
  tokensUsed += blobTokens;
846
873
  shown++;
847
874
  }
848
- catch (_13) {
875
+ catch (_12) {
849
876
  console.log(`\n--- ${relPath} (file not readable) ---`);
850
877
  shown++;
851
878
  }
@@ -862,7 +889,7 @@ Examples:
862
889
  if (options.imports) {
863
890
  const seenFiles = new Set();
864
891
  for (const r of filteredData) {
865
- const absP = (_6 = (_4 = r.path) !== null && _4 !== void 0 ? _4 : (_5 = r.metadata) === null || _5 === void 0 ? void 0 : _5.path) !== null && _6 !== void 0 ? _6 : "";
892
+ const absP = (_5 = (_3 = r.path) !== null && _3 !== void 0 ? _3 : (_4 = r.metadata) === null || _4 === void 0 ? void 0 : _4.path) !== null && _5 !== void 0 ? _5 : "";
866
893
  if (absP && !seenFiles.has(absP)) {
867
894
  seenFiles.add(absP);
868
895
  const imports = getImportsForFile(absP);
@@ -889,7 +916,7 @@ Examples:
889
916
  for (const r of filteredData) {
890
917
  const b = r.scoreBreakdown;
891
918
  if (b) {
892
- const absP = (_9 = (_7 = r.path) !== null && _7 !== void 0 ? _7 : (_8 = r.metadata) === null || _8 === void 0 ? void 0 : _8.path) !== null && _9 !== void 0 ? _9 : "";
919
+ const absP = (_8 = (_6 = r.path) !== null && _6 !== void 0 ? _6 : (_7 = r.metadata) === null || _7 === void 0 ? void 0 : _7.path) !== null && _8 !== void 0 ? _8 : "";
893
920
  const relPath = absP.startsWith(projectRoot)
894
921
  ? absP.slice(projectRoot.length + 1)
895
922
  : absP;
@@ -951,7 +978,7 @@ Examples:
951
978
  console.log(lines.join("\n"));
952
979
  }
953
980
  }
954
- catch (_14) {
981
+ catch (_13) {
955
982
  // Trace failed — skip silently
956
983
  }
957
984
  }
@@ -971,13 +998,13 @@ Examples:
971
998
  source: "cli",
972
999
  tool: "search",
973
1000
  query: pattern,
974
- project: (_10 = (0, project_root_1.findProjectRoot)(root)) !== null && _10 !== void 0 ? _10 : root,
1001
+ project: (_9 = (0, project_root_1.findProjectRoot)(root)) !== null && _9 !== void 0 ? _9 : root,
975
1002
  results: _searchResultCount,
976
1003
  ms: Date.now() - _searchStartMs,
977
1004
  error: _searchError,
978
1005
  });
979
1006
  }
980
- catch (_15) { }
1007
+ catch (_14) { }
981
1008
  if (vectorDb) {
982
1009
  try {
983
1010
  yield vectorDb.close();
@@ -48,6 +48,7 @@ const commander_1 = require("commander");
48
48
  const vector_db_1 = require("../lib/store/vector-db");
49
49
  const filter_builder_1 = require("../lib/utils/filter-builder");
50
50
  const exit_1 = require("../lib/utils/exit");
51
+ const project_registry_1 = require("../lib/utils/project-registry");
51
52
  const project_root_1 = require("../lib/utils/project-root");
52
53
  const arrow_1 = require("../lib/utils/arrow");
53
54
  exports.similar = new commander_1.Command("similar")
@@ -56,6 +57,8 @@ exports.similar = new commander_1.Command("similar")
56
57
  .option("-m, --max-count <n>", "Max results (default 5)", "5")
57
58
  .option("--threshold <score>", "Min similarity 0-1 (default 0)")
58
59
  .option("--root <dir>", "Project root directory")
60
+ .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
61
+ .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
59
62
  .option("--agent", "Compact output for AI agents", false)
60
63
  .action((target, opts) => __awaiter(void 0, void 0, void 0, function* () {
61
64
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
@@ -63,7 +66,9 @@ exports.similar = new commander_1.Command("similar")
63
66
  const threshold = Number.parseFloat(opts.threshold || "0") || 0;
64
67
  let vectorDb = null;
65
68
  try {
66
- const root = opts.root ? path.resolve(opts.root) : process.cwd();
69
+ const root = (0, project_registry_1.resolveRootOrExit)(opts.root);
70
+ if (root === null)
71
+ return;
67
72
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
68
73
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
69
74
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
@@ -106,7 +111,13 @@ exports.similar = new commander_1.Command("similar")
106
111
  return;
107
112
  }
108
113
  // Vector search using the source chunk's embedding
109
- const pathScope = `path LIKE '${(0, filter_builder_1.escapeSqlString)(projectRoot)}/%'`;
114
+ const { resolveScope, buildScopeWhere } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
115
+ const scope = resolveScope({
116
+ projectRoot,
117
+ in: opts.in,
118
+ exclude: opts.exclude,
119
+ });
120
+ const pathScope = buildScopeWhere(scope);
110
121
  const results = yield table
111
122
  .vectorSearch(sourceVector)
112
123
  .select([
@@ -61,7 +61,6 @@ const setup_helpers_1 = require("../lib/setup/setup-helpers");
61
61
  const retriever_1 = require("../lib/skeleton/retriever");
62
62
  const skeletonizer_1 = require("../lib/skeleton/skeletonizer");
63
63
  const vector_db_1 = require("../lib/store/vector-db");
64
- const file_utils_1 = require("../lib/utils/file-utils");
65
64
  const exit_1 = require("../lib/utils/exit");
66
65
  const project_registry_1 = require("../lib/utils/project-registry");
67
66
  const project_root_1 = require("../lib/utils/project-root");
@@ -156,27 +155,18 @@ Examples:
156
155
  };
157
156
  // Determine mode based on target
158
157
  const resolvedTarget = path.resolve(target);
159
- // Directory mode
158
+ // Directory mode is unsupported. Auto-picking files from a directory
159
+ // was confusingly magical (and on '.' it fell through to the resolver
160
+ // path and skeletonized .gitignore). Refuse and point at the file form.
160
161
  if (fs.existsSync(resolvedTarget) &&
161
162
  fs.statSync(resolvedTarget).isDirectory()) {
162
- const entries = fs.readdirSync(resolvedTarget, {
163
- withFileTypes: true,
164
- });
165
- const files = entries
166
- .filter((e) => e.isFile() &&
167
- (0, file_utils_1.isIndexableFile)(path.join(resolvedTarget, e.name)))
168
- .map((e) => path.join(resolvedTarget, e.name))
169
- .slice(0, Number.parseInt(options.limit, 10));
170
- if (files.length === 0) {
171
- console.error(`No indexable files in ${target}`);
172
- process.exitCode = 1;
173
- return;
174
- }
175
- for (const filePath of files) {
176
- const content = fs.readFileSync(filePath, "utf-8");
177
- const result = yield skeletonizer.skeletonizeFile(filePath, content, skeletonOpts);
178
- outputResult(result, options);
179
- }
163
+ console.error([
164
+ "skeleton expects a file or symbol, not a directory.",
165
+ "Try:",
166
+ " gmax skeleton src/foo.ts # one file's structure",
167
+ ' gmax search "<topic>" --agent # find relevant files first',
168
+ ].join("\n"));
169
+ process.exitCode = 1;
180
170
  return;
181
171
  }
182
172
  // Batch mode (comma-separated)
@@ -48,6 +48,7 @@ const commander_1 = require("commander");
48
48
  const vector_db_1 = require("../lib/store/vector-db");
49
49
  const exit_1 = require("../lib/utils/exit");
50
50
  const filter_builder_1 = require("../lib/utils/filter-builder");
51
+ const project_registry_1 = require("../lib/utils/project-registry");
51
52
  const project_root_1 = require("../lib/utils/project-root");
52
53
  const style = {
53
54
  bold: (s) => `\x1b[1m${s}\x1b[22m`,
@@ -174,7 +175,9 @@ exports.symbols = new commander_1.Command("symbols")
174
175
  .option("--agent", "Compact output for AI agents", false)
175
176
  .action((pattern, cmd) => __awaiter(void 0, void 0, void 0, function* () {
176
177
  var _a, _b;
177
- const root = cmd.root ? path.resolve(cmd.root) : process.cwd();
178
+ const root = (0, project_registry_1.resolveRootOrExit)(cmd.root);
179
+ if (root === null)
180
+ return;
178
181
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
179
182
  const limit = Number.parseInt(cmd.limit, 10);
180
183
  // Auto-scope to project root; --path narrows further within it
@@ -43,24 +43,28 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.testFind = void 0;
46
- const path = __importStar(require("node:path"));
47
46
  const commander_1 = require("commander");
48
47
  const impact_1 = require("../lib/graph/impact");
49
48
  const vector_db_1 = require("../lib/store/vector-db");
50
49
  const exit_1 = require("../lib/utils/exit");
50
+ const project_registry_1 = require("../lib/utils/project-registry");
51
51
  const project_root_1 = require("../lib/utils/project-root");
52
52
  exports.testFind = new commander_1.Command("test")
53
53
  .description("Find tests that exercise a symbol or file")
54
54
  .argument("<target>", "Symbol name or file path")
55
55
  .option("-d, --depth <n>", "Caller traversal depth (default 1, max 3)", "1")
56
56
  .option("--root <dir>", "Project root directory")
57
+ .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
58
+ .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
57
59
  .option("--agent", "Compact output for AI agents", false)
58
60
  .action((target, opts) => __awaiter(void 0, void 0, void 0, function* () {
59
61
  var _a;
60
62
  const depth = Math.min(Math.max(Number.parseInt(opts.depth || "1", 10), 1), 3);
61
63
  let vectorDb = null;
62
64
  try {
63
- const root = opts.root ? path.resolve(opts.root) : process.cwd();
65
+ const root = (0, project_registry_1.resolveRootOrExit)(opts.root);
66
+ if (root === null)
67
+ return;
64
68
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
65
69
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
66
70
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
@@ -72,7 +76,16 @@ exports.testFind = new commander_1.Command("test")
72
76
  process.exitCode = 1;
73
77
  return;
74
78
  }
75
- const tests = yield (0, impact_1.findTests)(symbols, vectorDb, projectRoot, depth);
79
+ const { resolveScope } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
80
+ const scope = resolveScope({
81
+ projectRoot,
82
+ in: opts.in,
83
+ exclude: opts.exclude,
84
+ });
85
+ const queryRoot = opts.in && opts.in.length > 0
86
+ ? scope.pathPrefix.replace(/\/$/, "")
87
+ : projectRoot;
88
+ const tests = yield (0, impact_1.findTests)(symbols, vectorDb, queryRoot, depth, scope.excludePrefixes);
76
89
  if (tests.length === 0) {
77
90
  console.log(`No tests found for ${target}.`);
78
91
  return;
@@ -43,12 +43,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.trace = void 0;
46
- const path = __importStar(require("node:path"));
47
46
  const commander_1 = require("commander");
48
47
  const graph_builder_1 = require("../lib/graph/graph-builder");
49
48
  const formatter_1 = require("../lib/output/formatter");
50
49
  const vector_db_1 = require("../lib/store/vector-db");
51
50
  const exit_1 = require("../lib/utils/exit");
51
+ const project_registry_1 = require("../lib/utils/project-registry");
52
52
  const project_root_1 = require("../lib/utils/project-root");
53
53
  function formatTraceAgent(graph, projectRoot) {
54
54
  if (!graph.center)
@@ -78,17 +78,27 @@ exports.trace = new commander_1.Command("trace")
78
78
  .argument("<symbol>", "The symbol to trace")
79
79
  .option("-d, --depth <n>", "Caller traversal depth (default 1, max 3)", "1")
80
80
  .option("--root <dir>", "Project root directory")
81
+ .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
82
+ .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
81
83
  .option("--agent", "Compact output for AI agents", false)
82
84
  .action((symbol, opts) => __awaiter(void 0, void 0, void 0, function* () {
83
85
  var _a;
84
86
  const depth = Math.min(Math.max(Number.parseInt(opts.depth || "1", 10), 1), 3);
85
- const root = opts.root ? path.resolve(opts.root) : process.cwd();
87
+ const root = (0, project_registry_1.resolveRootOrExit)(opts.root);
88
+ if (root === null)
89
+ return;
86
90
  let vectorDb = null;
87
91
  try {
88
92
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
89
93
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
90
94
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
91
- const graphBuilder = new graph_builder_1.GraphBuilder(vectorDb, projectRoot);
95
+ const { resolveScope } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
96
+ const scope = resolveScope({
97
+ projectRoot,
98
+ in: opts.in,
99
+ exclude: opts.exclude,
100
+ });
101
+ const graphBuilder = new graph_builder_1.GraphBuilder(vectorDb, scope.pathPrefix, scope.excludePrefixes);
92
102
  const graph = yield graphBuilder.buildGraphMultiHop(symbol, depth);
93
103
  if (opts.agent) {
94
104
  console.log(formatTraceAgent(graph, projectRoot));
@@ -397,8 +397,22 @@ class Daemon {
397
397
  },
398
398
  });
399
399
  this.processors.set(root, processor);
400
- // Subscribe with @parcel/watcher — native backend, no polling
401
- yield this.subscribeWatcher(root, processor);
400
+ // Subscribe with @parcel/watcher — native backend, no polling.
401
+ // If the kernel refuses (e.g. FSEvents slots stuck after a prior kill -9
402
+ // — see docs/known-limitations.md), fall straight through to poll mode.
403
+ // The retry/backoff path inside recoverWatcher is for transient overflows,
404
+ // not hard kernel-level subscribe failures, so we skip it on startup by
405
+ // priming failCount past MAX before invoking it.
406
+ try {
407
+ yield this.subscribeWatcher(root, processor);
408
+ }
409
+ catch (err) {
410
+ const name = path.basename(root);
411
+ console.error(`[daemon:${name}] Subscribe failed at startup (${err instanceof Error ? err.message : err}) — switching to poll mode`);
412
+ this.watcherFailCount.set(root, 1000); // > MAX_WATCHER_RETRIES
413
+ this.lastOverflowMs.set(root, Date.now());
414
+ this.recoverWatcher(root, processor);
415
+ }
402
416
  (0, watcher_store_1.registerWatcher)({
403
417
  pid: process.pid,
404
418
  projectRoot: root,
@@ -135,6 +135,10 @@ function handleCommand(daemon, cmd, conn) {
135
135
  try {
136
136
  const limitRaw = typeof cmd.limit === "number" ? cmd.limit : 10;
137
137
  const skeletonLimitRaw = typeof cmd.skeletonLimit === "number" ? cmd.skeletonLimit : undefined;
138
+ // Accept both the legacy single-string `exclude` and the new
139
+ // `excludePrefixes`/`inPrefixes` arrays (--in/--exclude on the CLI).
140
+ // Daemon may outlive a CLI restart, so keep both wire shapes for
141
+ // one release; drop the single-string form in v0.17.x.
138
142
  const filters = cmd.filters && typeof cmd.filters === "object" && !Array.isArray(cmd.filters)
139
143
  ? cmd.filters
140
144
  : undefined;
@@ -12,15 +12,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.GraphBuilder = void 0;
13
13
  const filter_builder_1 = require("../utils/filter-builder");
14
14
  class GraphBuilder {
15
- constructor(db, pathPrefix) {
15
+ constructor(db, pathPrefix, excludePrefixes) {
16
16
  this.db = db;
17
17
  // Normalize to ensure trailing slash for LIKE queries
18
18
  this.pathPrefix = pathPrefix ? (pathPrefix.endsWith("/") ? pathPrefix : `${pathPrefix}/`) : undefined;
19
+ this.excludePrefixes = (excludePrefixes !== null && excludePrefixes !== void 0 ? excludePrefixes : []).map((p) => p.endsWith("/") ? p : `${p}/`);
19
20
  }
20
21
  scopeWhere(condition) {
21
- if (!this.pathPrefix)
22
- return condition;
23
- return `${condition} AND path LIKE '${(0, filter_builder_1.escapeSqlString)(this.pathPrefix)}%'`;
22
+ let result = condition;
23
+ if (this.pathPrefix) {
24
+ result = `${result} AND path LIKE '${(0, filter_builder_1.escapeSqlString)(this.pathPrefix)}%'`;
25
+ }
26
+ for (const ex of this.excludePrefixes) {
27
+ result = `${result} AND path NOT LIKE '${(0, filter_builder_1.escapeSqlString)(ex)}%'`;
28
+ }
29
+ return result;
24
30
  }
25
31
  /**
26
32
  * Find all chunks that call the given symbol.
@@ -57,17 +57,22 @@ function resolveTargetSymbols(target, vectorDb, projectRoot) {
57
57
  * This catches cases where tests call methods of a class rather than the class name itself
58
58
  * (e.g., Swift tests call `handleNotification()` rather than referencing `DeepLinkRouter`).
59
59
  */
60
- function expandFileSymbols(symbols, vectorDb, projectRoot) {
60
+ function expandFileSymbols(symbols, vectorDb, projectRoot, excludePrefixes) {
61
61
  return __awaiter(this, void 0, void 0, function* () {
62
62
  if (symbols.length !== 1)
63
63
  return symbols;
64
64
  const table = yield vectorDb.ensureTable();
65
65
  const prefix = projectRoot.endsWith("/") ? projectRoot : `${projectRoot}/`;
66
+ let where = `array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbols[0])}') AND path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`;
67
+ for (const ex of excludePrefixes !== null && excludePrefixes !== void 0 ? excludePrefixes : []) {
68
+ const exNorm = ex.endsWith("/") ? ex : `${ex}/`;
69
+ where += ` AND path NOT LIKE '${(0, filter_builder_1.escapeSqlString)(exNorm)}%'`;
70
+ }
66
71
  // Find the file that defines this symbol
67
72
  const defRows = yield table
68
73
  .query()
69
74
  .select(["path"])
70
- .where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbols[0])}') AND path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`)
75
+ .where(where)
71
76
  .limit(1)
72
77
  .toArray();
73
78
  if (defRows.length === 0)
@@ -92,11 +97,11 @@ function expandFileSymbols(symbols, vectorDb, projectRoot) {
92
97
  * Find test files that exercise a set of symbols, using reverse call graph traversal.
93
98
  */
94
99
  function findTests(symbols_1, vectorDb_1, projectRoot_1) {
95
- return __awaiter(this, arguments, void 0, function* (symbols, vectorDb, projectRoot, depth = 1) {
96
- const graphBuilder = new graph_builder_1.GraphBuilder(vectorDb, projectRoot);
100
+ return __awaiter(this, arguments, void 0, function* (symbols, vectorDb, projectRoot, depth = 1, excludePrefixes) {
101
+ const graphBuilder = new graph_builder_1.GraphBuilder(vectorDb, projectRoot, excludePrefixes);
97
102
  const testHits = new Map(); // key: file+symbol
98
103
  // Expand single-symbol targets to include all symbols from the same file
99
- const expanded = yield expandFileSymbols(symbols, vectorDb, projectRoot);
104
+ const expanded = yield expandFileSymbols(symbols, vectorDb, projectRoot, excludePrefixes);
100
105
  for (const symbol of expanded) {
101
106
  yield walkCallers(symbol, graphBuilder, testHits, 0, depth, new Set());
102
107
  }
@@ -133,9 +138,13 @@ function walkCallers(symbol, graphBuilder, testHits, currentHop, maxDepth, visit
133
138
  * Returns files sorted by number of shared symbols (descending).
134
139
  */
135
140
  function findDependents(symbols_1, vectorDb_1, projectRoot_1, excludePaths_1) {
136
- return __awaiter(this, arguments, void 0, function* (symbols, vectorDb, projectRoot, excludePaths, limit = 10) {
141
+ return __awaiter(this, arguments, void 0, function* (symbols, vectorDb, projectRoot, excludePaths, limit = 10, excludePrefixes) {
137
142
  const table = yield vectorDb.ensureTable();
138
- const pathScope = `path LIKE '${(0, filter_builder_1.escapeSqlString)(projectRoot)}/%'`;
143
+ let pathScope = `path LIKE '${(0, filter_builder_1.escapeSqlString)(projectRoot)}/%'`;
144
+ for (const ex of excludePrefixes !== null && excludePrefixes !== void 0 ? excludePrefixes : []) {
145
+ const exNorm = ex.endsWith("/") ? ex : `${ex}/`;
146
+ pathScope += ` AND path NOT LIKE '${(0, filter_builder_1.escapeSqlString)(exNorm)}%'`;
147
+ }
139
148
  const counts = new Map();
140
149
  for (const sym of symbols) {
141
150
  const rows = yield table
@@ -362,6 +362,14 @@ class TreeSitterChunker {
362
362
  const allowedParents = ["program", "module", "source_file", "class_body", "export_statement"];
363
363
  if (parentType && !allowedParents.includes(parentType))
364
364
  return false;
365
+ // Exported declarations are part of the API surface, so always index
366
+ // them regardless of RHS shape. Without this, files whose only export
367
+ // is `export const typeDefs = gql\`...\`` (template literals, object
368
+ // literals, or non-PascalCase consts) had no defined_symbols entry
369
+ // and peek/extract returned "Symbol not found".
370
+ if (parentType === "export_statement" || parentType === "export_declaration") {
371
+ return true;
372
+ }
365
373
  const text = node.text || "";
366
374
  if (text.includes("=>"))
367
375
  return true;
@@ -32,6 +32,34 @@ function buildWhereClause(pathPrefix, filters, searchIntent) {
32
32
  : excludeFilter;
33
33
  parts.push(`path NOT LIKE '${(0, filter_builder_1.escapeSqlString)(absExclude)}%'`);
34
34
  }
35
+ // New array-shape: pre-resolved absolute exclude prefixes from
36
+ // resolveScope (or daemon IPC). Each becomes its own NOT LIKE clause.
37
+ const excludePrefixes = filters === null || filters === void 0 ? void 0 : filters.excludePrefixes;
38
+ if (Array.isArray(excludePrefixes)) {
39
+ for (const p of excludePrefixes) {
40
+ if (typeof p === "string" && p) {
41
+ const norm = (0, filter_builder_1.normalizePath)(p);
42
+ parts.push(`path NOT LIKE '${(0, filter_builder_1.escapeSqlString)(norm)}%'`);
43
+ }
44
+ }
45
+ }
46
+ // Multi-`--in` becomes an OR group of LIKE clauses. Single `--in`
47
+ // collapses into pathPrefix on the resolveScope side, so we don't see
48
+ // it here.
49
+ const inPrefixes = filters === null || filters === void 0 ? void 0 : filters.inPrefixes;
50
+ if (Array.isArray(inPrefixes) && inPrefixes.length > 0) {
51
+ const clauses = [];
52
+ for (const p of inPrefixes) {
53
+ if (typeof p === "string" && p) {
54
+ const norm = (0, filter_builder_1.normalizePath)(p);
55
+ clauses.push(`path LIKE '${(0, filter_builder_1.escapeSqlString)(norm)}%'`);
56
+ }
57
+ }
58
+ if (clauses.length === 1)
59
+ parts.push(clauses[0]);
60
+ else if (clauses.length > 1)
61
+ parts.push(`(${clauses.join(" OR ")})`);
62
+ }
35
63
  const langFilter = filters === null || filters === void 0 ? void 0 : filters.language;
36
64
  if (typeof langFilter === "string" && langFilter) {
37
65
  const ext = langFilter.startsWith(".") ? langFilter : `.${langFilter}`;