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
|
@@ -29,7 +29,9 @@ export function formatToolFailure(input) {
|
|
|
29
29
|
? "did not run (spawn failure)"
|
|
30
30
|
: `exited ${input.status}`;
|
|
31
31
|
const missing = input.reportMissing ? "no report file" : "no output";
|
|
32
|
-
const detail = firstOutputLine(input.stderr) ||
|
|
32
|
+
const detail = firstOutputLine(input.stderr) ||
|
|
33
|
+
firstOutputLine(input.stdout) ||
|
|
34
|
+
"no stderr";
|
|
33
35
|
return truncateForLedger(`${identity} ${what} with ${missing}: ${detail}`);
|
|
34
36
|
}
|
|
35
37
|
/**
|
|
@@ -70,3 +72,152 @@ export function skipUnlessToolRan(tool, input, fields) {
|
|
|
70
72
|
});
|
|
71
73
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
72
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* The record a runner leaves when its parser read NOTHING out of output the
|
|
77
|
+
* tool did produce (#1948).
|
|
78
|
+
*
|
|
79
|
+
* `skipUnlessToolRan` covers the adjacent case: the tool produced no output at
|
|
80
|
+
* all. The hole it leaves is the one that hid five parser bugs for months —
|
|
81
|
+
* vale (#1933), taplo, stylelint, phpstan (#1946), sqlfluff. Each received a
|
|
82
|
+
* real report, extracted zero diagnostics from it, and reported "succeeded, 0
|
|
83
|
+
* diagnostics", which is byte-for-byte what a genuinely clean file records.
|
|
84
|
+
* A reader could not answer "is this file clean, or did the parser fail?".
|
|
85
|
+
*
|
|
86
|
+
* THE RULE: record only when the exit status is NONZERO, the output handed to
|
|
87
|
+
* the parser is non-empty, and the parse yielded zero diagnostics. A nonzero
|
|
88
|
+
* exit is the tool asserting something is wrong; extracting nothing from that
|
|
89
|
+
* assertion means our reader disagrees with the tool, and one of the two is
|
|
90
|
+
* broken.
|
|
91
|
+
*
|
|
92
|
+
* Exit 0 with zero parsed is deliberately NOT recorded, even with non-empty
|
|
93
|
+
* output: that is the overwhelmingly common clean save. Linters print summary
|
|
94
|
+
* banners, empty JSON arrays, and progress noise on a clean run, so a row per
|
|
95
|
+
* clean save would drown the ledger and bury the real signal.
|
|
96
|
+
*
|
|
97
|
+
* FALSE NEGATIVES this rule accepts, stated plainly:
|
|
98
|
+
* - A tool that reports findings under exit 0. `swiftlint` is the live
|
|
99
|
+
* example among the adopters: a warning-only run exits 0 and still writes
|
|
100
|
+
* a full JSON array, so a swiftlint parser break that never trips a
|
|
101
|
+
* nonzero exit stays invisible here. (`vale` also exits 0 on findings by
|
|
102
|
+
* default, but its actual #1933 break exited 1, so it is not the example.)
|
|
103
|
+
* This gap is deliberate and is NOT closed by adding an output-length
|
|
104
|
+
* threshold for exit 0. That class belongs to the captured-fixture lane —
|
|
105
|
+
* `tests/clients/dispatch/runners/captured-real-output.test.ts` pins each
|
|
106
|
+
* parser against real recorded binary output, which catches a broken
|
|
107
|
+
* parser directly instead of guessing from output size. A length
|
|
108
|
+
* threshold here would fire on every clean run of every tool that prints
|
|
109
|
+
* a summary banner.
|
|
110
|
+
* - A tool whose parser breaks only for a SUBSET of findings still reports
|
|
111
|
+
* the ones it can read, so `parsedCount` is nonzero and nothing records.
|
|
112
|
+
*
|
|
113
|
+
* FALSE POSITIVES it accepts: a runner that legitimately parses zero
|
|
114
|
+
* diagnostics out of a failing run — `go-vet` exits nonzero for a SIBLING
|
|
115
|
+
* file's problem and then filters to the edited file, and `terragrunt` does
|
|
116
|
+
* the same across a unit directory. Such runners stay off this helper rather
|
|
117
|
+
* than being special-cased inside it; see the runner-family registries in
|
|
118
|
+
* `tests/clients/dispatch/runners/parsed-nothing-sweep.test.ts`.
|
|
119
|
+
*
|
|
120
|
+
* Bounded by `incrementDegradationCount`: one latest-reason entry per
|
|
121
|
+
* (kind, subject) with an exact tally, so a permanently broken parser costs
|
|
122
|
+
* ONE ledger row and not one per dispatched file. `subject` is the tool id
|
|
123
|
+
* because a parser break is a property of the tool and our reader of it, not
|
|
124
|
+
* of the project it ran in; aggregating on the tool keeps exactly the identity
|
|
125
|
+
* a reader needs ("which tool is stuck") while staying bounded across every
|
|
126
|
+
* workspace in the session.
|
|
127
|
+
*
|
|
128
|
+
* @returns true when a row was recorded.
|
|
129
|
+
*/
|
|
130
|
+
export function recordParsedNothing(input) {
|
|
131
|
+
const { tool, status, output, parsedCount } = input;
|
|
132
|
+
// Three conjuncts, one per clause of the rule above. Each is separately
|
|
133
|
+
// load-bearing and separately tested: `parsedCount` keeps a run that DID
|
|
134
|
+
// yield findings silent, `status` keeps the clean-save case silent (0 and a
|
|
135
|
+
// never-arrived null alike — neither is a tool asserting a problem), and
|
|
136
|
+
// `output` keeps "the tool said nothing at all" with `runner-empty-result`
|
|
137
|
+
// rather than double-recording it here.
|
|
138
|
+
if (parsedCount > 0)
|
|
139
|
+
return false;
|
|
140
|
+
if (!status)
|
|
141
|
+
return false;
|
|
142
|
+
const text = output ?? "";
|
|
143
|
+
if (!text.trim())
|
|
144
|
+
return false;
|
|
145
|
+
const entries = Object.entries(input.fields ?? {}).filter(([, value]) => value !== undefined && value !== "");
|
|
146
|
+
const identity = entries.length
|
|
147
|
+
? `${tool} (${entries.map(([key, value]) => `${key}=${value}`).join(", ")})`
|
|
148
|
+
: tool;
|
|
149
|
+
incrementDegradationCount({
|
|
150
|
+
kind: "runner-parsed-nothing",
|
|
151
|
+
subject: tool,
|
|
152
|
+
reason: truncateForLedger(`${identity} exited ${status} with ${text.length} chars of output but the parser read 0 diagnostics: ${firstOutputLine(text) || "no first line"}`),
|
|
153
|
+
});
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The one seam a runner uses to go from a spawn result to diagnostics (#1948).
|
|
158
|
+
*
|
|
159
|
+
* It composes the two gates that must BOTH hold before a runner may claim a
|
|
160
|
+
* clean file: `skipUnlessToolRan` (the tool produced nothing — #1816) and
|
|
161
|
+
* `recordParsedNothing` (the tool produced something the parser could not
|
|
162
|
+
* read). Runners call this instead of calling `skipUnlessToolRan` and then
|
|
163
|
+
* parsing on their own, so the second gate cannot be forgotten by whoever
|
|
164
|
+
* writes runner 53. `tests/clients/dispatch/runners/parsed-nothing-sweep.test.ts`
|
|
165
|
+
* enforces that.
|
|
166
|
+
*/
|
|
167
|
+
export function parseToolRun(tool, input, parse, options = {}) {
|
|
168
|
+
const skipped = skipUnlessToolRan(tool, input, options.fields);
|
|
169
|
+
if (skipped)
|
|
170
|
+
return { skipped, diagnostics: [], parsedNothing: false };
|
|
171
|
+
const output = options.parseOutput ?? input.output ?? input.result.stdout ?? "";
|
|
172
|
+
const diagnostics = [...parse(output)];
|
|
173
|
+
const parsedNothing = recordParsedNothing({
|
|
174
|
+
tool,
|
|
175
|
+
status: input.result.status ?? null,
|
|
176
|
+
output,
|
|
177
|
+
parsedCount: diagnostics.length,
|
|
178
|
+
fields: options.fields,
|
|
179
|
+
});
|
|
180
|
+
if (parsedNothing && options.skipWhenParsedNothing) {
|
|
181
|
+
return {
|
|
182
|
+
skipped: { status: "skipped", diagnostics: [], semantic: "none" },
|
|
183
|
+
diagnostics: [],
|
|
184
|
+
parsedNothing,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
return { skipped: null, diagnostics, parsedNothing };
|
|
188
|
+
}
|
|
189
|
+
export function finishParsedRun(input) {
|
|
190
|
+
const raw = `${input.result.stdout ?? ""}${input.result.stderr ?? ""}`;
|
|
191
|
+
const status = input.result.status ?? null;
|
|
192
|
+
if (input.diagnostics.length === 0) {
|
|
193
|
+
if (status !== 0 && raw.trim().length > 0) {
|
|
194
|
+
return {
|
|
195
|
+
status: "failed",
|
|
196
|
+
diagnostics: [
|
|
197
|
+
{
|
|
198
|
+
id: `${input.tool}:parse-error:1`,
|
|
199
|
+
message: `${input.tool} exited ${status} but its output could not be parsed`,
|
|
200
|
+
filePath: input.ctx.filePath,
|
|
201
|
+
line: 1,
|
|
202
|
+
column: 1,
|
|
203
|
+
severity: "warning",
|
|
204
|
+
semantic: "warning",
|
|
205
|
+
tool: input.tool,
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
semantic: "warning",
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
212
|
+
}
|
|
213
|
+
const mapped = input.classify?.(input.diagnostics);
|
|
214
|
+
if (mapped) {
|
|
215
|
+
return { ...mapped, diagnostics: input.diagnostics };
|
|
216
|
+
}
|
|
217
|
+
const hasBlocking = input.diagnostics.some((d) => d.semantic === "blocking");
|
|
218
|
+
return {
|
|
219
|
+
status: hasBlocking ? "failed" : "succeeded",
|
|
220
|
+
diagnostics: input.diagnostics,
|
|
221
|
+
semantic: hasBlocking ? "blocking" : "warning",
|
|
222
|
+
};
|
|
223
|
+
}
|
|
@@ -3,7 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import { safeSpawnAsync } from "../../safe-spawn.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
7
|
const vale = createAvailabilityChecker("vale", ".exe");
|
|
8
8
|
/**
|
|
9
9
|
* Check for a .vale.ini config file in cwd or parent dirs.
|
|
@@ -24,19 +24,18 @@ function findValeConfig(cwd) {
|
|
|
24
24
|
}
|
|
25
25
|
return undefined;
|
|
26
26
|
}
|
|
27
|
-
function parseValeOutput(raw, filePath) {
|
|
27
|
+
export function parseValeOutput(raw, filePath) {
|
|
28
28
|
const diagnostics = [];
|
|
29
29
|
if (!raw.trim())
|
|
30
30
|
return diagnostics;
|
|
31
31
|
try {
|
|
32
32
|
const parsed = JSON.parse(raw);
|
|
33
|
-
|
|
34
|
-
if (!files)
|
|
33
|
+
if (!parsed || typeof parsed !== "object")
|
|
35
34
|
return diagnostics;
|
|
36
|
-
for (const
|
|
37
|
-
if (!
|
|
35
|
+
for (const alerts of Object.values(parsed)) {
|
|
36
|
+
if (!Array.isArray(alerts))
|
|
38
37
|
continue;
|
|
39
|
-
for (const alert of
|
|
38
|
+
for (const alert of alerts) {
|
|
40
39
|
if (!alert.Message)
|
|
41
40
|
continue;
|
|
42
41
|
const severityMap = {
|
|
@@ -51,7 +50,7 @@ function parseValeOutput(raw, filePath) {
|
|
|
51
50
|
message: `[${alert.Check ?? "vale"}] ${alert.Message}`,
|
|
52
51
|
filePath,
|
|
53
52
|
line: alert.Line ?? 1,
|
|
54
|
-
column: alert.
|
|
53
|
+
column: alert.Span?.[0] ?? 1,
|
|
55
54
|
severity,
|
|
56
55
|
semantic: severity === "error" ? "blocking" : "warning",
|
|
57
56
|
tool: "vale",
|
|
@@ -80,7 +79,7 @@ const valeRunner = {
|
|
|
80
79
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
81
80
|
}
|
|
82
81
|
let cmd = null;
|
|
83
|
-
if (await
|
|
82
|
+
if (await vale.isAvailableAsync(cwd)) {
|
|
84
83
|
cmd = vale.getCommand(cwd);
|
|
85
84
|
}
|
|
86
85
|
else {
|
|
@@ -95,11 +94,14 @@ const valeRunner = {
|
|
|
95
94
|
// zero alerts, and reported clean prose. No exit-code table: Vale's
|
|
96
95
|
// nonzero codes vary with --minAlertLevel, so the conservative
|
|
97
96
|
// nothing-to-parse rule is the only safe discriminator here.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const
|
|
97
|
+
//
|
|
98
|
+
// #1948: `parseToolRun` adds the second gate. Vale's `Data.Files`
|
|
99
|
+
// envelope bug produced exactly this shape — exit 1, a full JSON
|
|
100
|
+
// report on stdout, zero alerts parsed — and left no record.
|
|
101
|
+
const run = parseToolRun("vale", { result }, (raw) => parseValeOutput(raw, ctx.filePath));
|
|
102
|
+
if (run.skipped)
|
|
103
|
+
return run.skipped;
|
|
104
|
+
const diagnostics = run.diagnostics;
|
|
103
105
|
if (diagnostics.length === 0) {
|
|
104
106
|
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
105
107
|
}
|
|
@@ -106,9 +106,7 @@ export function loadYamlRulesFresh(ruleDir, severityFilter) {
|
|
|
106
106
|
hash.update("\0");
|
|
107
107
|
}
|
|
108
108
|
const signature = hash.digest("hex");
|
|
109
|
-
const cache = severityFilter === "error"
|
|
110
|
-
? contentBlockingRulesCache
|
|
111
|
-
: contentRulesCache;
|
|
109
|
+
const cache = severityFilter === "error" ? contentBlockingRulesCache : contentRulesCache;
|
|
112
110
|
const cached = cache.get(ruleDir);
|
|
113
111
|
if (cached?.signature === signature)
|
|
114
112
|
return cached.rules;
|
|
@@ -2,7 +2,8 @@ import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
|
2
2
|
import { getLinterPolicyForCwd, hasYamllintConfig } 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 yamllint = createAvailabilityChecker("yamllint", ".exe");
|
|
7
8
|
export { hasYamllintConfig };
|
|
8
9
|
function parseYamllintParsable(raw, filePath) {
|
|
@@ -45,7 +46,7 @@ const yamllintRunner = {
|
|
|
45
46
|
ctx.log("yamllint: no config detected, running with default rules");
|
|
46
47
|
}
|
|
47
48
|
let cmd = null;
|
|
48
|
-
if (await
|
|
49
|
+
if (await yamllint.isAvailableAsync(cwd)) {
|
|
49
50
|
cmd = yamllint.getCommand(cwd);
|
|
50
51
|
}
|
|
51
52
|
else {
|
|
@@ -66,22 +67,18 @@ const yamllintRunner = {
|
|
|
66
67
|
// and tracebacks. So the gate judges "nothing to parse" on stdout alone,
|
|
67
68
|
// while the parser still reads both streams.
|
|
68
69
|
const raw = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
69
|
-
|
|
70
|
+
// #1948: classify on stdout as before, parse both streams as before, and
|
|
71
|
+
// record when a failing run yields nothing.
|
|
72
|
+
const run = parseToolRun("yamllint", { result, output: result.stdout }, (out) => parseYamllintParsable(out, ctx.filePath), { parseOutput: raw });
|
|
73
|
+
if (run.skipped)
|
|
74
|
+
return run.skipped;
|
|
75
|
+
const diagnostics = run.diagnostics;
|
|
76
|
+
return finishParsedRun({
|
|
77
|
+
tool: "yamllint",
|
|
78
|
+
ctx,
|
|
70
79
|
result,
|
|
71
|
-
output: result.stdout,
|
|
72
|
-
});
|
|
73
|
-
if (skipped)
|
|
74
|
-
return skipped;
|
|
75
|
-
const diagnostics = parseYamllintParsable(raw, ctx.filePath);
|
|
76
|
-
if (diagnostics.length === 0) {
|
|
77
|
-
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
78
|
-
}
|
|
79
|
-
const hasBlocking = diagnostics.some((d) => d.semantic === "blocking");
|
|
80
|
-
return {
|
|
81
|
-
status: hasBlocking ? "failed" : "succeeded",
|
|
82
80
|
diagnostics,
|
|
83
|
-
|
|
84
|
-
};
|
|
81
|
+
});
|
|
85
82
|
},
|
|
86
83
|
};
|
|
87
84
|
export default yamllintRunner;
|
|
@@ -48,7 +48,7 @@ const zigCheckRunner = {
|
|
|
48
48
|
skipTestFiles: false,
|
|
49
49
|
async run(ctx) {
|
|
50
50
|
const cwd = ctx.cwd || process.cwd();
|
|
51
|
-
if (!(await
|
|
51
|
+
if (!(await zig.isAvailableAsync(cwd))) {
|
|
52
52
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
53
53
|
}
|
|
54
54
|
const cmd = zig.getCommand(cwd);
|
|
@@ -56,7 +56,8 @@ export function insertSuppressComment(content, filePath, line, rule) {
|
|
|
56
56
|
.filter(Boolean);
|
|
57
57
|
if (!rules.includes(rule))
|
|
58
58
|
rules.push(rule);
|
|
59
|
-
lines[aboveIdx] =
|
|
59
|
+
lines[aboveIdx] =
|
|
60
|
+
existingAbove.slice(0, match.index) + match[1] + rules.join(", ");
|
|
60
61
|
return lines.join("\n");
|
|
61
62
|
}
|
|
62
63
|
// Match the indentation of the flagged line so the inserted comment lines
|
|
@@ -10,4 +10,9 @@
|
|
|
10
10
|
*
|
|
11
11
|
* The dispatcher must handle these semantics consistently.
|
|
12
12
|
*/
|
|
13
|
-
|
|
13
|
+
/** Closed telemetry taxonomy for expected runner skips. */
|
|
14
|
+
export const RUNNER_SKIP_REASONS = ["no-files-matched"];
|
|
15
|
+
/** Runtime guard for untyped/plugin-provided runner results. */
|
|
16
|
+
export function isRunnerSkipReason(value) {
|
|
17
|
+
return RUNNER_SKIP_REASONS.some((reason) => reason === value);
|
|
18
|
+
}
|
|
@@ -3,9 +3,13 @@ export function convertLspDiagnostics(diags, filePath, options = {}) {
|
|
|
3
3
|
return diags
|
|
4
4
|
.filter((d) => d.range?.start?.line !== undefined)
|
|
5
5
|
.map((d, idx) => {
|
|
6
|
-
const severityMap = {
|
|
6
|
+
const severityMap = {
|
|
7
|
+
1: "error",
|
|
8
|
+
2: "warning",
|
|
9
|
+
4: "hint",
|
|
10
|
+
};
|
|
7
11
|
const severity = severityMap[d.severity] ?? "info";
|
|
8
|
-
const semantic = d.severity === 1 ? "blocking" :
|
|
12
|
+
const semantic = d.severity === 1 ? "blocking" : d.severity === 2 ? "warning" : "none";
|
|
9
13
|
const code = String(d.code ?? "unknown");
|
|
10
14
|
// #692: identity ALWAYS derives from the diagnostic's own source — never
|
|
11
15
|
// from a caller-supplied scan label (see `scanOrigin`'s doc comment above).
|
|
@@ -137,7 +137,7 @@ let moduleLoadWindowOpen = false;
|
|
|
137
137
|
* the same as an empty store, so it reads as "no window" rather than throwing.
|
|
138
138
|
*/
|
|
139
139
|
export function isConsoleCaptureActive() {
|
|
140
|
-
return
|
|
140
|
+
return moduleLoadWindowOpen || consoleCaptureStorage?.getStore() === true;
|
|
141
141
|
}
|
|
142
142
|
/**
|
|
143
143
|
* Open the module-evaluation window. Called by `clients/console-guard-install.ts`
|
|
@@ -207,7 +207,7 @@ function inCaptureWindow(fn) {
|
|
|
207
207
|
* the terminal.
|
|
208
208
|
*/
|
|
209
209
|
function isCaptureSeam(prop) {
|
|
210
|
-
return prop === "on" || (typeof prop === "string" && prop.startsWith("register"));
|
|
210
|
+
return (prop === "on" || (typeof prop === "string" && prop.startsWith("register")));
|
|
211
211
|
}
|
|
212
212
|
/**
|
|
213
213
|
* Wrap every function value found in `value`, in place. Handles the two
|
|
@@ -312,7 +312,9 @@ export function withConsoleCaptureWindows(api) {
|
|
|
312
312
|
// value instead of tripping that invariant (S2a) — a frozen host API
|
|
313
313
|
// loses the capture window for that member, but keeps working.
|
|
314
314
|
const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
|
|
315
|
-
if (descriptor &&
|
|
315
|
+
if (descriptor &&
|
|
316
|
+
descriptor.writable === false &&
|
|
317
|
+
descriptor.configurable === false) {
|
|
316
318
|
return descriptor.value;
|
|
317
319
|
}
|
|
318
320
|
const cached = wrapperCache.get(prop);
|
|
@@ -81,7 +81,14 @@ export function inferTrustBoundaries(nameOrPath) {
|
|
|
81
81
|
boundaries.add("user-input");
|
|
82
82
|
boundaries.add("process-exec");
|
|
83
83
|
}
|
|
84
|
-
if (hasHintToken(normalized, [
|
|
84
|
+
if (hasHintToken(normalized, [
|
|
85
|
+
"auth",
|
|
86
|
+
"login",
|
|
87
|
+
"token",
|
|
88
|
+
"session",
|
|
89
|
+
"oauth",
|
|
90
|
+
"jwt",
|
|
91
|
+
])) {
|
|
85
92
|
boundaries.add("auth");
|
|
86
93
|
boundaries.add("user-input");
|
|
87
94
|
}
|
|
@@ -7,24 +7,10 @@
|
|
|
7
7
|
import { basename, extname } from "node:path";
|
|
8
8
|
// --- Extension Maps ---
|
|
9
9
|
export const KIND_EXTENSIONS = {
|
|
10
|
-
clojure: [
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
".edn",
|
|
15
|
-
],
|
|
16
|
-
cmake: [
|
|
17
|
-
".cmake",
|
|
18
|
-
],
|
|
19
|
-
csharp: [
|
|
20
|
-
".cs",
|
|
21
|
-
],
|
|
22
|
-
css: [
|
|
23
|
-
".css",
|
|
24
|
-
".less",
|
|
25
|
-
".sass",
|
|
26
|
-
".scss",
|
|
27
|
-
],
|
|
10
|
+
clojure: [".clj", ".cljc", ".cljs", ".edn"],
|
|
11
|
+
cmake: [".cmake"],
|
|
12
|
+
csharp: [".cs"],
|
|
13
|
+
css: [".css", ".less", ".sass", ".scss"],
|
|
28
14
|
cue: [".cue"],
|
|
29
15
|
// From llvm-project/clang/lib/Driver/Types.cpp clang::driver::types::lookupTypeForExtension:
|
|
30
16
|
cxx: [
|
|
@@ -61,49 +47,18 @@ export const KIND_EXTENSIONS = {
|
|
|
61
47
|
".cl",
|
|
62
48
|
".clcpp",
|
|
63
49
|
],
|
|
64
|
-
dart: [
|
|
65
|
-
|
|
66
|
-
],
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
],
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
],
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
],
|
|
77
|
-
fsharp: [
|
|
78
|
-
".fs",
|
|
79
|
-
".fsi",
|
|
80
|
-
".fsx",
|
|
81
|
-
],
|
|
82
|
-
gleam: [
|
|
83
|
-
".gleam",
|
|
84
|
-
],
|
|
85
|
-
go: [
|
|
86
|
-
".go",
|
|
87
|
-
],
|
|
88
|
-
haskell: [
|
|
89
|
-
".hs",
|
|
90
|
-
".lhs",
|
|
91
|
-
],
|
|
92
|
-
"helm-template": [
|
|
93
|
-
".tpl",
|
|
94
|
-
],
|
|
95
|
-
html: [
|
|
96
|
-
".htm",
|
|
97
|
-
".html",
|
|
98
|
-
],
|
|
99
|
-
java: [
|
|
100
|
-
".java",
|
|
101
|
-
],
|
|
102
|
-
json: [
|
|
103
|
-
".json",
|
|
104
|
-
".json5",
|
|
105
|
-
".jsonc",
|
|
106
|
-
],
|
|
50
|
+
dart: [".dart"],
|
|
51
|
+
docker: [".dockerfile"],
|
|
52
|
+
elixir: [".ex", ".exs"],
|
|
53
|
+
fish: [".fish"],
|
|
54
|
+
fsharp: [".fs", ".fsi", ".fsx"],
|
|
55
|
+
gleam: [".gleam"],
|
|
56
|
+
go: [".go"],
|
|
57
|
+
haskell: [".hs", ".lhs"],
|
|
58
|
+
"helm-template": [".tpl"],
|
|
59
|
+
html: [".htm", ".html"],
|
|
60
|
+
java: [".java"],
|
|
61
|
+
json: [".json", ".json5", ".jsonc"],
|
|
107
62
|
jsts: [
|
|
108
63
|
".cjs",
|
|
109
64
|
".cts",
|
|
@@ -116,75 +71,25 @@ export const KIND_EXTENSIONS = {
|
|
|
116
71
|
".tsx",
|
|
117
72
|
".vue",
|
|
118
73
|
],
|
|
119
|
-
kotlin: [
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
],
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
],
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
],
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
],
|
|
133
|
-
|
|
134
|
-
".ml",
|
|
135
|
-
".mli",
|
|
136
|
-
],
|
|
137
|
-
php: [
|
|
138
|
-
".php",
|
|
139
|
-
],
|
|
140
|
-
powershell: [
|
|
141
|
-
".ps1",
|
|
142
|
-
".psm1",
|
|
143
|
-
".psd1",
|
|
144
|
-
],
|
|
145
|
-
prisma: [
|
|
146
|
-
".prisma",
|
|
147
|
-
],
|
|
148
|
-
python: [
|
|
149
|
-
".py",
|
|
150
|
-
".pyi",
|
|
151
|
-
],
|
|
152
|
-
ruby: [
|
|
153
|
-
".gemspec",
|
|
154
|
-
".rake",
|
|
155
|
-
".rb",
|
|
156
|
-
".ru",
|
|
157
|
-
],
|
|
158
|
-
rust: [
|
|
159
|
-
".rs",
|
|
160
|
-
],
|
|
161
|
-
shell: [
|
|
162
|
-
".bash",
|
|
163
|
-
".sh",
|
|
164
|
-
".zsh",
|
|
165
|
-
],
|
|
166
|
-
sql: [
|
|
167
|
-
".sql",
|
|
168
|
-
],
|
|
169
|
-
swift: [
|
|
170
|
-
".swift",
|
|
171
|
-
],
|
|
172
|
-
terraform: [
|
|
173
|
-
".tf",
|
|
174
|
-
".tfvars",
|
|
175
|
-
],
|
|
74
|
+
kotlin: [".kt", ".kts"],
|
|
75
|
+
lua: [".lua"],
|
|
76
|
+
markdown: [".md", ".mdx"],
|
|
77
|
+
nix: [".nix"],
|
|
78
|
+
ocaml: [".ml", ".mli"],
|
|
79
|
+
php: [".php"],
|
|
80
|
+
powershell: [".ps1", ".psm1", ".psd1"],
|
|
81
|
+
prisma: [".prisma"],
|
|
82
|
+
python: [".py", ".pyi"],
|
|
83
|
+
ruby: [".gemspec", ".rake", ".rb", ".ru"],
|
|
84
|
+
rust: [".rs"],
|
|
85
|
+
shell: [".bash", ".sh", ".zsh"],
|
|
86
|
+
sql: [".sql"],
|
|
87
|
+
swift: [".swift"],
|
|
88
|
+
terraform: [".tf", ".tfvars"],
|
|
176
89
|
terragrunt: [],
|
|
177
|
-
toml: [
|
|
178
|
-
|
|
179
|
-
],
|
|
180
|
-
yaml: [
|
|
181
|
-
".yaml",
|
|
182
|
-
".yml",
|
|
183
|
-
],
|
|
184
|
-
zig: [
|
|
185
|
-
".zig",
|
|
186
|
-
".zon",
|
|
187
|
-
],
|
|
90
|
+
toml: [".toml"],
|
|
91
|
+
yaml: [".yaml", ".yml"],
|
|
92
|
+
zig: [".zig", ".zon"],
|
|
188
93
|
};
|
|
189
94
|
/** Return whether a path has an extension registered for the given file kind. */
|
|
190
95
|
export function hasKindExtension(filePath, kind) {
|
|
@@ -5,7 +5,8 @@ import * as fs from "node:fs";
|
|
|
5
5
|
import * as os from "node:os";
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import { minimatch } from "./deps/minimatch.js";
|
|
8
|
-
import {
|
|
8
|
+
import { isInSpawnTimeoutCooldown, noteSpawnTimeout, } from "./spawn-timeout-cooldown.js";
|
|
9
|
+
import { collectTrackedFiles, getTrackedFilesSnapshot, } from "./git-tracked-ignore.js";
|
|
9
10
|
import { getGlobalIgnorePatterns, getPiLensGlobalConfigPath, } from "./lens-config.js";
|
|
10
11
|
import { normalizeEphemeralMapKey, normalizeFilePath } from "./path-utils.js";
|
|
11
12
|
import { findPiLensConfigInDir, findPiLensProjectConfig, loadPiLensConfigInDir, loadPiLensProjectConfig, } from "./project-lens-config.js";
|
|
@@ -577,12 +578,27 @@ export async function detectFileChangedAfterCommand(filePath, command, args, cwd
|
|
|
577
578
|
catch {
|
|
578
579
|
return 0;
|
|
579
580
|
}
|
|
581
|
+
// #1995: a command cooling down after a spawn timeout must not be handed
|
|
582
|
+
// a second budget - autofix is one of the lanes that paid twice for a
|
|
583
|
+
// wedged .cmd shim. Return "no fix applied" (an honest not-checked).
|
|
584
|
+
if (isInSpawnTimeoutCooldown(command))
|
|
585
|
+
return 0;
|
|
580
586
|
const result = await safeSpawnAsync(command, args, {
|
|
581
587
|
timeout: 30000,
|
|
582
588
|
cwd,
|
|
583
589
|
});
|
|
584
590
|
if (result.error)
|
|
585
591
|
return 0;
|
|
592
|
+
if (result.failure === "timeout") {
|
|
593
|
+
noteSpawnTimeout({
|
|
594
|
+
tool: path.basename(command).replace(/\.[^.]+$/, ""),
|
|
595
|
+
command,
|
|
596
|
+
phase: "autofix",
|
|
597
|
+
durationMs: 30000,
|
|
598
|
+
teardown: result.timeoutTeardown,
|
|
599
|
+
});
|
|
600
|
+
return 0;
|
|
601
|
+
}
|
|
586
602
|
if (result.status !== 0 && !ignoreStatuses.includes(result.status ?? -1)) {
|
|
587
603
|
return 0;
|
|
588
604
|
}
|