pi-lens 3.8.73 → 3.8.74
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/CHANGELOG.md +1116 -0
- package/README.md +37 -21
- package/dist/clients/atomic-write.js +3 -3
- package/dist/clients/bus-events-logger.js +5 -1
- package/dist/clients/cache/rule-cache.js +43 -2
- package/dist/clients/cache-observability.js +211 -0
- package/dist/clients/call-graph.js +20 -0
- package/dist/clients/cascade-logger.js +5 -1
- package/dist/clients/complexity-client.js +4 -7
- package/dist/clients/dependency-checker.js +151 -20
- package/dist/clients/diagnostic-dispositions.js +22 -2
- package/dist/clients/dispatch/auxiliary-lsp.js +34 -0
- package/dist/clients/dispatch/dispatcher.js +16 -1
- package/dist/clients/dispatch/fact-store.js +11 -0
- package/dist/clients/dispatch/facts/function-facts.js +14 -0
- package/dist/clients/dispatch/facts/import-facts.js +100 -101
- package/dist/clients/dispatch/facts/tree-sitter-facts.js +5 -8
- package/dist/clients/dispatch/facts/try-catch-facts.js +13 -0
- package/dist/clients/dispatch/integration.js +94 -15
- package/dist/clients/dispatch/rules/async-noise.js +1 -0
- package/dist/clients/dispatch/rules/unsafe-boundary.js +7 -0
- package/dist/clients/dispatch/runners/ast-grep-napi.js +24 -1
- package/dist/clients/dispatch/runners/psscriptanalyzer.js +10 -1
- package/dist/clients/dispatch/runners/tree-sitter.js +153 -166
- package/dist/clients/dispatch/runners/utils/runner-helpers.js +17 -18
- package/dist/clients/feature-hints.js +53 -7
- package/dist/clients/file-kinds.js +86 -31
- package/dist/clients/fix-worklog.js +14 -2
- package/dist/clients/generated-artifacts.js +21 -0
- package/dist/clients/gzip-stage-write.js +84 -0
- package/dist/clients/installer/index.js +358 -129
- package/dist/clients/knip-client.js +64 -1
- package/dist/clients/language-profile.js +61 -72
- package/dist/clients/latency-logger.js +6 -16
- package/dist/clients/lens-config.js +128 -165
- package/dist/clients/lens-engine.js +39 -5
- package/dist/clients/lens-flag-registry.js +285 -0
- package/dist/clients/lens-map.js +2 -7
- package/dist/clients/log-cleanup.js +6 -17
- package/dist/clients/lsp/cascade-tier.js +22 -2
- package/dist/clients/lsp/client.js +74 -10
- package/dist/clients/lsp/config.js +62 -9
- package/dist/clients/lsp/index.js +34 -24
- package/dist/clients/lsp/language.js +4 -1
- package/dist/clients/lsp/launch.js +8 -6
- package/dist/clients/lsp/server.js +84 -29
- package/dist/clients/lsp-document-symbols.js +112 -0
- package/dist/clients/mcp/analyze.js +5 -4
- package/dist/clients/mcp/review.js +36 -3
- package/dist/clients/module-report.js +100 -80
- package/dist/clients/ndjson-logger.js +51 -21
- package/dist/clients/path-keyed-map.js +76 -0
- package/dist/clients/path-utils.js +35 -0
- package/dist/clients/performance-report.js +327 -0
- package/dist/clients/pipeline.js +7 -0
- package/dist/clients/project-changes.js +77 -4
- package/dist/clients/project-diagnostics/extractors.js +26 -115
- package/dist/clients/project-diagnostics/fresh-fetch.js +149 -19
- package/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +109 -0
- package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +37 -5
- package/dist/clients/project-diagnostics/scanner.js +247 -127
- package/dist/clients/project-lens-config.js +53 -26
- package/dist/clients/project-report.js +39 -14
- package/dist/clients/project-scale.js +8 -10
- package/dist/clients/project-snapshot-persist-worker.js +6 -0
- package/dist/clients/project-snapshot.js +611 -7
- package/dist/clients/quiet-window.js +9 -1
- package/dist/clients/read-expansion.js +22 -12
- package/dist/clients/read-guard.js +78 -0
- package/dist/clients/redact/secrets.js +449 -0
- package/dist/clients/resource-sampler.js +133 -14
- package/dist/clients/reverse-deps.js +48 -0
- package/dist/clients/review-graph/builder.js +1522 -179
- package/dist/clients/review-graph/persist-worker.js +10 -0
- package/dist/clients/review-graph/query.js +5 -0
- package/dist/clients/review-graph/shared-extraction-ir.js +90 -0
- package/dist/clients/review-graph-logger.js +24 -0
- package/dist/clients/runtime-agent-end.js +4 -2
- package/dist/clients/runtime-coordinator.js +2 -2
- package/dist/clients/runtime-session.js +433 -59
- package/dist/clients/runtime-tool-call.js +46 -14
- package/dist/clients/runtime-turn.js +97 -26
- package/dist/clients/safe-spawn.js +102 -15
- package/dist/clients/session-start-observability.js +15 -0
- package/dist/clients/session-state-store.js +2 -1
- package/dist/clients/sessionstart-logger.js +18 -0
- package/dist/clients/sg-runner.js +48 -33
- package/dist/clients/source-filter.js +49 -25
- package/dist/clients/startup-marker.js +6 -0
- package/dist/clients/startup-timing.js +9 -0
- package/dist/clients/todo-scanner.js +30 -3
- package/dist/clients/tree-sitter-cache.js +147 -139
- package/dist/clients/tree-sitter-client.js +1340 -67
- package/dist/clients/tree-sitter-logger.js +43 -1
- package/dist/clients/tree-sitter-query-loader.js +110 -11
- package/dist/clients/tree-sitter-shared.js +43 -19
- package/dist/clients/tree-sitter-symbol-extractor.js +71 -4
- package/dist/clients/widget-state.js +50 -14
- package/dist/clients/word-index-logger.js +44 -0
- package/dist/clients/word-index.js +252 -35
- package/dist/clients/workspace-topology.js +11 -1
- package/dist/index.js +45544 -39255
- package/dist/mcp/analyze-cli.js +3 -0
- package/dist/mcp/cli.js +110 -0
- package/dist/mcp/server.js +63 -8
- package/dist/tools/lens-diagnostics.js +47 -5
- package/dist/tools/lsp-navigation.js +5 -28
- package/dist/tools/symbol-search.js +21 -2
- package/docs/agent-guide.md +247 -0
- package/docs/astplayground.md +3 -4
- package/docs/audit1.md +5 -5
- package/docs/environment-variables.md +153 -11
- package/docs/features.md +4 -3
- package/docs/globalconfig.md +56 -4
- package/docs/language-coverage.md +2 -2
- package/docs/mcp.md +17 -16
- package/docs/servercapabilities.md +5 -2
- package/docs/settings.md +181 -0
- package/docs/tools.md +14 -0
- package/docs/tree-sitter_rules_catalog.md +11 -31
- package/docs/usage.md +13 -0
- package/package.json +139 -138
- package/rules/ast-grep-rules/rule-tests/hardcoded-url-js-test.yml +1 -0
- package/rules/ast-grep-rules/rule-tests/hardcoded-url-test.yml +2 -0
- package/rules/ast-grep-rules/rule-tests/no-case-declarations-js-test.yml +10 -0
- package/rules/ast-grep-rules/rule-tests/no-case-declarations-test.yml +10 -0
- package/rules/ast-grep-rules/rule-tests/no-javascript-url-js-test.yml +10 -0
- package/rules/ast-grep-rules/rule-tests/no-javascript-url-test.yml +10 -0
- package/rules/ast-grep-rules/rule-tests/strict-equality-js-test.yml +3 -0
- package/rules/ast-grep-rules/rule-tests/strict-equality-test.yml +3 -0
- package/rules/ast-grep-rules/rule-tests/strict-inequality-js-test.yml +3 -0
- package/rules/ast-grep-rules/rule-tests/strict-inequality-test.yml +4 -0
- package/rules/ast-grep-rules/rules/hardcoded-url-js.yml +22 -4
- package/rules/ast-grep-rules/rules/hardcoded-url.yml +22 -4
- package/rules/ast-grep-rules/rules/no-case-declarations-js.yml +4 -3
- package/rules/ast-grep-rules/rules/no-case-declarations.yml +4 -3
- package/rules/ast-grep-rules/rules/no-console-except-error-js.yml +10 -0
- package/rules/ast-grep-rules/rules/no-console-except-error.yml +10 -0
- package/rules/ast-grep-rules/rules/no-javascript-url-js.yml +42 -6
- package/rules/ast-grep-rules/rules/no-javascript-url.yml +42 -6
- package/rules/ast-grep-rules/rules/strict-equality-js.yml +14 -1
- package/rules/ast-grep-rules/rules/strict-equality.yml +14 -1
- package/rules/ast-grep-rules/rules/strict-inequality-js.yml +14 -1
- package/rules/ast-grep-rules/rules/strict-inequality.yml +14 -1
- package/rules/tree-sitter-queries/cpp/no-memset-sensitive-data.yml +9 -3
- package/rules/tree-sitter-queries/cpp/no-scoped-lock-without-args.yml +6 -6
- package/rules/tree-sitter-queries/css/calc-spacing.yml +8 -6
- package/rules/tree-sitter-queries/go/go-mutex-copy.yml +21 -18
- package/rules/tree-sitter-queries/go/go-shared-map-write-goroutine.yml +3 -3
- package/rules/tree-sitter-queries/java/infinite-loop-java.yml +3 -3
- package/rules/tree-sitter-queries/java/junit-call-super.yml +6 -4
- package/rules/tree-sitter-queries/java/no-double-checked-locking.yml +10 -8
- package/rules/tree-sitter-queries/java/no-field-shadowing.yml +1 -1
- package/rules/tree-sitter-queries/java/prepared-statement-valid-indices.yml +2 -3
- package/rules/tree-sitter-queries/java/resources-closed.yml +2 -2
- package/rules/tree-sitter-queries/java/springboot-default-package.yml +6 -3
- package/rules/tree-sitter-queries/java/switch-fall-through.yml +6 -4
- package/rules/tree-sitter-queries/java/switch-non-case-labels.yml +4 -3
- package/rules/tree-sitter-queries/java/tests-include-assertions.yml +6 -5
- package/rules/tree-sitter-queries/javascript/switch-case-termination-js.yml +5 -8
- package/rules/tree-sitter-queries/javascript/switch-non-case-labels.yml +3 -2
- package/rules/tree-sitter-queries/kotlin/prepared-statement-indices.yml +9 -5
- package/rules/tree-sitter-queries/php/this-in-static-context.yml +5 -5
- package/rules/tree-sitter-queries/python/exit-signature-check.yml +2 -9
- package/rules/tree-sitter-queries/python/in-operator-unsupported.yml +3 -4
- package/rules/tree-sitter-queries/python/no-super-torchscript.yml +4 -8
- package/rules/tree-sitter-queries/python/notimplemented-boolean-context.yml +1 -5
- package/rules/tree-sitter-queries/python/python-empty-except.yml +1 -1
- package/rules/tree-sitter-queries/python/return-in-init.yml +0 -2
- package/rules/tree-sitter-queries/python/yield-return-outside-function.yml +0 -3
- package/rules/tree-sitter-queries/ruby/ruby-command-injection.yml +1 -12
- package/rules/tree-sitter-queries/ruby/ruby-eval.yml +1 -1
- package/rules/tree-sitter-queries/ruby/ruby-insecure-deserialization.yml +4 -4
- package/rules/tree-sitter-queries/ruby/ruby-insecure-random.yml +4 -11
- package/rules/tree-sitter-queries/ruby/ruby-open-struct.yml +1 -1
- package/rules/tree-sitter-queries/ruby/ruby-string-eval.yml +2 -3
- package/rules/tree-sitter-queries/ruby/ruby-weak-hash.yml +4 -4
- package/rules/tree-sitter-queries/rust/rust-lock-held-across-await.yml +4 -3
- package/rules/tree-sitter-queries/typescript/console-statement.yml +11 -0
- package/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +4 -9
- package/rules/tree-sitter-queries/typescript/empty-switch-case.yml +13 -9
- package/rules/tree-sitter-queries/typescript/infinite-loop.yml +2 -2
- package/rules/tree-sitter-queries/typescript/mixed-async-styles.yml +10 -1
- package/rules/tree-sitter-queries/typescript/switch-case-termination.yml +5 -8
- package/rules/tree-sitter-queries/typescript/ts-insecure-random.yml +7 -10
- package/rules/tree-sitter-queries/typescript/unsafe-regex.yml +4 -2
- package/rules/typos/_typos.toml +17 -0
- package/skills/pi-lens-write-tree-sitter-rule/SKILL.md +5 -0
- package/dist/clients/config-enabled-shape.js +0 -39
- package/docs/env_variables.md +0 -10
- package/rules/tree-sitter-queries/abap-disabled/delete-where.yml +0 -46
- package/rules/tree-sitter-queries/cpp/no-confused-move-forward.yml +0 -59
- package/rules/tree-sitter-queries/cpp/noexcept-functions.yml +0 -58
- package/rules/tree-sitter-queries/java/mockito-initialized.yml +0 -66
- package/rules/tree-sitter-queries/java/no-wait-notify-on-thread.yml +0 -58
- package/rules/tree-sitter-queries/java/short-circuit-logic.yml +0 -57
- package/rules/tree-sitter-queries/java/spring-session-attributes-setcomplete.yml +0 -75
- package/rules/tree-sitter-queries/plsql-disabled/delete-update-where.yml +0 -56
- package/rules/tree-sitter-queries/plsql-disabled/end-loop-semicolon.yml +0 -51
- package/rules/tree-sitter-queries/plsql-disabled/fetch-bulk-collect-limit.yml +0 -47
- package/rules/tree-sitter-queries/plsql-disabled/forallsave-exceptions.yml +0 -51
- package/rules/tree-sitter-queries/plsql-disabled/nchar-nvarchar2-bytes.yml +0 -54
- package/rules/tree-sitter-queries/plsql-disabled/not-null-initialization.yml +0 -59
- package/rules/tree-sitter-queries/plsql-disabled/raise-application-error-codes.yml +0 -50
- package/rules/tree-sitter-queries/typescript-disabled/ts-path-traversal.yml +0 -71
|
@@ -22,6 +22,38 @@ const EMPTY_RESULT = {
|
|
|
22
22
|
unlistedDeps: [],
|
|
23
23
|
};
|
|
24
24
|
const ANALYSIS_TIMEOUT_MS = 30_000;
|
|
25
|
+
/**
|
|
26
|
+
* Every package name referenced as a KEY (at any nesting depth — npm's
|
|
27
|
+
* `overrides` and pnpm's `pnpm.overrides` allow nested "for this dependency's
|
|
28
|
+
* sub-dependency" overrides) in `package.json`'s `overrides`, `resolutions`
|
|
29
|
+
* (Yarn's equivalent), or `pnpm.overrides` fields. These are the project's
|
|
30
|
+
* own explicit signal that a package is deliberately present to pin a
|
|
31
|
+
* resolution — not a source-imported dependency knip's import graph can see.
|
|
32
|
+
* Missing/malformed `package.json` degrades to an empty set (never throws) —
|
|
33
|
+
* this is a best-effort narrowing, not a required input.
|
|
34
|
+
*/
|
|
35
|
+
export function readOverridePinnedPackageNames(targetDir) {
|
|
36
|
+
const names = new Set();
|
|
37
|
+
let pkg;
|
|
38
|
+
try {
|
|
39
|
+
pkg = JSON.parse(fs.readFileSync(path.join(targetDir, "package.json"), "utf-8"));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return names;
|
|
43
|
+
}
|
|
44
|
+
const collectKeys = (value) => {
|
|
45
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
46
|
+
return;
|
|
47
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
48
|
+
names.add(key);
|
|
49
|
+
collectKeys(nested);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
collectKeys(pkg.overrides);
|
|
53
|
+
collectKeys(pkg.resolutions);
|
|
54
|
+
collectKeys(pkg.pnpm?.overrides);
|
|
55
|
+
return names;
|
|
56
|
+
}
|
|
25
57
|
// --- Client ---
|
|
26
58
|
export class KnipClient {
|
|
27
59
|
knipAvailable = null;
|
|
@@ -206,7 +238,38 @@ export class KnipClient {
|
|
|
206
238
|
summary: "No issues found",
|
|
207
239
|
};
|
|
208
240
|
}
|
|
209
|
-
return this.parseOutput(output);
|
|
241
|
+
return this.dropOverridePinnedDeps(this.parseOutput(output), targetDir);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Drop `dependency`/`devDependency` issues for a package that's also
|
|
245
|
+
* referenced as an npm `overrides` (or Yarn `resolutions` / pnpm
|
|
246
|
+
* `pnpm.overrides`) key in this project's `package.json` (#968).
|
|
247
|
+
*
|
|
248
|
+
* A direct devDependency whose only job is pinning a vulnerable
|
|
249
|
+
* transitive/peer resolution has no source import — that's WORKING AS
|
|
250
|
+
* INTENDED, not dead code, and knip has no concept of "this dependency
|
|
251
|
+
* exists only to satisfy an overrides entry" (it only sees imports).
|
|
252
|
+
* `overrides`/`resolutions` are the project's own explicit, unambiguous
|
|
253
|
+
* signal that the package is deliberately present — the same class of
|
|
254
|
+
* signal `hardcoded-url`'s `SCREAMING_SNAKE_CASE` constant-name carve-out
|
|
255
|
+
* and `ts-ssrf`'s constant-identifier carve-out lean on elsewhere in this
|
|
256
|
+
* codebase — so this narrows the finding rather than suppressing
|
|
257
|
+
* `dependency`/`devDependency` issues wholesale: a devDependency that
|
|
258
|
+
* ISN'T also an overrides/resolutions key is still reported.
|
|
259
|
+
*/
|
|
260
|
+
dropOverridePinnedDeps(result, targetDir) {
|
|
261
|
+
if (result.unusedDeps.length === 0)
|
|
262
|
+
return result;
|
|
263
|
+
const pinned = readOverridePinnedPackageNames(targetDir);
|
|
264
|
+
if (pinned.size === 0)
|
|
265
|
+
return result;
|
|
266
|
+
const isPinnedDepIssue = (issue) => (issue.type === "dependency" || issue.type === "devDependency") &&
|
|
267
|
+
(pinned.has(issue.name) || (!!issue.package && pinned.has(issue.package)));
|
|
268
|
+
const issues = result.issues.filter((issue) => !isPinnedDepIssue(issue));
|
|
269
|
+
const unusedDeps = result.unusedDeps.filter((issue) => !isPinnedDepIssue(issue));
|
|
270
|
+
return unusedDeps.length === result.unusedDeps.length
|
|
271
|
+
? result
|
|
272
|
+
: { ...result, issues, unusedDeps };
|
|
210
273
|
}
|
|
211
274
|
async getKnipEnvironment(targetDir) {
|
|
212
275
|
const { getToolEnvironment } = await import("./installer/index.js");
|
|
@@ -1,48 +1,14 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { detectFileKind } from "./file-kinds.js";
|
|
3
|
+
import { CODE_KINDS, detectFileKind, DOTNET_CSHARP_ROOT_MARKERS, DOTNET_FSHARP_ROOT_MARKERS, KIND_EXTENSIONS, } from "./file-kinds.js";
|
|
4
4
|
import { getProjectIgnoreMatcher } from "./file-utils.js";
|
|
5
|
+
import { direntsHaveMarkerGlobMatch } from "./path-utils.js";
|
|
5
6
|
import { LANGUAGE_POLICY, } from "./language-policy.js";
|
|
6
7
|
import { getSourceFiles } from "./scan-utils.js";
|
|
7
8
|
import { readDirEntriesSafe, shouldRecurseIntoDir } from "./source-walker.js";
|
|
8
9
|
import { findNearestDirWithAnyBasename } from "./workspace-topology.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"python",
|
|
12
|
-
"go",
|
|
13
|
-
"rust",
|
|
14
|
-
"cxx",
|
|
15
|
-
"cmake",
|
|
16
|
-
"fish",
|
|
17
|
-
"shell",
|
|
18
|
-
"json",
|
|
19
|
-
"markdown",
|
|
20
|
-
"css",
|
|
21
|
-
"yaml",
|
|
22
|
-
"sql",
|
|
23
|
-
"ruby",
|
|
24
|
-
"html",
|
|
25
|
-
"docker",
|
|
26
|
-
"php",
|
|
27
|
-
"powershell",
|
|
28
|
-
"prisma",
|
|
29
|
-
"csharp",
|
|
30
|
-
"fsharp",
|
|
31
|
-
"java",
|
|
32
|
-
"kotlin",
|
|
33
|
-
"swift",
|
|
34
|
-
"dart",
|
|
35
|
-
"lua",
|
|
36
|
-
"zig",
|
|
37
|
-
"haskell",
|
|
38
|
-
"elixir",
|
|
39
|
-
"gleam",
|
|
40
|
-
"ocaml",
|
|
41
|
-
"clojure",
|
|
42
|
-
"terraform",
|
|
43
|
-
"nix",
|
|
44
|
-
"toml",
|
|
45
|
-
];
|
|
10
|
+
/** Every registered kind participates in project-language detection (#894). */
|
|
11
|
+
export const SUPPORTED_FILE_KINDS = Object.keys(KIND_EXTENSIONS);
|
|
46
12
|
const PROJECT_MARKERS_BY_KIND = {
|
|
47
13
|
jsts: ["package.json", "tsconfig.json", "jsconfig.json"],
|
|
48
14
|
python: ["pyproject.toml", "requirements.txt", "setup.py", "setup.cfg"],
|
|
@@ -72,6 +38,8 @@ const PROJECT_MARKERS_BY_KIND = {
|
|
|
72
38
|
terraform: [".terraform.lock.hcl"],
|
|
73
39
|
nix: ["flake.nix"],
|
|
74
40
|
toml: ["pyproject.toml", "Cargo.toml", "taplo.toml"],
|
|
41
|
+
csharp: DOTNET_CSHARP_ROOT_MARKERS,
|
|
42
|
+
fsharp: DOTNET_FSHARP_ROOT_MARKERS,
|
|
75
43
|
};
|
|
76
44
|
const ROOT_MARKERS_BY_KIND = {
|
|
77
45
|
jsts: [
|
|
@@ -114,7 +82,19 @@ const ROOT_MARKERS_BY_KIND = {
|
|
|
114
82
|
terraform: [".terraform.lock.hcl"],
|
|
115
83
|
nix: ["flake.nix"],
|
|
116
84
|
toml: ["pyproject.toml", "Cargo.toml", "taplo.toml"],
|
|
85
|
+
csharp: DOTNET_CSHARP_ROOT_MARKERS,
|
|
86
|
+
fsharp: DOTNET_FSHARP_ROOT_MARKERS,
|
|
117
87
|
};
|
|
88
|
+
function hasProjectMarker(projectRoot, marker) {
|
|
89
|
+
if (!marker.includes("*"))
|
|
90
|
+
return fs.existsSync(path.join(projectRoot, marker));
|
|
91
|
+
try {
|
|
92
|
+
return direntsHaveMarkerGlobMatch(fs.readdirSync(projectRoot, { withFileTypes: true }), marker);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
118
98
|
// Process-lifetime memo keyed on projectRoot. Only populated when the
|
|
119
99
|
// caller did not pass an explicit `sourceFiles` array — the explicit-array
|
|
120
100
|
// case is used by the warmup pipeline to inject pre-collected files and
|
|
@@ -142,7 +122,7 @@ function computeProjectLanguageProfile(projectRoot, sourceFiles) {
|
|
|
142
122
|
if (!markers)
|
|
143
123
|
continue;
|
|
144
124
|
for (const marker of markers) {
|
|
145
|
-
if (
|
|
125
|
+
if (hasProjectMarker(projectRoot, marker)) {
|
|
146
126
|
present[kind] = true;
|
|
147
127
|
configured[kind] = true;
|
|
148
128
|
break;
|
|
@@ -240,38 +220,26 @@ export function resolveLanguageRootForFile(filePath, workspaceRoot) {
|
|
|
240
220
|
// the tree itself), and the result is stored in the shared
|
|
241
221
|
// `languageProfileCache` so the subsequent sync caller skips the walk.
|
|
242
222
|
// ---------------------------------------------------------------------------
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
// warmup.
|
|
247
|
-
const WARMUP_SOURCE_EXTS = new Set([
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
".jsx",
|
|
252
|
-
".mjs",
|
|
253
|
-
".cjs",
|
|
254
|
-
".py",
|
|
255
|
-
".go",
|
|
256
|
-
".rs",
|
|
257
|
-
".rb",
|
|
258
|
-
".java",
|
|
259
|
-
".kt",
|
|
260
|
-
".swift",
|
|
261
|
-
".c",
|
|
262
|
-
".cc",
|
|
263
|
-
".cpp",
|
|
264
|
-
".cxx",
|
|
265
|
-
".h",
|
|
266
|
-
".hpp",
|
|
267
|
-
".cs",
|
|
268
|
-
]);
|
|
223
|
+
// Keep the warmup walker lightweight (no source-filter dependency), but derive
|
|
224
|
+
// its extension gate from the same authority as every other project-wide
|
|
225
|
+
// enumeration. Generated-artifact filtering remains intentionally absent here:
|
|
226
|
+
// warmup only needs language presence and never opens file contents.
|
|
227
|
+
export const WARMUP_SOURCE_EXTS = new Set(SUPPORTED_FILE_KINDS.flatMap((kind) => KIND_EXTENSIONS[kind]));
|
|
228
|
+
// Extensions belonging to CODE_KINDS — same derivation, used to give program
|
|
229
|
+
// source priority within the capped warmup budget (#894 review, see below).
|
|
230
|
+
const WARMUP_CODE_EXTS = new Set(SUPPORTED_FILE_KINDS.filter((kind) => CODE_KINDS.has(kind)).flatMap((kind) => KIND_EXTENSIONS[kind]));
|
|
269
231
|
// Language detection needs which languages are PRESENT, not every file — so the
|
|
270
232
|
// warmup walk is hard-capped. Without this, a walk rooted at a too-broad directory
|
|
271
233
|
// (e.g. $HOME, if startup-scan's canWarmCaches guard is bypassed) traverses the
|
|
272
234
|
// entire tree — #250's multi-hour home-dir scans. Generous enough to detect every
|
|
273
235
|
// language present in any real project (mirrors startup-scan's 2000 limit).
|
|
274
236
|
const MAX_WARMUP_SOURCE_FILES = 2000;
|
|
237
|
+
// Total matched-file ceiling for the warmup walk, as a multiple of `maxFiles`
|
|
238
|
+
// (#894 review): with per-category budgets the walk no longer stops at the
|
|
239
|
+
// first `maxFiles` matches, so this keeps its total work deterministic —
|
|
240
|
+
// at the default budget it tolerates up to ~20k data/doc files ahead of the
|
|
241
|
+
// code dirs before giving up on finding more code files.
|
|
242
|
+
const MATCHED_FILES_CEILING_FACTOR = 10;
|
|
275
243
|
export async function collectSourceFilesForWarmup(rootDir, maxFiles = MAX_WARMUP_SOURCE_FILES, yieldEvery = 100) {
|
|
276
244
|
const root = path.resolve(rootDir);
|
|
277
245
|
const ignoreMatcher = getProjectIgnoreMatcher(root);
|
|
@@ -280,9 +248,23 @@ export async function collectSourceFilesForWarmup(rootDir, maxFiles = MAX_WARMUP
|
|
|
280
248
|
// detection. Fail-open on no-git/spawn failure.
|
|
281
249
|
await ignoreMatcher.ensureTrackedIndex();
|
|
282
250
|
const stack = [root];
|
|
283
|
-
|
|
251
|
+
// #894 review: `maxFiles` is a PER-CATEGORY budget — code kinds and
|
|
252
|
+
// data/doc kinds (NON_CODE_KINDS: json/yaml/markdown/…) fill separate
|
|
253
|
+
// buffers. With a single shared budget, 2000+ locale/fixture JSON files
|
|
254
|
+
// encountered before the code dirs exhausted the cap and flipped
|
|
255
|
+
// `present[kind]` to false for the project's real languages. Each buffer
|
|
256
|
+
// keeps the original #250 cap; the walk stops once the code budget is
|
|
257
|
+
// full (real languages are then detected; extra non-code presence can't
|
|
258
|
+
// justify more walking) or after `MATCHED_FILES_CEILING_FACTOR * maxFiles`
|
|
259
|
+
// matching files total — a deterministic work bound in the same spirit as
|
|
260
|
+
// #250's cap, generous enough that a locale/fixture pile ahead of the code
|
|
261
|
+
// dirs can't starve detection, tight enough that a misrooted /
|
|
262
|
+
// data-dominated tree stays bounded (#250/#758 class).
|
|
263
|
+
const codeOut = [];
|
|
264
|
+
const nonCodeOut = [];
|
|
265
|
+
let matchedSeen = 0;
|
|
284
266
|
let processedSinceYield = 0;
|
|
285
|
-
while (stack.length > 0) {
|
|
267
|
+
walk: while (stack.length > 0) {
|
|
286
268
|
const current = stack.pop();
|
|
287
269
|
if (!current)
|
|
288
270
|
continue;
|
|
@@ -303,10 +285,17 @@ export async function collectSourceFilesForWarmup(rootDir, maxFiles = MAX_WARMUP
|
|
|
303
285
|
const ext = path.extname(entry.name).toLowerCase();
|
|
304
286
|
if (!WARMUP_SOURCE_EXTS.has(ext))
|
|
305
287
|
continue;
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
288
|
+
matchedSeen += 1;
|
|
289
|
+
const bucket = WARMUP_CODE_EXTS.has(ext) ? codeOut : nonCodeOut;
|
|
290
|
+
// Per-category hard cap — language detection only needs
|
|
291
|
+
// presence (#250), so overflowing files of a full category
|
|
292
|
+
// are dropped rather than evicting the other category.
|
|
293
|
+
if (bucket.length < maxFiles)
|
|
294
|
+
bucket.push(fullPath);
|
|
295
|
+
if (codeOut.length >= maxFiles ||
|
|
296
|
+
matchedSeen >= MATCHED_FILES_CEILING_FACTOR * maxFiles) {
|
|
297
|
+
break walk;
|
|
298
|
+
}
|
|
310
299
|
}
|
|
311
300
|
if (++processedSinceYield % yieldEvery === 0) {
|
|
312
301
|
// See countSourceFilesWithinLimitAsync for why setImmediate.
|
|
@@ -314,7 +303,7 @@ export async function collectSourceFilesForWarmup(rootDir, maxFiles = MAX_WARMUP
|
|
|
314
303
|
}
|
|
315
304
|
}
|
|
316
305
|
}
|
|
317
|
-
return
|
|
306
|
+
return [...codeOut, ...nonCodeOut];
|
|
318
307
|
}
|
|
319
308
|
export async function detectProjectLanguageProfileAsync(projectRoot) {
|
|
320
309
|
const cached = languageProfileCache.get(projectRoot);
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
1
|
import * as path from "node:path";
|
|
3
2
|
import { isTestMode } from "./env-utils.js";
|
|
4
3
|
import { getGlobalPiLensDir } from "./file-utils.js";
|
|
5
4
|
import { createNdjsonLogger } from "./ndjson-logger.js";
|
|
5
|
+
import { getMaxLogSizeMB } from "./log-cleanup.js";
|
|
6
6
|
const LATENCY_LOG_DIR = getGlobalPiLensDir();
|
|
7
7
|
const LATENCY_LOG_FILE = path.join(LATENCY_LOG_DIR, "latency.log");
|
|
8
|
-
const writer = createNdjsonLogger({
|
|
8
|
+
const writer = createNdjsonLogger({
|
|
9
|
+
filePath: LATENCY_LOG_FILE,
|
|
10
|
+
maxBytes: getMaxLogSizeMB() * 1024 * 1024,
|
|
11
|
+
});
|
|
9
12
|
export function logLatency(entry) {
|
|
10
13
|
if (isTestMode()) {
|
|
11
14
|
return;
|
|
12
15
|
}
|
|
13
|
-
writer.log({ ts: new Date().toISOString(),
|
|
16
|
+
writer.log({ ...entry, ts: new Date().toISOString(), pid: process.pid });
|
|
14
17
|
}
|
|
15
18
|
export function getLatencyLogPath() {
|
|
16
19
|
return LATENCY_LOG_FILE;
|
|
@@ -19,19 +22,6 @@ export function getLatencyLogPath() {
|
|
|
19
22
|
export function flushLatencyLog() {
|
|
20
23
|
return writer.flush();
|
|
21
24
|
}
|
|
22
|
-
export function readLatencyLog(limit = 100) {
|
|
23
|
-
try {
|
|
24
|
-
const content = fs.readFileSync(LATENCY_LOG_FILE, "utf-8");
|
|
25
|
-
const lines = content.trim().split(/\r?\n/).filter(Boolean);
|
|
26
|
-
return lines
|
|
27
|
-
.slice(-limit)
|
|
28
|
-
.map((line) => JSON.parse(line))
|
|
29
|
-
.reverse();
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
25
|
export function clearLatencyLog() {
|
|
36
26
|
// Enqueue the truncate in the same serialized queue so a clear cannot race a
|
|
37
27
|
// pending drain. Await flushLatencyLog() if you need the file empty on disk.
|