pi-lens 4.2.0 โ 4.3.0
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 +543 -0
- package/README.md +45 -2
- package/dist/clients/actionable-warnings.js +44 -16
- package/dist/clients/advisory-provenance.js +187 -51
- package/dist/clients/cache-manager.js +64 -5
- package/dist/clients/cascade-format.js +24 -0
- package/dist/clients/child-unref.js +17 -8
- package/dist/clients/config-diagnostic-codes.js +2 -0
- package/dist/clients/config-locations.js +237 -9
- package/dist/clients/config-resolve.js +7 -1
- package/dist/clients/degradation-ledger.js +33 -3
- package/dist/clients/dependency-checker.js +87 -64
- package/dist/clients/diagnostic-dispositions.js +39 -3
- package/dist/clients/dispatch/finding-policy.js +118 -1
- package/dist/clients/dispatch/indent-detect.js +8 -2
- package/dist/clients/dispatch/inline-suppressions.js +23 -4
- package/dist/clients/dispatch/rule-ignores.js +14 -7
- package/dist/clients/dispatch/runners/actionlint.js +26 -28
- package/dist/clients/dispatch/runners/biome-check.js +38 -25
- package/dist/clients/dispatch/runners/cpp-check.js +32 -49
- package/dist/clients/dispatch/runners/credo.js +16 -14
- package/dist/clients/dispatch/runners/cue-vet.js +25 -7
- package/dist/clients/dispatch/runners/dart-analyze.js +28 -47
- package/dist/clients/dispatch/runners/detekt.js +11 -4
- package/dist/clients/dispatch/runners/dotnet-build.js +27 -22
- package/dist/clients/dispatch/runners/elixir-check.js +31 -46
- package/dist/clients/dispatch/runners/eslint.js +29 -46
- package/dist/clients/dispatch/runners/gleam-check.js +91 -11
- package/dist/clients/dispatch/runners/go-vet.js +16 -2
- package/dist/clients/dispatch/runners/golangci-lint.js +38 -23
- package/dist/clients/dispatch/runners/hadolint.js +27 -15
- package/dist/clients/dispatch/runners/htmlhint.js +17 -8
- package/dist/clients/dispatch/runners/javac.js +31 -21
- package/dist/clients/dispatch/runners/ktlint.js +34 -60
- package/dist/clients/dispatch/runners/lsp.js +6 -5
- package/dist/clients/dispatch/runners/markdownlint.js +12 -5
- package/dist/clients/dispatch/runners/mypy.js +2 -1
- package/dist/clients/dispatch/runners/oxlint.js +22 -6
- package/dist/clients/dispatch/runners/php-lint.js +39 -16
- package/dist/clients/dispatch/runners/phpstan.js +1 -1
- package/dist/clients/dispatch/runners/rubocop.js +27 -18
- package/dist/clients/dispatch/runners/ruff.js +18 -19
- package/dist/clients/dispatch/runners/rust-clippy.js +6 -1
- package/dist/clients/dispatch/runners/shellcheck.js +6 -2
- package/dist/clients/dispatch/runners/spellcheck.js +10 -3
- package/dist/clients/dispatch/runners/sqlfluff.js +11 -2
- package/dist/clients/dispatch/runners/stylelint.js +10 -2
- package/dist/clients/dispatch/runners/swiftlint.js +11 -2
- package/dist/clients/dispatch/runners/taplo.js +12 -6
- package/dist/clients/dispatch/runners/tflint.js +30 -15
- package/dist/clients/dispatch/runners/tree-sitter.js +23 -0
- package/dist/clients/dispatch/runners/trivy-config.js +8 -2
- package/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +11 -1
- package/dist/clients/dispatch/runners/vale.js +15 -3
- package/dist/clients/dispatch/runners/yamllint.js +12 -2
- package/dist/clients/dispatch/runners/zig-check.js +27 -39
- package/dist/clients/dispatch/suppress-writer.js +13 -3
- package/dist/clients/effective-config.js +3 -1
- package/dist/clients/extension-log.js +48 -1
- package/dist/clients/finding-delivery-gate.js +53 -23
- package/dist/clients/formatters.js +49 -11
- package/dist/clients/git-guard.js +247 -34
- package/dist/clients/gradle-ktfmt-style.js +121 -25
- package/dist/clients/inline-blocker-dispositions.js +186 -0
- package/dist/clients/install-diagnostics.js +19 -6
- package/dist/clients/installer/index.js +281 -32
- package/dist/clients/jscpd-client.js +8 -0
- package/dist/clients/latency-logger.js +8 -0
- package/dist/clients/lens-config.js +66 -8
- package/dist/clients/lsp/cascade-tier.js +3 -0
- package/dist/clients/lsp/client.js +139 -11
- package/dist/clients/lsp/config.js +9 -7
- package/dist/clients/lsp/content-limits.js +41 -0
- package/dist/clients/lsp/index.js +42 -8
- package/dist/clients/lsp/launch.js +23 -2
- package/dist/clients/lsp/server.js +106 -25
- package/dist/clients/lsp/sync-kind.js +14 -0
- package/dist/clients/lsp/wait-policy/strategies.js +105 -16
- package/dist/clients/mcp/analyze.js +9 -2
- package/dist/clients/mcp/ipc.js +232 -44
- package/dist/clients/mcp/review.js +53 -7
- package/dist/clients/opengrep-client.js +11 -6
- package/dist/clients/path-utils.js +51 -9
- package/dist/clients/persistent-reverify.js +271 -0
- package/dist/clients/pipeline.js +136 -67
- package/dist/clients/project-diagnostics/fresh-fetch.js +21 -3
- package/dist/clients/project-lens-config.js +11 -3
- package/dist/clients/review-graph/builder.js +34 -26
- package/dist/clients/review-graph/git-identity.js +16 -10
- package/dist/clients/ruff-client.js +31 -4
- package/dist/clients/runtime-coordinator.js +38 -4
- package/dist/clients/runtime-tool-call.js +16 -5
- package/dist/clients/runtime-tool-result.js +27 -12
- package/dist/clients/runtime-turn.js +571 -287
- package/dist/clients/safe-spawn.js +256 -65
- package/dist/clients/skills-resolver.js +38 -2
- package/dist/clients/spawn-output-cap.js +84 -1
- package/dist/clients/tree-sitter-client.js +119 -58
- package/dist/clients/turn-end/lane.js +51 -0
- package/dist/clients/turn-end/lanes/govulncheck.js +112 -0
- package/dist/clients/turn-end/lanes/secrets.js +204 -0
- package/dist/clients/warm-attach.js +59 -0
- package/dist/clients/widget-state.js +294 -17
- package/dist/clients/workspace-topology.js +57 -3
- package/dist/clients/zizmor-config.js +26 -13
- package/dist/index.js +6342 -3995
- package/dist/mcp/analyze-cli.js +4 -0
- package/dist/mcp/server.js +33 -21
- package/dist/scripts/lib/web-tree-sitter-dir.mjs +123 -0
- package/dist/tools/lens-diagnostic-mark.js +14 -2
- package/dist/tools/lens-diagnostics.js +127 -33
- package/dist/tools/lsp-diagnostics.js +150 -12
- package/docs/adr/0001-stale-advisory-live-arm.md +29 -0
- package/docs/adr/0002-workspace-hash-rendezvous.md +30 -0
- package/docs/adr/0003-git-guard-latch-writer.md +203 -0
- package/docs/adr/0004-disposition-policy-seam.md +38 -0
- package/docs/adr/0005-tool-availability-enforcement-seam.md +29 -0
- package/docs/adr/0006-derived-state-benchmark-first.md +31 -0
- package/docs/adr/0007-end-to-end-witness-per-seam-slice.md +38 -0
- package/docs/adr/0008-turn-end-lane-interface.md +165 -0
- package/docs/adr/0009-reported-path-attribution.md +114 -0
- package/docs/adr/README.md +26 -0
- package/docs/agent-tools.md +18 -1
- package/docs/ast-grep_rules_catalog.md +4 -3
- package/docs/configuration.md +55 -1
- package/docs/engineering-principles.md +202 -0
- package/docs/environment-variables.md +61 -4
- package/docs/features.md +2 -2
- package/docs/globalconfig.md +6 -1
- package/docs/language-coverage.md +17 -0
- package/docs/lsp-capability-matrix.md +98 -31
- package/docs/pi-lens-fixer.md +57 -3
- package/docs/pi-lens-investigator.md +9 -0
- package/docs/pi-lens-reviewer.md +16 -0
- package/docs/pi-lens-subagent.md +9 -0
- package/docs/public-api-stability.md +1 -0
- package/docs/release-qa-baseline.md +2 -0
- package/docs/servercapabilities.md +21 -11
- package/docs/settings.md +12 -3
- package/docs/subagent-compat.md +1 -1
- package/package.json +5 -3
- package/rules/ast-grep-rules/rule-tests/no-assert-tuple-test.yml +13 -0
- package/rules/ast-grep-rules/rule-tests/no-boolean-in-except-test.yml +26 -0
- package/rules/ast-grep-rules/rule-tests/no-http-headers-bracket-access-test.yml +10 -0
- package/rules/ast-grep-rules/rule-tests/unchecked-numeric-parse-python-test.yml +18 -0
- package/rules/ast-grep-rules/rule-tests/unchecked-throwing-call-python-test.yml +4 -3
- package/rules/ast-grep-rules/rules/go-no-fmt-println.yml +2 -2
- package/rules/ast-grep-rules/rules/no-assert-tuple.yml +23 -13
- package/rules/ast-grep-rules/rules/no-boolean-in-except.yml +7 -3
- package/rules/ast-grep-rules/rules/no-console-except-error-js.yml +2 -2
- package/rules/ast-grep-rules/rules/no-console-except-error.yml +2 -2
- package/rules/ast-grep-rules/rules/no-http-headers-bracket-access.yml +12 -7
- package/rules/ast-grep-rules/rules/unchecked-numeric-parse-python.yml +44 -0
- package/rules/ast-grep-rules/rules/unchecked-throwing-call-python.yml +5 -3
- package/rules/tree-sitter-queries/typescript/console-statement.yml +2 -2
- package/scripts/install-selftest.mjs +21 -17
- package/scripts/lib/web-tree-sitter-dir.mjs +123 -0
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* vice versa.
|
|
8
8
|
*/
|
|
9
9
|
import * as path from "node:path";
|
|
10
|
+
import { parseInlineSuppressionRuleIds, splitInlineSuppressionPayload, } from "./inline-suppressions.js";
|
|
10
11
|
const HASH_COMMENT_EXTENSIONS = new Set([
|
|
11
12
|
".py",
|
|
12
13
|
".rb",
|
|
@@ -50,14 +51,23 @@ export function insertSuppressComment(content, filePath, line, rule) {
|
|
|
50
51
|
const suppressRe = /((?:\/\/|#)\s*pi-lens-ignore:\s*)(.+)$/;
|
|
51
52
|
const match = existingAbove !== undefined ? suppressRe.exec(existingAbove) : null;
|
|
52
53
|
if (match && existingAbove !== undefined) {
|
|
53
|
-
const
|
|
54
|
+
const prefix = match[1];
|
|
55
|
+
const payload = match[2];
|
|
56
|
+
if (prefix === undefined || payload === undefined)
|
|
57
|
+
return content;
|
|
58
|
+
const { ruleList, trailingReason } = splitInlineSuppressionPayload(payload);
|
|
59
|
+
const rules = ruleList
|
|
54
60
|
.split(",")
|
|
55
61
|
.map((r) => r.trim())
|
|
56
62
|
.filter(Boolean);
|
|
57
|
-
|
|
63
|
+
const ruleIds = parseInlineSuppressionRuleIds(ruleList);
|
|
64
|
+
if (!ruleIds.includes(rule))
|
|
58
65
|
rules.push(rule);
|
|
59
66
|
lines[aboveIdx] =
|
|
60
|
-
existingAbove.slice(0, match.index) +
|
|
67
|
+
existingAbove.slice(0, match.index) +
|
|
68
|
+
prefix +
|
|
69
|
+
rules.join(", ") +
|
|
70
|
+
trailingReason;
|
|
61
71
|
return lines.join("\n");
|
|
62
72
|
}
|
|
63
73
|
// Match the indentation of the flagged line so the inserted comment lines
|
|
@@ -229,7 +229,9 @@ export async function effectiveConfig(options = {}) {
|
|
|
229
229
|
const resolveAt = (dir) => resolvePiLensConfig({
|
|
230
230
|
cwd: dir,
|
|
231
231
|
globalDir: getGlobalPiLensDir(),
|
|
232
|
-
|
|
232
|
+
// Production resolution for the global tier โ see loadLSPConfig's
|
|
233
|
+
// comment (global-config-location PR, refs #2457).
|
|
234
|
+
globalConfigPath: getPiLensGlobalConfigPath(),
|
|
233
235
|
homeDir,
|
|
234
236
|
});
|
|
235
237
|
// The queried path resolves HERE, beside the resolution root it selects, so
|
|
@@ -211,6 +211,48 @@ function inCaptureWindow(fn) {
|
|
|
211
211
|
function isCaptureSeam(prop) {
|
|
212
212
|
return (prop === "on" || (typeof prop === "string" && prop.startsWith("register")));
|
|
213
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* True for a host SCHEMA value that happens to be callable.
|
|
216
|
+
*
|
|
217
|
+
* A tool definition's `parameters` is DATA the host reads, not a pi-lens
|
|
218
|
+
* callback โ but the schema builder pi-lens imports is host-provided
|
|
219
|
+
* (`clients/deps/typebox.ts`: the host resolves the bare `typebox`
|
|
220
|
+
* specifier), and on a host whose builders are ArkType-style the schema is a
|
|
221
|
+
* FUNCTION. `@oh-my-pi/pi-coding-agent` rewrites an extension's `typebox`
|
|
222
|
+
* import to its own `src/extensibility/legacy-typebox.ts`, which re-exports
|
|
223
|
+
* `@oh-my-pi/omptype`'s builders; those return a callable whose prototype
|
|
224
|
+
* carries `toJsonSchema`/`assert`. `inCaptureWindow` returns a plain rest
|
|
225
|
+
* wrapper with neither, so the host falls off its ark branch, the wire schema
|
|
226
|
+
* degrades to `undefined`, and every xd:// device renders `type Args =
|
|
227
|
+
* unknown` and rejects every call with `root: schema must be an object or
|
|
228
|
+
* boolean` (#3195).
|
|
229
|
+
*
|
|
230
|
+
* The predicate IS the host's own, copied from `@oh-my-pi/pi-ai@18.2.4`
|
|
231
|
+
* `src/utils/schema/wire.ts:21-27` (`isArkSchema`) โ the same test the host's
|
|
232
|
+
* `applyToolProxy` (`@oh-my-pi/pi-coding-agent@18.2.4`
|
|
233
|
+
* `src/extensibility/tool-proxy.ts:26`) uses to leave a callable schema
|
|
234
|
+
* unbound for exactly this hazard ("`bind()` โฆ drops the schema surface โฆ
|
|
235
|
+
* so a bound schema later stringifies to `undefined`"). Matching it means
|
|
236
|
+
* pi-lens declines to wrap precisely the values the host reads as schemas,
|
|
237
|
+
* and still wraps everything else.
|
|
238
|
+
* `tests/clients/pi-host-callable-schema-contract.test.ts` asserts against
|
|
239
|
+
* that upstream predicate (and against a fixture measured from a real
|
|
240
|
+
* `@oh-my-pi/omptype@18.2.4` schema), not against this copy of it.
|
|
241
|
+
*
|
|
242
|
+
* Deliberately NOT an allow-list of handler keys (`execute`, `renderResult`,
|
|
243
|
+
* `handler` โ the measured set today): that is `isCaptureSeam`'s #1434 S1a
|
|
244
|
+
* mistake one level down (defect shape 34, spelling enumerator). The host's
|
|
245
|
+
* own `ToolDefinition` already declares `onSession`, `renderCall` and
|
|
246
|
+
* `shellEnv`, and `registerCommand` takes `getArgumentCompletions`; the day
|
|
247
|
+
* pi-lens adopts one of those, an allow-list drops its console capture
|
|
248
|
+
* SILENTLY โ the #1333 regression this seam exists to prevent โ while this
|
|
249
|
+
* predicate keeps wrapping it.
|
|
250
|
+
*/
|
|
251
|
+
function isCallableSchema(value) {
|
|
252
|
+
return (typeof value === "function" &&
|
|
253
|
+
typeof value.toJsonSchema === "function" &&
|
|
254
|
+
typeof value.assert === "function");
|
|
255
|
+
}
|
|
214
256
|
/**
|
|
215
257
|
* Wrap every function value found in `value`, in place. Handles the two
|
|
216
258
|
* shapes a `register*`/`on` call takes today: a function passed directly
|
|
@@ -218,6 +260,11 @@ function isCaptureSeam(prop) {
|
|
|
218
260
|
* function ONE level inside a plain options/definition object
|
|
219
261
|
* (`options.handler`, `tool.execute`).
|
|
220
262
|
*
|
|
263
|
+
* Only the property walk screens for a callable schema (#3195): every
|
|
264
|
+
* function pi-lens passes as a register argument DIRECTLY is a handler
|
|
265
|
+
* (`on`, `registerMessageRenderer`), measured across the whole activation,
|
|
266
|
+
* so a schema can only ever arrive as a property of a definition object.
|
|
267
|
+
*
|
|
221
268
|
* Deliberately NOT recursive past that one level (#1434 perf review): a tool
|
|
222
269
|
* definition's `parameters`/`schema` is a large, deeply nested TypeBox object
|
|
223
270
|
* that holds no functions, but walking its full tree on every `registerTool`
|
|
@@ -249,7 +296,7 @@ function wrapFunctionsInPlace(value) {
|
|
|
249
296
|
const obj = value;
|
|
250
297
|
for (const key of Object.keys(obj)) {
|
|
251
298
|
const propValue = obj[key];
|
|
252
|
-
if (typeof propValue === "function") {
|
|
299
|
+
if (typeof propValue === "function" && !isCallableSchema(propValue)) {
|
|
253
300
|
assignWrapped(obj, key, propValue);
|
|
254
301
|
}
|
|
255
302
|
}
|
|
@@ -30,7 +30,11 @@
|
|
|
30
30
|
* deliver unchanged. A missed demotion is noise; a wrong drop can hide a
|
|
31
31
|
* live credential or CVE.
|
|
32
32
|
* The turn-end secrets (gitleaks/trivy-secrets) and govulncheck advisory both
|
|
33
|
-
* route through this gate in `clients/runtime-turn.ts
|
|
33
|
+
* route through this gate in `clients/runtime-turn.ts`, in ONE shared call
|
|
34
|
+
* (#3264) whose arms the secrets LANE (`clients/turn-end/lanes/secrets.ts`,
|
|
35
|
+
* #1892) renders. A lane declares its sources and never gates itself: the
|
|
36
|
+
* pass, its stat budget and its bounded records are per DELIVERY, not per
|
|
37
|
+
* lane.
|
|
34
38
|
*
|
|
35
39
|
* The SAME freshness family, one level up, covers cross-file drift: a cached
|
|
36
40
|
* inline blocker is a verdict about its file *and everything that file
|
|
@@ -222,9 +226,25 @@ function labeled(file, description, reason, ageSource, evidence = [], extra = {}
|
|
|
222
226
|
const RUNTIME_TURN_FILE = "clients/runtime-turn.ts";
|
|
223
227
|
const LENS_DIAGNOSTICS_FILE = "tools/lens-diagnostics.ts";
|
|
224
228
|
export const DELIVERY_SURFACES = {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
229
|
+
// #1892 (lane extraction): both secrets tiers are rendered by the secrets
|
|
230
|
+
// LANE (`clients/turn-end/lanes/secrets.ts`), so `clients/runtime-turn.ts`
|
|
231
|
+
// no longer reads `.live`/`.stale` itself โ the arm it can still be pinned
|
|
232
|
+
// to is the whole gated store object it hands the lane. That is WEAKER than
|
|
233
|
+
// the previous `gitleaksGate.live,` pin (it no longer says which partition
|
|
234
|
+
// feeds which tier) and the loss is deliberate: the live/stale split is a
|
|
235
|
+
// lane rule now, pinned behaviourally instead, by
|
|
236
|
+
// `tests/clients/runtime-turn-finding-freshness.test.ts` ("keeps an
|
|
237
|
+
// unmodified file's finding as a full-severity blocker" / "DEMOTES a
|
|
238
|
+
// finding whose file was edited after the scan โ kept, no line") and the
|
|
239
|
+
// committed witness goldens under `tests/fixtures/witness/`. What the pin
|
|
240
|
+
// still proves is what this registry exists for: the rows the tier renders
|
|
241
|
+
// came out of a real `gateFindingsByPathFreshness` call and not out of a
|
|
242
|
+
// hand-built object (the R2 binding chain in
|
|
243
|
+
// `tests/clients/finding-delivery-gate.test.ts` walks
|
|
244
|
+
// `gitleaksGate` โ `scannerGates` โ the call).
|
|
245
|
+
"runtime-turn:secrets-gitleaks": gated(RUNTIME_TURN_FILE, "Turn-end ๐ด secrets blocker, gitleaks cache.", ["gateFindingsByPathFreshness"], ["gitleaksGate,"]),
|
|
246
|
+
"runtime-turn:secrets-trivy": gated(RUNTIME_TURN_FILE, "Turn-end ๐ด secrets blocker, trivy secrets cache.", ["gateFindingsByPathFreshness"], ["trivySecretsGate,"]),
|
|
247
|
+
"runtime-turn:govulncheck-advisory": gated(RUNTIME_TURN_FILE, "Turn-end ๐ก๏ธ Go CVE advisory (call-site line only).", ["gateFindingsByPathFreshness"], ["scannerGates.govulncheck"]),
|
|
228
248
|
// Two tagged seams (the stale and live render branches below the same
|
|
229
249
|
// `sweepInlineBlockerFreshness` call) legitimately share one evidence
|
|
230
250
|
// occurrence โ evidenceMin: 2 tells the exclusive-assignment check both
|
|
@@ -234,9 +254,11 @@ export const DELIVERY_SURFACES = {
|
|
|
234
254
|
// third argument, so the literal is the call's opening rather than the
|
|
235
255
|
// whole single-line invocation.
|
|
236
256
|
["sweepInlineBlockerFreshness(runtime, cwd, {"], { evidenceMin: 2 }),
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
|
|
257
|
+
// The same shared gate call and the same two stores as the live secrets
|
|
258
|
+
// tier โ one lane renders both tiers, so this surface has no gate call and
|
|
259
|
+
// no arm read of its own; see the note above the live tier for what the pin
|
|
260
|
+
// does and does not prove.
|
|
261
|
+
"runtime-turn:stale-secrets-tier": gated(RUNTIME_TURN_FILE, "Turn-end ๐ demoted-secrets tier (drifted since scan).", ["gateFindingsByPathFreshness"], ["gitleaksGate,", "trivySecretsGate,"]),
|
|
240
262
|
// The evidence below is the literal header FRAGMENT including the
|
|
241
263
|
// interpolation โ proves the label is actually rendered, not merely
|
|
242
264
|
// computed and discarded (review round F1's "deleted the age
|
|
@@ -280,24 +302,32 @@ export const DELIVERY_SURFACES = {
|
|
|
280
302
|
// nowhere else in this file: a bare `applyFindingPolicy` would also be
|
|
281
303
|
// satisfied by an import line or an unrelated caller.
|
|
282
304
|
"runtime-turn:late-auxiliary-findings": gated(RUNTIME_TURN_FILE, "Turn-end late-auxiliary LSP findings (collect-later probe of aux " +
|
|
283
|
-
"client caches whose grace window expired).",
|
|
284
|
-
|
|
305
|
+
"client caches whose grace window expired).",
|
|
306
|
+
// #3248: the open-coded `applyFindingPolicy(...)` here and the identical
|
|
307
|
+
// one the late-RUNNER lane needed are now one `applyPushedFindingPolicy`
|
|
308
|
+
// call, so the gate and the evidence name that call. Same stack, same
|
|
309
|
+
// identities โ only the spelling moved.
|
|
310
|
+
["gateFindingsByPathFreshness", "applyPushedFindingPolicy"], ['"late-auxiliary-findings": {', "applyPushedFindingPolicy(gate.live, {"]),
|
|
311
|
+
"runtime-turn:late-runner-findings": gated(RUNTIME_TURN_FILE, "Turn-end CLI runner findings collected after the post-write path.",
|
|
312
|
+
// #3248: this lane was the last push surface with no disposition stage;
|
|
313
|
+
// it now routes through the same shared policy call as its
|
|
314
|
+
// late-auxiliary twin, which is what this gate pins.
|
|
315
|
+
["gateFindingsByPathFreshness", "applyPushedFindingPolicy"], ['"late-runner-findings"'], { evidenceMin: 2 }),
|
|
285
316
|
"runtime-turn:cascade-blocker": labeled(RUNTIME_TURN_FILE, "Turn-end ๐งช cascade neighbor blocker.", "Cascade results settle synchronously this turn where possible " +
|
|
286
317
|
"(`settleCascadeRuns`), but an unsettled compute can carry over to a " +
|
|
287
|
-
"later turn (bounded by a carry cap) โ
|
|
288
|
-
"
|
|
289
|
-
|
|
290
|
-
partialReason: "A carried-over cascade result (run.carriedTurns > 0) is rendered " +
|
|
291
|
-
"without an age label. Follow-up: surface carriedTurns as an explicit " +
|
|
292
|
-
"label when > 0, or route through formatCacheAgeLabel using the run's " +
|
|
293
|
-
"own timestamp.",
|
|
294
|
-
}),
|
|
318
|
+
"later turn (bounded by a carry cap) โ a carried-over result renders " +
|
|
319
|
+
"with an explicit `(carried N turns ยท scanned Xm ago)` label (#3167, " +
|
|
320
|
+
"#3168 F3), so the agent can tell it from a fresh observation.", "live", ["cascadeCarrySuffix("], { evidenceMin: 1 }),
|
|
295
321
|
"runtime-turn:cascade-coverage-advisory": labeled(RUNTIME_TURN_FILE, "Turn-end cascade-coverage-gap advisories (graph/binding/budget).", "Explains what the cascade check could NOT confirm this turn โ not a " +
|
|
296
322
|
"finding with a cited path, an absence-of-coverage disclosure computed " +
|
|
297
|
-
"from this turn's own indeterminate-run list.
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
323
|
+
"from this turn's own indeterminate-run list. An advisory computed from " +
|
|
324
|
+
"a carried indeterminate run is labeled `(carried N turns ยท <age>)` " +
|
|
325
|
+
"(#3167, #3168 F4 โ dropped on mixed carried/fresh buckets). The age " +
|
|
326
|
+
"half reads `scan age unknown` on this surface today: no indeterminate-" +
|
|
327
|
+
"run producer stamps `observedAt` (only the resolved-found plumb does, " +
|
|
328
|
+
"`clients/cascade-format.ts`), and one unstamped carried run collapses " +
|
|
329
|
+
"the whole bucket's age rather than claiming the stamped runs' (#3168 " +
|
|
330
|
+
"F13). The carry COUNT is always real.", "live", ["withCarryLabel("], { evidenceMin: 3 }),
|
|
301
331
|
// #3102: the cold-neighbour cascade run is BUILT here, in the quiet-window
|
|
302
332
|
// reconcile (`onResolvedFound` in index.ts), a turn earlier than the
|
|
303
333
|
// `runtime-turn:cascade-blocker` render that finally carries it โ so it is
|
|
@@ -360,9 +390,9 @@ export const DELIVERY_SURFACES = {
|
|
|
360
390
|
"lens-diagnostics:mode-delta": gated(LENS_DIAGNOSTICS_FILE, "`lens_diagnostics mode=delta` report (the tool's DEFAULT mode) โ " +
|
|
361
391
|
"re-serves the actionable-warnings/code-quality-warnings caches AND " +
|
|
362
392
|
"the persisted project-diagnostics delta report.", ["gateFindingsByPathFreshness"], [
|
|
363
|
-
'
|
|
393
|
+
'"lens-diagnostics-delta": {',
|
|
364
394
|
"applyDeltaFreshnessGate(",
|
|
365
|
-
'
|
|
395
|
+
'"lens-diagnostics-delta-project": {',
|
|
366
396
|
]),
|
|
367
397
|
"widget-state:footer": gated("clients/widget-state.ts", "TUI footer blocking/error/warning tally.", ["reconcileStaleWidgetFiles", "isBlocking"], ["isBlocking(diagnostic)", "reconcileStaleWidgetFiles()"]),
|
|
368
398
|
"agent-nudge:context-message": labeled("clients/agent-nudge.ts", "Post-edit file-touch nudge injected via `context`.", "Not a scanner finding store: no severity, no cited line, no cache โ the " +
|
|
@@ -1823,7 +1823,10 @@ export function clearFormatterRuntimeState() {
|
|
|
1823
1823
|
// is `session_start`'s block in runtime-session.ts.
|
|
1824
1824
|
}
|
|
1825
1825
|
const BOX_DRAWING_GLOBAL = /[\u2500-\u257F]/g;
|
|
1826
|
-
|
|
1826
|
+
/** Blank, or a rule made of box-drawing characters and spacing alone. */
|
|
1827
|
+
const DECORATION_RULE = /^[\u2500-\u257F\s]*$/;
|
|
1828
|
+
/** The renderer's section heading: one word, then box-drawing to end of line. */
|
|
1829
|
+
const DECORATION_HEADING = /^\S+[ \t]+[\u2500-\u257F][\u2500-\u257F\s]*$/;
|
|
1827
1830
|
/**
|
|
1828
1831
|
* First line of `text` that actually carries a diagnostic.
|
|
1829
1832
|
*
|
|
@@ -1839,18 +1842,53 @@ const HAS_BOX_DRAWING = /[\u2500-\u257F]/;
|
|
|
1839
1842
|
export function firstDiagnosticLine(text) {
|
|
1840
1843
|
for (const raw of (text ?? "").split("\n")) {
|
|
1841
1844
|
const line = stripAnsi(raw).trimEnd();
|
|
1842
|
-
|
|
1843
|
-
if (!stripped)
|
|
1845
|
+
if (isDecorativeLine(line))
|
|
1844
1846
|
continue;
|
|
1845
|
-
|
|
1846
|
-
// AND a short remainder so a real one-line error containing a box
|
|
1847
|
-
// character is not discarded.
|
|
1848
|
-
if (HAS_BOX_DRAWING.test(line) && stripped.length <= 24)
|
|
1849
|
-
continue;
|
|
1850
|
-
return stripped.slice(0, 300);
|
|
1847
|
+
return line.replace(BOX_DRAWING_GLOBAL, "").trim().slice(0, 300);
|
|
1851
1848
|
}
|
|
1852
1849
|
return undefined;
|
|
1853
1850
|
}
|
|
1851
|
+
/**
|
|
1852
|
+
* The two decoration SHAPES a renderer emits, and nothing else: a rule made of
|
|
1853
|
+
* box-drawing characters alone (blank lines included), and a `<word> โโโโ`
|
|
1854
|
+
* section heading whose remainder after the word is box-drawing only.
|
|
1855
|
+
*
|
|
1856
|
+
* The single normalization both readers above and below share, so the bounded
|
|
1857
|
+
* tail cannot surface what the first-line reader already rejects (#3312 review
|
|
1858
|
+
* R3-4: the tail put biome's `format โโโโ` banner back in front of the strict
|
|
1859
|
+
* #1337 seam). Matching SHAPES rather than "contains a box character and is
|
|
1860
|
+
* short" is #3312 review R4-1: the length heuristic deleted
|
|
1861
|
+
* `โ traceback source excerpt`, a diagnostic whose own text opens with a box
|
|
1862
|
+
* character. A line carrying any non-decorative text is never decoration.
|
|
1863
|
+
*/
|
|
1864
|
+
function isDecorativeLine(line) {
|
|
1865
|
+
return DECORATION_RULE.test(line) || DECORATION_HEADING.test(line);
|
|
1866
|
+
}
|
|
1867
|
+
const FORMATTER_ERROR_TAIL_LINES = 20;
|
|
1868
|
+
/**
|
|
1869
|
+
* Preserve a bounded tail of a formatter's diagnostic output.
|
|
1870
|
+
*
|
|
1871
|
+
* A traceback's first line is not actionable on its own (#3312). Keep the
|
|
1872
|
+
* final lines, which include the exception and its message, while bounding
|
|
1873
|
+
* the model-facing error when a formatter emits an unexpectedly large log.
|
|
1874
|
+
*
|
|
1875
|
+
* Decorative lines are dropped FIRST, by the same predicate `firstDiagnosticLine`
|
|
1876
|
+
* uses: a tail that kept them would hand the strict #1337 seam the banner this
|
|
1877
|
+
* module already decided is not a diagnostic, and every banner line it kept
|
|
1878
|
+
* would evict a real traceback line from the bound.
|
|
1879
|
+
*/
|
|
1880
|
+
export function diagnosticTail(text, maxLines = FORMATTER_ERROR_TAIL_LINES) {
|
|
1881
|
+
const lines = [];
|
|
1882
|
+
for (const raw of (text ?? "").split("\n")) {
|
|
1883
|
+
const line = stripAnsi(raw).trimEnd();
|
|
1884
|
+
if (isDecorativeLine(line))
|
|
1885
|
+
continue;
|
|
1886
|
+
lines.push(line.slice(0, 300));
|
|
1887
|
+
}
|
|
1888
|
+
if (lines.length === 0)
|
|
1889
|
+
return undefined;
|
|
1890
|
+
return lines.slice(-maxLines).join("\n");
|
|
1891
|
+
}
|
|
1854
1892
|
/**
|
|
1855
1893
|
* Resolve a formatter command without allowing the static command fallback to
|
|
1856
1894
|
* bypass a resolver's style-preservation refusal (#1345). `null` means the
|
|
@@ -1963,11 +2001,11 @@ export async function formatFile(filePath, formatter) {
|
|
|
1963
2001
|
changed: false,
|
|
1964
2002
|
outcome: "failed",
|
|
1965
2003
|
error: result.error?.message ||
|
|
1966
|
-
|
|
2004
|
+
diagnosticTail(result.stderr) ||
|
|
1967
2005
|
// biome, ktlint and `mix format` report on STDOUT; without this
|
|
1968
2006
|
// their diagnostic is discarded and the user is told only that
|
|
1969
2007
|
// the tool "exited with status 1".
|
|
1970
|
-
|
|
2008
|
+
diagnosticTail(result.stdout) ||
|
|
1971
2009
|
`${formatter.name} exited with status ${result.status}`,
|
|
1972
2010
|
};
|
|
1973
2011
|
}
|