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.
- package/dist/commands/context.js +4 -2
- package/dist/commands/diff.js +4 -35
- package/dist/commands/extract.js +36 -6
- package/dist/commands/impact.js +21 -3
- package/dist/commands/investigate.js +4 -2
- package/dist/commands/peek.js +48 -7
- 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 +20 -3
- package/dist/commands/review.js +10 -3
- package/dist/commands/search.js +64 -37
- package/dist/commands/similar.js +13 -2
- package/dist/commands/skeleton.js +10 -20
- 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/daemon.js +16 -2
- 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/index/chunker.js +8 -0
- package/dist/lib/search/searcher.js +28 -0
- package/dist/lib/utils/language.js +91 -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/plugins/grepmax/hooks/start.js +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();
|
|
@@ -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 (
|
|
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 = (
|
|
814
|
-
const startLine = (
|
|
815
|
-
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;
|
|
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 (
|
|
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 = (
|
|
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 = (
|
|
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 (
|
|
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: (
|
|
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 (
|
|
1007
|
+
catch (_14) { }
|
|
981
1008
|
if (vectorDb) {
|
|
982
1009
|
try {
|
|
983
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([
|
|
@@ -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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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)
|
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));
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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}`;
|