pi-lens 4.1.0 → 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +185 -0
- package/README.md +106 -0
- package/dist/clients/actionable-warnings-logger.js +2 -1
- package/dist/clients/actionable-warnings.js +14 -5
- package/dist/clients/advisory-provenance.js +48 -18
- package/dist/clients/ast-grep-client.js +1 -3
- package/dist/clients/atomic-write-staging.js +16 -4
- package/dist/clients/bash-file-access.js +99 -6
- package/dist/clients/biome-client.js +1 -3
- package/dist/clients/blocker-freshness.js +21 -8
- package/dist/clients/blocker-past-eof.js +16 -6
- package/dist/clients/bounded-cache.js +24 -8
- package/dist/clients/bounded-pid-file-lock.js +4 -3
- package/dist/clients/bounded-telemetry.js +14 -0
- package/dist/clients/cache-manager.js +6 -7
- package/dist/clients/cache-observability.js +782 -110
- package/dist/clients/call-graph.js +238 -63
- package/dist/clients/codebase-model.js +13 -5
- package/dist/clients/complexity-client.js +1 -3
- package/dist/clients/dead-code-client.js +19 -8
- package/dist/clients/dead-code-logger.js +1 -2
- package/dist/clients/deadline-utils.js +3 -1
- package/dist/clients/debug-heap.js +8 -1
- package/dist/clients/degradation-ledger.js +22 -1
- package/dist/clients/demoted-finding-render.js +155 -0
- package/dist/clients/dependency-checker.js +11 -7
- package/dist/clients/deps/pi-tui.js +8 -2
- package/dist/clients/deps/typebox.js +8 -3
- package/dist/clients/diagnostic-dispositions.js +39 -2
- package/dist/clients/diagnostic-line-freshness.js +5 -1
- package/dist/clients/dispatch/auxiliary-lsp.js +2 -3
- package/dist/clients/dispatch/dispatcher.js +13 -1
- package/dist/clients/dispatch/facts/function-facts.js +3 -1
- package/dist/clients/dispatch/facts/import-facts.js +2 -1
- package/dist/clients/dispatch/facts/tree-sitter-facts.js +1 -1
- package/dist/clients/dispatch/integration.js +9 -4
- package/dist/clients/dispatch/rules/error-swallowing.js +3 -1
- package/dist/clients/dispatch/rules/pass-through-wrappers.js +6 -2
- package/dist/clients/dispatch/rules/unsafe-boundary.js +31 -7
- package/dist/clients/dispatch/runners/actionlint.js +5 -2
- package/dist/clients/dispatch/runners/biome-check.js +10 -14
- package/dist/clients/dispatch/runners/cpp-check.js +5 -1
- package/dist/clients/dispatch/runners/detekt.js +9 -6
- package/dist/clients/dispatch/runners/dotnet-build.js +1 -1
- package/dist/clients/dispatch/runners/elixir-check.js +3 -1
- package/dist/clients/dispatch/runners/eslint.js +25 -6
- package/dist/clients/dispatch/runners/fish-indent.js +1 -1
- package/dist/clients/dispatch/runners/gleam-check.js +1 -1
- package/dist/clients/dispatch/runners/golangci-lint.js +3 -1
- package/dist/clients/dispatch/runners/hadolint.js +22 -15
- package/dist/clients/dispatch/runners/helm-lint.js +4 -1
- package/dist/clients/dispatch/runners/htmlhint.js +33 -16
- package/dist/clients/dispatch/runners/javac.js +1 -1
- package/dist/clients/dispatch/runners/ktlint.js +1 -1
- package/dist/clients/dispatch/runners/lsp.js +1 -1
- package/dist/clients/dispatch/runners/markdownlint.js +34 -12
- package/dist/clients/dispatch/runners/mypy.js +15 -17
- package/dist/clients/dispatch/runners/oxlint.js +231 -15
- package/dist/clients/dispatch/runners/php-lint.js +1 -1
- package/dist/clients/dispatch/runners/phpstan.js +52 -15
- package/dist/clients/dispatch/runners/psscriptanalyzer.js +10 -6
- package/dist/clients/dispatch/runners/pyright.js +23 -14
- package/dist/clients/dispatch/runners/rust-clippy.js +1 -1
- package/dist/clients/dispatch/runners/shellcheck.js +7 -9
- package/dist/clients/dispatch/runners/shfmt.js +5 -2
- package/dist/clients/dispatch/runners/spellcheck.js +10 -15
- package/dist/clients/dispatch/runners/sqlfluff.js +25 -17
- package/dist/clients/dispatch/runners/stylelint.js +40 -20
- package/dist/clients/dispatch/runners/swiftlint.js +8 -8
- package/dist/clients/dispatch/runners/taplo.js +110 -23
- package/dist/clients/dispatch/runners/terragrunt.js +13 -8
- package/dist/clients/dispatch/runners/tflint.js +25 -15
- package/dist/clients/dispatch/runners/tree-sitter.js +17 -3
- package/dist/clients/dispatch/runners/utils/candidate-probe.js +7 -1
- package/dist/clients/dispatch/runners/utils/runner-helpers.js +24 -1
- package/dist/clients/dispatch/runners/utils/tool-failure.js +152 -1
- package/dist/clients/dispatch/runners/vale.js +16 -14
- package/dist/clients/dispatch/runners/yaml-rule-parser.js +1 -3
- package/dist/clients/dispatch/runners/yamllint.js +13 -16
- package/dist/clients/dispatch/runners/zig-check.js +1 -1
- package/dist/clients/dispatch/suppress-writer.js +2 -1
- package/dist/clients/dispatch/types.js +6 -1
- package/dist/clients/dispatch/utils/lsp-diagnostics.js +6 -2
- package/dist/clients/extension-log.js +5 -3
- package/dist/clients/feature-hints.js +8 -1
- package/dist/clients/file-kinds.js +34 -129
- package/dist/clients/file-utils.js +17 -1
- package/dist/clients/finding-delivery-gate.js +79 -2
- package/dist/clients/formatters.js +144 -27
- package/dist/clients/freshness.js +38 -0
- package/dist/clients/git-guard.js +43 -13
- package/dist/clients/go-client.js +1 -3
- package/dist/clients/grammar-source.js +1 -1
- package/dist/clients/gzip-stage-write.js +30 -4
- package/dist/clients/host-edit-normalize.js +1 -3
- package/dist/clients/install-diagnostics.js +4 -2
- package/dist/clients/installer/index.js +86 -77
- package/dist/clients/instance-reaper.js +5 -2
- package/dist/clients/instance-registry.js +4 -2
- package/dist/clients/jscpd-client.js +11 -3
- package/dist/clients/knip-client.js +29 -15
- package/dist/clients/language-profile.js +11 -3
- package/dist/clients/latency-logger.js +13 -2
- package/dist/clients/lens-config.js +1 -1
- package/dist/clients/lens-map.js +8 -3
- package/dist/clients/live-bus-emitter.js +1 -1
- package/dist/clients/lsp/cascade-tier.js +27 -2
- package/dist/clients/lsp/client.js +144 -5
- package/dist/clients/lsp/config.js +4 -6
- package/dist/clients/lsp/edits.js +119 -32
- package/dist/clients/lsp/index.js +245 -41
- package/dist/clients/lsp/inferred-project.js +2 -1
- package/dist/clients/lsp/jvm-runtime.js +4 -1
- package/dist/clients/lsp/launch.js +11 -3
- package/dist/clients/lsp/pending-aux-coverage.js +152 -0
- package/dist/clients/lsp/server.js +19 -7
- package/dist/clients/lsp/tsserver-sync.js +10 -2
- package/dist/clients/lsp/workspace-diagnostics-cache.js +9 -4
- package/dist/clients/lsp-document-symbols.js +1 -3
- package/dist/clients/lsp-mutation.js +10 -20
- package/dist/clients/mcp/host-shim.js +1 -1
- package/dist/clients/memory-sampler.js +73 -7
- package/dist/clients/metrics-client.js +1 -3
- package/dist/clients/middle-man-analysis.js +8 -3
- package/dist/clients/module-report-lsp.js +7 -1
- package/dist/clients/module-report.js +28 -18
- package/dist/clients/ndjson-logger.js +117 -28
- package/dist/clients/opaque-mutation-scan.js +220 -0
- package/dist/clients/package-manager.js +6 -1
- package/dist/clients/pipeline.js +57 -9
- package/dist/clients/project-conventions.js +2 -8
- package/dist/clients/project-diagnostics/cache.js +10 -7
- package/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +2 -1
- package/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +3 -1
- package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +13 -9
- package/dist/clients/project-diagnostics/runner-adapters/trivy.js +3 -1
- package/dist/clients/project-diagnostics/scanner.js +22 -10
- package/dist/clients/project-lens-config.js +12 -5
- package/dist/clients/project-report.js +62 -27
- package/dist/clients/project-snapshot-fingerprint.js +48 -0
- package/dist/clients/project-snapshot-persist-worker.js +15 -1
- package/dist/clients/project-snapshot.js +472 -66
- package/dist/clients/read-expansion.js +12 -2
- package/dist/clients/read-guard-logger.js +12 -6
- package/dist/clients/read-guard-tool-lines.js +5 -2
- package/dist/clients/read-guard.js +88 -19
- package/dist/clients/resource-sampler.js +1 -1
- package/dist/clients/reverse-deps.js +6 -5
- package/dist/clients/review-graph/builder.js +236 -48
- package/dist/clients/review-graph/git-identity.js +8 -2
- package/dist/clients/review-graph/import-resolvers.js +10 -1
- package/dist/clients/review-graph/query.js +2 -1
- package/dist/clients/review-graph/revision-drift.js +21 -0
- package/dist/clients/review-graph/tsconfig-paths.js +4 -2
- package/dist/clients/review-graph/workspace-modules.js +8 -5
- package/dist/clients/ruff-client.js +6 -5
- package/dist/clients/runtime-agent-end.js +63 -31
- package/dist/clients/runtime-context.js +12 -4
- package/dist/clients/runtime-coordinator.js +127 -4
- package/dist/clients/runtime-session.js +55 -0
- package/dist/clients/runtime-tool-call.js +59 -3
- package/dist/clients/runtime-tool-result.js +148 -34
- package/dist/clients/runtime-turn.js +194 -8
- package/dist/clients/rust-client.js +1 -3
- package/dist/clients/safe-spawn.js +150 -11
- package/dist/clients/security-scan-client.js +1 -3
- package/dist/clients/session-event-guard.js +184 -0
- package/dist/clients/session-summary.js +3 -1
- package/dist/clients/sg-runner.js +1 -3
- package/dist/clients/source-filter.js +16 -8
- package/dist/clients/source-walker.js +1 -1
- package/dist/clients/spawn-timeout-cooldown.js +96 -0
- package/dist/clients/startup-scan.js +9 -3
- package/dist/clients/tool-definition.js +7 -0
- package/dist/clients/tool-policy.js +26 -11
- package/dist/clients/tool-render.js +2 -1
- package/dist/clients/tree-sitter-client.js +10 -4
- package/dist/clients/tree-sitter-logger.js +2 -1
- package/dist/clients/tree-sitter-symbol-extractor.js +12 -5
- package/dist/clients/warm-attach.js +12 -5
- package/dist/clients/widget-state.js +27 -11
- package/dist/clients/word-index.js +3 -1
- package/dist/clients/workspace-topology.js +8 -2
- package/dist/clients/zizmor-config.js +9 -1
- package/dist/i18n.js +13 -5
- package/dist/index.js +15811 -12810
- package/dist/mcp/server.js +3 -1
- package/dist/tools/ast-grep-replace.js +68 -12
- package/dist/tools/lens-diagnostic-mark.js +21 -7
- package/dist/tools/lens-diagnostics.js +174 -38
- package/dist/tools/lsp-diagnostics.js +24 -7
- package/dist/tools/lsp-navigation.js +3 -1
- package/dist/tools/module-report.js +1 -1
- package/dist/tools/project-report.js +1 -1
- package/dist/tools/symbol-search.js +5 -2
- package/docs/durable-store-audit-1202.md +1 -1
- package/docs/servercapabilities.md +1 -1
- package/package.json +160 -143
- package/rules/typos/_typos.toml +2 -2
- package/scripts/analyze-pi-lens-logs.mjs +31 -13
- package/scripts/download-grammars.js +9 -3
- package/scripts/install-selftest.mjs +99 -11
- package/scripts/lib/host-provided-deps.mjs +75 -0
- package/scripts/lib/warm-loader-cache.mjs +285 -0
- package/scripts/rpc-load-check.mjs +30 -9
- package/scripts/warm-loader-cache.mjs +233 -0
|
@@ -2,7 +2,8 @@ import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
|
2
2
|
import { getLinterPolicyForCwd, hasSqlfluffConfig } from "../../tool-policy.js";
|
|
3
3
|
import { PRIORITY } from "../priorities.js";
|
|
4
4
|
import { createAvailabilityChecker, resolveToolCommandWithInstallFallback, } from "./utils/runner-helpers.js";
|
|
5
|
-
import {
|
|
5
|
+
import { parseToolRun } from "./utils/tool-failure.js";
|
|
6
|
+
import { finishParsedRun } from "./utils/tool-failure.js";
|
|
6
7
|
const sqlfluff = createAvailabilityChecker("sqlfluff", ".exe");
|
|
7
8
|
// sqlfluff exit codes (its CLI docs): 0 = clean, 1 = violations found, 2 = a
|
|
8
9
|
// user error that stopped the lint (unknown dialect, unreadable config).
|
|
@@ -127,7 +128,7 @@ const sqlfluffRunner = {
|
|
|
127
128
|
ctx.log("sqlfluff: no config detected, using ANSI dialect defaults");
|
|
128
129
|
}
|
|
129
130
|
let cmd = null;
|
|
130
|
-
if (await
|
|
131
|
+
if (await sqlfluff.isAvailableAsync(cwd)) {
|
|
131
132
|
cmd = sqlfluff.getCommand(cwd);
|
|
132
133
|
}
|
|
133
134
|
else {
|
|
@@ -135,10 +136,19 @@ const sqlfluffRunner = {
|
|
|
135
136
|
}
|
|
136
137
|
if (!cmd)
|
|
137
138
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
138
|
-
|
|
139
|
+
// #1937: this used to be `args.splice(2, 0, "--dialect", "ansi")` on
|
|
140
|
+
// `["lint", "--format", "json", file]`, which inserts BETWEEN the flag
|
|
141
|
+
// and its value and yields `lint --format --dialect ansi json file`.
|
|
142
|
+
// sqlfluff rejects `--dialect` as the value of `--format` and exits 2,
|
|
143
|
+
// so every SQL project WITHOUT a .sqlfluff — exactly the case this
|
|
144
|
+
// dialect default exists to serve — was never linted. Built in order
|
|
145
|
+
// rather than patched by index, and pinned by the argv-tie assertion
|
|
146
|
+
// in runners/captured-real-output.test.ts.
|
|
147
|
+
const args = ["lint", "--format", "json"];
|
|
139
148
|
if (!hasConfig) {
|
|
140
|
-
args.
|
|
149
|
+
args.push("--dialect", "ansi");
|
|
141
150
|
}
|
|
151
|
+
args.push(ctx.filePath);
|
|
142
152
|
const result = await safeSpawnAsync(cmd, args, {
|
|
143
153
|
timeout: 20000,
|
|
144
154
|
cwd,
|
|
@@ -146,21 +156,19 @@ const sqlfluffRunner = {
|
|
|
146
156
|
// #1816: this runner read `result.status` zero times, so a user error
|
|
147
157
|
// fell through `parseSqlfluffOutput`'s JSON catch to zero violations and
|
|
148
158
|
// reported the SQL file as clean.
|
|
149
|
-
|
|
159
|
+
// #1948: sqlfluff exit 1 with a JSON report we read nothing out of is a
|
|
160
|
+
// parser break, not a clean file.
|
|
161
|
+
const run = parseToolRun("sqlfluff", { result, exitCodes: SQLFLUFF_EXIT_CODES }, (out) => parseSqlfluffOutput(out, ctx.filePath), { parseOutput: result.stdout ?? "" });
|
|
162
|
+
if (run.skipped)
|
|
163
|
+
return run.skipped;
|
|
164
|
+
const diagnostics = run.diagnostics;
|
|
165
|
+
return finishParsedRun({
|
|
166
|
+
tool: "sqlfluff",
|
|
167
|
+
ctx,
|
|
150
168
|
result,
|
|
151
|
-
exitCodes: SQLFLUFF_EXIT_CODES,
|
|
152
|
-
});
|
|
153
|
-
if (skipped)
|
|
154
|
-
return skipped;
|
|
155
|
-
const diagnostics = parseSqlfluffOutput(result.stdout ?? "", ctx.filePath);
|
|
156
|
-
if (diagnostics.length === 0) {
|
|
157
|
-
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
158
|
-
}
|
|
159
|
-
return {
|
|
160
|
-
status: "failed",
|
|
161
169
|
diagnostics,
|
|
162
|
-
semantic: "warning",
|
|
163
|
-
};
|
|
170
|
+
classify: () => ({ status: "failed", semantic: "warning" }),
|
|
171
|
+
});
|
|
164
172
|
},
|
|
165
173
|
};
|
|
166
174
|
export default sqlfluffRunner;
|
|
@@ -3,7 +3,8 @@ import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
|
3
3
|
import { getLinterPolicyForCwd, hasStylelintConfig, } from "../../tool-policy.js";
|
|
4
4
|
import { PRIORITY } from "../priorities.js";
|
|
5
5
|
import { createAvailabilityChecker, resolveToolCommandWithInstallFallback, } from "./utils/runner-helpers.js";
|
|
6
|
-
import {
|
|
6
|
+
import { parseToolRun } from "./utils/tool-failure.js";
|
|
7
|
+
import { finishParsedRun } from "./utils/tool-failure.js";
|
|
7
8
|
const stylelint = createAvailabilityChecker("stylelint", ".cmd");
|
|
8
9
|
// stylelint exit codes (its "Exit codes" docs page): 0 = no problems, 1 = a
|
|
9
10
|
// fatal error, 2 = lint problems found, 78 = a configuration error.
|
|
@@ -12,11 +13,34 @@ const stylelint = createAvailabilityChecker("stylelint", ".cmd");
|
|
|
12
13
|
// NOT a live stylelint: it is this repo's own hand-written fixture
|
|
13
14
|
// (tests/clients/dispatch/runners/stylelint-fixable.test.ts), which reports
|
|
14
15
|
// findings under exit 1. Rather than rewrite a fixture whose provenance is
|
|
15
|
-
// unknown, the table keeps 1 permissive.
|
|
16
|
-
// fatal stylelint under `--formatter json` writes nothing to stdout, so the
|
|
17
|
-
// nothing-to-parse rule still catches it. 78 is the rejection this table
|
|
16
|
+
// unknown, the table keeps 1 permissive. 78 is the rejection this table
|
|
18
17
|
// exists to name.
|
|
18
|
+
//
|
|
19
|
+
// #1937 corrected the safety argument that used to sit here: it claimed a
|
|
20
|
+
// fatal stylelint "writes nothing to stdout", which is true but useless,
|
|
21
|
+
// because a stylelint that DID find problems writes nothing to stdout either.
|
|
22
|
+
// `stylelintReport` below is what now separates the two.
|
|
19
23
|
const STYLELINT_EXIT_CODES = { ran: [1, 2] };
|
|
24
|
+
/**
|
|
25
|
+
* The JSON report out of a stylelint run, whichever stream carried it (#1937).
|
|
26
|
+
*
|
|
27
|
+
* stylelint 16+ writes the formatted report to STDERR whenever the run
|
|
28
|
+
* "errored" — which is every run that found an error-severity warning, i.e.
|
|
29
|
+
* every run with something to say. Reading stdout alone parsed nothing and
|
|
30
|
+
* called the stylesheet clean, the #1933 vale shape. Verified against
|
|
31
|
+
* stylelint 17.8.0 in tests/fixtures/runner-output/stylelint/.
|
|
32
|
+
*
|
|
33
|
+
* Only a stream that actually starts a JSON array counts. A crash's stack
|
|
34
|
+
* trace on stderr must stay "nothing to parse" so #1816's empty-result skip
|
|
35
|
+
* still fires instead of reporting a clean file.
|
|
36
|
+
*/
|
|
37
|
+
export function stylelintReport(stdout, stderr) {
|
|
38
|
+
for (const stream of [stdout, stderr]) {
|
|
39
|
+
if (stream?.trimStart().startsWith("["))
|
|
40
|
+
return stream;
|
|
41
|
+
}
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
20
44
|
// Stylelint's standard JSON output reports only aggregate fixableErrorCount /
|
|
21
45
|
// fixableWarningCount per file — there is no per-warning fix flag in the CLI
|
|
22
46
|
// surface. To route the actionable warnings here without a tool rewrite, we
|
|
@@ -104,7 +128,7 @@ const stylelintRunner = {
|
|
|
104
128
|
ctx.log("stylelint: no config detected, running with default rules");
|
|
105
129
|
}
|
|
106
130
|
let cmd = null;
|
|
107
|
-
if (await
|
|
131
|
+
if (await stylelint.isAvailableAsync(cwd)) {
|
|
108
132
|
cmd = stylelint.getCommand(cwd);
|
|
109
133
|
}
|
|
110
134
|
else {
|
|
@@ -116,23 +140,19 @@ const stylelintRunner = {
|
|
|
116
140
|
// #1816: this runner read `result.status` zero times, so an exit-78
|
|
117
141
|
// config error (or an exit-1 crash) fell through parseStylelintJson's
|
|
118
142
|
// catch to zero diagnostics and reported a clean stylesheet.
|
|
119
|
-
const
|
|
143
|
+
const raw = stylelintReport(result.stdout, result.stderr);
|
|
144
|
+
// #1948: the stdout-vs-stderr report bug landed here as exit 2 plus a
|
|
145
|
+
// full JSON report and zero parsed warnings. That now leaves a record.
|
|
146
|
+
const run = parseToolRun("stylelint", { result, output: raw, exitCodes: STYLELINT_EXIT_CODES }, (out) => parseStylelintJson(out, ctx.filePath));
|
|
147
|
+
if (run.skipped)
|
|
148
|
+
return run.skipped;
|
|
149
|
+
const diagnostics = run.diagnostics;
|
|
150
|
+
return finishParsedRun({
|
|
151
|
+
tool: "stylelint",
|
|
152
|
+
ctx,
|
|
120
153
|
result,
|
|
121
|
-
exitCodes: STYLELINT_EXIT_CODES,
|
|
122
|
-
});
|
|
123
|
-
if (skipped)
|
|
124
|
-
return skipped;
|
|
125
|
-
const raw = result.stdout ?? "";
|
|
126
|
-
const diagnostics = parseStylelintJson(raw, ctx.filePath);
|
|
127
|
-
if (diagnostics.length === 0) {
|
|
128
|
-
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
129
|
-
}
|
|
130
|
-
const hasBlocking = diagnostics.some((d) => d.semantic === "blocking");
|
|
131
|
-
return {
|
|
132
|
-
status: hasBlocking ? "failed" : "succeeded",
|
|
133
154
|
diagnostics,
|
|
134
|
-
|
|
135
|
-
};
|
|
155
|
+
});
|
|
136
156
|
},
|
|
137
157
|
};
|
|
138
158
|
export default stylelintRunner;
|
|
@@ -2,7 +2,7 @@ import * as path from "node:path";
|
|
|
2
2
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
3
3
|
import { PRIORITY } from "../priorities.js";
|
|
4
4
|
import { createAvailabilityChecker, resolveToolCommandWithInstallFallback, } from "./utils/runner-helpers.js";
|
|
5
|
-
import {
|
|
5
|
+
import { parseToolRun } from "./utils/tool-failure.js";
|
|
6
6
|
const swiftlint = createAvailabilityChecker("swiftlint", ".exe");
|
|
7
7
|
// SwiftLint rules whose rule class declares a `corrector` — i.e. rules that
|
|
8
8
|
// `swiftlint --fix` rewrites deterministically. The JSON reporter does not
|
|
@@ -118,7 +118,7 @@ const swiftlintRunner = {
|
|
|
118
118
|
async run(ctx) {
|
|
119
119
|
const cwd = ctx.cwd || process.cwd();
|
|
120
120
|
let cmd = null;
|
|
121
|
-
if (await
|
|
121
|
+
if (await swiftlint.isAvailableAsync(cwd)) {
|
|
122
122
|
cmd = swiftlint.getCommand(cwd);
|
|
123
123
|
}
|
|
124
124
|
else {
|
|
@@ -133,12 +133,12 @@ const swiftlintRunner = {
|
|
|
133
133
|
// stably enough to call any of them a rejection, so classification
|
|
134
134
|
// rests on the conservative rule — a nonzero exit with nothing to parse
|
|
135
135
|
// never ran.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const diagnostics =
|
|
136
|
+
// SwiftLint exits non-zero on violations — stdout still has the JSON, so
|
|
137
|
+
// a nonzero exit whose JSON yields nothing is a parser break (#1948).
|
|
138
|
+
const run = parseToolRun("swiftlint", { result }, (out) => parseSwiftLintOutput(out, ctx.filePath));
|
|
139
|
+
if (run.skipped)
|
|
140
|
+
return run.skipped;
|
|
141
|
+
const diagnostics = run.diagnostics;
|
|
142
142
|
if (diagnostics.length === 0) {
|
|
143
143
|
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
144
144
|
}
|
|
@@ -4,28 +4,101 @@ import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
|
4
4
|
import { getLinterPolicyForCwd } from "../../tool-policy.js";
|
|
5
5
|
import { PRIORITY } from "../priorities.js";
|
|
6
6
|
import { createAvailabilityChecker, lspPrimaryCoversFile, resolveToolCommandWithInstallFallback, } from "./utils/runner-helpers.js";
|
|
7
|
-
import {
|
|
7
|
+
import { parseToolRun } from "./utils/tool-failure.js";
|
|
8
8
|
const taplo = createAvailabilityChecker("taplo", ".exe");
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
// Verified against taplo 0.10.0: 0 = clean, 1 = the file is invalid (lint
|
|
10
|
+
// findings, or a schema it could not load), 2 = clap rejected the invocation.
|
|
11
|
+
//
|
|
12
|
+
// #1937 round 2: the parser fix alone turned the old silent-clean into a false
|
|
13
|
+
// BLOCKING diagnostic. clap prints `error: unexpected argument '--output'
|
|
14
|
+
// found`, which starts with the same lowercase `error:` a real taplo
|
|
15
|
+
// diagnostic does, so a mistyped flag would have reddened every valid TOML
|
|
16
|
+
// file. Both readings are wrong; the exit code is what separates them.
|
|
17
|
+
const TAPLO_EXIT_CODES = { ran: [1] };
|
|
18
|
+
/** `error: <summary>` or `warning: <summary>` at the head of a codespan block. */
|
|
19
|
+
const HEADER_PATTERN = /^\s*(error|warning):(.*)$/;
|
|
20
|
+
/** The box-drawing lead taplo puts on a codespan location line. */
|
|
21
|
+
const LOCATION_LEAD = "┌─";
|
|
22
|
+
/** The `:line:column` tail of a codespan location line. */
|
|
23
|
+
const LOCATION_PATTERN = /:(\d+):(\d+)$/;
|
|
24
|
+
/**
|
|
25
|
+
* Parse `taplo lint` output (#1937).
|
|
26
|
+
*
|
|
27
|
+
* This parser used to `JSON.parse` a `{ errors: [{ range, message, kind }] }`
|
|
28
|
+
* envelope that taplo has never emitted, fed by a `--output=json` flag taplo
|
|
29
|
+
* has never accepted. Real taplo rejected the flag with exit 2 and a usage
|
|
30
|
+
* error, the JSON parse threw, the catch returned [], and malformed TOML was
|
|
31
|
+
* reported clean. Same shape as the vale parser in #1933.
|
|
32
|
+
*
|
|
33
|
+
* Real taplo 0.10.0 writes codespan diagnostics to STDERR, interleaved with
|
|
34
|
+
* uppercase tracing lines:
|
|
35
|
+
*
|
|
36
|
+
* error: invalid TOML
|
|
37
|
+
* ┌─ /path/to/bad.toml:2:9
|
|
38
|
+
* │
|
|
39
|
+
* 2 │ [package
|
|
40
|
+
*
|
|
41
|
+
* Verified against tests/fixtures/runner-output/taplo/real.captured.json.
|
|
42
|
+
*
|
|
43
|
+
* A header alone is NOT enough to emit a diagnostic. The codespan location
|
|
44
|
+
* line must follow it. clap's own failures start with the same lowercase
|
|
45
|
+
* `error:` and carry no location, so without that requirement a rejected
|
|
46
|
+
* invocation becomes a blocking finding on line 1 of a perfectly valid file
|
|
47
|
+
* (#1937 round 2). The exit-code table in the runner is the primary guard;
|
|
48
|
+
* this is the structural one, and each covers a case the other does not.
|
|
49
|
+
*/
|
|
50
|
+
export function parseTaploOutput(raw, filePath) {
|
|
51
|
+
const diagnostics = [];
|
|
52
|
+
const lines = (raw ?? "").split(/\r?\n/);
|
|
53
|
+
for (let i = 0; i < lines.length; i++) {
|
|
54
|
+
// Lowercase `error:`/`warning:` starts a diagnostic. taplo's tracing
|
|
55
|
+
// lines are uppercase (`ERROR taplo:lint_files: ...`), so a
|
|
56
|
+
// case-sensitive match keeps them out.
|
|
57
|
+
//
|
|
58
|
+
// Both patterns below trim in code rather than with a trailing `\s*`,
|
|
59
|
+
// and the location one tests for its box-drawing lead separately
|
|
60
|
+
// instead of skipping to it with `.*`. Either shape backtracks
|
|
61
|
+
// super-linearly on a long line, and this parser reads bytes a third
|
|
62
|
+
// party wrote.
|
|
63
|
+
const header = HEADER_PATTERN.exec(lines[i].trimEnd());
|
|
64
|
+
if (!header)
|
|
65
|
+
continue;
|
|
66
|
+
const severityWord = header[1];
|
|
67
|
+
const summary = header[2].trim();
|
|
68
|
+
if (!summary)
|
|
69
|
+
continue;
|
|
70
|
+
// The location arrives on a following `┌─ file:line:col` line. Scan a
|
|
71
|
+
// short window rather than assuming adjacency: taplo puts a gutter line
|
|
72
|
+
// between them in some layouts.
|
|
73
|
+
let line = null;
|
|
74
|
+
let column = 1;
|
|
75
|
+
for (let j = i + 1; j < Math.min(i + 4, lines.length); j++) {
|
|
76
|
+
if (!lines[j].includes(LOCATION_LEAD))
|
|
77
|
+
continue;
|
|
78
|
+
const location = LOCATION_PATTERN.exec(lines[j].trimEnd());
|
|
79
|
+
if (!location)
|
|
80
|
+
continue;
|
|
81
|
+
line = Number.parseInt(location[1], 10) || 1;
|
|
82
|
+
column = Number.parseInt(location[2], 10) || 1;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
if (line === null)
|
|
86
|
+
continue;
|
|
87
|
+
const severity = severityWord === "warning" ? "warning" : "error";
|
|
88
|
+
diagnostics.push({
|
|
89
|
+
id: `taplo-${severity}-${line}-${column}`,
|
|
90
|
+
message: summary,
|
|
16
91
|
filePath,
|
|
17
|
-
line
|
|
18
|
-
column
|
|
19
|
-
severity
|
|
20
|
-
semantic: "blocking",
|
|
92
|
+
line,
|
|
93
|
+
column,
|
|
94
|
+
severity,
|
|
95
|
+
semantic: severity === "error" ? "blocking" : "warning",
|
|
21
96
|
tool: "taplo",
|
|
22
|
-
rule:
|
|
97
|
+
rule: `taplo/${severity}`,
|
|
23
98
|
fixable: false,
|
|
24
|
-
})
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
return [];
|
|
99
|
+
});
|
|
28
100
|
}
|
|
101
|
+
return diagnostics;
|
|
29
102
|
}
|
|
30
103
|
const taploRunner = {
|
|
31
104
|
id: "taplo",
|
|
@@ -57,7 +130,7 @@ const taploRunner = {
|
|
|
57
130
|
// different extension, so it stays as the checker's fallback only.
|
|
58
131
|
let cmd = findLocalBinUpwards("taplo", cwd) ?? null;
|
|
59
132
|
if (!cmd) {
|
|
60
|
-
if (await
|
|
133
|
+
if (await taplo.isAvailableAsync(cwd)) {
|
|
61
134
|
cmd = taplo.getCommand(cwd);
|
|
62
135
|
}
|
|
63
136
|
else {
|
|
@@ -67,11 +140,25 @@ const taploRunner = {
|
|
|
67
140
|
if (!cmd)
|
|
68
141
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
69
142
|
const absPath = path.resolve(cwd, ctx.filePath);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
143
|
+
// #1937: `--output=json` is not a taplo flag. taplo rejected it with a
|
|
144
|
+
// usage error, so this runner never linted anything. `--colors never`
|
|
145
|
+
// keeps ANSI escapes out of the bytes the parser reads.
|
|
146
|
+
const result = await safeSpawnAsync(cmd, ["check", "--colors", "never", absPath], { cwd, timeout: 15000 });
|
|
147
|
+
// taplo reports on stderr and leaves stdout empty, so the
|
|
148
|
+
// "nothing to parse" test must read the same string the parser gets —
|
|
149
|
+
// otherwise every real run looks like a failed spawn.
|
|
150
|
+
const raw = result.stderr?.trim() ? result.stderr : (result.stdout ?? "");
|
|
151
|
+
//
|
|
152
|
+
// taplo exits 1 only when a file is INVALID, and it does not always draw
|
|
153
|
+
// a codespan block when it does: a schema it could not load is reported
|
|
154
|
+
// through its tracing output alone. Claiming that file clean would be an
|
|
155
|
+
// outright lie, so taplo opts into `skipWhenParsedNothing` (#1948). This
|
|
156
|
+
// replaces a hand-rolled per-runner copy of the same rule, which wrote
|
|
157
|
+
// its own `runner-empty-result` row with its own wording.
|
|
158
|
+
const run = parseToolRun("taplo", { result, output: raw, exitCodes: TAPLO_EXIT_CODES }, (out) => parseTaploOutput(out, ctx.filePath), { skipWhenParsedNothing: true });
|
|
159
|
+
if (run.skipped)
|
|
160
|
+
return run.skipped;
|
|
161
|
+
const diagnostics = run.diagnostics;
|
|
75
162
|
if (diagnostics.length === 0) {
|
|
76
163
|
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
77
164
|
}
|
|
@@ -5,6 +5,7 @@ import { getLinterPolicyForCwd } from "../../tool-policy.js";
|
|
|
5
5
|
import { PRIORITY } from "../priorities.js";
|
|
6
6
|
import { createAvailabilityChecker, resolveAvailableOrInstall, } from "./utils/runner-helpers.js";
|
|
7
7
|
import { spawnFailedWithNoOutput } from "./utils/spawn-outcome.js";
|
|
8
|
+
import { finishParsedRun } from "./utils/tool-failure.js";
|
|
8
9
|
const terragrunt = createAvailabilityChecker("terragrunt", ".exe");
|
|
9
10
|
function normalizeSeverity(raw) {
|
|
10
11
|
if (typeof raw === "number")
|
|
@@ -149,15 +150,19 @@ const terragruntRunner = {
|
|
|
149
150
|
if (spawnFailedWithNoOutput(result))
|
|
150
151
|
return SKIPPED;
|
|
151
152
|
const diagnostics = parseTerragruntOutput(result.stdout || "", ctx.filePath, absPath);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
status: hasErrors ? "failed" : "succeeded",
|
|
153
|
+
return finishParsedRun({
|
|
154
|
+
tool: "terragrunt",
|
|
155
|
+
ctx,
|
|
156
|
+
result,
|
|
158
157
|
diagnostics,
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
classify: (diagnostics) => {
|
|
159
|
+
const hasErrors = diagnostics.some((d) => d.severity === "error");
|
|
160
|
+
return {
|
|
161
|
+
status: hasErrors ? "failed" : "succeeded",
|
|
162
|
+
semantic: hasErrors ? "blocking" : "warning",
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
});
|
|
161
166
|
},
|
|
162
167
|
};
|
|
163
168
|
export default terragruntRunner;
|
|
@@ -2,9 +2,10 @@ import * as path from "node:path";
|
|
|
2
2
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
3
3
|
import { getLinterPolicyForCwd } from "../../tool-policy.js";
|
|
4
4
|
import { findNearestDirWithAnyBasename } from "../../workspace-topology.js";
|
|
5
|
-
import { createAvailabilityChecker, resolveAvailableOrInstall, } from "./utils/runner-helpers.js";
|
|
6
|
-
import { spawnFailedWithNoOutput } from "./utils/spawn-outcome.js";
|
|
7
5
|
import { PRIORITY } from "../priorities.js";
|
|
6
|
+
import { createAvailabilityChecker, resolveAvailableOrInstall, } from "./utils/runner-helpers.js";
|
|
7
|
+
import { parseToolRun } from "./utils/tool-failure.js";
|
|
8
|
+
import { finishParsedRun } from "./utils/tool-failure.js";
|
|
8
9
|
const tflint = createAvailabilityChecker("tflint", ".exe");
|
|
9
10
|
const TFLINT_CONFIG = ".tflint.hcl";
|
|
10
11
|
/**
|
|
@@ -58,7 +59,7 @@ const tflintRunner = {
|
|
|
58
59
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
59
60
|
}
|
|
60
61
|
let cmd = null;
|
|
61
|
-
if (await
|
|
62
|
+
if (await tflint.isAvailableAsync(cwd)) {
|
|
62
63
|
cmd = tflint.getCommand(cwd);
|
|
63
64
|
}
|
|
64
65
|
else {
|
|
@@ -82,19 +83,28 @@ const tflintRunner = {
|
|
|
82
83
|
cwd: fileDir,
|
|
83
84
|
timeout: 30000,
|
|
84
85
|
});
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
86
|
+
// #1948: tflint exits nonzero and writes its JSON report to stdout, so a
|
|
87
|
+
// nonzero exit whose report yields zero diagnostics is a parser break,
|
|
88
|
+
// not a clean file. No exit-code table: tflint's nonzero codes are not
|
|
89
|
+
// verified against a real binary here, so the conservative
|
|
90
|
+
// nothing-to-parse rule stays the only discriminator.
|
|
91
|
+
const run = parseToolRun("tflint", { result }, (out) => parseTflintOutput(out, ctx.filePath));
|
|
92
|
+
if (run.skipped)
|
|
93
|
+
return run.skipped;
|
|
94
|
+
const diagnostics = run.diagnostics;
|
|
95
|
+
return finishParsedRun({
|
|
96
|
+
tool: "tflint",
|
|
97
|
+
ctx,
|
|
98
|
+
result,
|
|
95
99
|
diagnostics,
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
classify: (diagnostics) => {
|
|
101
|
+
const hasErrors = diagnostics.some((d) => d.severity === "error");
|
|
102
|
+
return {
|
|
103
|
+
status: hasErrors ? "failed" : "succeeded",
|
|
104
|
+
semantic: hasErrors ? "blocking" : "warning",
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
});
|
|
98
108
|
},
|
|
99
109
|
};
|
|
100
110
|
export default tflintRunner;
|
|
@@ -300,7 +300,9 @@ const ENTITY_EXTRACTION_LINE_THRESHOLD = 5;
|
|
|
300
300
|
*/
|
|
301
301
|
function relativeForIgnoreGlob(filePath, root) {
|
|
302
302
|
const rel = path.relative(root, filePath);
|
|
303
|
-
const normalized = (rel.startsWith("..") ? filePath : rel)
|
|
303
|
+
const normalized = (rel.startsWith("..") ? filePath : rel)
|
|
304
|
+
.split(path.sep)
|
|
305
|
+
.join("/");
|
|
304
306
|
return normalized;
|
|
305
307
|
}
|
|
306
308
|
function matchesIgnorePaths(filePath, root, patterns) {
|
|
@@ -311,7 +313,17 @@ function matchesIgnorePaths(filePath, root, patterns) {
|
|
|
311
313
|
}
|
|
312
314
|
const treeSitterRunner = {
|
|
313
315
|
id: "tree-sitter",
|
|
314
|
-
appliesTo: [
|
|
316
|
+
appliesTo: [
|
|
317
|
+
"jsts",
|
|
318
|
+
"python",
|
|
319
|
+
"go",
|
|
320
|
+
"rust",
|
|
321
|
+
"ruby",
|
|
322
|
+
"cxx",
|
|
323
|
+
"csharp",
|
|
324
|
+
"php",
|
|
325
|
+
"css",
|
|
326
|
+
],
|
|
315
327
|
priority: PRIORITY.STRUCTURAL_ANALYSIS,
|
|
316
328
|
enabledByDefault: true,
|
|
317
329
|
skipTestFiles: false, // Run on test files too (structural issues matter there)
|
|
@@ -323,7 +335,9 @@ const treeSitterRunner = {
|
|
|
323
335
|
logTreeSitter({
|
|
324
336
|
phase: "runner_skip",
|
|
325
337
|
filePath: ctx.filePath,
|
|
326
|
-
reason: isTreeSitterWasmAborted()
|
|
338
|
+
reason: isTreeSitterWasmAborted()
|
|
339
|
+
? "wasm_aborted"
|
|
340
|
+
: "client_unavailable",
|
|
327
341
|
status: "skipped",
|
|
328
342
|
});
|
|
329
343
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
@@ -80,5 +80,11 @@ export async function probeAvailabilityCandidates(candidates, probeArgs, timeout
|
|
|
80
80
|
// above rather than through a thrown spawn.
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
return {
|
|
83
|
+
return {
|
|
84
|
+
foundPath: null,
|
|
85
|
+
sawTransient,
|
|
86
|
+
transientCause,
|
|
87
|
+
hostStallMs,
|
|
88
|
+
evidence,
|
|
89
|
+
};
|
|
84
90
|
}
|
|
@@ -19,6 +19,7 @@ import { getServersForFileWithConfig, isServerDisabled, } from "../../../lsp/con
|
|
|
19
19
|
import { findGlobalBinary } from "../../../package-manager.js";
|
|
20
20
|
import { safeSpawnAsync } from "../../../safe-spawn.js";
|
|
21
21
|
import { getToolCommandSpec, shouldAutoInstallTool, } from "../../../tool-policy.js";
|
|
22
|
+
import { isInSpawnTimeoutCooldown } from "../../../spawn-timeout-cooldown.js";
|
|
22
23
|
import { classifyProbeFailure, createAvailabilityLatch, describeInstallAttempt, describeProbeEvidence, isLatchingOutcome, logAvailabilityDecision, startHostStallSampler, transientRetryDelayMs, } from "./availability-policy.js";
|
|
23
24
|
export { createAvailabilityLatch, classifyProbeFailure, describeProbeEvidence, describeUnavailability, isTransientDecision, logAvailabilityDecision, startHostStallSampler, } from "./availability-policy.js";
|
|
24
25
|
/**
|
|
@@ -777,6 +778,27 @@ export function createAvailabilityChecker(command, windowsExt = "", versionArgs
|
|
|
777
778
|
return false;
|
|
778
779
|
}
|
|
779
780
|
const cmd = await findCommand(resolvedCwd);
|
|
781
|
+
// #1995: a command cooling down after a RUNTIME timeout (lint or
|
|
782
|
+
// autofix lane blew its real budget) must not re-probe on every
|
|
783
|
+
// edit - the positive verdict is effectively cooled. Consult-only:
|
|
784
|
+
// probe timeouts do NOT arm the cooldown here, because a single
|
|
785
|
+
// slow --version under host stall (#1467) is transient evidence,
|
|
786
|
+
// and session-long suppression from it would contradict the
|
|
787
|
+
// #1494 retry ladder this classification exists for.
|
|
788
|
+
if (isInSpawnTimeoutCooldown(cmd)) {
|
|
789
|
+
noteDecision(cache, resolvedCwd, {
|
|
790
|
+
available: false,
|
|
791
|
+
outcome: "transient",
|
|
792
|
+
cause: "probe-timeout",
|
|
793
|
+
elapsedMs: 0,
|
|
794
|
+
classifiedBy: "caller",
|
|
795
|
+
evidence: {
|
|
796
|
+
command: cmd,
|
|
797
|
+
status: null,
|
|
798
|
+
},
|
|
799
|
+
});
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
780
802
|
const env = await options.environment?.(resolvedCwd);
|
|
781
803
|
// The probe budget is enforced by a HOST-side timer, so host event-loop
|
|
782
804
|
// stalls are charged to the child. Measure the stall that overlapped the
|
|
@@ -856,7 +878,8 @@ export function createAvailabilityChecker(command, windowsExt = "", versionArgs
|
|
|
856
878
|
outcome: cache.outcome,
|
|
857
879
|
cause: cache.cause,
|
|
858
880
|
elapsedMs: cache.elapsedMs,
|
|
859
|
-
latched: cache.available !== false ||
|
|
881
|
+
latched: cache.available !== false ||
|
|
882
|
+
isLatchingOutcome(cache.outcome ?? "missing"),
|
|
860
883
|
retryAtMs: cache.retryAtMs,
|
|
861
884
|
};
|
|
862
885
|
}
|