grepmax 0.16.1 → 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();
@@ -780,7 +805,7 @@ Examples:
780
805
  }
781
806
  }
782
807
  }
783
- catch (_12) { }
808
+ catch (_11) { }
784
809
  }
785
810
  return;
786
811
  }
@@ -812,9 +837,9 @@ Examples:
812
837
  let shown = 0;
813
838
  console.log(resultCountHeader(filteredData, parseInt(options.m, 10)));
814
839
  for (const r of filteredData) {
815
- 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 : "";
816
- 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;
817
- 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;
818
843
  const relPath = absP.startsWith(projectRoot)
819
844
  ? absP.slice(projectRoot.length + 1)
820
845
  : absP;
@@ -847,7 +872,7 @@ Examples:
847
872
  tokensUsed += blobTokens;
848
873
  shown++;
849
874
  }
850
- catch (_13) {
875
+ catch (_12) {
851
876
  console.log(`\n--- ${relPath} (file not readable) ---`);
852
877
  shown++;
853
878
  }
@@ -864,7 +889,7 @@ Examples:
864
889
  if (options.imports) {
865
890
  const seenFiles = new Set();
866
891
  for (const r of filteredData) {
867
- 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 : "";
868
893
  if (absP && !seenFiles.has(absP)) {
869
894
  seenFiles.add(absP);
870
895
  const imports = getImportsForFile(absP);
@@ -891,7 +916,7 @@ Examples:
891
916
  for (const r of filteredData) {
892
917
  const b = r.scoreBreakdown;
893
918
  if (b) {
894
- 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 : "";
895
920
  const relPath = absP.startsWith(projectRoot)
896
921
  ? absP.slice(projectRoot.length + 1)
897
922
  : absP;
@@ -953,7 +978,7 @@ Examples:
953
978
  console.log(lines.join("\n"));
954
979
  }
955
980
  }
956
- catch (_14) {
981
+ catch (_13) {
957
982
  // Trace failed — skip silently
958
983
  }
959
984
  }
@@ -973,13 +998,13 @@ Examples:
973
998
  source: "cli",
974
999
  tool: "search",
975
1000
  query: pattern,
976
- 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,
977
1002
  results: _searchResultCount,
978
1003
  ms: Date.now() - _searchStartMs,
979
1004
  error: _searchError,
980
1005
  });
981
1006
  }
982
- catch (_15) { }
1007
+ catch (_14) { }
983
1008
  if (vectorDb) {
984
1009
  try {
985
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([
@@ -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));
@@ -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
@@ -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}`;
@@ -48,6 +48,8 @@ exports.getProject = getProject;
48
48
  exports.removeProject = removeProject;
49
49
  exports.getParentProject = getParentProject;
50
50
  exports.getChildProjects = getChildProjects;
51
+ exports.resolveRootOrExit = resolveRootOrExit;
52
+ exports.resolveProjectRoot = resolveProjectRoot;
51
53
  const fs = __importStar(require("node:fs"));
52
54
  const path = __importStar(require("node:path"));
53
55
  const proper_lockfile_1 = __importDefault(require("proper-lockfile"));
@@ -126,3 +128,44 @@ function getChildProjects(root) {
126
128
  const prefix = root.endsWith("/") ? root : `${root}/`;
127
129
  return loadRegistry().filter((e) => e.root !== root && e.root.startsWith(prefix));
128
130
  }
131
+ /**
132
+ * Resolve a `--root` argument with cwd fallback, printing the helper's
133
+ * error message and setting process.exitCode = 1 on failure. Returns
134
+ * null when the caller should bail out. Used at command entry points.
135
+ */
136
+ function resolveRootOrExit(arg) {
137
+ if (!arg)
138
+ return process.cwd();
139
+ try {
140
+ return resolveProjectRoot(arg);
141
+ }
142
+ catch (err) {
143
+ console.error(err instanceof Error ? err.message : String(err));
144
+ process.exitCode = 1;
145
+ return null;
146
+ }
147
+ }
148
+ /**
149
+ * Resolve a `--root` argument that may be either a path or a registered
150
+ * project name. Throws on no-match or duplicate-name so callers can
151
+ * report a uniform error.
152
+ */
153
+ function resolveProjectRoot(arg) {
154
+ if (arg.includes("/") || arg.includes("\\"))
155
+ return path.resolve(arg);
156
+ const resolved = path.resolve(arg);
157
+ if (fs.existsSync(resolved))
158
+ return resolved;
159
+ const matches = loadRegistry().filter((p) => p.name === arg);
160
+ if (matches.length === 1)
161
+ return matches[0].root;
162
+ if (matches.length === 0) {
163
+ const all = loadRegistry();
164
+ const list = all.length > 0
165
+ ? all.map((p) => ` ${p.name.padEnd(24)} ${p.root}`).join("\n")
166
+ : " (none registered)";
167
+ throw new Error(`No registered project named "${arg}".\nAvailable:\n${list}`);
168
+ }
169
+ const paths = matches.map((p) => ` ${p.root}`).join("\n");
170
+ throw new Error(`Multiple registered projects named "${arg}":\n${paths}\nPass an absolute path to disambiguate.`);
171
+ }