@herbertgao/pi-extensions 2026.9.14 โ†’ 2026.9.16

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.
Files changed (206) hide show
  1. package/README.md +8 -3
  2. package/THIRD_PARTY_NOTICES.md +28 -2
  3. package/node_modules/@herbertgao/pi-cc-extensions/package.json +1 -1
  4. package/node_modules/@narumitw/pi-btw/dist/index.ts +12 -6
  5. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
  6. package/node_modules/@narumitw/pi-btw/package.json +4 -4
  7. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +2 -0
  8. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +16 -4
  9. package/node_modules/pi-jev-auto-mode/CHANGELOG.md +8 -0
  10. package/node_modules/pi-jev-auto-mode/README.md +7 -0
  11. package/node_modules/pi-jev-auto-mode/package.json +1 -1
  12. package/node_modules/pi-jev-auto-mode/src/extension.ts +21 -2
  13. package/node_modules/pi-jev-auto-mode/src/records.ts +68 -2
  14. package/node_modules/pi-jev-auto-mode/src/settings.ts +22 -0
  15. package/node_modules/pi-jev-auto-mode/src/ui.ts +10 -0
  16. package/node_modules/pi-lens/CHANGELOG.md +498 -0
  17. package/node_modules/pi-lens/README.md +45 -2
  18. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +23 -7
  19. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +187 -51
  20. package/node_modules/pi-lens/dist/clients/cache-manager.js +64 -5
  21. package/node_modules/pi-lens/dist/clients/child-unref.js +17 -8
  22. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +2 -0
  23. package/node_modules/pi-lens/dist/clients/config-locations.js +237 -9
  24. package/node_modules/pi-lens/dist/clients/config-resolve.js +7 -1
  25. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +33 -3
  26. package/node_modules/pi-lens/dist/clients/dependency-checker.js +87 -64
  27. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +39 -3
  28. package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +118 -1
  29. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +8 -2
  30. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +23 -4
  31. package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +14 -7
  32. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +26 -28
  33. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +38 -25
  34. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +32 -49
  35. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +16 -14
  36. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +25 -7
  37. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +28 -47
  38. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +11 -4
  39. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +27 -22
  40. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +31 -46
  41. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +29 -46
  42. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +91 -11
  43. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +16 -2
  44. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +38 -23
  45. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +27 -15
  46. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +17 -8
  47. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +31 -21
  48. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +34 -60
  49. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +6 -5
  50. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +12 -5
  51. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +2 -1
  52. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +22 -6
  53. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +39 -16
  54. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +1 -1
  55. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +27 -18
  56. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +18 -19
  57. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +6 -1
  58. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +6 -2
  59. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +10 -3
  60. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +11 -2
  61. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +10 -2
  62. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +11 -2
  63. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +12 -6
  64. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +30 -15
  65. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +23 -0
  66. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +8 -2
  67. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +11 -1
  68. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +15 -3
  69. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +12 -2
  70. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +27 -39
  71. package/node_modules/pi-lens/dist/clients/dispatch/suppress-writer.js +13 -3
  72. package/node_modules/pi-lens/dist/clients/effective-config.js +3 -1
  73. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +42 -11
  74. package/node_modules/pi-lens/dist/clients/formatters.js +49 -11
  75. package/node_modules/pi-lens/dist/clients/git-guard.js +247 -34
  76. package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +121 -25
  77. package/node_modules/pi-lens/dist/clients/inline-blocker-dispositions.js +186 -0
  78. package/node_modules/pi-lens/dist/clients/install-diagnostics.js +19 -6
  79. package/node_modules/pi-lens/dist/clients/installer/index.js +281 -32
  80. package/node_modules/pi-lens/dist/clients/jscpd-client.js +8 -0
  81. package/node_modules/pi-lens/dist/clients/latency-logger.js +8 -0
  82. package/node_modules/pi-lens/dist/clients/lens-config.js +66 -8
  83. package/node_modules/pi-lens/dist/clients/lsp/client.js +139 -11
  84. package/node_modules/pi-lens/dist/clients/lsp/config.js +9 -7
  85. package/node_modules/pi-lens/dist/clients/lsp/content-limits.js +41 -0
  86. package/node_modules/pi-lens/dist/clients/lsp/index.js +1 -1
  87. package/node_modules/pi-lens/dist/clients/lsp/launch.js +23 -2
  88. package/node_modules/pi-lens/dist/clients/lsp/server.js +106 -25
  89. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +14 -0
  90. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +105 -16
  91. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +232 -44
  92. package/node_modules/pi-lens/dist/clients/mcp/review.js +53 -7
  93. package/node_modules/pi-lens/dist/clients/opengrep-client.js +11 -6
  94. package/node_modules/pi-lens/dist/clients/pipeline.js +121 -66
  95. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +21 -3
  96. package/node_modules/pi-lens/dist/clients/project-lens-config.js +11 -3
  97. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +34 -26
  98. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +16 -10
  99. package/node_modules/pi-lens/dist/clients/ruff-client.js +31 -4
  100. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +38 -4
  101. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +16 -5
  102. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +27 -12
  103. package/node_modules/pi-lens/dist/clients/runtime-turn.js +447 -280
  104. package/node_modules/pi-lens/dist/clients/safe-spawn.js +256 -65
  105. package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +84 -1
  106. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +119 -58
  107. package/node_modules/pi-lens/dist/clients/turn-end/lane.js +51 -0
  108. package/node_modules/pi-lens/dist/clients/turn-end/lanes/govulncheck.js +112 -0
  109. package/node_modules/pi-lens/dist/clients/turn-end/lanes/secrets.js +204 -0
  110. package/node_modules/pi-lens/dist/clients/warm-attach.js +59 -0
  111. package/node_modules/pi-lens/dist/clients/widget-state.js +145 -14
  112. package/node_modules/pi-lens/dist/clients/workspace-topology.js +57 -3
  113. package/node_modules/pi-lens/dist/clients/zizmor-config.js +26 -13
  114. package/node_modules/pi-lens/dist/index.js +5695 -3733
  115. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +4 -0
  116. package/node_modules/pi-lens/dist/mcp/server.js +33 -21
  117. package/node_modules/pi-lens/dist/scripts/lib/web-tree-sitter-dir.mjs +123 -0
  118. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +43 -16
  119. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +118 -7
  120. package/node_modules/pi-lens/docs/adr/0001-stale-advisory-live-arm.md +29 -0
  121. package/node_modules/pi-lens/docs/adr/0002-workspace-hash-rendezvous.md +30 -0
  122. package/node_modules/pi-lens/docs/adr/0003-git-guard-latch-writer.md +203 -0
  123. package/node_modules/pi-lens/docs/adr/0004-disposition-policy-seam.md +38 -0
  124. package/node_modules/pi-lens/docs/adr/0005-tool-availability-enforcement-seam.md +29 -0
  125. package/node_modules/pi-lens/docs/adr/0006-derived-state-benchmark-first.md +31 -0
  126. package/node_modules/pi-lens/docs/adr/0007-end-to-end-witness-per-seam-slice.md +38 -0
  127. package/node_modules/pi-lens/docs/adr/0008-turn-end-lane-interface.md +165 -0
  128. package/node_modules/pi-lens/docs/adr/0009-reported-path-attribution.md +114 -0
  129. package/node_modules/pi-lens/docs/adr/README.md +26 -0
  130. package/node_modules/pi-lens/docs/agent-tools.md +18 -1
  131. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +4 -3
  132. package/node_modules/pi-lens/docs/configuration.md +55 -1
  133. package/node_modules/pi-lens/docs/engineering-principles.md +202 -0
  134. package/node_modules/pi-lens/docs/environment-variables.md +61 -4
  135. package/node_modules/pi-lens/docs/features.md +2 -2
  136. package/node_modules/pi-lens/docs/globalconfig.md +6 -1
  137. package/node_modules/pi-lens/docs/language-coverage.md +17 -0
  138. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +98 -31
  139. package/node_modules/pi-lens/docs/pi-lens-fixer.md +57 -3
  140. package/node_modules/pi-lens/docs/pi-lens-investigator.md +9 -0
  141. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +16 -0
  142. package/node_modules/pi-lens/docs/pi-lens-subagent.md +9 -0
  143. package/node_modules/pi-lens/docs/public-api-stability.md +1 -0
  144. package/node_modules/pi-lens/docs/release-qa-baseline.md +2 -0
  145. package/node_modules/pi-lens/docs/servercapabilities.md +21 -11
  146. package/node_modules/pi-lens/docs/settings.md +12 -3
  147. package/node_modules/pi-lens/docs/subagent-compat.md +1 -1
  148. package/node_modules/pi-lens/package.json +5 -3
  149. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-assert-tuple-test.yml +13 -0
  150. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-boolean-in-except-test.yml +26 -0
  151. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-http-headers-bracket-access-test.yml +10 -0
  152. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/unchecked-numeric-parse-python-test.yml +18 -0
  153. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/unchecked-throwing-call-python-test.yml +4 -3
  154. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-fmt-println.yml +2 -2
  155. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-assert-tuple.yml +23 -13
  156. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-boolean-in-except.yml +7 -3
  157. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-console-except-error-js.yml +2 -2
  158. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-console-except-error.yml +2 -2
  159. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-http-headers-bracket-access.yml +12 -7
  160. package/node_modules/pi-lens/rules/ast-grep-rules/rules/unchecked-numeric-parse-python.yml +44 -0
  161. package/node_modules/pi-lens/rules/ast-grep-rules/rules/unchecked-throwing-call-python.yml +5 -3
  162. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/console-statement.yml +2 -2
  163. package/node_modules/pi-lens/scripts/install-selftest.mjs +21 -17
  164. package/node_modules/pi-lens/scripts/lib/web-tree-sitter-dir.mjs +123 -0
  165. package/node_modules/pi-mcp-adapter/CHANGELOG.md +29 -0
  166. package/node_modules/pi-mcp-adapter/OAUTH.md +369 -0
  167. package/node_modules/pi-mcp-adapter/README.md +12 -5
  168. package/node_modules/pi-mcp-adapter/config.ts +31 -8
  169. package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +1 -1
  170. package/node_modules/pi-mcp-adapter/dist/config.js +33 -8
  171. package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
  172. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +15 -2
  173. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +1 -1
  174. package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +2 -0
  175. package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +27 -2
  176. package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +1 -1
  177. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +1 -1
  178. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -1
  179. package/node_modules/pi-mcp-adapter/dist/server-manager.js +6 -4
  180. package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +1 -1
  181. package/node_modules/pi-mcp-adapter/dist/types.d.ts +2 -2
  182. package/node_modules/pi-mcp-adapter/dist/utils.d.ts +2 -0
  183. package/node_modules/pi-mcp-adapter/dist/utils.js +10 -3
  184. package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -1
  185. package/node_modules/pi-mcp-adapter/index.ts +37 -20
  186. package/node_modules/pi-mcp-adapter/init.ts +14 -15
  187. package/node_modules/pi-mcp-adapter/json-schema-validator.ts +16 -2
  188. package/node_modules/pi-mcp-adapter/mcp-auth.ts +27 -2
  189. package/node_modules/pi-mcp-adapter/metadata-cache.ts +1 -1
  190. package/node_modules/pi-mcp-adapter/package.json +3 -2
  191. package/node_modules/pi-mcp-adapter/proxy-modes.ts +1 -1
  192. package/node_modules/pi-mcp-adapter/server-manager.ts +6 -3
  193. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +99 -6
  194. package/node_modules/pi-mcp-adapter/types.ts +2 -2
  195. package/node_modules/pi-mcp-adapter/ui-session.ts +60 -27
  196. package/node_modules/pi-mcp-adapter/utils.ts +11 -3
  197. package/node_modules/pi-multi-account/CHANGELOG.md +6 -0
  198. package/node_modules/pi-multi-account/README.md +15 -1
  199. package/node_modules/pi-multi-account/index.ts +295 -41
  200. package/node_modules/pi-multi-account/package.json +1 -1
  201. package/node_modules/pi-next-cue/.github/workflows/publish.yml +20 -0
  202. package/node_modules/pi-next-cue/LICENSE +21 -0
  203. package/node_modules/pi-next-cue/README.md +81 -0
  204. package/node_modules/pi-next-cue/index.ts +358 -0
  205. package/node_modules/pi-next-cue/package.json +28 -0
  206. package/package.json +10 -7
