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
|
@@ -91,7 +91,11 @@ async function resolveCompiler(absPath, cwd) {
|
|
|
91
91
|
const clCmd = clChecker.getCommand(cwd);
|
|
92
92
|
if (clCmd) {
|
|
93
93
|
// Already probed in a previous turn and resolved.
|
|
94
|
-
return {
|
|
94
|
+
return {
|
|
95
|
+
command: clCmd,
|
|
96
|
+
args: ["/nologo", "/Zs", absPath],
|
|
97
|
+
flavor: "msvc",
|
|
98
|
+
};
|
|
95
99
|
}
|
|
96
100
|
const clProbe = await safeSpawnAsync("cl", [], { timeout: 5000 });
|
|
97
101
|
if (!clProbe.error && clProbe.status !== null) {
|
|
@@ -4,7 +4,7 @@ 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 } from "./utils/runner-helpers.js";
|
|
7
|
-
import {
|
|
7
|
+
import { parseToolRun } from "./utils/tool-failure.js";
|
|
8
8
|
const detekt = createAvailabilityChecker("detekt", ".bat");
|
|
9
9
|
const DETEKT_CONFIG_CANDIDATES = [
|
|
10
10
|
"detekt.yml",
|
|
@@ -146,11 +146,14 @@ const detektRunner = {
|
|
|
146
146
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
147
147
|
const absPath = path.resolve(cwd, ctx.filePath);
|
|
148
148
|
const result = await safeSpawnAsync(cmd, ["--input", absPath, "--config", configPath], { cwd, timeout: 60000 });
|
|
149
|
+
// #1948: detekt reports across both streams, so the gate and the parser
|
|
150
|
+
// read the same concatenated string. A nonzero exit that yields zero
|
|
151
|
+
// findings out of real output is a parser break, not clean Kotlin.
|
|
149
152
|
const raw = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const diagnostics =
|
|
153
|
+
const run = parseToolRun("detekt", { result, output: raw }, (out) => parseDetektOutput(out, ctx.filePath));
|
|
154
|
+
if (run.skipped)
|
|
155
|
+
return run.skipped;
|
|
156
|
+
const diagnostics = run.diagnostics;
|
|
154
157
|
if (diagnostics.length === 0) {
|
|
155
158
|
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
156
159
|
}
|
|
@@ -163,4 +166,4 @@ const detektRunner = {
|
|
|
163
166
|
},
|
|
164
167
|
};
|
|
165
168
|
export default detektRunner;
|
|
166
|
-
export {
|
|
169
|
+
export { DETEKT_FIXABLE_RULES, parseDetektOutput };
|
|
@@ -120,7 +120,7 @@ const dotnetBuildRunner = {
|
|
|
120
120
|
skipTestFiles: false,
|
|
121
121
|
async run(ctx) {
|
|
122
122
|
const cwd = ctx.cwd || process.cwd();
|
|
123
|
-
if (!(await
|
|
123
|
+
if (!(await dotnet.isAvailableAsync(cwd))) {
|
|
124
124
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
125
125
|
}
|
|
126
126
|
const cmd = dotnet.getCommand(cwd);
|
|
@@ -88,7 +88,9 @@ function parseElixirOutput(raw, filePath, cwd = process.cwd()) {
|
|
|
88
88
|
if (matchesTarget(sourcePath)) {
|
|
89
89
|
diagnostics.push({
|
|
90
90
|
id: `elixir-check-${severity}-${lineStr}-${colStr || "1"}`,
|
|
91
|
-
message: severity === "error"
|
|
91
|
+
message: severity === "error"
|
|
92
|
+
? `[error] ${message.trim()}`
|
|
93
|
+
: message.trim(),
|
|
92
94
|
filePath,
|
|
93
95
|
line: Number.parseInt(lineStr, 10) || 1,
|
|
94
96
|
column: Number.parseInt(colStr || "1", 10) || 1,
|
|
@@ -88,13 +88,22 @@ const eslintRunner = {
|
|
|
88
88
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
89
89
|
}
|
|
90
90
|
const result = await safeSpawnAsync(cmd, ["--format", "json", "--no-error-on-unmatched-pattern", ctx.filePath], { timeout: 30000, cwd });
|
|
91
|
-
// ESLint exits
|
|
91
|
+
// ESLint exits 2 on fatal/config errors — nothing was linted, so this
|
|
92
|
+
// is an unavailable run, not a clean or failed one.
|
|
92
93
|
if (result.status === 2) {
|
|
93
94
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
94
95
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
// ESLint exits 0 whenever nothing reached ERROR severity — that
|
|
97
|
+
// includes a run that found only warnings (#1954), which also prints a
|
|
98
|
+
// full JSON report. So parse stdout unconditionally and branch on the
|
|
99
|
+
// parsed diagnostic count instead of letting the exit code discard the
|
|
100
|
+
// warning case. Stderr is only a fallback for a failing run whose
|
|
101
|
+
// stdout went missing; parsing stderr noise on a healthy exit-0 run
|
|
102
|
+
// would turn deprecation chatter into spurious parse-error findings.
|
|
103
|
+
const stdout = result.stdout ?? "";
|
|
104
|
+
let raw = stdout;
|
|
105
|
+
if (raw.length === 0 && result.status !== 0) {
|
|
106
|
+
raw = result.stderr || "";
|
|
98
107
|
}
|
|
99
108
|
const parsed = parseEslintJson(raw, ctx.filePath);
|
|
100
109
|
if (parsed.parseError && raw.trim().length > 0) {
|
|
@@ -104,7 +113,9 @@ const eslintRunner = {
|
|
|
104
113
|
diagnostics: [
|
|
105
114
|
{
|
|
106
115
|
id: "eslint:parse-error:1",
|
|
107
|
-
message: "ESLint JSON parse failed: " +
|
|
116
|
+
message: "ESLint JSON parse failed: " +
|
|
117
|
+
parsed.parseError +
|
|
118
|
+
(preview ? " (output preview: " + preview + ")" : ""),
|
|
108
119
|
filePath: ctx.filePath,
|
|
109
120
|
line: 1,
|
|
110
121
|
column: 1,
|
|
@@ -121,8 +132,16 @@ const eslintRunner = {
|
|
|
121
132
|
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
122
133
|
}
|
|
123
134
|
const hasErrors = diagnostics.some((d) => d.semantic === "blocking");
|
|
135
|
+
// A warning-only result on exit 0 is ESLint's normal outcome, not a
|
|
136
|
+
// failure: exit 0 means nothing hit ERROR severity. Keying status off
|
|
137
|
+
// blocking severity plus exit code (the sibling convention from oxlint,
|
|
138
|
+
// biome-check, golangci-lint, rubocop) keeps plan.ts's fallback group
|
|
139
|
+
// ["eslint", "oxlint", "biome-check-json"] stopping at eslint instead
|
|
140
|
+
// of re-running oxlint and biome-check-json on every warning-only save.
|
|
141
|
+
// The findings reach delivery regardless of status — dispatcher.ts
|
|
142
|
+
// buckets by each diagnostic's own `semantic`.
|
|
124
143
|
return {
|
|
125
|
-
status: "failed",
|
|
144
|
+
status: !hasErrors && result.status === 0 ? "succeeded" : "failed",
|
|
126
145
|
diagnostics,
|
|
127
146
|
semantic: hasErrors ? "blocking" : "warning",
|
|
128
147
|
};
|
|
@@ -11,7 +11,7 @@ const fishIndentRunner = {
|
|
|
11
11
|
skipTestFiles: false,
|
|
12
12
|
async run(ctx) {
|
|
13
13
|
const cwd = ctx.cwd || process.cwd();
|
|
14
|
-
const available = await
|
|
14
|
+
const available = await fishIndent.isAvailableAsync(cwd);
|
|
15
15
|
if (!available)
|
|
16
16
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
17
17
|
const cmd = fishIndent.getCommand(cwd);
|
|
@@ -43,7 +43,7 @@ const gleamCheckRunner = {
|
|
|
43
43
|
skipTestFiles: false,
|
|
44
44
|
async run(ctx) {
|
|
45
45
|
const cwd = ctx.cwd || process.cwd();
|
|
46
|
-
if (!(await
|
|
46
|
+
if (!(await gleam.isAvailableAsync(cwd))) {
|
|
47
47
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
48
48
|
}
|
|
49
49
|
const cmd = gleam.getCommand(cwd);
|
|
@@ -31,7 +31,9 @@ function describeReplacement(replacement) {
|
|
|
31
31
|
return "Delete this code";
|
|
32
32
|
if (replacement.NewLines && replacement.NewLines.length > 0) {
|
|
33
33
|
const preview = replacement.NewLines[0].trim();
|
|
34
|
-
const hint = replacement.NewLines.length > 1
|
|
34
|
+
const hint = replacement.NewLines.length > 1
|
|
35
|
+
? ` (+${replacement.NewLines.length - 1} more lines)`
|
|
36
|
+
: "";
|
|
35
37
|
return `Replace with: ${preview}${hint}`;
|
|
36
38
|
}
|
|
37
39
|
return "Apply golangci-lint suggested fix";
|
|
@@ -3,7 +3,8 @@ import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
|
3
3
|
import { getLinterPolicyForCwd } 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 hadolint = createAvailabilityChecker("hadolint", ".exe");
|
|
8
9
|
function parseHadolintOutput(raw, filePath) {
|
|
9
10
|
try {
|
|
@@ -43,7 +44,7 @@ const hadolintRunner = {
|
|
|
43
44
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
44
45
|
}
|
|
45
46
|
let cmd = null;
|
|
46
|
-
if (await
|
|
47
|
+
if (await hadolint.isAvailableAsync(cwd)) {
|
|
47
48
|
cmd = hadolint.getCommand(cwd);
|
|
48
49
|
}
|
|
49
50
|
else {
|
|
@@ -53,20 +54,26 @@ const hadolintRunner = {
|
|
|
53
54
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
54
55
|
}
|
|
55
56
|
const result = await safeSpawnAsync(cmd, ["--format", "json", "--no-fail", path.resolve(cwd, ctx.filePath)], { cwd });
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
57
|
+
// #1948: hadolint runs with `--no-fail`, so it exits 0 even when it finds
|
|
58
|
+
// something. A nonzero exit therefore means hadolint itself failed, and
|
|
59
|
+
// zero parsed diagnostics out of whatever it printed is a parser break.
|
|
60
|
+
const run = parseToolRun("hadolint", { result }, (out) => parseHadolintOutput(out, ctx.filePath));
|
|
61
|
+
if (run.skipped)
|
|
62
|
+
return run.skipped;
|
|
63
|
+
const diagnostics = run.diagnostics;
|
|
64
|
+
return finishParsedRun({
|
|
65
|
+
tool: "hadolint",
|
|
66
|
+
ctx,
|
|
67
|
+
result,
|
|
67
68
|
diagnostics,
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
classify: (diagnostics) => {
|
|
70
|
+
const hasErrors = diagnostics.some((d) => d.severity === "error");
|
|
71
|
+
return {
|
|
72
|
+
status: "failed",
|
|
73
|
+
semantic: hasErrors ? "blocking" : "warning",
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
});
|
|
70
77
|
},
|
|
71
78
|
};
|
|
72
79
|
export default hadolintRunner;
|
|
@@ -15,7 +15,10 @@ const SKIPPED = {
|
|
|
15
15
|
};
|
|
16
16
|
function isWithin(root, candidate) {
|
|
17
17
|
const relative = path.relative(normalizeMapKey(root), normalizeMapKey(candidate));
|
|
18
|
-
return relative === "" ||
|
|
18
|
+
return (relative === "" ||
|
|
19
|
+
(!relative.startsWith(`..${path.sep}`) &&
|
|
20
|
+
relative !== ".." &&
|
|
21
|
+
!path.isAbsolute(relative)));
|
|
19
22
|
}
|
|
20
23
|
function diagnosticLocation(message, chartRoot) {
|
|
21
24
|
const location = message.match(/(?:^|\s)([^:\r\n]+\.(?:ya?ml|tpl|json))(?::(\d+))?(?::\d+)?(?::|\s)/i);
|
|
@@ -3,7 +3,8 @@ import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
|
3
3
|
import { getLinterPolicyForCwd } 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 htmlhint = createAvailabilityChecker("htmlhint");
|
|
8
9
|
const HTMLHINT_RULES = {
|
|
9
10
|
"tag-pair": true,
|
|
@@ -55,7 +56,7 @@ const htmlhintRunner = {
|
|
|
55
56
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
56
57
|
}
|
|
57
58
|
let cmd = null;
|
|
58
|
-
if (await
|
|
59
|
+
if (await htmlhint.isAvailableAsync(cwd)) {
|
|
59
60
|
cmd = htmlhint.getCommand(cwd);
|
|
60
61
|
}
|
|
61
62
|
else {
|
|
@@ -64,28 +65,44 @@ const htmlhintRunner = {
|
|
|
64
65
|
if (!cmd) {
|
|
65
66
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
66
67
|
}
|
|
67
|
-
|
|
68
|
+
// htmlhint's --rules flag takes a comma-separated ruleid list
|
|
69
|
+
// ("tag-pair,id-class-value=underline"), NOT a JSON object. The old JSON
|
|
70
|
+
// blob parsed as one bogus rule id, leaving ZERO rules enabled — every
|
|
71
|
+
// file read CLEAN (exit 0, no output) no matter what it contained.
|
|
72
|
+
// Root-caused live against htmlhint 1.x after the tier-1 parser smoke
|
|
73
|
+
// caught htmlhint reporting 0 findings on the planted tag-pair defect.
|
|
74
|
+
// Entries set to false are omitted: under --rules semantics an unlisted
|
|
75
|
+
// rule is off, which is exactly what `false` means here.
|
|
76
|
+
const rulesArg = Object.entries(HTMLHINT_RULES)
|
|
77
|
+
.flatMap(([rule, enabled]) => (enabled === true ? [rule] : []))
|
|
78
|
+
.join(",");
|
|
68
79
|
const result = await safeSpawnAsync(cmd, [
|
|
69
80
|
"--rules",
|
|
70
|
-
|
|
81
|
+
rulesArg,
|
|
71
82
|
"--format",
|
|
72
83
|
"unix",
|
|
73
84
|
path.resolve(cwd, ctx.filePath),
|
|
74
85
|
], { cwd });
|
|
86
|
+
// #1948: htmlhint exits 1 when it finds errors and prints them in `unix`
|
|
87
|
+
// format on stdout. Zero parsed out of a nonzero exit is a parser break.
|
|
75
88
|
const output = result.stdout || result.stderr || "";
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const diagnostics =
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
status: "failed",
|
|
89
|
+
const run = parseToolRun("htmlhint", { result, output }, (out) => parseHtmlhintOutput(out, ctx.filePath));
|
|
90
|
+
if (run.skipped)
|
|
91
|
+
return run.skipped;
|
|
92
|
+
const diagnostics = run.diagnostics;
|
|
93
|
+
return finishParsedRun({
|
|
94
|
+
tool: "htmlhint",
|
|
95
|
+
ctx,
|
|
96
|
+
result,
|
|
86
97
|
diagnostics,
|
|
87
|
-
|
|
88
|
-
|
|
98
|
+
classify: (diagnostics) => {
|
|
99
|
+
const hasErrors = diagnostics.some((d) => d.severity === "error");
|
|
100
|
+
return {
|
|
101
|
+
status: "failed",
|
|
102
|
+
semantic: hasErrors ? "blocking" : "warning",
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
});
|
|
89
106
|
},
|
|
90
107
|
};
|
|
91
108
|
export default htmlhintRunner;
|
|
@@ -54,7 +54,7 @@ const javacRunner = {
|
|
|
54
54
|
ctx.log?.("javac: skipped — file is inside a Maven/Gradle project; a classpath-less compile would emit false positives");
|
|
55
55
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
56
56
|
}
|
|
57
|
-
if (!(await
|
|
57
|
+
if (!(await javac.isAvailableAsync(cwd))) {
|
|
58
58
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
59
59
|
}
|
|
60
60
|
const cmd = javac.getCommand(cwd);
|
|
@@ -23,7 +23,7 @@ import { demoteInferredProjectDiagnostics } from "../../lsp/inferred-project.js"
|
|
|
23
23
|
import { enabledAuxiliaryLspServerIds, retagAuxiliaryDiagnostics, } from "../auxiliary-lsp.js";
|
|
24
24
|
import { readFileContent } from "./utils.js";
|
|
25
25
|
import { tryWarmAttachedCodeActions, tryWarmAttachedDiagnostics, } from "../../warm-attach.js";
|
|
26
|
-
import { contentHash, WARM_CODE_ACTION_LOOKUP_LIMIT
|
|
26
|
+
import { contentHash, WARM_CODE_ACTION_LOOKUP_LIMIT } from "../../mcp/ipc.js";
|
|
27
27
|
const LSP_MAX_FILE_BYTES = RUNTIME_CONFIG.pipeline.lspMaxFileBytes;
|
|
28
28
|
const LSP_MAX_FILE_LINES = RUNTIME_CONFIG.pipeline.lspMaxFileLines;
|
|
29
29
|
const LSP_SPAWN_BUDGET_MS = RUNTIME_CONFIG.pipeline.lspSpawnBudgetMs;
|
|
@@ -2,7 +2,9 @@ import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
|
2
2
|
import { getLinterPolicyForCwd, markdownlintConfigArgs, } 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 { isInSpawnTimeoutCooldown, noteSpawnTimeout, } from "../../spawn-timeout-cooldown.js";
|
|
7
|
+
import { finishParsedRun } from "./utils/tool-failure.js";
|
|
6
8
|
const markdownlint = createAvailabilityChecker("markdownlint-cli2", ".cmd");
|
|
7
9
|
// markdownlint-cli2 exit codes (its README's "Exit Codes" section): 0 = no
|
|
8
10
|
// findings, 1 = lint findings, 2 = unexpected error (unreadable config, bad
|
|
@@ -98,7 +100,7 @@ const markdownlintRunner = {
|
|
|
98
100
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
99
101
|
}
|
|
100
102
|
let cmd = null;
|
|
101
|
-
if (await
|
|
103
|
+
if (await markdownlint.isAvailableAsync(cwd)) {
|
|
102
104
|
cmd = markdownlint.getCommand(cwd);
|
|
103
105
|
}
|
|
104
106
|
else {
|
|
@@ -106,6 +108,13 @@ const markdownlintRunner = {
|
|
|
106
108
|
}
|
|
107
109
|
if (!cmd)
|
|
108
110
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
111
|
+
// #1995: a command cooling down after a spawn timeout already consumed
|
|
112
|
+
// its budget in another lane (availability verify, autofix --fix). Skip
|
|
113
|
+
// without spawning — "not checked", never re-reported as clean.
|
|
114
|
+
if (isInSpawnTimeoutCooldown(cmd)) {
|
|
115
|
+
ctx.log(`markdownlint: ${cmd} is cooling down after a spawn timeout — skipping (one bounded failure budget per edit)`);
|
|
116
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
117
|
+
}
|
|
109
118
|
// Shared config-args seam (#1247): the autofix path consumes the same
|
|
110
119
|
// builder, so the package-owned fallback config can never drift.
|
|
111
120
|
const configArgs = markdownlintConfigArgs(cwd);
|
|
@@ -113,23 +122,36 @@ const markdownlintRunner = {
|
|
|
113
122
|
timeout: 15000,
|
|
114
123
|
cwd,
|
|
115
124
|
});
|
|
125
|
+
// #1995: a timeout is negative runtime evidence. Arm the cooldown so
|
|
126
|
+
// the autofix and availability lanes cannot hand this command a second
|
|
127
|
+
// budget within the same session.
|
|
128
|
+
if (result.failure === "timeout") {
|
|
129
|
+
noteSpawnTimeout({
|
|
130
|
+
tool: "markdownlint",
|
|
131
|
+
command: cmd,
|
|
132
|
+
phase: "lint",
|
|
133
|
+
durationMs: 15000,
|
|
134
|
+
teardown: result.timeoutTeardown,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
116
137
|
// #1816: this runner read `result.status` zero times, so an exit-2
|
|
117
138
|
// config error with an empty stdout parsed to zero diagnostics and was
|
|
118
139
|
// reported as a clean Markdown file. An empty result must distinguish
|
|
119
140
|
// clean from errored.
|
|
120
141
|
const raw = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
121
|
-
|
|
142
|
+
// #1948: one seam for both gates — the tool produced nothing, and the
|
|
143
|
+
// tool produced something the parser could not read.
|
|
144
|
+
const run = parseToolRun("markdownlint", { result, output: raw, exitCodes: MARKDOWNLINT_EXIT_CODES }, (out) => parseMarkdownlintOutput(out, ctx.filePath));
|
|
145
|
+
if (run.skipped)
|
|
146
|
+
return run.skipped;
|
|
147
|
+
const diagnostics = run.diagnostics;
|
|
148
|
+
return finishParsedRun({
|
|
149
|
+
tool: "markdownlint",
|
|
150
|
+
ctx,
|
|
122
151
|
result,
|
|
123
|
-
|
|
124
|
-
|
|
152
|
+
diagnostics,
|
|
153
|
+
classify: () => ({ status: "succeeded", semantic: "warning" }),
|
|
125
154
|
});
|
|
126
|
-
if (skipped)
|
|
127
|
-
return skipped;
|
|
128
|
-
const diagnostics = parseMarkdownlintOutput(raw, ctx.filePath);
|
|
129
|
-
if (diagnostics.length === 0) {
|
|
130
|
-
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
131
|
-
}
|
|
132
|
-
return { status: "succeeded", diagnostics, semantic: "warning" };
|
|
133
155
|
},
|
|
134
156
|
};
|
|
135
157
|
export default markdownlintRunner;
|
|
@@ -3,7 +3,8 @@ import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
|
3
3
|
import { hasMypyConfig } 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 mypy = createAvailabilityChecker("mypy", "");
|
|
8
9
|
// mypy exit codes: 0 = no type errors, 1 = type errors found, 2 = a blocking
|
|
9
10
|
// error. 2 is NOT a rejected invocation on its own — mypy reports a source
|
|
@@ -67,7 +68,7 @@ const mypyRunner = {
|
|
|
67
68
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
68
69
|
}
|
|
69
70
|
let cmd = null;
|
|
70
|
-
if (await
|
|
71
|
+
if (await mypy.isAvailableAsync(cwd)) {
|
|
71
72
|
cmd = mypy.getCommand(cwd);
|
|
72
73
|
}
|
|
73
74
|
else {
|
|
@@ -81,23 +82,20 @@ const mypyRunner = {
|
|
|
81
82
|
// stdout only (see MYPY_EXIT_CODES) so an exit-2 SYNTAX error, which
|
|
82
83
|
// does write a diagnostic there, still reaches the parser.
|
|
83
84
|
const raw = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
84
|
-
|
|
85
|
+
// #1948: `parseOutput` keeps the split this runner already had — the gate
|
|
86
|
+
// judges "did it run" on stdout alone, the parser still reads both
|
|
87
|
+
// streams — so adding the parsed-nothing record does not widen the
|
|
88
|
+
// did-it-run verdict.
|
|
89
|
+
const run = parseToolRun("mypy", { result, output: result.stdout, exitCodes: MYPY_EXIT_CODES }, (out) => parseMypyOutput(out, ctx.filePath, cwd), { parseOutput: raw });
|
|
90
|
+
if (run.skipped)
|
|
91
|
+
return run.skipped;
|
|
92
|
+
const diagnostics = run.diagnostics;
|
|
93
|
+
return finishParsedRun({
|
|
94
|
+
tool: "mypy",
|
|
95
|
+
ctx,
|
|
85
96
|
result,
|
|
86
|
-
output: result.stdout,
|
|
87
|
-
exitCodes: MYPY_EXIT_CODES,
|
|
88
|
-
});
|
|
89
|
-
if (skipped)
|
|
90
|
-
return skipped;
|
|
91
|
-
const diagnostics = parseMypyOutput(raw, ctx.filePath, cwd);
|
|
92
|
-
if (diagnostics.length === 0) {
|
|
93
|
-
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
94
|
-
}
|
|
95
|
-
const hasBlocking = diagnostics.some((d) => d.semantic === "blocking");
|
|
96
|
-
return {
|
|
97
|
-
status: hasBlocking ? "failed" : "succeeded",
|
|
98
97
|
diagnostics,
|
|
99
|
-
|
|
100
|
-
};
|
|
98
|
+
});
|
|
101
99
|
},
|
|
102
100
|
};
|
|
103
101
|
export default mypyRunner;
|