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,6 +2,7 @@
|
|
|
2
2
|
import { logExtension } from "./extension-log.js";
|
|
3
3
|
import { LEDGER_FIELD_MAX, truncateForLedger } from "./ledger-bounds.js";
|
|
4
4
|
import { logLatency } from "./latency-logger.js";
|
|
5
|
+
import { getSinkWriteFailures, resetSinkWriteFailures, } from "./ndjson-logger.js";
|
|
5
6
|
// Re-exported so existing importers keep one name for the ledger's bound.
|
|
6
7
|
export { LEDGER_FIELD_MAX, truncateForLedger };
|
|
7
8
|
const ENTRIES_PER_KIND = 20;
|
|
@@ -143,12 +144,28 @@ function boundedKind(value) {
|
|
|
143
144
|
return groups.size < MAX_DISTINCT_KINDS - 1 ? kind : OVERFLOW_KIND;
|
|
144
145
|
}
|
|
145
146
|
export function getDegradationSummary() {
|
|
146
|
-
|
|
147
|
+
const summary = [...groups.entries()].map(([kind, group]) => ({
|
|
147
148
|
kind,
|
|
148
149
|
count: group.count,
|
|
149
150
|
droppedCount: group.count - group.entries.length,
|
|
150
151
|
latestReasons: group.entries.map((entry) => ({ ...entry })),
|
|
151
152
|
}));
|
|
153
|
+
// Folded in at read time, not written into `groups` (#1970) — see the
|
|
154
|
+
// `log-sink-write-failure` doc comment on `DegradationKind` for why this
|
|
155
|
+
// kind never goes through `recordDegradation`.
|
|
156
|
+
const sinkFailures = getSinkWriteFailures();
|
|
157
|
+
if (sinkFailures.length > 0) {
|
|
158
|
+
summary.push({
|
|
159
|
+
kind: "log-sink-write-failure",
|
|
160
|
+
count: sinkFailures.reduce((total, sink) => total + sink.droppedCount, 0),
|
|
161
|
+
droppedCount: 0,
|
|
162
|
+
latestReasons: sinkFailures.map((sink) => ({
|
|
163
|
+
subject: truncateForLedger(sink.file),
|
|
164
|
+
reason: truncateForLedger(`${sink.droppedCount} dropped write(s) after reopen-retry failed`),
|
|
165
|
+
})),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return summary;
|
|
152
169
|
}
|
|
153
170
|
function isRenderableSummary(value) {
|
|
154
171
|
if (!Array.isArray(value))
|
|
@@ -197,6 +214,10 @@ export function resetDegradationLedger() {
|
|
|
197
214
|
onceKeys.clear();
|
|
198
215
|
tallies.clear();
|
|
199
216
|
ledgerGeneration++;
|
|
217
|
+
// #1970, catalog shape 17: the sink write-failure tally is a
|
|
218
|
+
// process-lifetime latch too — it re-arms alongside the rest of the
|
|
219
|
+
// ledger rather than surviving past the session that observed it.
|
|
220
|
+
resetSinkWriteFailures();
|
|
200
221
|
}
|
|
201
222
|
export const DEGRADATION_ENTRIES_PER_KIND = ENTRIES_PER_KIND;
|
|
202
223
|
export const DEGRADATION_MAX_DISTINCT_KINDS = MAX_DISTINCT_KINDS;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Body degradation for a DEMOTED finding (#1944).
|
|
3
|
+
*
|
|
4
|
+
* The demotion gates (#1631 dependency-drift, #1641 past-EOF) move a record
|
|
5
|
+
* out of the authoritative blocker channel and into the advisory channel.
|
|
6
|
+
* Until this module existed they changed the CHANNEL and nothing else: the
|
|
7
|
+
* advisory embedded the blocker body verbatim, so the agent still read
|
|
8
|
+
* "🔴 STOP — 11 issue(s) must be fixed" with dead line numbers under two
|
|
9
|
+
* hedge lines it ignored. Live evidence (session 01a0234c, 2026-08-21): the
|
|
10
|
+
* agent spent four re-run detours and about 7.4k tokens on a record whose
|
|
11
|
+
* coordinates no longer existed, then explained an unrelated REAL blocker
|
|
12
|
+
* away as "mid-edit state" in the same session.
|
|
13
|
+
*
|
|
14
|
+
* The rule this module owns, once, for every gated surface: a demoted body
|
|
15
|
+
* loses its authority vocabulary and its dead coordinates, and it says so in
|
|
16
|
+
* its own words. `clients/finding-delivery-gate.ts` documents the surrounding
|
|
17
|
+
* contract; this is the one implementation of its "Demotion rendering" arm
|
|
18
|
+
* for surfaces that carry a RENDERED BODY rather than a per-diagnostic row.
|
|
19
|
+
* Per-row surfaces (`widget-state.ts`, `tools/lens-diagnostics.ts`) already
|
|
20
|
+
* swap the coordinate for `PAST_EOF_STALE_MARKER` at render time and have no
|
|
21
|
+
* body to degrade.
|
|
22
|
+
*
|
|
23
|
+
* Pure and leaf: no store, no coordinator, no I/O. The caller supplies the
|
|
24
|
+
* dead lines it already computed while deciding to demote, so this module
|
|
25
|
+
* never re-derives a verdict from rendered prose (the
|
|
26
|
+
* re-derivation-vs-correlation screen).
|
|
27
|
+
*/
|
|
28
|
+
/** Suffix that replaces a cited coordinate the file no longer has. */
|
|
29
|
+
export const DEAD_LINE_ANNOTATION = "(line no longer exists)";
|
|
30
|
+
/**
|
|
31
|
+
* Authority vocabulary a demoted body must not carry, with its degraded
|
|
32
|
+
* replacement. Ordered: the full banner rewrite runs first so the narrower
|
|
33
|
+
* fallbacks only ever see text the banner rule did not already cover.
|
|
34
|
+
*
|
|
35
|
+
* This table is the single source of truth for the rule. A surface must not
|
|
36
|
+
* hand-roll its own "strip the STOP" pass — that is how #1631, #1641, and
|
|
37
|
+
* #1664 each ended up with a different answer to the same question.
|
|
38
|
+
*
|
|
39
|
+
* POSITION-ANCHORED (#1944 review F1). These patterns run over the BANNER
|
|
40
|
+
* region only, never the whole body. Diagnostic messages are arbitrary text
|
|
41
|
+
* from arbitrary runners, and they legitimately contain both tokens: the
|
|
42
|
+
* reviewer's probe turned ts(2304) `Cannot find name 'STOP'` into `Cannot
|
|
43
|
+
* find name 'Previously flagged'`, and mangled an English "must be fixed"
|
|
44
|
+
* inside a rule message. Rewriting a finding's own words is a worse defect
|
|
45
|
+
* than the banner it was cleaning up. See `splitBannerRegion`.
|
|
46
|
+
*/
|
|
47
|
+
const AUTHORITY_REWRITES = [
|
|
48
|
+
[
|
|
49
|
+
/[^\S\n]*(?:🔴|⛔|❌)?[^\S\n]*STOP[^\S\n]*[—–-][^\S\n]*(\d+)[^\S\n]+issue\(s\)[^\S\n]+must be fixed:?/g,
|
|
50
|
+
"$1 issue(s) were flagged before this file changed; the coordinates below may no longer exist:",
|
|
51
|
+
],
|
|
52
|
+
[/(?:🔴|⛔|❌)[^\S\n]*STOP\b[^\S\n]*[—–-]?/g, "Previously flagged —"],
|
|
53
|
+
[/\bSTOP\b/g, "Previously flagged"],
|
|
54
|
+
[/\bmust be fixed\b/g, "were flagged"],
|
|
55
|
+
];
|
|
56
|
+
/** ` L310: message` / `L310 message` at the head of a rendered row. */
|
|
57
|
+
const CITED_LINE_RE = /^([^\S\n]*)L(\d+)\b/;
|
|
58
|
+
/**
|
|
59
|
+
* Split a rendered body into its BANNER region and its CONTENT region
|
|
60
|
+
* (#1944 review F1).
|
|
61
|
+
*
|
|
62
|
+
* The body this module degrades is `InlineBlockerRecord.summary`, and its one
|
|
63
|
+
* producer is `formatDiagnostics(inlineBlockers, "blocking")`
|
|
64
|
+
* (`clients/dispatch/utils/format-utils.ts`), reaching the store as
|
|
65
|
+
* `dispatchResult.blockerOutput` -> `PipelineResult.inlineBlockerSummary` ->
|
|
66
|
+
* `recordInlineBlockers`. (`buildEnrichedBlockerOutput` in
|
|
67
|
+
* `clients/pipeline.ts` renders the same shape with code snippets, but it
|
|
68
|
+
* feeds the TOOL-RESULT output shown inline during the write, not this
|
|
69
|
+
* stored summary. #1944 review round: naming it here as a producer was
|
|
70
|
+
* wrong.) The shape is a header, then one ` L<n>: <message>` row per
|
|
71
|
+
* diagnostic, then indented continuations (`💡 Fix: …`) and an optional
|
|
72
|
+
* `… and N more` tail. So the first cited-line row is the boundary:
|
|
73
|
+
* everything above it is ours to rewrite, everything from it down is the
|
|
74
|
+
* findings' own words.
|
|
75
|
+
*
|
|
76
|
+
* A body with no cited row at all — `formatDiagnostics` omits `L<n>` for a
|
|
77
|
+
* lineless diagnostic — falls back to the first NON-BLANK line. Leading
|
|
78
|
+
* blanks must be skipped rather than counted (#1944 verify round, Edge-D):
|
|
79
|
+
* `formatDiagnostics` opens its output with a newline, so a naive "first
|
|
80
|
+
* line is the banner" rule degrades an empty string and leaves the real
|
|
81
|
+
* header at full authority. Production is currently saved from that only by
|
|
82
|
+
* an undocumented `.trim()` at `clients/pipeline.ts` (`inlineBlockerSummary`),
|
|
83
|
+
* and this module claims to own the rule, so it must hold on its own input.
|
|
84
|
+
*/
|
|
85
|
+
function splitBannerRegion(lines) {
|
|
86
|
+
const firstCitedRow = lines.findIndex((line) => CITED_LINE_RE.test(line));
|
|
87
|
+
if (firstCitedRow !== -1)
|
|
88
|
+
return { bannerEnd: firstCitedRow };
|
|
89
|
+
const firstNonBlank = lines.findIndex((line) => line.trim() !== "");
|
|
90
|
+
return { bannerEnd: firstNonBlank === -1 ? 0 : firstNonBlank + 1 };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Degrade one demoted finding's rendered body.
|
|
94
|
+
*
|
|
95
|
+
* Two independent transforms, deliberately separable so each can be proven by
|
|
96
|
+
* its own test (and so deleting either one reds a different assertion):
|
|
97
|
+
*
|
|
98
|
+
* 1. Authority vocabulary: the STOP banner and the "must be fixed"
|
|
99
|
+
* imperative are rewritten to past-tense, non-directive wording.
|
|
100
|
+
* 2. Dead coordinates: every cited `L<n>` whose line the file no longer has
|
|
101
|
+
* renders as `L<n> (line no longer exists)`. The message text stays —
|
|
102
|
+
* demote, never drop (#1419).
|
|
103
|
+
*/
|
|
104
|
+
export function degradeDemotedFindingBody(summary, options = {}) {
|
|
105
|
+
const deadLines = new Set(options.deadLines ?? []);
|
|
106
|
+
let authorityMarkersRemoved = 0;
|
|
107
|
+
const sourceLines = summary.split("\n");
|
|
108
|
+
const { bannerEnd } = splitBannerRegion(sourceLines);
|
|
109
|
+
const degradedLines = sourceLines.map((line, index) => {
|
|
110
|
+
// Content rows keep every character. Only the banner region is ours.
|
|
111
|
+
if (index >= bannerEnd)
|
|
112
|
+
return line;
|
|
113
|
+
let rewritten = line;
|
|
114
|
+
for (const [pattern, replacement] of AUTHORITY_REWRITES) {
|
|
115
|
+
rewritten = rewritten.replace(pattern, (...args) => {
|
|
116
|
+
authorityMarkersRemoved += 1;
|
|
117
|
+
// Expand `$n` against this match's capture groups by hand. A nested
|
|
118
|
+
// `String.replace` with the SAME global regex would clobber the
|
|
119
|
+
// outer call's `lastIndex` mid-iteration.
|
|
120
|
+
return replacement.replace(/\$(\d)/g, (_, digit) => String(args[Number(digit)] ?? ""));
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return rewritten;
|
|
124
|
+
});
|
|
125
|
+
let body = degradedLines.join("\n");
|
|
126
|
+
const deadLinesAnnotated = [];
|
|
127
|
+
if (deadLines.size > 0) {
|
|
128
|
+
body = degradedLines
|
|
129
|
+
.map((line) => {
|
|
130
|
+
const hit = CITED_LINE_RE.exec(line);
|
|
131
|
+
if (!hit)
|
|
132
|
+
return line;
|
|
133
|
+
const cited = Number.parseInt(hit[2] ?? "", 10);
|
|
134
|
+
if (!Number.isFinite(cited) || !deadLines.has(cited))
|
|
135
|
+
return line;
|
|
136
|
+
deadLinesAnnotated.push(cited);
|
|
137
|
+
return `${hit[1] ?? ""}L${cited} ${DEAD_LINE_ANNOTATION}${line.slice(hit[0].length)}`;
|
|
138
|
+
})
|
|
139
|
+
.join("\n");
|
|
140
|
+
}
|
|
141
|
+
return { body, authorityMarkersRemoved, deadLinesAnnotated };
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The one-line note a surface appends when it RETIRES a demoted finding after
|
|
145
|
+
* its single degraded delivery. Rendered inside the advisory itself, so an
|
|
146
|
+
* empty advisory section can only ever mean "nothing to say" — if something
|
|
147
|
+
* was dropped, this line is in the payload (#1944 AC3).
|
|
148
|
+
*/
|
|
149
|
+
export function formatRetirementNote(deadLines) {
|
|
150
|
+
const cited = deadLines.length > 0
|
|
151
|
+
? ` (cited ${deadLines.length === 1 ? "line" : "lines"} ${deadLines.join(", ")})`
|
|
152
|
+
: "";
|
|
153
|
+
return (`Retired after this delivery${cited}: the file no longer has these lines, ` +
|
|
154
|
+
"so this finding cannot be re-confirmed and will not be served again.");
|
|
155
|
+
}
|
|
@@ -55,8 +55,7 @@ export function parseMadgeSkips(stderr) {
|
|
|
55
55
|
const headerIdx = lines.findIndex((l) => /Skipped\s+\d+\s+file/i.test(l));
|
|
56
56
|
if (headerIdx === -1)
|
|
57
57
|
return { total: 0, local: [] };
|
|
58
|
-
const total = Number.parseInt(lines[headerIdx].match(/Skipped\s+(\d+)/i)?.[1] ?? "0", 10) ||
|
|
59
|
-
0;
|
|
58
|
+
const total = Number.parseInt(lines[headerIdx].match(/Skipped\s+(\d+)/i)?.[1] ?? "0", 10) || 0;
|
|
60
59
|
const specifiers = lines
|
|
61
60
|
.slice(headerIdx + 1)
|
|
62
61
|
.map((l) => l.trim())
|
|
@@ -203,9 +202,7 @@ export class DependencyChecker {
|
|
|
203
202
|
// next time the registry is walked.
|
|
204
203
|
static instances = new Set();
|
|
205
204
|
constructor(verbose = false) {
|
|
206
|
-
this.log = verbose
|
|
207
|
-
? createSubsystemLogger("deps")
|
|
208
|
-
: () => { };
|
|
205
|
+
this.log = verbose ? createSubsystemLogger("deps") : () => { };
|
|
209
206
|
DependencyChecker.instances.add(new WeakRef(this));
|
|
210
207
|
}
|
|
211
208
|
/** Drop this instance's memoized madge resolution for every project root. */
|
|
@@ -435,7 +432,9 @@ export class DependencyChecker {
|
|
|
435
432
|
const cached = this.importCache.get(normalized);
|
|
436
433
|
// Fast path: neither mtime NOR size moved (#1105 — size guards the
|
|
437
434
|
// mtime-preserving content change that mtime alone would miss).
|
|
438
|
-
if (cached &&
|
|
435
|
+
if (cached &&
|
|
436
|
+
cached.timestamp >= stat.mtimeMs &&
|
|
437
|
+
cached.size === stat.size) {
|
|
439
438
|
return false;
|
|
440
439
|
}
|
|
441
440
|
// Compare actual imports
|
|
@@ -567,7 +566,12 @@ export class DependencyChecker {
|
|
|
567
566
|
if (skips.local.length > 0) {
|
|
568
567
|
this.log(`madge skipped ${skips.local.length} local file(s) (possible silent cycle-miss): ${skips.local.slice(0, 5).join(", ")}`);
|
|
569
568
|
}
|
|
570
|
-
return {
|
|
569
|
+
return {
|
|
570
|
+
ok: true,
|
|
571
|
+
circular,
|
|
572
|
+
circularFiles,
|
|
573
|
+
localSkips: skips.local.length,
|
|
574
|
+
};
|
|
571
575
|
}
|
|
572
576
|
catch (err) {
|
|
573
577
|
this.log(`Check error: ${err.message}`);
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Centralized accessor for `@earendil-works/pi-tui
|
|
3
|
-
*
|
|
2
|
+
* Centralized accessor for `@earendil-works/pi-tui`, routed through here for a
|
|
3
|
+
* uniform dep surface. pi-tui is a pi-bundled core package: the host resolves
|
|
4
|
+
* the bare specifier from its own runtime, so pi-lens declares it as an OPTIONAL
|
|
5
|
+
* peer plus a devDependency and never as a runtime dependency. A runtime
|
|
6
|
+
* dependency makes `npm install --omit=dev` vendor a private second copy that
|
|
7
|
+
* Node evaluates at import — ~97ms of the 838ms git-install module import in
|
|
8
|
+
* #1926. `scripts/lib/host-provided-deps.mjs` holds the list, and
|
|
9
|
+
* `tests/packaging.test.ts` pins the declaration.
|
|
4
10
|
*
|
|
5
11
|
* Re-export named bindings, not `export *`: with the package kept external, a
|
|
6
12
|
* wildcard re-export leaves the namespace undefined at runtime under the bundle.
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Centralized accessor for `typebox
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Centralized accessor for `typebox`, routed through here for a uniform dep
|
|
3
|
+
* surface. typebox is a pi-bundled core package: the host resolves the bare
|
|
4
|
+
* specifier from its own runtime, so pi-lens declares it as an OPTIONAL peer
|
|
5
|
+
* plus a devDependency and never as a runtime dependency. A runtime dependency
|
|
6
|
+
* makes `npm install --omit=dev` vendor a private second copy that Node
|
|
7
|
+
* evaluates at import — 688 files, ~400ms of the 838ms git-install module
|
|
8
|
+
* import in #1926. `scripts/lib/host-provided-deps.mjs` holds the list, and
|
|
9
|
+
* `tests/packaging.test.ts` pins the declaration.
|
|
5
10
|
*
|
|
6
11
|
* Re-export named bindings, not `export *`: with the package kept external, a
|
|
7
12
|
* wildcard re-export leaves the namespace undefined at runtime under the bundle.
|
|
@@ -171,6 +171,35 @@ export function _setBeforeDispositionCacheRefreshForTests(hook) {
|
|
|
171
171
|
export function _setDispositionStatForTests(statSync) {
|
|
172
172
|
dispositionStatSync = statSync ?? fs.statSync;
|
|
173
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Zero-I/O guard for cache-only callers (lens_diagnostics delta/all): true
|
|
176
|
+
* when ANY disposition mark or session defer exists, so a caller can skip its
|
|
177
|
+
* whole strict/weak filter — including every file stat/read the strict branch
|
|
178
|
+
* would need — in the overwhelmingly common no-marks case. Same hoist shape as
|
|
179
|
+
* applyDispositionsMultiFile's #1625 F2 empty-store early return; `readState`
|
|
180
|
+
* is mtime+size stat-cached, so repeated calls cost one stat.
|
|
181
|
+
*/
|
|
182
|
+
export function hasAnyDispositionMarks(cwd) {
|
|
183
|
+
return Boolean(readState(cwd).dispositions) || deferredThisSession.size > 0;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* True when the project's disposition store holds at least one STRICT-anchored
|
|
187
|
+
* `false-positive` mark — the only disposition kind whose immediate application
|
|
188
|
+
* needs file content. A store with only weak suppress/defer marks can never
|
|
189
|
+
* produce a strict-anchor match, so cache-only callers can serve them through
|
|
190
|
+
* the zero-I/O weak filter and skip every stat/read the strict path would
|
|
191
|
+
* charge.
|
|
192
|
+
*/
|
|
193
|
+
export function hasStrictDispositionMarks(cwd) {
|
|
194
|
+
const dispositions = readState(cwd).dispositions;
|
|
195
|
+
if (!dispositions)
|
|
196
|
+
return false;
|
|
197
|
+
for (const key of Object.keys(dispositions)) {
|
|
198
|
+
if (dispositions[key]?.disposition === "false-positive")
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
174
203
|
// Test seam for `applyDispositionsMultiFile`'s per-group content read (#1625
|
|
175
204
|
// F2) — Vitest can't `vi.spyOn` a bare ESM `node:fs` named export directly
|
|
176
205
|
// ("Module namespace is not configurable in ESM"), so the F2 regression test
|
|
@@ -191,7 +220,13 @@ function readState(cwd) {
|
|
|
191
220
|
return stateCache.state;
|
|
192
221
|
}
|
|
193
222
|
const empty = {};
|
|
194
|
-
stateCache = {
|
|
223
|
+
stateCache = {
|
|
224
|
+
path: p,
|
|
225
|
+
missing: true,
|
|
226
|
+
mtimeMs: -1,
|
|
227
|
+
size: -1,
|
|
228
|
+
state: empty,
|
|
229
|
+
};
|
|
195
230
|
return empty;
|
|
196
231
|
}
|
|
197
232
|
if (stateCache &&
|
|
@@ -205,7 +240,9 @@ function readState(cwd) {
|
|
|
205
240
|
try {
|
|
206
241
|
const parsed = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
207
242
|
state =
|
|
208
|
-
parsed && typeof parsed === "object"
|
|
243
|
+
parsed && typeof parsed === "object"
|
|
244
|
+
? parsed
|
|
245
|
+
: {};
|
|
209
246
|
}
|
|
210
247
|
catch {
|
|
211
248
|
// Now that writeState is tmp+rename atomic, a torn read (another process
|
|
@@ -228,7 +228,11 @@ export function demotePastEofDiagnostics(args) {
|
|
|
228
228
|
return d; // no transition either direction
|
|
229
229
|
if (isPastEof)
|
|
230
230
|
risingEdgeLines.push(d.line);
|
|
231
|
-
return {
|
|
231
|
+
return {
|
|
232
|
+
...d,
|
|
233
|
+
stale: isPastEof,
|
|
234
|
+
staleReason: isPastEof ? "past-eof" : undefined,
|
|
235
|
+
};
|
|
232
236
|
});
|
|
233
237
|
if (risingEdgeLines.length === 0) {
|
|
234
238
|
return { diagnostics: out, demotedCount: 0 };
|
|
@@ -55,8 +55,7 @@ export function isNosemgrepSuppressed(d, content) {
|
|
|
55
55
|
.includes(ruleId);
|
|
56
56
|
};
|
|
57
57
|
// The finding's own line (inline OK), then the line above (standalone comment only).
|
|
58
|
-
return (checkLine(lines[startLine], false) ||
|
|
59
|
-
checkLine(lines[startLine - 1], true));
|
|
58
|
+
return (checkLine(lines[startLine], false) || checkLine(lines[startLine - 1], true));
|
|
60
59
|
}
|
|
61
60
|
/**
|
|
62
61
|
* zizmor's own native inline suppression: `# zizmor: ignore[audit-id[,audit-id]]`
|
|
@@ -91,7 +90,7 @@ export function isZizmorIgnoreSuppressed(d, content) {
|
|
|
91
90
|
// findings block only when the workspace opted into curated/authored rules;
|
|
92
91
|
// everything else stays advisory) — shared here instead of copy-pasted per
|
|
93
92
|
// profile so a future policy change (e.g. a WARNING-blocking tier) is one edit.
|
|
94
|
-
const blockOnErrorWhenAllowed = (d, { blockingAllowed }) =>
|
|
93
|
+
const blockOnErrorWhenAllowed = (d, { blockingAllowed }) => blockingAllowed && d.severity === 1 ? "blocking" : "warning";
|
|
95
94
|
export const AUXILIARY_LSP_PROFILES = [
|
|
96
95
|
{
|
|
97
96
|
serverId: "opengrep",
|
|
@@ -36,6 +36,7 @@ import { getToolPlan } from "./plan.js";
|
|
|
36
36
|
import { resolveRunnerPath } from "./runner-context.js";
|
|
37
37
|
import { applyRulePolicy, rulePolicyMapFromConfig } from "./rule-policy.js";
|
|
38
38
|
import { getToolProfile } from "./tool-profile.js";
|
|
39
|
+
import { isRunnerSkipReason } from "./types.js";
|
|
39
40
|
import { formatDiagnostics } from "./utils/format-utils.js";
|
|
40
41
|
// --- Runner Registry ---
|
|
41
42
|
export class RunnerRegistry {
|
|
@@ -606,6 +607,12 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
|
|
|
606
607
|
onRunnerResult?.(runnerId, result);
|
|
607
608
|
const runnerEnd = Date.now();
|
|
608
609
|
const duration = runnerEnd - runnerStart;
|
|
610
|
+
// Runner definitions are a typed API, but embedders/plugins can still
|
|
611
|
+
// return untyped objects at runtime. Admit only the closed taxonomy and
|
|
612
|
+
// only on actual skips so free text cannot enter durable latency metadata.
|
|
613
|
+
const skipReason = result.status === "skipped" && isRunnerSkipReason(result.skipReason)
|
|
614
|
+
? result.skipReason
|
|
615
|
+
: undefined;
|
|
609
616
|
latencies.push({
|
|
610
617
|
runnerId,
|
|
611
618
|
startTime: runnerStart,
|
|
@@ -614,6 +621,9 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
|
|
|
614
621
|
status: result.status,
|
|
615
622
|
diagnosticCount: result.diagnostics.length,
|
|
616
623
|
semantic: result.semantic ?? semantic,
|
|
624
|
+
...(skipReason !== undefined && {
|
|
625
|
+
skipReason,
|
|
626
|
+
}),
|
|
617
627
|
...(result.unconfirmedServerIds !== undefined && {
|
|
618
628
|
unconfirmedServerIds: result.unconfirmedServerIds,
|
|
619
629
|
}),
|
|
@@ -640,7 +650,9 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
|
|
|
640
650
|
failureKind: result.failureKind,
|
|
641
651
|
failureMessage: result.failureMessage,
|
|
642
652
|
}
|
|
643
|
-
:
|
|
653
|
+
: skipReason
|
|
654
|
+
? { skipReason }
|
|
655
|
+
: undefined,
|
|
644
656
|
});
|
|
645
657
|
recordRunner(ctx.filePath, runnerId, result.status, result.diagnostics.length, duration, ctx.writeIndex);
|
|
646
658
|
diagnostics.push(...result.diagnostics);
|
|
@@ -76,7 +76,9 @@ function parameterName(param) {
|
|
|
76
76
|
// JavaScript's grammar keeps a leaf parameter as the identifier itself;
|
|
77
77
|
// there is no named child to unwrap. TypeScript parameter wrapper nodes still
|
|
78
78
|
// use their first named child (type annotation/default/rest target).
|
|
79
|
-
return param.type === "identifier"
|
|
79
|
+
return param.type === "identifier"
|
|
80
|
+
? param.text
|
|
81
|
+
: (firstNamedChild(param)?.text ?? "");
|
|
80
82
|
}
|
|
81
83
|
function getFunctionName(node) {
|
|
82
84
|
if (node.type === "function_declaration") {
|
|
@@ -122,7 +122,8 @@ export const importFactProvider = {
|
|
|
122
122
|
if (callee?.type === "import") {
|
|
123
123
|
dynamicRaw.push({ source, kind: "import" });
|
|
124
124
|
}
|
|
125
|
-
else if (callee?.type === "identifier" &&
|
|
125
|
+
else if (callee?.type === "identifier" &&
|
|
126
|
+
callee.text === "require") {
|
|
126
127
|
dynamicRaw.push({ source, kind: "require" });
|
|
127
128
|
hasCjs = true;
|
|
128
129
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* shell.
|
|
9
9
|
*/
|
|
10
10
|
import { childrenOfType, firstChildOfType, withTreeSitterRoot, walk, } from "../../tree-sitter-shared.js";
|
|
11
|
-
export { childrenOfType, firstChildOfType, walk
|
|
11
|
+
export { childrenOfType, firstChildOfType, walk };
|
|
12
12
|
export const withFactTree = withTreeSitterRoot;
|
|
13
13
|
/**
|
|
14
14
|
* Provider shell for tree-sitter-backed fact extractors: read `file.content`,
|
|
@@ -789,7 +789,8 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
789
789
|
action: "reused_unchanged",
|
|
790
790
|
savedToSnapshot: reverseDepsSaved,
|
|
791
791
|
importsFileCount: Object.keys(reverseDepsIndex.imports).length,
|
|
792
|
-
importedByFileCount: Object.keys(reverseDepsIndex.importedBy)
|
|
792
|
+
importedByFileCount: Object.keys(reverseDepsIndex.importedBy)
|
|
793
|
+
.length,
|
|
793
794
|
},
|
|
794
795
|
});
|
|
795
796
|
}
|
|
@@ -841,7 +842,8 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
841
842
|
action: "refresh_from_review_graph",
|
|
842
843
|
savedToSnapshot: reverseDepsSaved,
|
|
843
844
|
importsFileCount: Object.keys(reverseDepsIndex.imports).length,
|
|
844
|
-
importedByFileCount: Object.keys(reverseDepsIndex.importedBy)
|
|
845
|
+
importedByFileCount: Object.keys(reverseDepsIndex.importedBy)
|
|
846
|
+
.length,
|
|
845
847
|
importEdgeCount: Object.values(reverseDepsIndex.imports).reduce((total, imports) => total + imports.length, 0),
|
|
846
848
|
},
|
|
847
849
|
});
|
|
@@ -1517,7 +1519,9 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1517
1519
|
// #1179: `.filter()` here operates on `rawDiags.diags`; the
|
|
1518
1520
|
// `inconclusive`/`binding` flags read above stay on the `rawDiags`
|
|
1519
1521
|
// wrapper and are unaffected by this copy (the shape-5 fix).
|
|
1520
|
-
const diags = convertLspDiagnostics(rawDiags.diags
|
|
1522
|
+
const diags = convertLspDiagnostics(rawDiags.diags
|
|
1523
|
+
.filter((d) => d.severity === 1)
|
|
1524
|
+
.slice(0, MAX_PER_FILE), neighborPath);
|
|
1521
1525
|
const durationMs = Date.now() - neighborStart;
|
|
1522
1526
|
// #1899: the A5 same-write cache write stood here.
|
|
1523
1527
|
if (diags.length === 0) {
|
|
@@ -1827,7 +1831,8 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1827
1831
|
if (entry.idleTimer !== undefined)
|
|
1828
1832
|
clearTimeout(entry.idleTimer);
|
|
1829
1833
|
entry.idleTimer = undefined;
|
|
1830
|
-
if (!reverseDepsTimersToRelease.has(key) &&
|
|
1834
|
+
if (!reverseDepsTimersToRelease.has(key) &&
|
|
1835
|
+
reverseDepsEntriesAtStart.has(entry)) {
|
|
1831
1836
|
reverseDepsTimersToRelease.add(key);
|
|
1832
1837
|
}
|
|
1833
1838
|
}
|
|
@@ -10,7 +10,9 @@ export const errorSwallowingRule = {
|
|
|
10
10
|
return [];
|
|
11
11
|
const diagnostics = [];
|
|
12
12
|
for (const s of summaries) {
|
|
13
|
-
if (s.isEmpty &&
|
|
13
|
+
if (s.isEmpty &&
|
|
14
|
+
!s.isDocumentedLocalFallback &&
|
|
15
|
+
s.boundaryCategory !== "fs") {
|
|
14
16
|
diagnostics.push({
|
|
15
17
|
id: `error-swallowing:${ctx.filePath}:${s.line}:${s.column}`,
|
|
16
18
|
tool: "fact-rules",
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { isTestFile } from "../../file-utils.js";
|
|
2
2
|
const ALIAS_COMMENT_RE = /\b(alias|backward\s*compat|backwards\s*compat|compatibility|shim|adapter)\b/i;
|
|
3
3
|
function hasAliasCommentNear(line, comments) {
|
|
4
|
-
return comments.some((comment) => comment.line >= line - 2 &&
|
|
4
|
+
return comments.some((comment) => comment.line >= line - 2 &&
|
|
5
|
+
comment.line <= line &&
|
|
6
|
+
ALIAS_COMMENT_RE.test(comment.text));
|
|
5
7
|
}
|
|
6
8
|
export const passThroughWrappersRule = {
|
|
7
9
|
id: "pass-through-wrappers",
|
|
@@ -16,7 +18,9 @@ export const passThroughWrappersRule = {
|
|
|
16
18
|
return [];
|
|
17
19
|
const diagnostics = [];
|
|
18
20
|
for (const fn of summaries) {
|
|
19
|
-
if (!fn.isPassThroughWrapper ||
|
|
21
|
+
if (!fn.isPassThroughWrapper ||
|
|
22
|
+
fn.statementCount !== 1 ||
|
|
23
|
+
fn.isBoundaryWrapper) {
|
|
20
24
|
continue;
|
|
21
25
|
}
|
|
22
26
|
if (hasAliasCommentNear(fn.line, comments))
|
|
@@ -10,16 +10,40 @@
|
|
|
10
10
|
// All other entries are exact callee names — prevents e.g. "spawn" matching "spawned.map"
|
|
11
11
|
// or "fetch" matching "fetchWithRetry".
|
|
12
12
|
const IO_NAMESPACE_PREFIXES = [
|
|
13
|
-
"db.",
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
13
|
+
"db.",
|
|
14
|
+
"prisma.",
|
|
15
|
+
"knex.",
|
|
16
|
+
"mongoose.",
|
|
17
|
+
"sequelize.",
|
|
18
|
+
"http.",
|
|
19
|
+
"https.",
|
|
20
|
+
"net.",
|
|
21
|
+
"dns.",
|
|
22
|
+
"redis.",
|
|
23
|
+
"mongo.",
|
|
24
|
+
"pg.",
|
|
25
|
+
"mysql.",
|
|
26
|
+
"s3.",
|
|
27
|
+
"storage.",
|
|
28
|
+
"bucket.",
|
|
17
29
|
"fs.promises.",
|
|
18
30
|
];
|
|
19
31
|
const IO_EXACT_CALLEES = new Set([
|
|
20
|
-
"fetch",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
32
|
+
"fetch",
|
|
33
|
+
"axios",
|
|
34
|
+
"got",
|
|
35
|
+
"request",
|
|
36
|
+
"spawn",
|
|
37
|
+
"exec",
|
|
38
|
+
"execSync",
|
|
39
|
+
"spawnSync",
|
|
40
|
+
"readFile",
|
|
41
|
+
"writeFile",
|
|
42
|
+
"appendFile",
|
|
43
|
+
"readdir",
|
|
44
|
+
"mkdir",
|
|
45
|
+
"unlink",
|
|
46
|
+
"stat",
|
|
23
47
|
]);
|
|
24
48
|
const CC_THRESHOLD = 6; // raised from 4 — avoids flagging simple async wrappers with one IO call
|
|
25
49
|
function callsToBoundary(outgoingCalls) {
|
|
@@ -12,7 +12,10 @@ function toDiagnostic(issue, filePath) {
|
|
|
12
12
|
const column = issue.column && issue.column > 0 ? issue.column : 1;
|
|
13
13
|
const rule = issue.kind || "actionlint";
|
|
14
14
|
const message = issue.message || "GitHub Actions workflow issue";
|
|
15
|
-
const idMessage = message
|
|
15
|
+
const idMessage = message
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
18
|
+
.slice(0, 80);
|
|
16
19
|
return {
|
|
17
20
|
id: `actionlint-${rule}-${line}-${column}-${idMessage}`,
|
|
18
21
|
message,
|
|
@@ -66,7 +69,7 @@ const actionlintRunner = {
|
|
|
66
69
|
async run(ctx) {
|
|
67
70
|
const cwd = ctx.cwd || process.cwd();
|
|
68
71
|
let cmd = null;
|
|
69
|
-
if (await
|
|
72
|
+
if (await actionlint.isAvailableAsync(cwd)) {
|
|
70
73
|
cmd = actionlint.getCommand(cwd);
|
|
71
74
|
}
|
|
72
75
|
else {
|
|
@@ -12,6 +12,7 @@ import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
|
12
12
|
import { biomeConfigArgs, getAutofixCapability, getJstsLintPolicyForCwd, } from "../../tool-policy.js";
|
|
13
13
|
import { PRIORITY } from "../priorities.js";
|
|
14
14
|
import { resolveToolCommandWithInstallFallback } from "./utils/runner-helpers.js";
|
|
15
|
+
import { finishParsedRun } from "./utils/tool-failure.js";
|
|
15
16
|
/**
|
|
16
17
|
* Map biome's own severity vocabulary onto the four-tier `Diagnostic.severity`
|
|
17
18
|
* (clients/dispatch/types.ts), the same way `normalizeRuleSeverity` does for
|
|
@@ -254,7 +255,9 @@ const biomeCheckJsonRunner = {
|
|
|
254
255
|
diagnostics: [
|
|
255
256
|
{
|
|
256
257
|
id: "biome:parse-error:1",
|
|
257
|
-
message: "Biome JSON parse failed: " +
|
|
258
|
+
message: "Biome JSON parse failed: " +
|
|
259
|
+
parsed.parseError +
|
|
260
|
+
(preview ? " (output preview: " + preview + ")" : ""),
|
|
258
261
|
filePath: ctx.filePath,
|
|
259
262
|
line: 1,
|
|
260
263
|
column: 1,
|
|
@@ -266,19 +269,12 @@ const biomeCheckJsonRunner = {
|
|
|
266
269
|
semantic: "warning",
|
|
267
270
|
};
|
|
268
271
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
semantic = "warning";
|
|
276
|
-
}
|
|
277
|
-
return {
|
|
278
|
-
status: semantic === "blocking" ? "failed" : "succeeded",
|
|
279
|
-
diagnostics,
|
|
280
|
-
semantic,
|
|
281
|
-
};
|
|
272
|
+
return finishParsedRun({
|
|
273
|
+
tool: "biome",
|
|
274
|
+
ctx,
|
|
275
|
+
result: checkResult,
|
|
276
|
+
diagnostics: parsed.diagnostics,
|
|
277
|
+
});
|
|
282
278
|
},
|
|
283
279
|
};
|
|
284
280
|
export default biomeCheckJsonRunner;
|