@@ -1,5 +1,7 @@
1
1
  import * as path from "node:path";
2
+ import { pathsEqual } from "../../path-utils.js";
2
3
  import { findLocalBinUpwards } from "../../package-manager.js";
4
+ import { stripAnsi } from "../../sanitize.js";
3
5
  import { safeSpawnAsync } from "../../safe-spawn.js";
4
6
  import { resolveRunnerCwd } from "../../tool-cwd.js";
5
7
  import { getLinterPolicyForCwd } from "../../tool-policy.js";
@@ -12,6 +14,7 @@ const taplo = createAvailabilityChecker("taplo", ".exe");
12
14
  //
13
15
  // #1937 round 2: the parser fix alone turned the old silent-clean into a false
14
16
  // BLOCKING diagnostic. clap prints `error: unexpected argument '--output'
17
+ // EXIT TABLE (taplo 0.9 measured fixture): 0 clean; 1 findings; other nonzero rejected/error.
15
18
  // found`, which starts with the same lowercase `error:` a real taplo
16
19
  // diagnostic does, so a mistyped flag would have reddened every valid TOML
17
20
  // file. Both readings are wrong; the exit code is what separates them.
@@ -21,7 +24,7 @@ const HEADER_PATTERN = /^\s*(error|warning):(.*)$/;
21
24
  /** The box-drawing lead taplo puts on a codespan location line. */
