grepmax 0.26.68 → 0.26.70
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 +11 -1
- package/dist/commands/audit.js +9 -1
- package/dist/commands/mcp.js +16 -3
- package/dist/commands/project.js +7 -1
- package/dist/commands/skeleton.js +13 -10
- package/dist/commands/surprises.js +2 -0
- package/dist/lib/analysis/surprising-connections.js +16 -2
- package/dist/lib/daemon/graph-handler.js +23 -8
- package/dist/lib/daemon/rows-handler.js +48 -4
- package/dist/lib/daemon/watcher-manager.js +2 -8
- package/dist/lib/index/watcher-ignore.js +183 -0
- package/dist/lib/index/watcher.js +5 -9
- package/dist/lib/output/agent-budget.js +39 -0
- package/dist/lib/output/project-coverage.js +16 -0
- package/package.json +1 -1
- 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.70",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Robert Owens",
|
|
16
16
|
"email": "robowens@me.com"
|
package/README.md
CHANGED
|
@@ -223,6 +223,16 @@ gmax surprises --experimental --exclude generated --top 10
|
|
|
223
223
|
|
|
224
224
|
The CLI and MCP output include score, max similarity, pair count, representative symbols, directory buckets, top similarities, applied penalties, and `gmax skeleton` follow-up hints. On large monorepos, prefer `--in`/`--exclude`; the default scan is capped at 50,000 rows and user-provided `--max-rows` is capped at 100,000. If a narrow `--in` scope returns no findings, increase `--dir-depth` so subdirectories are compared inside the scope.
|
|
225
225
|
|
|
226
|
+
Surprises excludes generated code, bindings, fixtures and GraphQL schema/operation output by default. Use CLI `--include-generated` or MCP `include_generated: true` to include these families; `--include-tests` / `include_tests` controls test files separately.
|
|
227
|
+
|
|
228
|
+
### Analysis coverage and compact output
|
|
229
|
+
|
|
230
|
+
Project overviews disclose when their language, directory, role and symbol breakdowns cover the first 200,000 chunks. CLI and MCP output label sampled chunk/file counts and show the full project chunk inventory separately. Inventory counting streams only paths in small batches with an execution deadline.
|
|
231
|
+
|
|
232
|
+
Architecture audits omit unresolved same-name definitions from rankings, cycles and dead-code claims, and report the number omitted. Compact CLI audit and MCP cycle rows retain total file/edge counts and show up to eight complete paths within 1,000 characters, with an explicit omitted-file count.
|
|
233
|
+
|
|
234
|
+
CLI `gmax skeleton --agent` and MCP `code_skeleton` text show at most 120 lines / 6,000 characters per file and report omitted lines and characters. CLI human output and JSON structure remain complete.
|
|
235
|
+
|
|
226
236
|
## Background Daemon
|
|
227
237
|
|
|
228
238
|
A single daemon watches your projects via native OS file events (FSEvents/inotify). Changes are detected in sub-second and incrementally reindexed. All writes to LanceDB are routed through the daemon via IPC, eliminating lock contention.
|
|
@@ -240,7 +250,7 @@ gmax status # See all projects + watcher status
|
|
|
240
250
|
|
|
241
251
|
Adding or indexing a project can start the daemon automatically. MCP acquires primary-project watch leases lazily when an index-reading tool is used; discovery and catalog listing start no background work. 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 up to four worker processes by default, added only when work backs up, with one kept free for searches; full-table compaction and new FTS/ANN index builds are disabled by containment. Existing indexes remain readable. Host safety stops persist across restart and block heavy operations.
|
|
242
252
|
|
|
243
|
-
`gmax watch status` reports failed files, native watcher recovery or polling, dropped-event counts, and the last complete filesystem reconciliation. FSEvents dropped-event warnings keep the native stream attached and preserve the accompanying events. A reconciliation covers the gap; repeated warnings coalesce into one later scan, with a 30-second scan interval. Recovery stays visible until a complete scan covers the latest gap. Other backend errors still trigger subscription recovery; repeated terminal failures fall back to five-minute polling, where recent edits can lag. Search and MCP responses retain health warnings even after the file queue drains. Live edit batches can run during reconciliation instead of waiting for the full filesystem walk. Whole-project reindex and removal still quiesce the watcher and drain its work before changing the project. A complete reconciliation means the filesystem scan completed; queued embedding work can still be pending.
|
|
253
|
+
`gmax watch status` reports failed files, native watcher recovery or polling, dropped-event counts, and the last complete filesystem reconciliation. FSEvents dropped-event warnings keep the native stream attached and preserve the accompanying events. A reconciliation covers the gap; repeated warnings coalesce into one later scan, with a 30-second scan interval. Recovery stays visible until a complete scan covers the latest gap. Other backend errors still trigger subscription recovery; repeated terminal failures fall back to five-minute polling, where recent edits can lag. Search and MCP responses retain health warnings even after the file queue drains. On macOS, gmax also forwards up to eight existing policy-excluded directories to the native event stream, prioritizing dependency, build and development output. Discovery is bounded; remaining paths retain glob filtering and file-policy checks. Policy edits refresh exclusions, and a policy change during subscription setup is checked before keeping a directory exclusion. Live edit batches can run during reconciliation instead of waiting for the full filesystem walk. Whole-project reindex and removal still quiesce the watcher and drain its work before changing the project. A complete reconciliation means the filesystem scan completed; queued embedding work can still be pending.
|
|
244
254
|
|
|
245
255
|
Live filesystem edits take priority over background catchup and cleanup. Background batches dispatch one file at a time, with at most four catchup files or 50 cleanup paths. They yield between files when a live edit arrives or the two-second dispatch window is reached; an active file or store commit finishes before yielding. Catchup and cleanup alternate, and one background file is admitted after three consecutive live batches so cleanup continues during sustained editing. Existing retry backoff and worker limits remain in effect.
|
|
246
256
|
|
package/dist/commands/audit.js
CHANGED
|
@@ -43,8 +43,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
43
43
|
};
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
exports.audit = void 0;
|
|
46
|
+
exports.formatHuman = formatHuman;
|
|
47
|
+
exports.formatAgent = formatAgent;
|
|
46
48
|
const commander_1 = require("commander");
|
|
47
49
|
const graph_handler_1 = require("../lib/daemon/graph-handler");
|
|
50
|
+
const agent_budget_1 = require("../lib/output/agent-budget");
|
|
48
51
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
49
52
|
const exit_1 = require("../lib/utils/exit");
|
|
50
53
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
@@ -67,6 +70,8 @@ function formatCycle(files) {
|
|
|
67
70
|
function formatHuman(r) {
|
|
68
71
|
const out = [];
|
|
69
72
|
out.push(`${style.bold("Audit")} ${style.dim(`— ${r.scannedChunks} chunks across ${r.scannedFiles} files`)}`);
|
|
73
|
+
if (r.ambiguousSymbols)
|
|
74
|
+
out.push(`Unresolved symbol names omitted: ${r.ambiguousSymbols}`);
|
|
70
75
|
out.push("");
|
|
71
76
|
out.push(style.bold("God nodes") + style.dim(" (most depended-upon symbols)"));
|
|
72
77
|
if (r.godNodes.length === 0) {
|
|
@@ -122,8 +127,10 @@ function formatHuman(r) {
|
|
|
122
127
|
return out.join("\n");
|
|
123
128
|
}
|
|
124
129
|
function formatAgent(r) {
|
|
130
|
+
var _a;
|
|
125
131
|
const lines = [];
|
|
126
132
|
lines.push(`scanned\t${r.scannedChunks}\t${r.scannedFiles}`);
|
|
133
|
+
lines.push(`ambiguous_symbols_omitted\t${(_a = r.ambiguousSymbols) !== null && _a !== void 0 ? _a : 0}`);
|
|
127
134
|
for (const g of r.godNodes) {
|
|
128
135
|
const ambiguous = g.defFiles > 1 ? `\tdefs=${g.defFiles}` : "";
|
|
129
136
|
lines.push(`god\t${g.symbol}\t${g.file}:${g.line + 1}\t${g.inboundFiles}\t${g.totalRefs}${ambiguous}`);
|
|
@@ -132,7 +139,8 @@ function formatAgent(r) {
|
|
|
132
139
|
lines.push(`hub\t${h.file}\t${h.dependents}\t${h.defines}\t${h.fanOut}`);
|
|
133
140
|
}
|
|
134
141
|
for (const c of r.fileCycles) {
|
|
135
|
-
|
|
142
|
+
const shown = (0, agent_budget_1.boundedAgentList)(c.files);
|
|
143
|
+
lines.push(`cycle\t${shown.text}\t${c.files.length}\t${c.edgeCount}\tomitted_files=${shown.omitted}`);
|
|
136
144
|
}
|
|
137
145
|
for (const d of r.deadCandidates) {
|
|
138
146
|
lines.push(`dead\t${d.symbol}\t${d.file}:${d.line + 1}`);
|
package/dist/commands/mcp.js
CHANGED
|
@@ -83,9 +83,11 @@ const embedding_generation_1 = require("../lib/index/embedding-generation");
|
|
|
83
83
|
const embedding_status_1 = require("../lib/index/embedding-status");
|
|
84
84
|
const index_config_1 = require("../lib/index/index-config");
|
|
85
85
|
const syncer_1 = require("../lib/index/syncer");
|
|
86
|
+
const agent_budget_1 = require("../lib/output/agent-budget");
|
|
86
87
|
const agent_search_formatter_1 = require("../lib/output/agent-search-formatter");
|
|
87
88
|
const index_state_footer_1 = require("../lib/output/index-state-footer");
|
|
88
89
|
const mcp_results_1 = require("../lib/output/mcp-results");
|
|
90
|
+
const project_coverage_1 = require("../lib/output/project-coverage");
|
|
89
91
|
const searcher_1 = require("../lib/search/searcher");
|
|
90
92
|
const annotator_1 = require("../lib/skeleton/annotator");
|
|
91
93
|
const skeletonizer_1 = require("../lib/skeleton/skeletonizer");
|
|
@@ -1133,7 +1135,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1133
1135
|
const annotated = sourceContent
|
|
1134
1136
|
? (0, annotator_1.annotateSkeletonLines)(skeleton, sourceContent)
|
|
1135
1137
|
: skeleton;
|
|
1136
|
-
parts.push(`// ${t} (~${tokenEstimate} tokens)\n\n${annotated}`);
|
|
1138
|
+
parts.push(`// ${t} (~${tokenEstimate} tokens)\n\n${(0, agent_budget_1.boundedAgentText)(annotated)}`);
|
|
1137
1139
|
}
|
|
1138
1140
|
}
|
|
1139
1141
|
if (fmt === "json") {
|
|
@@ -1518,6 +1520,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1518
1520
|
}
|
|
1519
1521
|
const lines = [];
|
|
1520
1522
|
lines.push(`Audit — ${audit.scannedChunks} chunks across ${audit.scannedFiles} files`);
|
|
1523
|
+
if (audit.ambiguousSymbols)
|
|
1524
|
+
lines.push(`Unresolved symbol names omitted: ${audit.ambiguousSymbols}`);
|
|
1521
1525
|
lines.push("");
|
|
1522
1526
|
lines.push("God nodes (most depended-upon symbols):");
|
|
1523
1527
|
for (const g of audit.godNodes) {
|
|
@@ -1535,7 +1539,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1535
1539
|
lines.push("");
|
|
1536
1540
|
lines.push("File dependency cycles (symbol-derived):");
|
|
1537
1541
|
for (const c of audit.fileCycles) {
|
|
1538
|
-
|
|
1542
|
+
const shown = (0, agent_budget_1.boundedAgentList)(c.files);
|
|
1543
|
+
lines.push(` ${shown.text} - ${c.files.length} files, ${c.edgeCount} internal edges; omitted_files=${shown.omitted}`);
|
|
1539
1544
|
}
|
|
1540
1545
|
if (audit.fileCycles.length === 0)
|
|
1541
1546
|
lines.push(" none");
|
|
@@ -1581,6 +1586,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1581
1586
|
maxRows,
|
|
1582
1587
|
includeTests: Boolean(args.include_tests),
|
|
1583
1588
|
includeEval: Boolean(args.include_eval),
|
|
1589
|
+
includeGenerated: Boolean(args.include_generated),
|
|
1584
1590
|
in: typeof args.in === "string" ? [args.in] : undefined,
|
|
1585
1591
|
exclude: typeof args.exclude === "string" ? [args.exclude] : undefined,
|
|
1586
1592
|
};
|
|
@@ -1990,7 +1996,10 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1990
1996
|
const pct = (count) => Math.round((count / overview.chunks) * 100);
|
|
1991
1997
|
const lines = [];
|
|
1992
1998
|
lines.push(`Project: ${projectName} (${root})`);
|
|
1993
|
-
lines.push(`Last indexed: ${(_a = proj.lastIndexed) !== null && _a !== void 0 ? _a : "unknown"} • ${
|
|
1999
|
+
lines.push(`Last indexed: ${(_a = proj.lastIndexed) !== null && _a !== void 0 ? _a : "unknown"} • ${(0, project_coverage_1.projectCounts)(overview)}`);
|
|
2000
|
+
const coverage = (0, project_coverage_1.projectCoverageNotice)(overview);
|
|
2001
|
+
if (coverage)
|
|
2002
|
+
lines.push(`Coverage: ${coverage}`);
|
|
1994
2003
|
lines.push("");
|
|
1995
2004
|
lines.push(`Languages: ${overview.extEntries
|
|
1996
2005
|
.map(([ext, count]) => `${ext} (${pct(count)}%)`)
|
|
@@ -2976,6 +2985,10 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2976
2985
|
.boolean()
|
|
2977
2986
|
.describe("Include eval/experiment/script files")
|
|
2978
2987
|
.optional(),
|
|
2988
|
+
include_generated: zod_1.z
|
|
2989
|
+
.boolean()
|
|
2990
|
+
.describe("Include generated code, bindings and fixtures")
|
|
2991
|
+
.optional(),
|
|
2979
2992
|
},
|
|
2980
2993
|
}, handleSurprisingConnections);
|
|
2981
2994
|
tool("get_neighbors", {
|
package/dist/commands/project.js
CHANGED
|
@@ -46,6 +46,7 @@ exports.project = void 0;
|
|
|
46
46
|
const path = __importStar(require("node:path"));
|
|
47
47
|
const commander_1 = require("commander");
|
|
48
48
|
const rows_handler_1 = require("../lib/daemon/rows-handler");
|
|
49
|
+
const project_coverage_1 = require("../lib/output/project-coverage");
|
|
49
50
|
const daemon_client_1 = require("../lib/utils/daemon-client");
|
|
50
51
|
const exit_1 = require("../lib/utils/exit");
|
|
51
52
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
@@ -80,9 +81,12 @@ exports.project = new commander_1.Command("project")
|
|
|
80
81
|
const projects = (0, project_registry_1.listProjects)();
|
|
81
82
|
const proj = projects.find((p) => p.root === root);
|
|
82
83
|
const { chunks, files, extEntries, dirEntries, roleEntries, topSymbols, entryPoints, } = overview;
|
|
84
|
+
const coverage = (0, project_coverage_1.projectCoverageNotice)(overview);
|
|
83
85
|
if (opts.agent) {
|
|
84
86
|
console.log(`name\t${projectName}`);
|
|
85
87
|
console.log(`root\t${root}`);
|
|
88
|
+
if (coverage)
|
|
89
|
+
console.log(`coverage\t${coverage}`);
|
|
86
90
|
console.log(`chunks\t${chunks}`);
|
|
87
91
|
console.log(`files\t${files}`);
|
|
88
92
|
console.log(`last_indexed\t${(_b = proj === null || proj === void 0 ? void 0 : proj.lastIndexed) !== null && _b !== void 0 ? _b : "unknown"}`);
|
|
@@ -100,7 +104,9 @@ exports.project = new commander_1.Command("project")
|
|
|
100
104
|
}
|
|
101
105
|
else {
|
|
102
106
|
console.log(`Project: ${projectName} (${root})`);
|
|
103
|
-
console.log(`Last indexed: ${(_c = proj === null || proj === void 0 ? void 0 : proj.lastIndexed) !== null && _c !== void 0 ? _c : "unknown"} • ${
|
|
107
|
+
console.log(`Last indexed: ${(_c = proj === null || proj === void 0 ? void 0 : proj.lastIndexed) !== null && _c !== void 0 ? _c : "unknown"} • ${(0, project_coverage_1.projectCounts)(overview)}\n`);
|
|
108
|
+
if (coverage)
|
|
109
|
+
console.log(`Coverage: ${coverage}\n`);
|
|
104
110
|
console.log(`Languages: ${extEntries.map(([ext, count]) => `${ext} (${Math.round((count / chunks) * 100)}%)`).join(", ")}\n`);
|
|
105
111
|
console.log("Directory structure:");
|
|
106
112
|
for (const [dir, data] of dirEntries) {
|
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* gmax skeleton - Show code skeleton (signatures without implementation)
|
|
4
|
-
*
|
|
5
|
-
* Usage:
|
|
6
|
-
* gmax skeleton <file> # Skeleton of a file
|
|
7
|
-
* gmax skeleton <symbol> # Find symbol and skeleton its file
|
|
8
|
-
* gmax skeleton "query" # Search and skeleton top results
|
|
9
|
-
*/
|
|
10
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
3
|
if (k2 === undefined) k2 = k;
|
|
12
4
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -51,6 +43,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
51
43
|
};
|
|
52
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
45
|
exports.skeleton = void 0;
|
|
46
|
+
const agent_budget_1 = require("../lib/output/agent-budget");
|
|
47
|
+
/**
|
|
48
|
+
* gmax skeleton - Show code skeleton (signatures without implementation)
|
|
49
|
+
*
|
|
50
|
+
* Usage:
|
|
51
|
+
* gmax skeleton <file> # Skeleton of a file
|
|
52
|
+
* gmax skeleton <symbol> # Find symbol and skeleton its file
|
|
53
|
+
* gmax skeleton "query" # Search and skeleton top results
|
|
54
|
+
*/
|
|
54
55
|
const fs = __importStar(require("node:fs"));
|
|
55
56
|
const commander_1 = require("commander");
|
|
56
57
|
const config_1 = require("../config");
|
|
@@ -392,7 +393,9 @@ Examples:
|
|
|
392
393
|
}
|
|
393
394
|
else {
|
|
394
395
|
for (const result of results) {
|
|
395
|
-
console.log(
|
|
396
|
+
console.log(options.agent
|
|
397
|
+
? (0, agent_budget_1.boundedAgentText)(result.skeleton)
|
|
398
|
+
: result.skeleton);
|
|
396
399
|
console.log(""); // Blank line between files
|
|
397
400
|
}
|
|
398
401
|
}
|
|
@@ -423,6 +426,6 @@ function outputResult(result, options) {
|
|
|
423
426
|
}, null, 2));
|
|
424
427
|
}
|
|
425
428
|
else {
|
|
426
|
-
console.log(result.skeleton);
|
|
429
|
+
console.log(options.agent ? (0, agent_budget_1.boundedAgentText)(result.skeleton) : result.skeleton);
|
|
427
430
|
}
|
|
428
431
|
}
|
|
@@ -132,6 +132,7 @@ exports.surprises = new commander_1.Command("surprises")
|
|
|
132
132
|
.option("--min-sim <n>", "Minimum similarity 0-1", String(surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.minSimilarity))
|
|
133
133
|
.option("--max-rows <n>", `Maximum indexed rows to scan (capped at ${surprising_connections_1.MAX_SURPRISE_ROWS})`, String(surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.maxRows))
|
|
134
134
|
.option("--include-tests", "Include test files", false)
|
|
135
|
+
.option("--include-generated", "Include generated code, bindings and fixtures", false)
|
|
135
136
|
.option("--include-eval", "Include eval/experiment/script files", false)
|
|
136
137
|
.option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
|
|
137
138
|
.option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
|
|
@@ -180,6 +181,7 @@ exports.surprises = new commander_1.Command("surprises")
|
|
|
180
181
|
maxRows: parseIntOption(opts.maxRows, surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.maxRows, surprising_connections_1.MAX_SURPRISE_ROWS),
|
|
181
182
|
includeTests: Boolean(opts.includeTests),
|
|
182
183
|
includeEval: Boolean(opts.includeEval),
|
|
184
|
+
includeGenerated: Boolean(opts.includeGenerated),
|
|
183
185
|
in: analysisIn,
|
|
184
186
|
exclude: analysisExclude,
|
|
185
187
|
};
|
|
@@ -45,6 +45,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
45
45
|
exports.SURPRISE_COLUMNS = exports.MAX_SURPRISE_ROWS = exports.DEFAULT_SURPRISE_OPTIONS = void 0;
|
|
46
46
|
exports.normalizeSurpriseOptions = normalizeSurpriseOptions;
|
|
47
47
|
exports.isTestPath = isTestPath;
|
|
48
|
+
exports.isGeneratedPath = isGeneratedPath;
|
|
48
49
|
exports.isEvalPath = isEvalPath;
|
|
49
50
|
exports.relPath = relPath;
|
|
50
51
|
exports.lineLabel = lineLabel;
|
|
@@ -73,6 +74,7 @@ exports.DEFAULT_SURPRISE_OPTIONS = {
|
|
|
73
74
|
maxRows: 50000,
|
|
74
75
|
includeTests: false,
|
|
75
76
|
includeEval: false,
|
|
77
|
+
includeGenerated: false,
|
|
76
78
|
};
|
|
77
79
|
exports.MAX_SURPRISE_ROWS = 100000;
|
|
78
80
|
/**
|
|
@@ -152,8 +154,8 @@ const TEST_FILE_RE = /\.(test|spec)\.[cm]?[jt]sx?$/i;
|
|
|
152
154
|
const NATIVE_TEST_DIR_RE = /(^|\/)\w+Tests?(\/|$)/;
|
|
153
155
|
const NATIVE_TEST_FILE_RE = /Tests?\.(swift|kt|java)$/;
|
|
154
156
|
function normalizeSurpriseOptions(opts = {}) {
|
|
155
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
156
|
-
return Object.assign(Object.assign(Object.assign({}, exports.DEFAULT_SURPRISE_OPTIONS), opts), { sample: Math.max(1, Math.floor((_a = opts.sample) !== null && _a !== void 0 ? _a : exports.DEFAULT_SURPRISE_OPTIONS.sample)), neighbors: Math.max(1, Math.floor((_b = opts.neighbors) !== null && _b !== void 0 ? _b : exports.DEFAULT_SURPRISE_OPTIONS.neighbors)), dirDepth: Math.max(1, Math.floor((_c = opts.dirDepth) !== null && _c !== void 0 ? _c : exports.DEFAULT_SURPRISE_OPTIONS.dirDepth)), minSimilarity: Math.max(0, (_d = opts.minSimilarity) !== null && _d !== void 0 ? _d : exports.DEFAULT_SURPRISE_OPTIONS.minSimilarity), maxRows: Math.max(1, Math.min(exports.MAX_SURPRISE_ROWS, Math.floor((_e = opts.maxRows) !== null && _e !== void 0 ? _e : exports.DEFAULT_SURPRISE_OPTIONS.maxRows))), includeTests: (_f = opts.includeTests) !== null && _f !== void 0 ? _f : exports.DEFAULT_SURPRISE_OPTIONS.includeTests, includeEval: (_g = opts.includeEval) !== null && _g !== void 0 ? _g : exports.DEFAULT_SURPRISE_OPTIONS.includeEval });
|
|
157
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
158
|
+
return Object.assign(Object.assign(Object.assign({}, exports.DEFAULT_SURPRISE_OPTIONS), opts), { sample: Math.max(1, Math.floor((_a = opts.sample) !== null && _a !== void 0 ? _a : exports.DEFAULT_SURPRISE_OPTIONS.sample)), neighbors: Math.max(1, Math.floor((_b = opts.neighbors) !== null && _b !== void 0 ? _b : exports.DEFAULT_SURPRISE_OPTIONS.neighbors)), dirDepth: Math.max(1, Math.floor((_c = opts.dirDepth) !== null && _c !== void 0 ? _c : exports.DEFAULT_SURPRISE_OPTIONS.dirDepth)), minSimilarity: Math.max(0, (_d = opts.minSimilarity) !== null && _d !== void 0 ? _d : exports.DEFAULT_SURPRISE_OPTIONS.minSimilarity), maxRows: Math.max(1, Math.min(exports.MAX_SURPRISE_ROWS, Math.floor((_e = opts.maxRows) !== null && _e !== void 0 ? _e : exports.DEFAULT_SURPRISE_OPTIONS.maxRows))), includeTests: (_f = opts.includeTests) !== null && _f !== void 0 ? _f : exports.DEFAULT_SURPRISE_OPTIONS.includeTests, includeEval: (_g = opts.includeEval) !== null && _g !== void 0 ? _g : exports.DEFAULT_SURPRISE_OPTIONS.includeEval, includeGenerated: (_h = opts.includeGenerated) !== null && _h !== void 0 ? _h : false });
|
|
157
159
|
}
|
|
158
160
|
function isTestPath(filePath) {
|
|
159
161
|
return (TEST_DIR_RE.test(filePath) ||
|
|
@@ -161,6 +163,11 @@ function isTestPath(filePath) {
|
|
|
161
163
|
NATIVE_TEST_DIR_RE.test(filePath) ||
|
|
162
164
|
NATIVE_TEST_FILE_RE.test(filePath));
|
|
163
165
|
}
|
|
166
|
+
function isGeneratedPath(filePath) {
|
|
167
|
+
return (/(^|\/)(?:generated|__generated__|bindings|fixtures|__fixtures__)(\/|$)/i.test(filePath) ||
|
|
168
|
+
/\.generated\.[^.]+$/i.test(filePath) ||
|
|
169
|
+
/\/[^/]*GraphQL\/Sources\/(?:Schema|Operations)\//i.test(filePath));
|
|
170
|
+
}
|
|
164
171
|
function isEvalPath(filePath) {
|
|
165
172
|
return (/(^|\/)src\/eval[^/]*\.ts$/i.test(filePath) ||
|
|
166
173
|
/(^|\/)(benchmarks?|experiments|scripts)(\/|$)/i.test(filePath));
|
|
@@ -477,6 +484,8 @@ function filterableCodeMeta(row, opts) {
|
|
|
477
484
|
return false;
|
|
478
485
|
if (!opts.includeEval && isEvalPath(row.relPath))
|
|
479
486
|
return false;
|
|
487
|
+
if (!opts.includeGenerated && isGeneratedPath(row.relPath))
|
|
488
|
+
return false;
|
|
480
489
|
return row.definedSymbols.length > 0;
|
|
481
490
|
}
|
|
482
491
|
function hasCodeContent(content) {
|
|
@@ -633,6 +642,7 @@ function analyzeSurprisingConnections(table_1, projectRoot_1) {
|
|
|
633
642
|
weakCode: 0,
|
|
634
643
|
tests: 0,
|
|
635
644
|
evalHarness: 0,
|
|
645
|
+
generated: 0,
|
|
636
646
|
sameDirBucket: 0,
|
|
637
647
|
graphEdge: 0,
|
|
638
648
|
belowThreshold: 0,
|
|
@@ -671,6 +681,10 @@ function analyzeSurprisingConnections(table_1, projectRoot_1) {
|
|
|
671
681
|
filters.tests++;
|
|
672
682
|
continue;
|
|
673
683
|
}
|
|
684
|
+
if (!opts.includeGenerated && isGeneratedPath(target.relPath)) {
|
|
685
|
+
filters.generated++;
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
674
688
|
if (!opts.includeEval && isEvalPath(target.relPath)) {
|
|
675
689
|
filters.evalHarness++;
|
|
676
690
|
continue;
|
|
@@ -537,13 +537,15 @@ function findFileCycles(deps, prefix, top) {
|
|
|
537
537
|
* `top` caps each list; `deadTotal` reports the full pre-cap dead count.
|
|
538
538
|
*/
|
|
539
539
|
function computeAudit(rows, prefix, top) {
|
|
540
|
-
var _a;
|
|
541
|
-
var
|
|
542
|
-
|
|
540
|
+
var _a, _b, _c, _d, _e, _f;
|
|
541
|
+
var _g;
|
|
542
|
+
var _h;
|
|
543
|
+
// Only a uniquely defined name can establish an architectural edge.
|
|
543
544
|
const defs = new Map();
|
|
544
545
|
// Distinct files defining each name — name-based edges can't tell same-name
|
|
545
546
|
// symbols apart, so multi-file definitions get flagged in the output.
|
|
546
547
|
const defFileCounts = new Map();
|
|
548
|
+
const defLocations = new Map();
|
|
547
549
|
// Distinct files that reference a symbol (cross-file inbound edges).
|
|
548
550
|
const inboundFiles = new Map();
|
|
549
551
|
const inboundTotal = new Map();
|
|
@@ -562,9 +564,15 @@ function computeAudit(rows, prefix, top) {
|
|
|
562
564
|
if (!defs.has(s)) {
|
|
563
565
|
defs.set(s, { file, line, exported, complexity: 0 });
|
|
564
566
|
}
|
|
567
|
+
else {
|
|
568
|
+
(_h = defs.get(s)).exported || (_h.exported = exported);
|
|
569
|
+
}
|
|
565
570
|
if (!defFileCounts.has(s))
|
|
566
571
|
defFileCounts.set(s, new Set());
|
|
567
572
|
defFileCounts.get(s).add(file);
|
|
573
|
+
if (!defLocations.has(s))
|
|
574
|
+
defLocations.set(s, new Set());
|
|
575
|
+
defLocations.get(s).add(`${file}:${line}`);
|
|
568
576
|
if (!fileDefs.has(file))
|
|
569
577
|
fileDefs.set(file, new Set());
|
|
570
578
|
fileDefs.get(file).add(s);
|
|
@@ -586,7 +594,7 @@ function computeAudit(rows, prefix, top) {
|
|
|
586
594
|
continue;
|
|
587
595
|
// Builtin method names (get, set, push, …) leak in via prototype/member
|
|
588
596
|
// definitions and their inbound counts are meaningless name collisions.
|
|
589
|
-
if ((0, callsites_1.isBuiltinCallee)(symbol))
|
|
597
|
+
if ((0, callsites_1.isBuiltinCallee)(symbol) || ((_a = defLocations.get(symbol)) === null || _a === void 0 ? void 0 : _a.size) !== 1)
|
|
590
598
|
continue;
|
|
591
599
|
const refFiles = inboundFiles.get(symbol);
|
|
592
600
|
if (!refFiles)
|
|
@@ -603,7 +611,7 @@ function computeAudit(rows, prefix, top) {
|
|
|
603
611
|
line: info.line,
|
|
604
612
|
inboundFiles: external,
|
|
605
613
|
totalRefs: inboundTotal.get(symbol) || 0,
|
|
606
|
-
defFiles: (
|
|
614
|
+
defFiles: (_g = (_b = defFileCounts.get(symbol)) === null || _b === void 0 ? void 0 : _b.size) !== null && _g !== void 0 ? _g : 1,
|
|
607
615
|
});
|
|
608
616
|
}
|
|
609
617
|
godNodes.sort((a, b) => b.inboundFiles - a.inboundFiles || b.totalRefs - a.totalRefs);
|
|
@@ -613,6 +621,8 @@ function computeAudit(rows, prefix, top) {
|
|
|
613
621
|
for (const [file, syms] of fileDefs) {
|
|
614
622
|
const dependents = new Set();
|
|
615
623
|
for (const s of syms) {
|
|
624
|
+
if ((0, callsites_1.isBuiltinCallee)(s) || ((_c = defLocations.get(s)) === null || _c === void 0 ? void 0 : _c.size) !== 1)
|
|
625
|
+
continue;
|
|
616
626
|
const refFiles = inboundFiles.get(s);
|
|
617
627
|
if (!refFiles)
|
|
618
628
|
continue;
|
|
@@ -626,7 +636,9 @@ function computeAudit(rows, prefix, top) {
|
|
|
626
636
|
const out = fileOutRefs.get(file);
|
|
627
637
|
if (out)
|
|
628
638
|
for (const s of out)
|
|
629
|
-
if (defs.has(s)
|
|
639
|
+
if (defs.has(s) &&
|
|
640
|
+
!(0, callsites_1.isBuiltinCallee)(s) &&
|
|
641
|
+
((_d = defLocations.get(s)) === null || _d === void 0 ? void 0 : _d.size) === 1)
|
|
630
642
|
fanOut++;
|
|
631
643
|
hubFiles.push({
|
|
632
644
|
file: rel(file, prefix),
|
|
@@ -639,7 +651,7 @@ function computeAudit(rows, prefix, top) {
|
|
|
639
651
|
const fileDeps = new Map();
|
|
640
652
|
for (const [file, refs] of fileOutRefs) {
|
|
641
653
|
for (const s of refs) {
|
|
642
|
-
if ((0, callsites_1.isBuiltinCallee)(s))
|
|
654
|
+
if ((0, callsites_1.isBuiltinCallee)(s) || ((_e = defLocations.get(s)) === null || _e === void 0 ? void 0 : _e.size) !== 1)
|
|
643
655
|
continue;
|
|
644
656
|
const defFiles = defFileCounts.get(s);
|
|
645
657
|
if (!defFiles || defFiles.size !== 1)
|
|
@@ -657,7 +669,9 @@ function computeAudit(rows, prefix, top) {
|
|
|
657
669
|
// references anywhere (including their own file).
|
|
658
670
|
const deadAll = [];
|
|
659
671
|
for (const [symbol, info] of defs) {
|
|
660
|
-
if (info.exported
|
|
672
|
+
if (info.exported ||
|
|
673
|
+
((_f = defLocations.get(symbol)) === null || _f === void 0 ? void 0 : _f.size) !== 1 ||
|
|
674
|
+
(0, callsites_1.isBuiltinCallee)(symbol))
|
|
661
675
|
continue;
|
|
662
676
|
if ((inboundTotal.get(symbol) || 0) > 0)
|
|
663
677
|
continue;
|
|
@@ -665,6 +679,7 @@ function computeAudit(rows, prefix, top) {
|
|
|
665
679
|
}
|
|
666
680
|
deadAll.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|
|
667
681
|
return {
|
|
682
|
+
ambiguousSymbols: [...defLocations.values()].filter((locations) => locations.size > 1).length,
|
|
668
683
|
scannedChunks: rows.length,
|
|
669
684
|
scannedFiles: files.size,
|
|
670
685
|
godNodes: godNodes.slice(0, top),
|
|
@@ -41,8 +41,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
41
41
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
42
|
});
|
|
43
43
|
};
|
|
44
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
45
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
46
|
+
var m = o[Symbol.asyncIterator], i;
|
|
47
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
48
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
49
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
50
|
+
};
|
|
44
51
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
-
exports.MAX_LOCATE_ROWS = exports.ReadVerbError = void 0;
|
|
52
|
+
exports.MAX_LOCATE_ROWS = exports.MAX_PROJECT_ROWS = exports.ReadVerbError = void 0;
|
|
46
53
|
exports.withLocalStore = withLocalStore;
|
|
47
54
|
exports.scopeToWire = scopeToWire;
|
|
48
55
|
exports.assertReadableProject = assertReadableProject;
|
|
@@ -51,6 +58,7 @@ exports.clampInt = clampInt;
|
|
|
51
58
|
exports.asReadVerbResponse = asReadVerbResponse;
|
|
52
59
|
exports.runSymbols = runSymbols;
|
|
53
60
|
exports.handleRowsSymbols = handleRowsSymbols;
|
|
61
|
+
exports.countProjectRows = countProjectRows;
|
|
54
62
|
exports.runProject = runProject;
|
|
55
63
|
exports.handleRowsProject = handleRowsProject;
|
|
56
64
|
exports.runLocate = runLocate;
|
|
@@ -270,15 +278,42 @@ function handleRowsSymbols(deps, payload) {
|
|
|
270
278
|
}));
|
|
271
279
|
});
|
|
272
280
|
}
|
|
281
|
+
// --- rows.project -----------------------------------------------------------
|
|
282
|
+
exports.MAX_PROJECT_ROWS = 200000;
|
|
283
|
+
/** Count a path-only stream in bounded Arrow batches with a native deadline.
|
|
284
|
+
* The SDK's countRows API has no execution timeout. */
|
|
285
|
+
function countProjectRows(query, timeoutMs) {
|
|
286
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
287
|
+
var _a, e_1, _b, _c;
|
|
288
|
+
let count = 0;
|
|
289
|
+
try {
|
|
290
|
+
for (var _d = true, _e = __asyncValues((0, query_timeout_1.streamQueryRows)(query, "project inventory count", timeoutMs)), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
291
|
+
_c = _f.value;
|
|
292
|
+
_d = false;
|
|
293
|
+
const _row = _c;
|
|
294
|
+
count++;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
298
|
+
finally {
|
|
299
|
+
try {
|
|
300
|
+
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
301
|
+
}
|
|
302
|
+
finally { if (e_1) throw e_1.error; }
|
|
303
|
+
}
|
|
304
|
+
return count;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
273
307
|
/**
|
|
274
308
|
* The `project` scan and its aggregation. Returns the finished overview rather
|
|
275
309
|
* than the (up to 200 000) rows it was computed from — see the file header.
|
|
276
310
|
*/
|
|
277
311
|
function runProject(deps, projectRoot) {
|
|
278
312
|
return __awaiter(this, void 0, void 0, function* () {
|
|
313
|
+
var _a;
|
|
279
314
|
const table = yield tableOf(deps);
|
|
280
315
|
const prefix = projectRoot.endsWith("/") ? projectRoot : `${projectRoot}/`;
|
|
281
|
-
const
|
|
316
|
+
const scanned = yield table
|
|
282
317
|
.query()
|
|
283
318
|
.select([
|
|
284
319
|
"path",
|
|
@@ -289,8 +324,15 @@ function runProject(deps, projectRoot) {
|
|
|
289
324
|
"referenced_symbols",
|
|
290
325
|
])
|
|
291
326
|
.where((0, filter_builder_1.pathStartsWith)(prefix))
|
|
292
|
-
.limit(
|
|
293
|
-
.toArray(
|
|
327
|
+
.limit(exports.MAX_PROJECT_ROWS + 1)
|
|
328
|
+
.toArray({
|
|
329
|
+
timeoutMs: (_a = deps.queryTimeoutMs) !== null && _a !== void 0 ? _a : query_timeout_1.QUERY_EXECUTION_OPTIONS.timeoutMs,
|
|
330
|
+
});
|
|
331
|
+
const sampled = scanned.length > exports.MAX_PROJECT_ROWS;
|
|
332
|
+
const rows = sampled ? scanned.slice(0, exports.MAX_PROJECT_ROWS) : scanned;
|
|
333
|
+
const totalChunks = sampled
|
|
334
|
+
? yield countProjectRows(table.query().select(["path"]).where((0, filter_builder_1.pathStartsWith)(prefix)), deps.queryTimeoutMs)
|
|
335
|
+
: rows.length;
|
|
294
336
|
const nodePath = yield Promise.resolve().then(() => __importStar(require("node:path")));
|
|
295
337
|
const files = new Set();
|
|
296
338
|
const extCounts = new Map();
|
|
@@ -343,6 +385,8 @@ function runProject(deps, projectRoot) {
|
|
|
343
385
|
}
|
|
344
386
|
}
|
|
345
387
|
return {
|
|
388
|
+
sampled,
|
|
389
|
+
totalChunks,
|
|
346
390
|
chunks: rows.length,
|
|
347
391
|
files: files.size,
|
|
348
392
|
extEntries: Array.from(extCounts.entries())
|
|
@@ -51,7 +51,6 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
|
51
51
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
52
|
exports.WatcherManager = void 0;
|
|
53
53
|
const path = __importStar(require("node:path"));
|
|
54
|
-
const watcher = __importStar(require("@parcel/watcher"));
|
|
55
54
|
const batch_processor_1 = require("../index/batch-processor");
|
|
56
55
|
const cache_coherence_1 = require("../index/cache-coherence");
|
|
57
56
|
const file_policy_1 = require("../index/file-policy");
|
|
@@ -327,7 +326,7 @@ class WatcherManager {
|
|
|
327
326
|
catch (_c) { }
|
|
328
327
|
this.deps.subscriptions.delete(root);
|
|
329
328
|
}
|
|
330
|
-
const sub = yield
|
|
329
|
+
const { subscription: sub } = yield (0, watcher_ignore_1.subscribeWithNativeExclusions)(root, (err, events) => {
|
|
331
330
|
var _a, _b;
|
|
332
331
|
var _c, _d;
|
|
333
332
|
if (this.deps.processors.get(root) !== processor ||
|
|
@@ -360,12 +359,7 @@ class WatcherManager {
|
|
|
360
359
|
processor.handleFileEvent(event.type === "delete" ? "unlink" : "change", event.path);
|
|
361
360
|
}
|
|
362
361
|
this.deps.touchActivity();
|
|
363
|
-
},
|
|
364
|
-
ignore: [
|
|
365
|
-
...watcher_1.WATCHER_IGNORE_GLOBS,
|
|
366
|
-
...(yield (0, watcher_ignore_1.readProjectWatcherIgnores)(root)),
|
|
367
|
-
],
|
|
368
|
-
});
|
|
362
|
+
}, watcher_1.WATCHER_IGNORE_GLOBS);
|
|
369
363
|
if (this.deps.processors.get(root) !== processor ||
|
|
370
364
|
this.watcherGenerations.get(root) !== generation) {
|
|
371
365
|
yield sub.unsubscribe();
|
|
@@ -41,15 +41,27 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
41
41
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
42
|
});
|
|
43
43
|
};
|
|
44
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
45
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
46
|
+
var m = o[Symbol.asyncIterator], i;
|
|
47
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
48
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
49
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
50
|
+
};
|
|
44
51
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
45
52
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
53
|
};
|
|
47
54
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
55
|
exports.nativeProjectIgnoreGlobs = nativeProjectIgnoreGlobs;
|
|
49
56
|
exports.readProjectWatcherIgnores = readProjectWatcherIgnores;
|
|
57
|
+
exports.readNativeWatcherIgnores = readNativeWatcherIgnores;
|
|
58
|
+
exports.watcherIgnoreIdentity = watcherIgnoreIdentity;
|
|
59
|
+
exports.subscribeWithNativeExclusions = subscribeWithNativeExclusions;
|
|
50
60
|
const fs = __importStar(require("node:fs/promises"));
|
|
51
61
|
const path = __importStar(require("node:path"));
|
|
62
|
+
const watcher = __importStar(require("@parcel/watcher"));
|
|
52
63
|
const ignore_1 = __importDefault(require("ignore"));
|
|
64
|
+
const file_policy_1 = require("./file-policy");
|
|
53
65
|
/** Only the common positive gitignore/glob subset is safe to push into Parcel.
|
|
54
66
|
* Negations, escapes and unsupported syntax stay with ProjectFilePolicy, so a
|
|
55
67
|
* native ignore can never hide a file that an explicit re-inclusion needs. */
|
|
@@ -102,3 +114,174 @@ function readProjectWatcherIgnores(root) {
|
|
|
102
114
|
}
|
|
103
115
|
});
|
|
104
116
|
}
|
|
117
|
+
const NATIVE_EXCLUSION_LIMIT = 8;
|
|
118
|
+
const DISCOVERY_DIRECTORY_LIMIT = 512;
|
|
119
|
+
const DISCOVERY_ENTRY_LIMIT = 16384;
|
|
120
|
+
const DISCOVERY_DEPTH_LIMIT = 4;
|
|
121
|
+
const DISCOVERY_TIME_MS = 1000;
|
|
122
|
+
/** FSEvents only receives Parcel's literal paths, not its glob filters. Select
|
|
123
|
+
* bounded, existing directories that file policy already excludes. Never hide
|
|
124
|
+
* a source ancestor merely because some descendants are ignored. */
|
|
125
|
+
function readNativeWatcherIgnores(root, baseGlobs) {
|
|
126
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
127
|
+
var _a, e_1, _b, _c;
|
|
128
|
+
const policy = new file_policy_1.ProjectFilePolicy(root);
|
|
129
|
+
const candidates = [];
|
|
130
|
+
const discoveryPriority = (directory, depth) => {
|
|
131
|
+
const parts = path.relative(root, directory).split(path.sep);
|
|
132
|
+
// Package containers often hold nested build roots. Inspect those before
|
|
133
|
+
// descending authored source trees, which can exhaust the entry budget.
|
|
134
|
+
const container = parts.some((part) => ["packages", "apps", "services", "ios", "android", "macos"].includes(part));
|
|
135
|
+
const source = parts.some((part) => ["src", "source", "Sources", "tests", "test", "Tests"].includes(part));
|
|
136
|
+
return (container ? 1000 : 0) - (source ? 2000 : 0) - depth;
|
|
137
|
+
};
|
|
138
|
+
const pending = [{ directory: root, depth: 0 }];
|
|
139
|
+
const deadline = performance.now() + DISCOVERY_TIME_MS;
|
|
140
|
+
let entries = 0;
|
|
141
|
+
let visited = 0;
|
|
142
|
+
for (let cursor = 0; cursor < pending.length; cursor++) {
|
|
143
|
+
if (visited++ >= DISCOVERY_DIRECTORY_LIMIT || performance.now() > deadline)
|
|
144
|
+
break;
|
|
145
|
+
let next = cursor;
|
|
146
|
+
for (let i = cursor + 1; i < pending.length; i++) {
|
|
147
|
+
if (discoveryPriority(pending[i].directory, pending[i].depth) >
|
|
148
|
+
discoveryPriority(pending[next].directory, pending[next].depth))
|
|
149
|
+
next = i;
|
|
150
|
+
}
|
|
151
|
+
[pending[cursor], pending[next]] = [pending[next], pending[cursor]];
|
|
152
|
+
const { directory, depth } = pending[cursor];
|
|
153
|
+
try {
|
|
154
|
+
const dir = yield fs.opendir(directory);
|
|
155
|
+
try {
|
|
156
|
+
for (var _d = true, dir_1 = (e_1 = void 0, __asyncValues(dir)), dir_1_1; dir_1_1 = yield dir_1.next(), _a = dir_1_1.done, !_a; _d = true) {
|
|
157
|
+
_c = dir_1_1.value;
|
|
158
|
+
_d = false;
|
|
159
|
+
const entry = _c;
|
|
160
|
+
if (++entries > DISCOVERY_ENTRY_LIMIT || performance.now() > deadline)
|
|
161
|
+
break;
|
|
162
|
+
if (!entry.isDirectory() || entry.isSymbolicLink())
|
|
163
|
+
continue;
|
|
164
|
+
const absolute = path.join(directory, entry.name);
|
|
165
|
+
const classified = yield policy.classifyDirectory(absolute);
|
|
166
|
+
if (classified.status === "excluded") {
|
|
167
|
+
if (!["default ignore policy", "project ignore policy"].includes(classified.reason))
|
|
168
|
+
continue;
|
|
169
|
+
const relative = path
|
|
170
|
+
.relative(root, absolute)
|
|
171
|
+
.split(path.sep)
|
|
172
|
+
.join("/");
|
|
173
|
+
// Public Parcel options distinguish literals from globs. Skip names
|
|
174
|
+
// it could interpret as a pattern; existing glob filtering remains.
|
|
175
|
+
if (!/^[A-Za-z0-9_. /-]+$/.test(relative))
|
|
176
|
+
continue;
|
|
177
|
+
const priority = entry.name === ".git" || entry.name === "node_modules"
|
|
178
|
+
? depth === 0
|
|
179
|
+
? 1000
|
|
180
|
+
: 500
|
|
181
|
+
: entry.name === ".dev"
|
|
182
|
+
? depth === 0
|
|
183
|
+
? 950
|
|
184
|
+
: 550
|
|
185
|
+
: entry.name === ".build" || entry.name === "DerivedData"
|
|
186
|
+
? 800
|
|
187
|
+
: entry.name === ".next"
|
|
188
|
+
? 700
|
|
189
|
+
: depth === 0
|
|
190
|
+
? 600
|
|
191
|
+
: 200;
|
|
192
|
+
candidates.push({ relative, score: priority - depth * 10 });
|
|
193
|
+
}
|
|
194
|
+
else if (classified.status === "traverse" &&
|
|
195
|
+
depth < DISCOVERY_DEPTH_LIMIT &&
|
|
196
|
+
pending.length < DISCOVERY_DIRECTORY_LIMIT) {
|
|
197
|
+
pending.push({ directory: absolute, depth: depth + 1 });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
202
|
+
finally {
|
|
203
|
+
try {
|
|
204
|
+
if (!_d && !_a && (_b = dir_1.return)) yield _b.call(dir_1);
|
|
205
|
+
}
|
|
206
|
+
finally { if (e_1) throw e_1.error; }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch (_e) {
|
|
210
|
+
// Failed/partial discovery only loses an optimization. Existing glob
|
|
211
|
+
// delivery and authoritative file-policy classification remain in force.
|
|
212
|
+
}
|
|
213
|
+
if (entries >= DISCOVERY_ENTRY_LIMIT)
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
candidates.sort((a, b) => b.score - a.score || a.relative.localeCompare(b.relative));
|
|
217
|
+
const literalPaths = candidates
|
|
218
|
+
.slice(0, NATIVE_EXCLUSION_LIMIT)
|
|
219
|
+
.map((c) => c.relative);
|
|
220
|
+
return [...literalPaths, ...(yield nativeWatcherFilters(root, baseGlobs))];
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
function nativeWatcherFilters(root, baseGlobs) {
|
|
224
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
225
|
+
const projectGlobs = yield readProjectWatcherIgnores(root);
|
|
226
|
+
return [...baseGlobs, ...projectGlobs].map((pattern) => {
|
|
227
|
+
if (pattern.includes("*"))
|
|
228
|
+
return pattern;
|
|
229
|
+
// Keep file exclusions and excess directory literals out of ignorePaths:
|
|
230
|
+
// passing more than eight silently defeats macOS stream exclusion.
|
|
231
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
232
|
+
return new RegExp(`^${escaped}(?:/|$)`);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function verifyNativeWatcherIgnores(root, baseGlobs, ignores) {
|
|
237
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
238
|
+
const policy = new file_policy_1.ProjectFilePolicy(root);
|
|
239
|
+
const literals = [];
|
|
240
|
+
// Recheck the selected paths, rather than repeating timed discovery. A
|
|
241
|
+
// different partial scan must not cause subscription churn or disable the
|
|
242
|
+
// exclusions we already proved safe. New candidates can wait for refresh.
|
|
243
|
+
for (const pattern of ignores) {
|
|
244
|
+
if (typeof pattern !== "string" || pattern.includes("*"))
|
|
245
|
+
continue;
|
|
246
|
+
const classified = yield policy.classifyDirectory(path.join(root, pattern));
|
|
247
|
+
if (classified.status === "excluded" &&
|
|
248
|
+
["default ignore policy", "project ignore policy"].includes(classified.reason))
|
|
249
|
+
literals.push(pattern);
|
|
250
|
+
}
|
|
251
|
+
return [...literals, ...(yield nativeWatcherFilters(root, baseGlobs))];
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function watcherIgnoreIdentity(ignores) {
|
|
255
|
+
return JSON.stringify(ignores.map((pattern) => String(pattern)));
|
|
256
|
+
}
|
|
257
|
+
/** A policy edit during subscribe has no event listener yet. Check again after
|
|
258
|
+
* attaching so an old directory exclusion cannot permanently hide newly
|
|
259
|
+
* re-included source. Sustained policy churn falls back to immutable globs. */
|
|
260
|
+
function subscribeWithNativeExclusions(root, callback, baseGlobs, initialIgnores) {
|
|
261
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
262
|
+
let ignores = initialIgnores !== null && initialIgnores !== void 0 ? initialIgnores : (yield readNativeWatcherIgnores(root, baseGlobs));
|
|
263
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
264
|
+
const subscription = yield watcher.subscribe(root, callback, {
|
|
265
|
+
ignore: ignores,
|
|
266
|
+
});
|
|
267
|
+
let verified;
|
|
268
|
+
try {
|
|
269
|
+
verified = yield verifyNativeWatcherIgnores(root, baseGlobs, ignores);
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
yield subscription.unsubscribe();
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
275
|
+
const policy = watcherIgnoreIdentity(ignores);
|
|
276
|
+
if (policy === watcherIgnoreIdentity(verified))
|
|
277
|
+
return { subscription, policy };
|
|
278
|
+
yield subscription.unsubscribe();
|
|
279
|
+
ignores = verified;
|
|
280
|
+
}
|
|
281
|
+
const fallback = [...baseGlobs];
|
|
282
|
+
return {
|
|
283
|
+
subscription: yield watcher.subscribe(root, callback, { ignore: fallback }),
|
|
284
|
+
policy: watcherIgnoreIdentity(fallback),
|
|
285
|
+
};
|
|
286
|
+
});
|
|
287
|
+
}
|
|
@@ -52,7 +52,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
52
52
|
exports.WATCHER_IGNORE_GLOBS = void 0;
|
|
53
53
|
exports.startWatcher = startWatcher;
|
|
54
54
|
const path = __importStar(require("node:path"));
|
|
55
|
-
const watcher = __importStar(require("@parcel/watcher"));
|
|
56
55
|
const batch_processor_1 = require("./batch-processor");
|
|
57
56
|
const cache_coherence_1 = require("./cache-coherence");
|
|
58
57
|
const file_policy_1 = require("./file-policy");
|
|
@@ -135,11 +134,8 @@ function startWatcher(opts) {
|
|
|
135
134
|
const update = subscriptionUpdate
|
|
136
135
|
.catch(() => { })
|
|
137
136
|
.then(() => __awaiter(this, void 0, void 0, function* () {
|
|
138
|
-
const ignores =
|
|
139
|
-
|
|
140
|
-
...(yield (0, watcher_ignore_1.readProjectWatcherIgnores)(projectRoot)),
|
|
141
|
-
];
|
|
142
|
-
const policy = JSON.stringify(ignores);
|
|
137
|
+
const ignores = yield (0, watcher_ignore_1.readNativeWatcherIgnores)(projectRoot, exports.WATCHER_IGNORE_GLOBS);
|
|
138
|
+
const policy = (0, watcher_ignore_1.watcherIgnoreIdentity)(ignores);
|
|
143
139
|
if (closing ||
|
|
144
140
|
(subscription && !subscriptionFailed && subscriptionPolicy === policy))
|
|
145
141
|
return;
|
|
@@ -149,7 +145,7 @@ function startWatcher(opts) {
|
|
|
149
145
|
subscription = null;
|
|
150
146
|
if (closing)
|
|
151
147
|
return;
|
|
152
|
-
const next = yield
|
|
148
|
+
const { subscription: next, policy: attachedPolicy } = yield (0, watcher_ignore_1.subscribeWithNativeExclusions)(projectRoot, (err, events) => {
|
|
153
149
|
var _a;
|
|
154
150
|
if (closing || generation !== subscriptionGeneration)
|
|
155
151
|
return;
|
|
@@ -168,13 +164,13 @@ function startWatcher(opts) {
|
|
|
168
164
|
}
|
|
169
165
|
for (const event of events)
|
|
170
166
|
processor.handleFileEvent(event.type === "delete" ? "unlink" : "change", event.path);
|
|
171
|
-
},
|
|
167
|
+
}, exports.WATCHER_IGNORE_GLOBS, ignores);
|
|
172
168
|
if (closing) {
|
|
173
169
|
yield next.unsubscribe();
|
|
174
170
|
return;
|
|
175
171
|
}
|
|
176
172
|
subscription = next;
|
|
177
|
-
subscriptionPolicy =
|
|
173
|
+
subscriptionPolicy = attachedPolicy;
|
|
178
174
|
}));
|
|
179
175
|
subscriptionUpdate = update;
|
|
180
176
|
return update;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.boundedAgentText = boundedAgentText;
|
|
4
|
+
exports.boundedAgentList = boundedAgentList;
|
|
5
|
+
/** A bounded source excerpt with explicit loss counts. Full/JSON output stays exact. */
|
|
6
|
+
function boundedAgentText(text, maxChars = 6000, maxLines = 120) {
|
|
7
|
+
var _a;
|
|
8
|
+
const lines = text.split("\n");
|
|
9
|
+
const lineLimited = lines.slice(0, maxLines).join("\n");
|
|
10
|
+
let end = Math.min(lineLimited.length, maxChars);
|
|
11
|
+
// Do not emit half of a UTF-16 surrogate pair at the character boundary.
|
|
12
|
+
if (end > 0 &&
|
|
13
|
+
/[\uD800-\uDBFF]/.test(lineLimited[end - 1]) &&
|
|
14
|
+
/[\uDC00-\uDFFF]/.test((_a = lineLimited[end]) !== null && _a !== void 0 ? _a : ""))
|
|
15
|
+
end--;
|
|
16
|
+
const excerpt = lineLimited.slice(0, end);
|
|
17
|
+
if (excerpt.length === text.length)
|
|
18
|
+
return text;
|
|
19
|
+
const shownLines = excerpt ? excerpt.split("\n").length : 0;
|
|
20
|
+
return `${excerpt}\n… omitted ${Math.max(0, lines.length - shownLines)} lines, ${text.length - excerpt.length} characters`;
|
|
21
|
+
}
|
|
22
|
+
/** Preserve complete fields so loss counts describe files, not partial names. */
|
|
23
|
+
function boundedAgentList(items, maxItems = 8, maxChars = 1000) {
|
|
24
|
+
const shown = [];
|
|
25
|
+
let length = 0;
|
|
26
|
+
for (const item of items.slice(0, maxItems)) {
|
|
27
|
+
const field = item
|
|
28
|
+
.replace(/\\/g, "\\\\")
|
|
29
|
+
.replace(/\t/g, "\\t")
|
|
30
|
+
.replace(/\r/g, "\\r")
|
|
31
|
+
.replace(/\n/g, "\\n");
|
|
32
|
+
const nextLength = length + field.length + (shown.length ? 1 : 0);
|
|
33
|
+
if (nextLength > maxChars)
|
|
34
|
+
break;
|
|
35
|
+
shown.push(field);
|
|
36
|
+
length = nextLength;
|
|
37
|
+
}
|
|
38
|
+
return { text: shown.join(","), omitted: items.length - shown.length };
|
|
39
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.projectCounts = projectCounts;
|
|
4
|
+
exports.projectCoverageNotice = projectCoverageNotice;
|
|
5
|
+
function projectCounts(overview) {
|
|
6
|
+
const qualifier = overview.sampled ? "sampled " : "";
|
|
7
|
+
return `${overview.chunks} ${qualifier}chunks • ${overview.files} ${qualifier}files`;
|
|
8
|
+
}
|
|
9
|
+
function projectCoverageNotice(overview) {
|
|
10
|
+
if (!overview.sampled)
|
|
11
|
+
return null;
|
|
12
|
+
const total = overview.totalChunks === undefined
|
|
13
|
+
? "total chunk count unavailable"
|
|
14
|
+
: `${overview.totalChunks} total chunks`;
|
|
15
|
+
return `partial: ${overview.chunks} sampled chunks, ${overview.files} sampled files; ${total}; all breakdowns below describe the sample`;
|
|
16
|
+
}
|
package/package.json
CHANGED