grepmax 0.26.40 → 0.26.41
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/.claude-plugin/marketplace.json +1 -1
- package/README.md +4 -0
- package/dist/commands/codex.js +22 -16
- package/dist/commands/impact.js +2 -2
- package/dist/commands/mcp.js +37 -14
- package/dist/commands/peek.js +2 -2
- package/dist/commands/status.js +32 -28
- package/dist/commands/test-find.js +2 -2
- package/dist/commands/trace.js +1 -1
- package/dist/commands/watch.js +7 -0
- package/dist/lib/daemon/daemon.js +47 -15
- package/dist/lib/daemon/graph-handler.js +1 -1
- package/dist/lib/daemon/ipc-handler.js +10 -8
- package/dist/lib/daemon/search-handler.js +18 -8
- package/dist/lib/daemon/watcher-manager.js +37 -4
- package/dist/lib/graph/graph-builder.js +52 -4
- package/dist/lib/graph/impact-rollup.js +2 -2
- package/dist/lib/graph/impact.js +20 -8
- package/dist/lib/graph/test-hits.js +13 -7
- package/dist/lib/index/batch-processor.js +27 -2
- package/dist/lib/index/grammar-loader.js +22 -9
- package/dist/lib/output/formatter.js +1 -1
- package/dist/lib/output/index-state-footer.js +26 -6
- package/dist/lib/utils/resource-snapshot.js +20 -0
- package/dist/lib/utils/tests-footer.js +8 -16
- package/dist/lib/workers/embedding-error.js +16 -0
- package/dist/lib/workers/embeddings/mlx-client.js +94 -66
- package/dist/lib/workers/orchestrator.js +3 -2
- package/dist/lib/workers/pool.js +1 -1
- package/dist/lib/workers/process-child.js +2 -1
- package/package.json +1 -2
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"name": "grepmax",
|
|
11
11
|
"source": "./plugins/grepmax",
|
|
12
12
|
"description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
|
|
13
|
-
"version": "0.26.
|
|
13
|
+
"version": "0.26.41",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Robert Owens",
|
|
16
16
|
"email": "robowens@me.com"
|
package/README.md
CHANGED
|
@@ -222,6 +222,10 @@ gmax status # See all projects + watcher status
|
|
|
222
222
|
|
|
223
223
|
The daemon auto-starts when you run `gmax add`, `gmax index`, `gmax remove`, `gmax summarize`, or `gmax mcp`. It shuts down after 4 hours of inactivity, and hands off to a fresh daemon after 24 hours or once its memory footprint passes 2.5 GB. File-change batches are processed on at most four worker processes, added only when work backs up, with one kept free for searches; LanceDB compaction runs after writes rather than on every maintenance tick.
|
|
224
224
|
|
|
225
|
+
`gmax watch status` reports failed files, native watcher recovery or polling, dropped-event counts, and the last complete filesystem reconciliation. Polling scans run every five minutes, so recent edits can lag. Search and MCP responses retain health warnings even after the file queue drains. A complete reconciliation means the filesystem scan completed; queued embedding work can still be pending.
|
|
226
|
+
|
|
227
|
+
Temporary embedding backend outages preserve existing indexed rows and retry with backoff from five seconds up to one minute, without exhausting individual files' retry budgets. MLX failures report bounded transport/protocol details in the daemon log. `gmax status --json` includes the most recent resource snapshot when a recycle threshold has been reached; these counters help investigate memory growth, whose cause requires measurement over time.
|
|
228
|
+
|
|
225
229
|
## Running under the Claude Code sandbox
|
|
226
230
|
|
|
227
231
|
Claude Code's Bash sandbox (the default on macOS) allows writes only under the working directory,
|
package/dist/commands/codex.js
CHANGED
|
@@ -24,6 +24,10 @@ const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
|
24
24
|
const AGENTS_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".codex", "AGENTS.md");
|
|
25
25
|
const SKILL_START = "<!-- gmax:start -->";
|
|
26
26
|
const SKILL_END = "<!-- gmax:end -->";
|
|
27
|
+
function ownedSkillBlocks() {
|
|
28
|
+
// A new start marker cannot terminate an earlier incomplete block.
|
|
29
|
+
return new RegExp(`${SKILL_START}(?:(?!${SKILL_START})[\\s\\S])*?${SKILL_END}`, "g");
|
|
30
|
+
}
|
|
27
31
|
function getPackageRoot() {
|
|
28
32
|
return node_path_1.default.resolve(__dirname, "../..");
|
|
29
33
|
}
|
|
@@ -51,20 +55,22 @@ function writeSkillToAgents(skill) {
|
|
|
51
55
|
return;
|
|
52
56
|
}
|
|
53
57
|
const content = node_fs_1.default.readFileSync(AGENTS_PATH, "utf-8");
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
// Only complete marker pairs establish ownership. Unmarked prose, legacy
|
|
59
|
+
// instructions, and incomplete markers belong to the user and stay intact.
|
|
60
|
+
const markerRe = ownedSkillBlocks();
|
|
61
|
+
let replaced = false;
|
|
62
|
+
const updated = content.replace(markerRe, () => {
|
|
63
|
+
if (replaced)
|
|
64
|
+
return "";
|
|
65
|
+
replaced = true;
|
|
66
|
+
return block;
|
|
67
|
+
});
|
|
68
|
+
const separator = content.endsWith("\n\n")
|
|
69
|
+
? ""
|
|
70
|
+
: content.endsWith("\n")
|
|
71
|
+
? "\n"
|
|
72
|
+
: "\n\n";
|
|
73
|
+
node_fs_1.default.writeFileSync(AGENTS_PATH, replaced ? updated : `${content}${separator}${block}`);
|
|
68
74
|
}
|
|
69
75
|
function installPlugin() {
|
|
70
76
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -101,9 +107,9 @@ function uninstallPlugin() {
|
|
|
101
107
|
if (node_fs_1.default.existsSync(AGENTS_PATH)) {
|
|
102
108
|
let content = node_fs_1.default.readFileSync(AGENTS_PATH, "utf-8");
|
|
103
109
|
// Remove marked block
|
|
104
|
-
const markerRe =
|
|
110
|
+
const markerRe = ownedSkillBlocks();
|
|
105
111
|
if (markerRe.test(content)) {
|
|
106
|
-
content = content.replace(
|
|
112
|
+
content = content.replace(ownedSkillBlocks(), "");
|
|
107
113
|
node_fs_1.default.writeFileSync(AGENTS_PATH, content || "");
|
|
108
114
|
console.log("✅ gmax instructions removed from AGENTS.md");
|
|
109
115
|
}
|
package/dist/commands/impact.js
CHANGED
|
@@ -203,7 +203,7 @@ exports.impact = new commander_1.Command("impact")
|
|
|
203
203
|
console.log(`dep: ${rel(d.file)}\t${d.sharedSymbols}`);
|
|
204
204
|
}
|
|
205
205
|
for (const t of groupedTests) {
|
|
206
|
-
console.log(`test: ${rel(t.file)}:${t.line + 1}\t${(0, test_hits_1.hopLabelAgent)(t.hops)}${(0, test_hits_1.formatViaAgent)(t.via)}`);
|
|
206
|
+
console.log(`test: ${rel(t.file)}:${t.line + 1}\t${(0, test_hits_1.hopLabelAgent)(t.hops, t.evidence)}${(0, test_hits_1.formatViaAgent)(t.via)}`);
|
|
207
207
|
}
|
|
208
208
|
if (!nonTestDeps.length && !tests.length) {
|
|
209
209
|
console.log("(no impact detected)");
|
|
@@ -225,7 +225,7 @@ exports.impact = new commander_1.Command("impact")
|
|
|
225
225
|
if (groupedTests.length > 0) {
|
|
226
226
|
console.log(`Affected tests (${groupedTests.length}):`);
|
|
227
227
|
for (const t of groupedTests) {
|
|
228
|
-
console.log(` ${rel(t.file)}:${t.line + 1} (${(0, test_hits_1.hopLabelHuman)(t.hops)}${(0, test_hits_1.formatViaHuman)(t.via)})`);
|
|
228
|
+
console.log(` ${rel(t.file)}:${t.line + 1} (${(0, test_hits_1.hopLabelHuman)(t.hops, t.evidence)}${(0, test_hits_1.formatViaHuman)(t.via)})`);
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
else {
|
package/dist/commands/mcp.js
CHANGED
|
@@ -78,10 +78,12 @@ const surprising_connections_1 = require("../lib/analysis/surprising-connections
|
|
|
78
78
|
const graph_handler_1 = require("../lib/daemon/graph-handler");
|
|
79
79
|
const rows_handler_1 = require("../lib/daemon/rows-handler");
|
|
80
80
|
const vector_handler_1 = require("../lib/daemon/vector-handler");
|
|
81
|
+
const test_hits_1 = require("../lib/graph/test-hits");
|
|
81
82
|
const embedding_status_1 = require("../lib/index/embedding-status");
|
|
82
83
|
const index_config_1 = require("../lib/index/index-config");
|
|
83
84
|
const syncer_1 = require("../lib/index/syncer");
|
|
84
85
|
const agent_search_formatter_1 = require("../lib/output/agent-search-formatter");
|
|
86
|
+
const index_state_footer_1 = require("../lib/output/index-state-footer");
|
|
85
87
|
const searcher_1 = require("../lib/search/searcher");
|
|
86
88
|
const annotator_1 = require("../lib/skeleton/annotator");
|
|
87
89
|
const skeletonizer_1 = require("../lib/skeleton/skeletonizer");
|
|
@@ -424,12 +426,18 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
424
426
|
function searchStore(args) {
|
|
425
427
|
return readStore("search", {
|
|
426
428
|
daemon: () => { var _a; return (0, daemon_client_1.sendDaemonCommand)(Object.assign({ cmd: ((_a = args.filters) === null || _a === void 0 ? void 0 : _a.projectRoots) ? "search-v2" : "search" }, args), { timeoutMs: 60000 }); },
|
|
427
|
-
render: (resp) =>
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
429
|
+
render: (resp) => {
|
|
430
|
+
const warnings = Array.isArray(resp.warnings)
|
|
431
|
+
? resp.warnings.filter((w) => typeof w === "string")
|
|
432
|
+
: [];
|
|
433
|
+
const health = (0, index_state_footer_1.formatIndexStateFooter)(resp.indexState, { agent: true });
|
|
434
|
+
if (health)
|
|
435
|
+
warnings.push(health);
|
|
436
|
+
return {
|
|
437
|
+
data: Array.isArray(resp.data) ? resp.data : [],
|
|
438
|
+
warnings,
|
|
439
|
+
};
|
|
440
|
+
},
|
|
433
441
|
inProcess: () => localStore((deps) => __awaiter(this, void 0, void 0, function* () {
|
|
434
442
|
const searcher = new searcher_1.Searcher(deps.vectorDb);
|
|
435
443
|
return searcher.search(args.query, args.limit, { rerank: args.rerank, seeds: args.seeds }, args.filters, args.pathPrefix);
|
|
@@ -907,7 +915,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
907
915
|
traceLines.push(` -> ${callee.symbol} ${rel}:${callee.line + 1}`);
|
|
908
916
|
}
|
|
909
917
|
else {
|
|
910
|
-
traceLines.push(` -> ${callee.symbol} (not indexed)`);
|
|
918
|
+
traceLines.push(` -> ${callee.symbol} (${callee.resolution === "ambiguous-member" ? "receiver unresolved" : "not indexed"})`);
|
|
911
919
|
}
|
|
912
920
|
}
|
|
913
921
|
}
|
|
@@ -1116,7 +1124,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1116
1124
|
lines.push(` -> ${callee.symbol} ${rel}:${callee.line + 1}`);
|
|
1117
1125
|
}
|
|
1118
1126
|
else {
|
|
1119
|
-
lines.push(` -> ${callee.symbol} (not indexed)`);
|
|
1127
|
+
lines.push(` -> ${callee.symbol} (${callee.resolution === "ambiguous-member" ? "receiver unresolved" : "not indexed"})`);
|
|
1120
1128
|
}
|
|
1121
1129
|
}
|
|
1122
1130
|
if (graph.callees.length > 15) {
|
|
@@ -1330,7 +1338,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1330
1338
|
for (const c of graph.callees.slice(0, maxCallees)) {
|
|
1331
1339
|
const loc = c.file
|
|
1332
1340
|
? `${rel(c.file)}:${c.line + 1}`
|
|
1333
|
-
:
|
|
1341
|
+
: c.resolution === "ambiguous-member"
|
|
1342
|
+
? "(receiver unresolved)"
|
|
1343
|
+
: "(not indexed)";
|
|
1334
1344
|
parts.push(` -> ${c.symbol} ${loc}`);
|
|
1335
1345
|
}
|
|
1336
1346
|
if (graph.callees.length > maxCallees) {
|
|
@@ -1729,15 +1739,20 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1729
1739
|
}
|
|
1730
1740
|
function handleIndexStatus() {
|
|
1731
1741
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1732
|
-
var _a;
|
|
1742
|
+
var _a, _b;
|
|
1733
1743
|
try {
|
|
1734
1744
|
const globalConfig = (0, index_config_1.readGlobalConfig)();
|
|
1735
1745
|
const projects = (0, project_registry_1.listProjects)();
|
|
1736
1746
|
const currentProject = resolveMcpProject(undefined, projectRoot);
|
|
1737
1747
|
const identity = (0, embedding_status_1.projectEmbeddingStatus)(currentProject, globalConfig);
|
|
1738
1748
|
const { chunks, files } = yield indexTotals(projects);
|
|
1739
|
-
|
|
1740
|
-
const
|
|
1749
|
+
const daemonStatus = yield (0, daemon_client_1.sendDaemonCommand)({ cmd: "status" });
|
|
1750
|
+
const watched = (Array.isArray(daemonStatus.projects) ? daemonStatus.projects : []);
|
|
1751
|
+
const daemonProject = watched.find((p) => p.root === (currentProject === null || currentProject === void 0 ? void 0 : currentProject.root));
|
|
1752
|
+
// Only use the registry when no daemon answered; sandboxed reads stay IPC-only.
|
|
1753
|
+
const watcher = daemonStatus.ok
|
|
1754
|
+
? undefined
|
|
1755
|
+
: (0, watcher_store_1.getWatcherCoveringPath)(projectRoot);
|
|
1741
1756
|
let watcherLine = "Watcher: not running";
|
|
1742
1757
|
if (watcher) {
|
|
1743
1758
|
const status = (_a = watcher.status) !== null && _a !== void 0 ? _a : "unknown";
|
|
@@ -1750,6 +1765,14 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1750
1765
|
watcherLine += " — search results may be incomplete";
|
|
1751
1766
|
}
|
|
1752
1767
|
}
|
|
1768
|
+
if (daemonProject) {
|
|
1769
|
+
watcherLine = `Watcher: ${(_b = daemonProject.status) !== null && _b !== void 0 ? _b : "unknown"} (${path.basename(daemonProject.root)}/)`;
|
|
1770
|
+
const health = (0, index_state_footer_1.formatIndexStateFooter)(daemonProject.indexState, {
|
|
1771
|
+
agent: true,
|
|
1772
|
+
});
|
|
1773
|
+
if (health)
|
|
1774
|
+
watcherLine += `\n${health}`;
|
|
1775
|
+
}
|
|
1753
1776
|
// Health overview only. The per-project listing (names, roots,
|
|
1754
1777
|
// per-project chunk counts) lives in `list_projects` — keep this tool
|
|
1755
1778
|
// focused on index health and avoid the N-project LanceDB scans.
|
|
@@ -2125,7 +2148,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2125
2148
|
return ok(`No tests found for ${target}.`);
|
|
2126
2149
|
const rel = (p) => p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p;
|
|
2127
2150
|
const lines = tests.map((t) => {
|
|
2128
|
-
const hop = t.hops
|
|
2151
|
+
const hop = (0, test_hits_1.hopLabelAgent)(t.hops, t.evidence);
|
|
2129
2152
|
return `${rel(t.file)}:${t.line + 1} ${t.symbol} (${hop})`;
|
|
2130
2153
|
});
|
|
2131
2154
|
return ok(`Tests for ${target}:\n${lines.join("\n")}`);
|
|
@@ -2218,7 +2241,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2218
2241
|
if (tests.length > 0) {
|
|
2219
2242
|
sections.push(`Affected tests (${tests.length}):`);
|
|
2220
2243
|
for (const t of tests) {
|
|
2221
|
-
const hop = t.hops
|
|
2244
|
+
const hop = (0, test_hits_1.hopLabelAgent)(t.hops, t.evidence);
|
|
2222
2245
|
sections.push(` ${rel(t.file)}:${t.line + 1} ${t.symbol} (${hop})`);
|
|
2223
2246
|
}
|
|
2224
2247
|
}
|
package/dist/commands/peek.js
CHANGED
|
@@ -269,7 +269,7 @@ exports.peek = new commander_1.Command("peek")
|
|
|
269
269
|
// Dedupe by symbol — repeated references arrive once per chunk.
|
|
270
270
|
const seenCallees = new Set();
|
|
271
271
|
const calleeList = graph.callees
|
|
272
|
-
.filter((c) => c.file || !(0, callsites_1.isBuiltinCallee)(c.symbol))
|
|
272
|
+
.filter((c) => c.file || c.resolution || !(0, callsites_1.isBuiltinCallee)(c.symbol))
|
|
273
273
|
.filter((c) => {
|
|
274
274
|
if (seenCallees.has(c.symbol))
|
|
275
275
|
return false;
|
|
@@ -277,7 +277,7 @@ exports.peek = new commander_1.Command("peek")
|
|
|
277
277
|
return true;
|
|
278
278
|
})
|
|
279
279
|
.map((c) => ({
|
|
280
|
-
symbol: c.symbol,
|
|
280
|
+
symbol: c.resolution ? `${c.symbol} (receiver unresolved)` : c.symbol,
|
|
281
281
|
file: c.file,
|
|
282
282
|
line: c.line,
|
|
283
283
|
}));
|
package/dist/commands/status.js
CHANGED
|
@@ -50,6 +50,7 @@ const commander_1 = require("commander");
|
|
|
50
50
|
const config_1 = require("../config");
|
|
51
51
|
const embedding_status_1 = require("../lib/index/embedding-status");
|
|
52
52
|
const index_config_1 = require("../lib/index/index-config");
|
|
53
|
+
const index_state_footer_1 = require("../lib/output/index-state-footer");
|
|
53
54
|
const daemon_client_1 = require("../lib/utils/daemon-client");
|
|
54
55
|
const exit_1 = require("../lib/utils/exit");
|
|
55
56
|
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
@@ -74,11 +75,15 @@ function loadStatusView(projects) {
|
|
|
74
75
|
return (0, store_access_1.withStoreRead)("status", {
|
|
75
76
|
daemon: () => (0, daemon_client_1.sendDaemonCommand)({ cmd: "status" }),
|
|
76
77
|
render: (resp) => __awaiter(this, void 0, void 0, function* () {
|
|
78
|
+
var _a;
|
|
77
79
|
const watchers = new Map();
|
|
78
80
|
const entries = Array.isArray(resp.projects) ? resp.projects : [];
|
|
81
|
+
const health = new Map();
|
|
79
82
|
for (const entry of entries) {
|
|
80
83
|
if (entry && typeof entry.root === "string") {
|
|
81
|
-
watchers.set(entry.root, { status: "watching" });
|
|
84
|
+
watchers.set(entry.root, { status: (_a = entry.status) !== null && _a !== void 0 ? _a : "watching" });
|
|
85
|
+
if (entry.indexState)
|
|
86
|
+
health.set(entry.root, entry.indexState);
|
|
82
87
|
}
|
|
83
88
|
}
|
|
84
89
|
// One project-stats call per project. A failure here is not fatal: the
|
|
@@ -91,17 +96,15 @@ function loadStatusView(projects) {
|
|
|
91
96
|
chunkCounts.set(project.root, stats.chunks);
|
|
92
97
|
}
|
|
93
98
|
}
|
|
94
|
-
const daemon = {
|
|
95
|
-
|
|
96
|
-
uptimeSec: typeof resp.uptime === "number" ? resp.uptime : null,
|
|
99
|
+
const daemon = Object.assign(Object.assign({}, (resp.resources !== undefined
|
|
100
|
+
? { resources: resp.resources }
|
|
101
|
+
: {})), { pid: typeof resp.pid === "number" ? resp.pid : null, uptimeSec: typeof resp.uptime === "number" ? resp.uptime : null,
|
|
97
102
|
// Older daemons do not report these.
|
|
98
|
-
workers: typeof resp.workers === "number" ? resp.workers : null,
|
|
99
|
-
workerThreads: resp.workerThreads &&
|
|
103
|
+
workers: typeof resp.workers === "number" ? resp.workers : null, workerThreads: resp.workerThreads &&
|
|
100
104
|
typeof resp.workerThreads.value === "number"
|
|
101
105
|
? resp.workerThreads.value
|
|
102
|
-
: null
|
|
103
|
-
};
|
|
104
|
-
return { watchers, chunkCounts, daemon };
|
|
106
|
+
: null });
|
|
107
|
+
return { watchers, chunkCounts, daemon, health };
|
|
105
108
|
}),
|
|
106
109
|
inProcess: () => __awaiter(this, void 0, void 0, function* () {
|
|
107
110
|
(0, watcher_store_1.listWatchers)(); // cleans stale entries as side effect
|
|
@@ -149,6 +152,8 @@ function projectState(project, watcher) {
|
|
|
149
152
|
return "pending";
|
|
150
153
|
if (projectStatus === "error")
|
|
151
154
|
return "error";
|
|
155
|
+
if ((watcher === null || watcher === void 0 ? void 0 : watcher.status) === "degraded")
|
|
156
|
+
return "degraded";
|
|
152
157
|
if ((watcher === null || watcher === void 0 ? void 0 : watcher.status) === "syncing")
|
|
153
158
|
return "indexing";
|
|
154
159
|
if (watcher)
|
|
@@ -195,12 +200,7 @@ function buildStatusJson(input) {
|
|
|
195
200
|
: null);
|
|
196
201
|
}
|
|
197
202
|
return {
|
|
198
|
-
daemon: {
|
|
199
|
-
running: d !== undefined,
|
|
200
|
-
pid: (_d = d === null || d === void 0 ? void 0 : d.pid) !== null && _d !== void 0 ? _d : null,
|
|
201
|
-
since: (d === null || d === void 0 ? void 0 : d.uptimeSec) != null ? now - d.uptimeSec * 1000 : null,
|
|
202
|
-
workerThreads: (_e = d === null || d === void 0 ? void 0 : d.workerThreads) !== null && _e !== void 0 ? _e : null,
|
|
203
|
-
},
|
|
203
|
+
daemon: Object.assign({ running: d !== undefined, pid: (_d = d === null || d === void 0 ? void 0 : d.pid) !== null && _d !== void 0 ? _d : null, since: (d === null || d === void 0 ? void 0 : d.uptimeSec) != null ? now - d.uptimeSec * 1000 : null, workerThreads: (_e = d === null || d === void 0 ? void 0 : d.workerThreads) !== null && _e !== void 0 ? _e : null }, ((d === null || d === void 0 ? void 0 : d.resources) !== undefined ? { resources: d.resources } : {})),
|
|
204
204
|
settings: {
|
|
205
205
|
embedMode: globalConfig.embedMode,
|
|
206
206
|
modelTier: globalConfig.modelTier,
|
|
@@ -210,14 +210,9 @@ function buildStatusJson(input) {
|
|
|
210
210
|
},
|
|
211
211
|
workersRunning,
|
|
212
212
|
indexing: input.indexing,
|
|
213
|
-
projects: input.projects.map((project) => { var _a,
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
chunks: (_b = (_a = view.chunkCounts.get(project.root)) !== null && _a !== void 0 ? _a : project.chunkCount) !== null && _b !== void 0 ? _b : 0,
|
|
217
|
-
indexedAt: toMs(project.lastIndexed),
|
|
218
|
-
state: projectState(project, view.watchers.get(project.root)),
|
|
219
|
-
embedding: (0, embedding_status_1.projectEmbeddingStatus)(project, globalConfig).state,
|
|
220
|
-
}); }),
|
|
213
|
+
projects: input.projects.map((project) => { var _a; var _b, _c; return (Object.assign({ name: project.name, root: project.root, chunks: (_c = (_b = view.chunkCounts.get(project.root)) !== null && _b !== void 0 ? _b : project.chunkCount) !== null && _c !== void 0 ? _c : 0, indexedAt: toMs(project.lastIndexed), state: projectState(project, view.watchers.get(project.root)), embedding: (0, embedding_status_1.projectEmbeddingStatus)(project, globalConfig).state }, (((_a = view.health) === null || _a === void 0 ? void 0 : _a.has(project.root))
|
|
214
|
+
? { health: view.health.get(project.root) }
|
|
215
|
+
: {}))); }),
|
|
221
216
|
at: now,
|
|
222
217
|
};
|
|
223
218
|
}
|
|
@@ -268,7 +263,8 @@ Examples:
|
|
|
268
263
|
gmax status --json The same, plus daemon and settings, as JSON
|
|
269
264
|
`)
|
|
270
265
|
.action((opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
271
|
-
var _a, _b
|
|
266
|
+
var _a, _b;
|
|
267
|
+
var _c, _d, _e, _f;
|
|
272
268
|
const globalConfig = (0, index_config_1.readGlobalConfig)();
|
|
273
269
|
const projects = (0, project_registry_1.listProjects)();
|
|
274
270
|
const indexing = (0, lock_1.isLocked)(config_1.PATHS.globalRoot);
|
|
@@ -319,9 +315,9 @@ Examples:
|
|
|
319
315
|
for (const project of projects) {
|
|
320
316
|
const st = projectState(project, watchers.get(project.root));
|
|
321
317
|
const isCurrent = project.root === currentRoot;
|
|
322
|
-
const count = (
|
|
318
|
+
const count = (_c = chunkCounts.get(project.root)) !== null && _c !== void 0 ? _c : project.chunkCount;
|
|
323
319
|
const identity = (0, embedding_status_1.projectEmbeddingStatus)(project, globalConfig);
|
|
324
|
-
console.log(`${project.name}\t${formatChunks(count)}\t${formatAge(project.lastIndexed)}\t${st}\tembedding=${identity.state}${isCurrent ? "\tcurrent" : ""}`);
|
|
320
|
+
console.log(`${project.name}\t${formatChunks(count)}\t${formatAge(project.lastIndexed)}\t${st}\tembedding=${identity.state}${isCurrent ? "\tcurrent" : ""}${((_a = view.health) === null || _a === void 0 ? void 0 : _a.get(project.root)) ? `\t${(_d = (0, index_state_footer_1.formatIndexStateFooter)(view.health.get(project.root), { agent: true })) !== null && _d !== void 0 ? _d : ""}` : ""}`);
|
|
325
321
|
}
|
|
326
322
|
const legacyNotice = (0, embedding_status_1.formatLegacyEmbeddingNotice)((0, embedding_status_1.countLegacyEmbeddingProjects)(projects, globalConfig), { agent: true });
|
|
327
323
|
if (legacyNotice)
|
|
@@ -337,13 +333,16 @@ Examples:
|
|
|
337
333
|
const watcher = watchers.get(project.root);
|
|
338
334
|
// Status column
|
|
339
335
|
let statusStr;
|
|
340
|
-
const projectStatus = (
|
|
336
|
+
const projectStatus = (_e = project.status) !== null && _e !== void 0 ? _e : "indexed";
|
|
341
337
|
if (projectStatus === "pending") {
|
|
342
338
|
statusStr = style.yellow("pending");
|
|
343
339
|
}
|
|
344
340
|
else if (projectStatus === "error") {
|
|
345
341
|
statusStr = style.red("error");
|
|
346
342
|
}
|
|
343
|
+
else if ((watcher === null || watcher === void 0 ? void 0 : watcher.status) === "degraded") {
|
|
344
|
+
statusStr = style.red("degraded");
|
|
345
|
+
}
|
|
347
346
|
else if ((watcher === null || watcher === void 0 ? void 0 : watcher.status) === "syncing") {
|
|
348
347
|
statusStr = style.yellow("indexing");
|
|
349
348
|
}
|
|
@@ -354,7 +353,7 @@ Examples:
|
|
|
354
353
|
statusStr = style.dim("idle");
|
|
355
354
|
}
|
|
356
355
|
// Chunks column
|
|
357
|
-
const count = (
|
|
356
|
+
const count = (_f = chunkCounts.get(project.root)) !== null && _f !== void 0 ? _f : project.chunkCount;
|
|
358
357
|
const chunks = `${formatChunks(count)} chunks`;
|
|
359
358
|
// Age column
|
|
360
359
|
const age = formatAge(project.lastIndexed);
|
|
@@ -363,6 +362,11 @@ Examples:
|
|
|
363
362
|
const marker = isCurrent ? style.cyan(" ←") : "";
|
|
364
363
|
const name = project.name.padEnd(nameWidth);
|
|
365
364
|
console.log(` ${name} ${chunks.padEnd(12)} ${age.padEnd(10)} ${statusStr} embedding:${embedding}${marker}`);
|
|
365
|
+
const health = (0, index_state_footer_1.formatIndexStateFooter)((_b = view.health) === null || _b === void 0 ? void 0 : _b.get(project.root), {
|
|
366
|
+
agent: false,
|
|
367
|
+
});
|
|
368
|
+
if (health)
|
|
369
|
+
console.log(` ${health}`);
|
|
366
370
|
}
|
|
367
371
|
const legacyNotice = (0, embedding_status_1.formatLegacyEmbeddingNotice)((0, embedding_status_1.countLegacyEmbeddingProjects)(projects, globalConfig));
|
|
368
372
|
if (legacyNotice)
|
|
@@ -127,13 +127,13 @@ exports.testFind = new commander_1.Command("test")
|
|
|
127
127
|
const grouped = (0, test_hits_1.groupTestHitsByFile)(tests);
|
|
128
128
|
if (opts.agent) {
|
|
129
129
|
for (const t of grouped) {
|
|
130
|
-
console.log(`${rel(t.file)}:${t.line + 1}\t${(0, test_hits_1.hopLabelAgent)(t.hops)}${(0, test_hits_1.formatViaAgent)(t.via)}`);
|
|
130
|
+
console.log(`${rel(t.file)}:${t.line + 1}\t${(0, test_hits_1.hopLabelAgent)(t.hops, t.evidence)}${(0, test_hits_1.formatViaAgent)(t.via)}`);
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
else {
|
|
134
134
|
console.log(`Tests for ${target}:\n`);
|
|
135
135
|
for (const t of grouped) {
|
|
136
|
-
console.log(` ${rel(t.file)}:${t.line + 1} (${(0, test_hits_1.hopLabelHuman)(t.hops)}${(0, test_hits_1.formatViaHuman)(t.via)})`);
|
|
136
|
+
console.log(` ${rel(t.file)}:${t.line + 1} (${(0, test_hits_1.hopLabelHuman)(t.hops, t.evidence)}${(0, test_hits_1.formatViaHuman)(t.via)})`);
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
}
|
package/dist/commands/trace.js
CHANGED
|
@@ -121,7 +121,7 @@ function formatTraceAgent(graph, projectRoot, raw = false) {
|
|
|
121
121
|
lines.push(`-> ${c.symbol}\t${rel(c.file)}:${c.line}`);
|
|
122
122
|
}
|
|
123
123
|
else {
|
|
124
|
-
lines.push(`-> ${c.symbol}\t(not indexed)`);
|
|
124
|
+
lines.push(`-> ${c.symbol}\t(${c.resolution === "ambiguous-member" ? "receiver unresolved" : "not indexed"})`);
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
return lines.join("\n");
|
package/dist/commands/watch.js
CHANGED
|
@@ -50,6 +50,7 @@ const commander_1 = require("commander");
|
|
|
50
50
|
const config_1 = require("../config");
|
|
51
51
|
const syncer_1 = require("../lib/index/syncer");
|
|
52
52
|
const watcher_1 = require("../lib/index/watcher");
|
|
53
|
+
const index_state_footer_1 = require("../lib/output/index-state-footer");
|
|
53
54
|
const meta_cache_1 = require("../lib/store/meta-cache");
|
|
54
55
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
55
56
|
const daemon_restart_1 = require("../lib/utils/daemon-restart");
|
|
@@ -366,6 +367,7 @@ exports.watch
|
|
|
366
367
|
.command("status")
|
|
367
368
|
.description("Show running watchers")
|
|
368
369
|
.action(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
370
|
+
var _a, _b, _c;
|
|
369
371
|
const { sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
|
|
370
372
|
const resp = yield sendDaemonCommand({ cmd: "status" });
|
|
371
373
|
if (resp.ok) {
|
|
@@ -378,6 +380,11 @@ exports.watch
|
|
|
378
380
|
}
|
|
379
381
|
for (const p of projects) {
|
|
380
382
|
console.log(` - ${p.root} [${p.status}]`);
|
|
383
|
+
const health = (0, index_state_footer_1.formatIndexStateFooter)(p.indexState, { agent: true });
|
|
384
|
+
if (health)
|
|
385
|
+
console.log(` ${health}`);
|
|
386
|
+
if (p.indexState)
|
|
387
|
+
console.log(` watcher=${(_a = p.indexState.watcherMode) !== null && _a !== void 0 ? _a : "unknown"} overflows=${(_b = p.indexState.overflowCount) !== null && _b !== void 0 ? _b : 0} lastReconciled=${p.indexState.lastReconciledAt ? new Date(p.indexState.lastReconciledAt).toISOString() : "unknown"} catchupMs=${(_c = p.indexState.catchupMs) !== null && _c !== void 0 ? _c : "unknown"}`);
|
|
381
388
|
}
|
|
382
389
|
if (projects.length === 0) {
|
|
383
390
|
console.log(" (no projects)");
|
|
@@ -71,6 +71,7 @@ const operation_coordinator_1 = require("../utils/operation-coordinator");
|
|
|
71
71
|
const process_1 = require("../utils/process");
|
|
72
72
|
const process_footprint_1 = require("../utils/process-footprint");
|
|
73
73
|
const project_registry_1 = require("../utils/project-registry");
|
|
74
|
+
const resource_snapshot_1 = require("../utils/resource-snapshot");
|
|
74
75
|
const root_availability_1 = require("../utils/root-availability");
|
|
75
76
|
const watcher_store_1 = require("../utils/watcher-store");
|
|
76
77
|
const pool_1 = require("../workers/pool");
|
|
@@ -129,6 +130,7 @@ const RECYCLE_FORCE_AFTER_MS = envNum("GMAX_DAEMON_RECYCLE_FORCE_AFTER_MS", 60 *
|
|
|
129
130
|
class Daemon {
|
|
130
131
|
constructor() {
|
|
131
132
|
this.processors = new Map();
|
|
133
|
+
this.lastResourceSnapshot = null;
|
|
132
134
|
this.searchers = new Map();
|
|
133
135
|
this.subscriptions = new Map();
|
|
134
136
|
this.vectorDb = null;
|
|
@@ -767,13 +769,13 @@ class Daemon {
|
|
|
767
769
|
* on every search to annotate partial-result responses (Phase 6).
|
|
768
770
|
*/
|
|
769
771
|
indexState(root) {
|
|
770
|
-
var _a;
|
|
771
|
-
var
|
|
772
|
+
var _a, _b, _c, _d;
|
|
773
|
+
var _e, _f, _g, _h, _j;
|
|
772
774
|
const processor = this.processors.get(root);
|
|
773
|
-
const batchPending = (
|
|
774
|
-
const processing = (
|
|
775
|
+
const batchPending = (_e = (_a = processor === null || processor === void 0 ? void 0 : processor.progress) === null || _a === void 0 ? void 0 : _a.pendingFiles) !== null && _e !== void 0 ? _e : 0;
|
|
776
|
+
const processing = (_f = (_b = processor === null || processor === void 0 ? void 0 : processor.progress) === null || _b === void 0 ? void 0 : _b.processing) !== null && _f !== void 0 ? _f : false;
|
|
775
777
|
// status === "pending" means the initial full index hasn't completed.
|
|
776
|
-
const initialPending = ((
|
|
778
|
+
const initialPending = ((_c = (0, project_registry_1.getProject)(root)) === null || _c === void 0 ? void 0 : _c.status) === "pending";
|
|
777
779
|
// A full index (--reset / initial) bypasses the batch processor; its
|
|
778
780
|
// onProgress feeds indexProgress, giving a real remaining count.
|
|
779
781
|
const fullIdx = this.indexProgress.get(root);
|
|
@@ -785,7 +787,7 @@ class Daemon {
|
|
|
785
787
|
// index genuinely has work outstanding; a post-FSEvents-drop catchup mostly
|
|
786
788
|
// re-verifies unchanged files. Only claim "verifying" with a real sample and
|
|
787
789
|
// no initial-index work pending.
|
|
788
|
-
const { recentFiles, recentReindexed } = (
|
|
790
|
+
const { recentFiles, recentReindexed } = (_g = processor === null || processor === void 0 ? void 0 : processor.progress) !== null && _g !== void 0 ? _g : {
|
|
789
791
|
recentFiles: 0,
|
|
790
792
|
recentReindexed: 0,
|
|
791
793
|
};
|
|
@@ -794,11 +796,9 @@ class Daemon {
|
|
|
794
796
|
recentFiles >= 50 &&
|
|
795
797
|
recentReindexed === 0 &&
|
|
796
798
|
pendingFiles > 0;
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
verifying,
|
|
801
|
-
};
|
|
799
|
+
const health = this.watcherManager.health(root);
|
|
800
|
+
return Object.assign(Object.assign({ indexing: !!fullIdx || processing || batchPending > 0 || initialPending, pendingFiles,
|
|
801
|
+
verifying }, health), { failedFiles: Math.max((_h = (_d = processor === null || processor === void 0 ? void 0 : processor.progress) === null || _d === void 0 ? void 0 : _d.failedFiles) !== null && _h !== void 0 ? _h : 0, (_j = health.failedFiles) !== null && _j !== void 0 ? _j : 0) });
|
|
802
802
|
}
|
|
803
803
|
search(payload, signal) {
|
|
804
804
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -836,10 +836,18 @@ class Daemon {
|
|
|
836
836
|
};
|
|
837
837
|
}
|
|
838
838
|
listProjects() {
|
|
839
|
-
return [...this.processors.keys()].map((root) =>
|
|
840
|
-
root
|
|
841
|
-
|
|
842
|
-
|
|
839
|
+
return [...this.processors.keys()].map((root) => {
|
|
840
|
+
const indexState = this.indexState(root);
|
|
841
|
+
return {
|
|
842
|
+
root,
|
|
843
|
+
status: indexState.failedFiles || indexState.degraded
|
|
844
|
+
? "degraded"
|
|
845
|
+
: indexState.indexing
|
|
846
|
+
? "syncing"
|
|
847
|
+
: "watching",
|
|
848
|
+
indexState,
|
|
849
|
+
};
|
|
850
|
+
});
|
|
843
851
|
}
|
|
844
852
|
uptime() {
|
|
845
853
|
return Math.floor((Date.now() - this.startTime) / 1000);
|
|
@@ -1810,6 +1818,28 @@ class Daemon {
|
|
|
1810
1818
|
}));
|
|
1811
1819
|
});
|
|
1812
1820
|
}
|
|
1821
|
+
resourceSnapshot() {
|
|
1822
|
+
return this.lastResourceSnapshot;
|
|
1823
|
+
}
|
|
1824
|
+
logResourceSnapshot(reason, footprintMb) {
|
|
1825
|
+
var _a, _b;
|
|
1826
|
+
var _c, _d;
|
|
1827
|
+
let lanceCacheBytes = null;
|
|
1828
|
+
try {
|
|
1829
|
+
lanceCacheBytes = (_c = (_a = this.vectorDb) === null || _a === void 0 ? void 0 : _a.cacheSizeBytes()) !== null && _c !== void 0 ? _c : null;
|
|
1830
|
+
}
|
|
1831
|
+
catch (_e) { }
|
|
1832
|
+
this.lastResourceSnapshot = (0, resource_snapshot_1.buildResourceSnapshot)({
|
|
1833
|
+
reason,
|
|
1834
|
+
footprintMb,
|
|
1835
|
+
lanceCacheBytes,
|
|
1836
|
+
workers: this.workerCount(),
|
|
1837
|
+
pendingFiles: [...this.processors.values()].reduce((sum, processor) => sum + processor.progress.pendingFiles, 0),
|
|
1838
|
+
operations: this.operations.activeCount,
|
|
1839
|
+
maintenance: (_d = (_b = this.vectorDb) === null || _b === void 0 ? void 0 : _b.isMaintenanceActive()) !== null && _d !== void 0 ? _d : false,
|
|
1840
|
+
});
|
|
1841
|
+
console.log(`[daemon] Resource snapshot: ${JSON.stringify(this.lastResourceSnapshot)}`);
|
|
1842
|
+
}
|
|
1813
1843
|
/**
|
|
1814
1844
|
* Gracefully hand off to a fresh daemon when this one has grown too old or
|
|
1815
1845
|
* too large. Only fires when quiet — no active compaction and no in-flight
|
|
@@ -1851,6 +1881,7 @@ class Daemon {
|
|
|
1851
1881
|
this.recycleReason = ageExceeded
|
|
1852
1882
|
? `age ${(ageMs / 3600000).toFixed(1)}h > ${(MAX_LIFETIME_MS / 3600000).toFixed(1)}h`
|
|
1853
1883
|
: `footprint ${Math.round(footprintMb !== null && footprintMb !== void 0 ? footprintMb : 0)}MB > ${RSS_WATERMARK_MB}MB`;
|
|
1884
|
+
this.logResourceSnapshot("recycle-due", footprintMb);
|
|
1854
1885
|
}
|
|
1855
1886
|
// Defer while busy; re-checked on every heartbeat until it goes through.
|
|
1856
1887
|
if ((_a = this.vectorDb) === null || _a === void 0 ? void 0 : _a.isMaintenanceActive())
|
|
@@ -1868,6 +1899,7 @@ class Daemon {
|
|
|
1868
1899
|
}
|
|
1869
1900
|
return;
|
|
1870
1901
|
}
|
|
1902
|
+
this.logResourceSnapshot("recycling", footprintMb);
|
|
1871
1903
|
const forced = busy ? " (forced over in-flight work)" : "";
|
|
1872
1904
|
console.log(`[daemon] Recycling (${this.recycleReason})${forced} — handing off to a fresh daemon`);
|
|
1873
1905
|
this.recycling = true;
|
|
@@ -451,7 +451,7 @@ function runGraphRisk(db, args, signal) {
|
|
|
451
451
|
file: (_a = loc === null || loc === void 0 ? void 0 : loc.file) !== null && _a !== void 0 ? _a : "",
|
|
452
452
|
line: (_b = loc === null || loc === void 0 ? void 0 : loc.line) !== null && _b !== void 0 ? _b : 0,
|
|
453
453
|
callerCount: callers.length,
|
|
454
|
-
hasTests: tests.
|
|
454
|
+
hasTests: tests.some((test) => !test.evidence),
|
|
455
455
|
};
|
|
456
456
|
}), signal);
|
|
457
457
|
});
|
|
@@ -122,7 +122,8 @@ function startHeartbeat(conn, intervalMs = HEARTBEAT_INTERVAL_MS) {
|
|
|
122
122
|
*/
|
|
123
123
|
function handleCommand(daemon, cmd, conn) {
|
|
124
124
|
return __awaiter(this, void 0, void 0, function* () {
|
|
125
|
-
var _a
|
|
125
|
+
var _a;
|
|
126
|
+
var _b, _c, _d, _e, _f, _g, _h, _j;
|
|
126
127
|
try {
|
|
127
128
|
(0, logger_1.debug)("daemon", `ipc cmd=${cmd.cmd}${cmd.root ? ` root=${cmd.root}` : ""}`);
|
|
128
129
|
if (cmd.cmd !== "ping" &&
|
|
@@ -185,6 +186,7 @@ function handleCommand(daemon, cmd, conn) {
|
|
|
185
186
|
diskPressure: daemon.getDiskPressure(),
|
|
186
187
|
mlx: daemon.getMlxStatus(),
|
|
187
188
|
workers: daemon.workerCount(),
|
|
189
|
+
resources: (_b = (_a = daemon.resourceSnapshot) === null || _a === void 0 ? void 0 : _a.call(daemon)) !== null && _b !== void 0 ? _b : null,
|
|
188
190
|
workerThreads: config_1.WORKER_THREADS_SETTING,
|
|
189
191
|
};
|
|
190
192
|
case "optimize":
|
|
@@ -200,14 +202,14 @@ function handleCommand(daemon, cmd, conn) {
|
|
|
200
202
|
return Object.assign({ ok: true }, (yield daemon.projectStats(root)));
|
|
201
203
|
}
|
|
202
204
|
case "shutdown": {
|
|
203
|
-
const reason = String((
|
|
204
|
-
const fromPid = (
|
|
205
|
-
const fromPpid = (
|
|
206
|
-
const fromVer = (
|
|
205
|
+
const reason = String((_c = cmd.reason) !== null && _c !== void 0 ? _c : "unknown");
|
|
206
|
+
const fromPid = (_d = cmd.from_pid) !== null && _d !== void 0 ? _d : "?";
|
|
207
|
+
const fromPpid = (_e = cmd.from_ppid) !== null && _e !== void 0 ? _e : "?";
|
|
208
|
+
const fromVer = (_f = cmd.from_version) !== null && _f !== void 0 ? _f : "?";
|
|
207
209
|
const fromArgv = Array.isArray(cmd.from_argv)
|
|
208
210
|
? cmd.from_argv.join(" ")
|
|
209
211
|
: "?";
|
|
210
|
-
const fromParentCmd = (
|
|
212
|
+
const fromParentCmd = (_g = cmd.from_parent_cmd) !== null && _g !== void 0 ? _g : "?";
|
|
211
213
|
console.log(`[daemon] shutdown command received via IPC: reason=${reason} from_pid=${fromPid} from_ppid=${fromPpid} from_version=${fromVer} from_argv=[${fromArgv}] from_parent_cmd=[${fromParentCmd}]`);
|
|
212
214
|
setImmediate(() => daemon.shutdown());
|
|
213
215
|
return { ok: true };
|
|
@@ -272,8 +274,8 @@ function handleCommand(daemon, cmd, conn) {
|
|
|
272
274
|
return { ok: false, error: "invalid project_roots" };
|
|
273
275
|
}
|
|
274
276
|
for (const prefix of [
|
|
275
|
-
...((
|
|
276
|
-
...((
|
|
277
|
+
...((_h = filters === null || filters === void 0 ? void 0 : filters.inPrefixes) !== null && _h !== void 0 ? _h : []),
|
|
278
|
+
...((_j = filters === null || filters === void 0 ? void 0 : filters.excludePrefixes) !== null && _j !== void 0 ? _j : []),
|
|
277
279
|
]) {
|
|
278
280
|
(0, path_containment_1.resolveContainedPath)(projectRoot, prefix);
|
|
279
281
|
}
|