22
25
  const LOCATION_LEAD = "โ”Œโ”€";
23
26
  /** The `:line:column` tail of a codespan location line. */
24
- const LOCATION_PATTERN = /:(\d+):(\d+)$/;
27
+ const LOCATION_PATTERN = /^\s*โ”Œโ”€\s+(.+?):(\d+):(\d+)$/;
25
28
  /**
26
29
  * Parse `taplo lint` output (#1937).
27
30
  *
@@ -48,9 +51,10 @@ const LOCATION_PATTERN = /:(\d+):(\d+)$/;
48
51
  * (#1937 round 2). The exit-code table in the runner is the primary guard;
49
52
  * this is the structural one, and each covers a case the other does not.
50
53
  */
51
- export function parseTaploOutput(raw, filePath) {
54
+ export function parseTaploOutput(raw, filePath, cwd) {
52
55
  const diagnostics = [];
53
- const lines = (raw ?? "").split(/\r?\n/);
56
+ const absTarget = path.resolve(cwd, filePath);
57
+ const lines = stripAnsi(raw ?? "").split(/\r?\n/);
54
58
  for (let i = 0; i < lines.length; i++) {
55
59
  // Lowercase `error:`/`warning:` starts a diagnostic. taplo's tracing
56
60
  // lines are uppercase (`ERROR taplo:lint_files: ...`), so a
@@ -79,8 +83,10 @@ export function parseTaploOutput(raw, filePath) {
79
83
  const location = LOCATION_PATTERN.exec(lines[j].trimEnd());
80
84
  if (!location)
81
85
  continue;
82
- line = Number.parseInt(location[1], 10) || 1;
83
- column = Number.parseInt(location[2], 10) || 1;
86
+ if (!pathsEqual(path.resolve(cwd, location[1].trim()), absTarget))
87
+ continue;
88
+ line = Number.parseInt(location[2], 10) || 1;
89
+ column = Number.parseInt(location[3], 10) || 1;
84
90
  break;
85
91
  }
86
92
  if (line === null)
@@ -155,7 +161,7 @@ const taploRunner = {
155
161
  // outright lie, so taplo opts into `skipWhenParsedNothing` (#1948). This
156
162
  // replaces a hand-rolled per-runner copy of the same rule, which wrote
157
163
  // its own `runner-empty-result` row with its own wording.
158
- const run = parseToolRun("taplo", { result, output: raw, exitCodes: TAPLO_EXIT_CODES }, (out) => parseTaploOutput(out, ctx.filePath), { skipWhenParsedNothing: true });
164
+ const run = parseToolRun("taplo", { result, output: raw, exitCodes: TAPLO_EXIT_CODES }, (out) => parseTaploOutput(out, ctx.filePath, cwd), { skipWhenParsedNothing: true });
159
165
  if (run.skipped)
160
166
  return run.skipped;
161
167
  const diagnostics = run.diagnostics;
@@ -1,4 +1,5 @@
1
1
  import * as path from "node:path";
2
+ import { pathsEqual } from "../../path-utils.js";
2
3
  import { safeSpawnAsync } from "../../safe-spawn.js";
3
4
  import { resolveRunnerCwd } from "../../tool-cwd.js";
4
5
  import { getLinterPolicyForCwd } from "../../tool-policy.js";
@@ -23,24 +24,34 @@ function findTflintConfig(fileDir) {
23
24
  const dir = findNearestDirWithAnyBasename(fileDir, [TFLINT_CONFIG]);
24
25
  return dir ? path.join(dir, TFLINT_CONFIG) : null;
25
26
  }
26
- function parseTflintOutput(raw, filePath) {
27
+ function parseTflintOutput(raw, filePath, toolCwd) {
27
28
  try {
28
29
  const parsed = JSON.parse(raw);
29
30
  const issues = parsed.issues ?? [];
30
- return issues.map((issue) => {
31
+ // #3295: tflint loads EVERY `*.tf` in its working directory โ€” `--filter` is
32
+ // best-effort โ€” and names each issue's own `range.filename`, relative to
33
+ // the directory it ran in (`toolCwd`, which is the file's dir, not the
34
+ // runner cwd).
35
+ const absTarget = path.resolve(toolCwd, filePath);
36
+ return issues.flatMap((issue) => {
37
+ if (issue.range?.filename &&
38
+ !pathsEqual(path.resolve(toolCwd, issue.range.filename), absTarget))
39
+ return [];
31
40
  const severity = issue.rule.severity === "error" ? "error" : "warning";
32
- return {
33
- id: `tflint-${issue.rule.name}-${issue.range.start.line}`,
34
- message: `[${issue.rule.name}] ${issue.message}`,
35
- filePath,
36
- line: issue.range.start.line,
37
- column: issue.range.start.column,
38
- severity,
39
- semantic: severity === "error" ? "blocking" : "warning",
40
- tool: "tflint",
41
- rule: issue.rule.name,
42
- fixable: false,
43
- };
41
+ return [
42
+ {
43
+ id: `tflint-${issue.rule.name}-${issue.range.start.line}`,
44
+ message: `[${issue.rule.name}] ${issue.message}`,
45
+ filePath,
46
+ line: issue.range.start.line,
47
+ column: issue.range.start.column,
48
+ severity,
49
+ semantic: severity === "error" ? "blocking" : "warning",
50
+ tool: "tflint",
51
+ rule: issue.rule.name,
52
+ fixable: false,
53
+ },
54
+ ];
44
55
  });
45
56
  }
46
57
  catch {
@@ -88,7 +99,11 @@ const tflintRunner = {
88
99
  // not a clean file. No exit-code table: tflint's nonzero codes are not
89
100
  // verified against a real binary here, so the conservative
90
101
  // nothing-to-parse rule stays the only discriminator.
91
- const run = parseToolRun("tflint", { result }, (out) => parseTflintOutput(out, ctx.filePath));
102
+ const run = parseToolRun("tflint", {
103
+ result,
104
+ // EXIT TABLE (TFLint 0.52 measured fixture): 0 clean; 1 error; 2 findings; other nonzero rejected.
105
+ exitCodes: { ran: [1, 2] },
106
+ }, (out) => parseTflintOutput(out, absPath, fileDir));
92
107
  if (run.skipped)
93
108
  return run.skipped;
94
109
  const diagnostics = run.diagnostics;
@@ -585,6 +585,29 @@ const treeSitterRunner = {
585
585
  }
586
586
  }
587
587
  if (diagnostics.length === 0) {
588
+ // #3409: zero diagnostics because the language's GRAMMAR never loaded is
589
+ // not a clean file, and `runQueriesOnFile` returns [] for both. The
590
+ // honest discriminator, once the run has already demanded the language,
591
+ // is the client's own loaded-language map: a grammar that could not be
592
+ // resolved, fetched, or decoded is absent from it. Reported through this
593
+ // runner's existing "the analysis did not run" arm (the same
594
+ // `runner_skip` + `status: "skipped"` shape as client-unavailable,
595
+ // init-failed and unsupported-extension) instead of as a pass with
596
+ // nothing found โ€” which is what made a degraded language on a compiled
597
+ // host indistinguishable from a clean file in `runner_complete`.
598
+ //
599
+ // The abort term keeps a mid-run WASM abort (#402, its own
600
+ // `runtime_abort` record) from being misreported as a missing grammar.
601
+ if (!isTreeSitterWasmAborted() && !client.getLanguage(languageId)) {
602
+ logTreeSitter({
603
+ phase: "runner_skip",
604
+ filePath,
605
+ languageId,
606
+ reason: "grammar_unavailable",
607
+ status: "skipped",
608
+ });
609
+ return { status: "skipped", diagnostics: [], semantic: "none" };
610
+ }
588
611
  logTreeSitter({
589
612
  phase: "runner_complete",
590
613
  filePath,
@@ -38,6 +38,7 @@
38
38
  import { formatToolFailure } from "./utils/tool-failure.js";
39
39
  import * as fs from "node:fs";
40
40
  import * as path from "node:path";
41
+ import { pathsEqual } from "../../path-utils.js";
41
42
  import { incrementDegradationCount } from "../../degradation-ledger.js";
42
43
  import { safeSpawnAsync } from "../../safe-spawn.js";
43
44
  import { resolveRunnerCwd } from "../../tool-cwd.js";
@@ -89,7 +90,7 @@ function normalizeSeverity(raw) {
89
90
  * CauseMetadata: { StartLine } }] }] }`. CRITICAL โ†’ blocking, the rest advisory.
90
91
  * Defensive: malformed input returns `[]`. Exported for unit tests.
91
92
  */
92
- export function parseTrivyConfigOutput(raw, filePath) {
93
+ export function parseTrivyConfigOutput(raw, filePath, cwd = process.cwd()) {
93
94
  if (!raw.trim())
94
95
  return [];
95
96
  let parsed;
@@ -103,9 +104,14 @@ export function parseTrivyConfigOutput(raw, filePath) {
103
104
  if (!Array.isArray(results))
104
105
  return [];
105
106
  const diagnostics = [];
107
+ const absTarget = path.resolve(cwd, filePath);
106
108
  for (const resultEntry of results) {
107
109
  if (!resultEntry || typeof resultEntry !== "object")
108
110
  continue;
111
+ const target = resultEntry.Target;
112
+ if (typeof target !== "string" ||
113
+ !pathsEqual(path.resolve(cwd, target), absTarget))
114
+ continue;
109
115
  const rows = resultEntry
110
116
  .Misconfigurations;
111
117
  if (!Array.isArray(rows))
@@ -232,7 +238,7 @@ const trivyConfigRunner = {
232
238
  });
233
239
  return { status: "skipped", diagnostics: [], semantic: "none" };
234
240
  }
235
- const diagnostics = parseTrivyConfigOutput(result.stdout || "", ctx.filePath);
241
+ const diagnostics = parseTrivyConfigOutput(result.stdout || "", ctx.filePath, cwd);
236
242
  if (diagnostics.length === 0) {
237
243
  return { status: "succeeded", diagnostics: [], semantic: "none" };
238
244
  }
@@ -4,6 +4,8 @@
4
4
  * Common patterns for parsing tool output into standardized diagnostics.
5
5
  * Supports the common `file:line:col: message` format used by most linters.
6
6
  */
7
+ import * as path from "node:path";
8
+ import { pathsEqual } from "../../../path-utils.js";
7
9
  import { stripAnsi } from "../../../sanitize.js";
8
10
  import { getAutofixCapability } from "../../../tool-policy.js";
9
11
  /**
@@ -11,8 +13,13 @@ import { getAutofixCapability } from "../../../tool-policy.js";
11
13
  * Common format: file:line:col: message (with variations)
12
14
  */
13
15
  function createLineParser(config) {
14
- return (raw, filePath) => {
16
+ return (raw, filePath, cwd) => {
15
17
  const diagnostics = [];
18
+ // #3295: `config.regex` is documented above as capturing the FILE in group
19
+ // 1, and every parser built here then dropped it and stamped the dispatched
20
+ // path on every line. This is the shared factory, so the predicate lives
21
+ // here once rather than in each `createLineParser` caller.
22
+ const absTarget = path.resolve(cwd, filePath);
16
23
  // Optionally strip ANSI codes (for tools that output colored text)
17
24
  const clean = config.stripAnsi !== false ? stripAnsi(raw) : raw;
18
25
  const lines = clean.split("\n").filter((l) => l.trim());
@@ -20,6 +27,9 @@ function createLineParser(config) {
20
27
  const match = line.match(config.regex);
21
28
  if (!match)
22
29
  continue;
30
+ const reported = match[1];
31
+ if (reported && !pathsEqual(path.resolve(cwd, reported), absTarget))
32
+ continue;
23
33
  const lineNum = parseInt(match[2], 10);
24
34
  const colNum = parseInt(match[3], 10);
25
35
  const severity = config.getSeverity
@@ -1,5 +1,6 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
+ import { pathsEqual } from "../../path-utils.js";
3
4
  import { safeSpawnAsync } from "../../safe-spawn.js";
4
5
  import { resolveRunnerCwd } from "../../tool-cwd.js";
5
6
  import { PRIORITY } from "../priorities.js";
@@ -25,17 +26,24 @@ function findValeConfig(cwd) {
25
26
  }
26
27
  return undefined;
27
28
  }
28
- export function parseValeOutput(raw, filePath) {
29
+ export function parseValeOutput(raw, filePath, cwd) {
29
30
  const diagnostics = [];
30
31
  if (!raw.trim())
31
32
  return diagnostics;
33
+ const absTarget = path.resolve(cwd, filePath);
32
34
  try {
33
35
  const parsed = JSON.parse(raw);
34
36
  if (!parsed || typeof parsed !== "object")
35
37
  return diagnostics;
36
- for (const alerts of Object.values(parsed)) {
38
+ // #3295: vale's top level is keyed BY the linted file's path โ€” the only
39
+ // place the reported path appears. `Object.values` dropped it, so a
40
+ // `.vale.ini` glob or a directory argv charged a second file's alerts to
41
+ // the dispatched one.
42
+ for (const [reported, alerts] of Object.entries(parsed)) {
37
43
  if (!Array.isArray(alerts))
38
44
  continue;
45
+ if (reported && !pathsEqual(path.resolve(cwd, reported), absTarget))
46
+ continue;
39
47
  for (const alert of alerts) {
40
48
  if (!alert.Message)
41
49
  continue;
@@ -98,7 +106,11 @@ const valeRunner = {
98
106
  // #1948: `parseToolRun` adds the second gate. Vale's `Data.Files`
99
107
  // envelope bug produced exactly this shape โ€” exit 1, a full JSON
100
108
  // report on stdout, zero alerts parsed โ€” and left no record.
101
- const run = parseToolRun("vale", { result }, (raw) => parseValeOutput(raw, ctx.filePath));
109
+ const run = parseToolRun("vale", {
110
+ result,
111
+ // EXIT TABLE (Vale 3.9.6 docs https://vale.sh/docs/topics/metrics/): 0 clean; 1 findings; 2 error; other nonzero rejected.
112
+ exitCodes: { ran: [1, 2] },
113
+ }, (raw) => parseValeOutput(raw, ctx.filePath, cwd));
102
114
  if (run.skipped)
103
115
  return run.skipped;
104
116
  const diagnostics = run.diagnostics;
@@ -1,3 +1,5 @@
1
+ import * as path from "node:path";
2
+ import { pathsEqual } from "../../path-utils.js";
1
3
  import { safeSpawnAsync } from "../../safe-spawn.js";
2
4
  import { logRunnerAdvisoryOnce } from "../../tool-cwd.js";
3
5
  import { getLinterPolicyForCwd, hasYamllintConfig } from "../../tool-policy.js";
@@ -7,14 +9,17 @@ import { parseToolRun } from "./utils/tool-failure.js";
7
9
  import { finishParsedRun } from "./utils/tool-failure.js";
8
10
  const yamllint = createAvailabilityChecker("yamllint", ".exe");
9
11
  export { hasYamllintConfig };
10
- function parseYamllintParsable(raw, filePath) {
12
+ function parseYamllintParsable(raw, filePath, cwd) {
11
13
  const diagnostics = [];
14
+ const absTarget = path.resolve(cwd, filePath);
12
15
  for (const line of raw.split(/\r?\n/)) {
13
16
  if (!line.trim())
14
17
  continue;
15
18
  const match = line.match(/^(.*?):(\d+):(\d+):\s*\[(error|warning)\]\s*(.*?)\s*\(([^)]+)\)\s*$/i);
16
19
  if (!match)
17
20
  continue;
21
+ if (!pathsEqual(path.resolve(cwd, match[1]), absTarget))
22
+ continue;
18
23
  const severity = match[4].toLowerCase() === "error" ? "error" : "warning";
19
24
  diagnostics.push({
20
25
  id: `yamllint-${match[2]}-${match[3]}-${match[6]}`,
@@ -70,7 +75,12 @@ const yamllintRunner = {
70
75
  const raw = `${result.stdout ?? ""}${result.stderr ?? ""}`;
71
76
  // #1948: classify on stdout as before, parse both streams as before, and
72
77
  // record when a failing run yields nothing.
73
- const run = parseToolRun("yamllint", { result, output: result.stdout }, (out) => parseYamllintParsable(out, ctx.filePath), { parseOutput: raw });
78
+ const run = parseToolRun("yamllint", {
79
+ result,
80
+ output: result.stdout,
81
+ // EXIT TABLE (yamllint 1.35 docs https://yamllint.readthedocs.io/en/stable/): 0 clean; 1 findings; 2 error; other nonzero rejected.
82
+ exitCodes: { ran: [1, 2] },
83
+ }, (out) => parseYamllintParsable(out, ctx.filePath, cwd), { parseOutput: raw });
74
84
  if (run.skipped)
75
85
  return run.skipped;
76
86
  const diagnostics = run.diagnostics;
@@ -1,21 +1,23 @@
1
1
  import * as path from "node:path";
2
+ import { pathsEqual } from "../../path-utils.js";
2
3
  import { safeSpawnAsync } from "../../safe-spawn.js";
3
4
  import { resolveRunnerCwd } from "../../tool-cwd.js";
4
5
  import { createAvailabilityChecker } from "./utils/runner-helpers.js";
5
6
  import { PRIORITY } from "../priorities.js";
7
+ import { finishParsedRun, parseToolRun } from "./utils/tool-failure.js";
6
8
  // zig rejects `--version`; the version subcommand is `zig version`. Using the
7
9
  // default probe would make this runner skip on every machine.
8
10
  const zig = createAvailabilityChecker("zig", ".exe", ["version"]);
9
- function parseZigOutput(raw, filePath) {
11
+ function parseZigOutput(raw, filePath, cwd) {
10
12
  const diagnostics = [];
13
+ const absTarget = path.resolve(cwd, filePath);
11
14
  for (const line of raw.split(/\r?\n/)) {
12
15
  const match = line.match(/^(.*?):(\d+):(\d+):\s*(error|warning|note):\s*(.+)$/);
13
16
  if (!match)
14
17
  continue;
15
18
  const [, rawFile, lineStr, colStr, level, message] = match;
16
- const resolvedSource = path.resolve(rawFile.trim());
17
- const resolvedTarget = path.resolve(filePath);
18
- if (resolvedSource !== resolvedTarget)
19
+ // #3278: one seam for reported-path attribution โ€” see javac.ts.
20
+ if (!pathsEqual(path.resolve(cwd, rawFile.trim()), absTarget))
19
21
  continue;
20
22
  const severity = level === "error" ? "error" : "warning";
21
23
  diagnostics.push({
@@ -35,12 +37,6 @@ function parseZigOutput(raw, filePath) {
35
37
  }
36
38
  return diagnostics;
37
39
  }
38
- function firstOutputLine(result) {
39
- return `${result.stderr || ""}\n${result.stdout || ""}`
40
- .trim()
41
- .split(/\r?\n/, 1)[0]
42
- .slice(0, 200);
43
- }
44
40
  const zigCheckRunner = {
45
41
  id: "zig-check",
46
42
  appliesTo: ["zig"],
@@ -57,35 +53,27 @@ const zigCheckRunner = {
57
53
  if (result.error && !result.stdout && !result.stderr) {
58
54
  return { status: "skipped", diagnostics: [], semantic: "none" };
59
55
  }
60
- const diagnostics = parseZigOutput(`${result.stderr || ""}\n${result.stdout || ""}`, ctx.filePath);
61
- if (diagnostics.length === 0) {
62
- if (result.status && result.status !== 0) {
63
- return {
64
- status: "failed",
65
- diagnostics: [
66
- {
67
- id: "zig-check-nonzero-no-diagnostics",
68
- message: firstOutputLine(result) ||
69
- "zig build-exe exited non-zero without structured diagnostics",
70
- filePath: ctx.filePath,
71
- severity: "warning",
72
- semantic: "warning",
73
- tool: "zig",
74
- rule: "zig-check",
75
- fixable: false,
76
- },
77
- ],
78
- semantic: "warning",
79
- };
80
- }
81
- return { status: "succeeded", diagnostics: [], semantic: "none" };
82
- }
83
- const hasErrors = diagnostics.some((d) => d.severity === "error");
84
- return {
85
- status: hasErrors ? "failed" : "succeeded",
86
- diagnostics,
87
- semantic: "warning",
88
- };
56
+ const raw = `${result.stdout || ""}\n${result.stderr || ""}`;
57
+ const parsed = parseToolRun("zig-check", {
58
+ result,
59
+ output: raw,
60
+ // EXIT TABLE (Zig 0.13 measured fixture): 0 clean; 1 findings; 2 error; other nonzero rejected.
61
+ exitCodes: { ran: [1, 2] },
62
+ }, (output) => parseZigOutput(output, ctx.filePath, cwd));
63
+ if (parsed.skipped)
64
+ return parsed.skipped;
65
+ return finishParsedRun({
66
+ tool: "zig-check",
67
+ ctx,
68
+ result,
69
+ diagnostics: parsed.diagnostics,
70
+ classify: (diagnostics) => ({
71
+ status: diagnostics.some((d) => d.severity === "error")
72
+ ? "failed"
73
+ : "succeeded",
74
+ semantic: "warning",
75
+ }),
76
+ });
89
77
  },
90
78
  };
91
79
  export default zigCheckRunner;
@@ -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 rules = match[2]
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
- if (!rules.includes(rule))
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) + match[1] + rules.join(", ");
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
- globalConfigPath: getPiLensGlobalConfigPath(homeDir),
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
@@ -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
- "runtime-turn:secrets-gitleaks": gated(RUNTIME_TURN_FILE, "Turn-end ๐Ÿ”ด secrets blocker, gitleaks cache.", ["gateFindingsByPathFreshness"], ['store: "gitleaks"']),
226
- "runtime-turn:secrets-trivy": gated(RUNTIME_TURN_FILE, "Turn-end ๐Ÿ”ด secrets blocker, trivy secrets cache.", ["gateFindingsByPathFreshness"], ['store: "trivy-secrets"']),
227
- "runtime-turn:govulncheck-advisory": gated(RUNTIME_TURN_FILE, "Turn-end ๐Ÿ›ก๏ธ Go CVE advisory (call-site line only).", ["gateFindingsByPathFreshness"], ['store: "govulncheck"']),
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
- // Same two gate calls as the live secrets tier โ€” this tier renders their
238
- // `.stale` arm, so it has no OWN gate call to point at.
239
- "runtime-turn:stale-secrets-tier": gated(RUNTIME_TURN_FILE, "Turn-end ๐Ÿ”‘ demoted-secrets tier (drifted since scan).", ["gateFindingsByPathFreshness"], ['store: "gitleaks"', 'store: "trivy-secrets"']),
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,8 +302,17 @@ 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).", ["gateFindingsByPathFreshness", "applyFindingPolicy"], ['store: "late-auxiliary-findings"', "applyFindingPolicy(gate.live, {"]),
284
- "runtime-turn:late-runner-findings": gated(RUNTIME_TURN_FILE, "Turn-end CLI runner findings collected after the post-write path.", ["gateFindingsByPathFreshness"], ['store: "late-runner-findings"'], { evidenceMin: 2 }),
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
318
  "later turn (bounded by a carry cap) โ€” a carried-over result renders " +
@@ -359,9 +390,9 @@ export const DELIVERY_SURFACES = {
359
390
  "lens-diagnostics:mode-delta": gated(LENS_DIAGNOSTICS_FILE, "`lens_diagnostics mode=delta` report (the tool's DEFAULT mode) โ€” " +
360
391
  "re-serves the actionable-warnings/code-quality-warnings caches AND " +
361
392
  "the persisted project-diagnostics delta report.", ["gateFindingsByPathFreshness"], [
362
- 'store: "lens-diagnostics-delta"',
393
+ '"lens-diagnostics-delta": {',
363
394
  "applyDeltaFreshnessGate(",
364
- 'store: "lens-diagnostics-delta-project"',
395
+ '"lens-diagnostics-delta-project": {',
365
396
  ]),
366
397
  "widget-state:footer": gated("clients/widget-state.ts", "TUI footer blocking/error/warning tally.", ["reconcileStaleWidgetFiles", "isBlocking"], ["isBlocking(diagnostic)", "reconcileStaleWidgetFiles()"]),
367
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 " +