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.
- package/dist/commands/context.js +4 -2
- package/dist/commands/diff.js +4 -35
- package/dist/commands/extract.js +16 -6
- package/dist/commands/impact.js +21 -3
- package/dist/commands/investigate.js +4 -2
- package/dist/commands/peek.js +16 -11
- package/dist/commands/project.js +8 -7
- package/dist/commands/recent.js +10 -8
- package/dist/commands/related.js +13 -2
- package/dist/commands/remove.js +12 -31
- package/dist/commands/review.js +10 -3
- package/dist/commands/search.js +61 -36
- package/dist/commands/similar.js +13 -2
- package/dist/commands/symbols.js +4 -1
- package/dist/commands/test-find.js +16 -3
- package/dist/commands/trace.js +13 -3
- package/dist/lib/daemon/ipc-handler.js +4 -0
- package/dist/lib/graph/graph-builder.js +10 -4
- package/dist/lib/graph/impact.js +16 -7
- package/dist/lib/search/searcher.js +28 -0
- package/dist/lib/utils/project-registry.js +43 -0
- package/dist/lib/utils/scope-filter.js +104 -0
- package/package.json +1 -1
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
package/dist/commands/search.js
CHANGED
|
@@ -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("--
|
|
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
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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: ${(
|
|
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 && !((
|
|
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
|
|
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 && ((
|
|
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 (
|
|
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 = (
|
|
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, ((
|
|
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 = ((
|
|
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 = (
|
|
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 (
|
|
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 = (
|
|
816
|
-
const startLine = (
|
|
817
|
-
const endLine = (
|
|
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 (
|
|
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 = (
|
|
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 = (
|
|
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 (
|
|
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: (
|
|
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 (
|
|
1007
|
+
catch (_14) { }
|
|
983
1008
|
if (vectorDb) {
|
|
984
1009
|
try {
|
|
985
1010
|
yield vectorDb.close();
|
package/dist/commands/similar.js
CHANGED
|
@@ -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 =
|
|
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
|
|
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([
|
package/dist/commands/symbols.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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
|
|
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;
|
package/dist/commands/trace.js
CHANGED
|
@@ -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 =
|
|
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
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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.
|
package/dist/lib/graph/impact.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
+
}
|