@herbertgao/pi-extensions 2026.9.2 → 2026.9.3

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 (221) hide show
  1. package/README.md +3 -2
  2. package/THIRD_PARTY_NOTICES.md +24 -0
  3. package/node_modules/@herbertgao/sol-pi/LICENSE +19 -0
  4. package/node_modules/@herbertgao/sol-pi/README.md +159 -0
  5. package/node_modules/@herbertgao/sol-pi/SECURITY.md +26 -0
  6. package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +19 -0
  7. package/node_modules/@herbertgao/sol-pi/agents-install.md +150 -0
  8. package/node_modules/@herbertgao/sol-pi/assets/sol-pi-hero.png +0 -0
  9. package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +69 -0
  10. package/node_modules/@herbertgao/sol-pi/docs/configuration.md +75 -0
  11. package/node_modules/@herbertgao/sol-pi/package.json +76 -0
  12. package/node_modules/@herbertgao/sol-pi/scripts/check-pi-compat.mjs +32 -0
  13. package/node_modules/@herbertgao/sol-pi/scripts/check-sol-pi-config.mjs +120 -0
  14. package/node_modules/@herbertgao/sol-pi/sol-pi.example.json +10 -0
  15. package/node_modules/@herbertgao/sol-pi/src/sol-pi/config.ts +135 -0
  16. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/file-queue.ts +74 -0
  17. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +185 -0
  18. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/then-run.ts +128 -0
  19. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/archive.ts +53 -0
  20. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/candidate.ts +101 -0
  21. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/config.ts +71 -0
  22. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/index.ts +220 -0
  23. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/journal.ts +25 -0
  24. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/provider.ts +164 -0
  25. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/receipt.ts +177 -0
  26. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/index.ts +227 -0
  27. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/ledger.ts +20 -0
  28. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/observation.ts +252 -0
  29. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts +237 -0
  30. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +455 -0
  31. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/index.ts +49 -0
  32. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/plan.ts +79 -0
  33. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +208 -0
  34. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/tools.ts +100 -0
  35. package/node_modules/@herbertgao/sol-pi/src/sol-pi/index.ts +42 -0
  36. package/node_modules/@herbertgao/sol-pi/src/sol-pi/runtime-paths.ts +17 -0
  37. package/node_modules/@herbertgao/sol-pi/src/sol-pi/tui.ts +71 -0
  38. package/node_modules/@narumitw/pi-btw/dist/index.ts +12 -2
  39. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +2 -2
  40. package/node_modules/@narumitw/pi-btw/package.json +1 -1
  41. package/node_modules/@narumitw/pi-btw/src/btw.ts +13 -2
  42. package/node_modules/pi-lens/CHANGELOG.md +176 -0
  43. package/node_modules/pi-lens/README.md +13 -8
  44. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -1
  45. package/node_modules/pi-lens/dist/clients/analysed-root.js +1 -0
  46. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +1 -1
  47. package/node_modules/pi-lens/dist/clients/biome-client.js +13 -2
  48. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +2 -0
  49. package/node_modules/pi-lens/dist/clients/cache-observability.js +122 -1
  50. package/node_modules/pi-lens/dist/clients/complexity-client.js +5 -0
  51. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +2 -0
  52. package/node_modules/pi-lens/dist/clients/config-resolve.js +6 -3
  53. package/node_modules/pi-lens/dist/clients/config-schema.js +17 -0
  54. package/node_modules/pi-lens/dist/clients/config-warn.js +2 -2
  55. package/node_modules/pi-lens/dist/clients/dead-code-client.js +8 -1
  56. package/node_modules/pi-lens/dist/clients/dependency-checker.js +5 -1
  57. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +38 -15
  58. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +1 -1
  59. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +5 -1
  60. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +2 -1
  61. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +2 -3
  62. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +4 -3
  63. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +2 -1
  64. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +2 -1
  65. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +2 -1
  66. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +2 -1
  67. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +2 -1
  68. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +2 -1
  69. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +2 -1
  70. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +2 -1
  71. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +2 -1
  72. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +2 -1
  73. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +2 -1
  74. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  75. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +3 -1
  76. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +2 -1
  77. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +2 -1
  78. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +2 -1
  79. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +2 -1
  80. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +19 -1
  81. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +3 -2
  82. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +2 -1
  83. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +2 -1
  84. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +2 -1
  85. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +2 -1
  86. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +2 -1
  87. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +12 -12
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +2 -1
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +2 -1
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +2 -2
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +12 -11
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -1
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -1
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +2 -2
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +3 -2
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +3 -2
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +2 -1
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +2 -1
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +2 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +2 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +5 -0
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +2 -2
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +8 -2
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +2 -1
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +4 -3
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +2 -1
  109. package/node_modules/pi-lens/dist/clients/effective-config.js +12 -2
  110. package/node_modules/pi-lens/dist/clients/extension-log.js +2 -0
  111. package/node_modules/pi-lens/dist/clients/file-role.js +18 -1
  112. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +4 -0
  113. package/node_modules/pi-lens/dist/clients/formatters.js +54 -188
  114. package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -0
  115. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +42 -6
  116. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +48 -7
  117. package/node_modules/pi-lens/dist/clients/installer/index.js +196 -52
  118. package/node_modules/pi-lens/dist/clients/jscpd-client.js +9 -1
  119. package/node_modules/pi-lens/dist/clients/knip-client.js +9 -2
  120. package/node_modules/pi-lens/dist/clients/language-profile.js +12 -2
  121. package/node_modules/pi-lens/dist/clients/latency-logger.js +2 -0
  122. package/node_modules/pi-lens/dist/clients/lens-config.js +4 -0
  123. package/node_modules/pi-lens/dist/clients/lsp/config.js +36 -7
  124. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +24 -1
  125. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +78 -0
  126. package/node_modules/pi-lens/dist/clients/lsp/index.js +281 -45
  127. package/node_modules/pi-lens/dist/clients/lsp/server.js +62 -15
  128. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +37 -4
  129. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +3 -0
  130. package/node_modules/pi-lens/dist/clients/mcp/session.js +2 -0
  131. package/node_modules/pi-lens/dist/clients/opengrep-client.js +2 -0
  132. package/node_modules/pi-lens/dist/clients/package-manager.js +2 -1
  133. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +8 -3
  134. package/node_modules/pi-lens/dist/clients/pipeline.js +3 -2
  135. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +38 -10
  136. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +10 -9
  137. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  138. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +2 -0
  139. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +2 -0
  140. package/node_modules/pi-lens/dist/clients/ruff-client.js +18 -9
  141. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +9 -0
  142. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +5 -0
  143. package/node_modules/pi-lens/dist/clients/runtime-session.js +13 -4
  144. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +4 -1
  145. package/node_modules/pi-lens/dist/clients/runtime-turn.js +48 -1
  146. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -2
  147. package/node_modules/pi-lens/dist/clients/session-event-guard.js +69 -1
  148. package/node_modules/pi-lens/dist/clients/sg-runner.js +2 -1
  149. package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +108 -0
  150. package/node_modules/pi-lens/dist/clients/test-runner-client.js +291 -76
  151. package/node_modules/pi-lens/dist/clients/tool-config.js +199 -0
  152. package/node_modules/pi-lens/dist/clients/tool-cwd.js +299 -0
  153. package/node_modules/pi-lens/dist/clients/tool-probe.js +53 -0
  154. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +42 -0
  155. package/node_modules/pi-lens/dist/clients/trivy-client.js +9 -1
  156. package/node_modules/pi-lens/dist/clients/turn-context.js +52 -0
  157. package/node_modules/pi-lens/dist/clients/widget-state.js +4 -3
  158. package/node_modules/pi-lens/dist/index.js +21969 -20005
  159. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -2
  160. package/node_modules/pi-lens/dist/mcp/server.js +135 -159
  161. package/node_modules/pi-lens/dist/tools/activate-tools.js +10 -7
  162. package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +2 -13
  163. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +8 -13
  164. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +83 -39
  165. package/node_modules/pi-lens/dist/tools/effective-config.js +4 -3
  166. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +1 -10
  167. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +353 -85
  168. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +21 -110
  169. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +20 -44
  170. package/node_modules/pi-lens/dist/tools/module-report.js +5 -9
  171. package/node_modules/pi-lens/dist/tools/project-report.js +9 -17
  172. package/node_modules/pi-lens/dist/tools/render-compact.js +280 -1
  173. package/node_modules/pi-lens/dist/tools/symbol-search.js +2 -2
  174. package/node_modules/pi-lens/docs/agent-guide.md +3 -4
  175. package/node_modules/pi-lens/docs/agent-tools.md +46 -22
  176. package/node_modules/pi-lens/docs/configuration.md +9 -0
  177. package/node_modules/pi-lens/docs/environment-variables.md +0 -5
  178. package/node_modules/pi-lens/docs/features.md +5 -5
  179. package/node_modules/pi-lens/docs/globalconfig.md +11 -1
  180. package/node_modules/pi-lens/docs/mcp.md +13 -1
  181. package/node_modules/pi-lens/docs/pi-lens-fixer.md +71 -0
  182. package/node_modules/pi-lens/docs/pi-lens-investigator.md +15 -0
  183. package/node_modules/pi-lens/docs/pi-lens-monitor.md +88 -0
  184. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +35 -0
  185. package/node_modules/pi-lens/docs/pi-lens-subagent.md +12 -4
  186. package/node_modules/pi-lens/docs/public-api-stability.md +1 -0
  187. package/node_modules/pi-lens/docs/real-harness.md +46 -0
  188. package/node_modules/pi-lens/docs/release-qa-baseline.md +5 -1
  189. package/node_modules/pi-lens/docs/servercapabilities.md +1 -6
  190. package/node_modules/pi-lens/docs/settings.md +37 -2
  191. package/node_modules/pi-lens/docs/tools_improvement2.md +4 -4
  192. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
  193. package/node_modules/pi-lens/docs/usage.md +13 -3
  194. package/node_modules/pi-lens/package.json +10 -1
  195. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +7 -3
  196. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +8 -6
  197. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +19 -9
  198. package/node_modules/pi-web-access/CHANGELOG.md +29 -0
  199. package/node_modules/pi-web-access/README.md +33 -22
  200. package/node_modules/pi-web-access/abortable.ts +17 -0
  201. package/node_modules/pi-web-access/crawl4ai.ts +204 -0
  202. package/node_modules/pi-web-access/credential-source.ts +1 -0
  203. package/node_modules/pi-web-access/curator-page.ts +20 -2
  204. package/node_modules/pi-web-access/curator-run.ts +44 -0
  205. package/node_modules/pi-web-access/curator-server.ts +3 -1
  206. package/node_modules/pi-web-access/duckduckgo.ts +1 -1
  207. package/node_modules/pi-web-access/extract.ts +57 -12
  208. package/node_modules/pi-web-access/fetch-params.ts +1 -1
  209. package/node_modules/pi-web-access/gemini-search.ts +9 -5
  210. package/node_modules/pi-web-access/index.ts +25 -18
  211. package/node_modules/pi-web-access/openai-search.ts +35 -13
  212. package/node_modules/pi-web-access/package.json +2 -2
  213. package/node_modules/pi-web-access/page-query.ts +5 -2
  214. package/node_modules/pi-web-access/query-rewrite.ts +5 -2
  215. package/node_modules/pi-web-access/serpapi.ts +220 -0
  216. package/node_modules/pi-web-access/ssrf-protection.ts +5 -1
  217. package/node_modules/pi-web-access/storage.ts +10 -0
  218. package/node_modules/pi-web-access/summary-review.ts +34 -21
  219. package/node_modules/pi-web-access/utils.ts +6 -4
  220. package/package.json +7 -4
  221. package/node_modules/pi-lens/dist/tools/ast-dump.js +0 -103
@@ -42,6 +42,7 @@ import { LEGACY_ROOT_LSP_KEYS, LSP_NAMESPACE_KEY } from "./config-locations.js";
42
42
  // every config loader's import graph for one type alias.
43
43
  import { DENY_KEY, } from "./config-core/schema.js";
44
44
  import { flagConfigSectionKeys, GLOBAL_NON_FLAG_CONFIG_SECTIONS, LENS_FLAGS, PROJECT_FOREIGN_CONFIG_NAMESPACES, PROJECT_NON_FLAG_CONFIG_SECTIONS, } from "./lens-flag-registry.js";
45
+ import { LENS_TOOL_NAMES } from "./tool-config.js";
45
46
  /** The JSON Schema dialect the published artifact declares. */
46
47
  const CONFIG_SCHEMA_DIALECT = "https://json-schema.org/draft/2020-12/schema";
47
48
  /**
@@ -154,6 +155,22 @@ function buildConfigSchema() {
154
155
  ]) {
155
156
  properties[key] = opaque("stable");
156
157
  }
158
+ properties.tools = {
159
+ type: "object",
160
+ additionalProperties: true,
161
+ properties: Object.fromEntries(LENS_TOOL_NAMES.map((name) => [
162
+ name,
163
+ {
164
+ type: "object",
165
+ additionalProperties: true,
166
+ properties: {
167
+ enabled: { type: "boolean", [STABILITY_TIER_KEY]: "experimental" },
168
+ },
169
+ [STABILITY_TIER_KEY]: "experimental",
170
+ },
171
+ ])),
172
+ [STABILITY_TIER_KEY]: "stable",
173
+ };
157
174
  // Namespaces owned by another tool that ride in the same file (`trivy`,
158
175
  // `helm`). Reserved so they survive validation, `experimental` because their
159
176
  // shape belongs to those runners, not to this schema.
@@ -22,7 +22,7 @@
22
22
  * extraction — the prose is not API (the code is), but silently rewording a
23
23
  * warning inside a refactor is still not a thing this PR does.
24
24
  */
25
- import { DEPRECATED_CONFIG_SURFACES, } from "./config-diagnostic-codes.js";
25
+ import { DEPRECATED_CONFIG_SURFACES, withConfigDiagnosticCode, } from "./config-diagnostic-codes.js";
26
26
  import { recordDegradationOnce } from "./degradation-ledger.js";
27
27
  import { errorClassName } from "./error-class.js";
28
28
  import { logExtension } from "./extension-log.js";
@@ -378,7 +378,7 @@ export function warnIgnoredConfigOnce(options) {
378
378
  logExtension({
379
379
  subsystem,
380
380
  level: "warn",
381
- message,
381
+ message: withConfigDiagnosticCode(message, code),
382
382
  metadata: { configPath: file, reason, code, ...(key ? { key } : {}) },
383
383
  });
384
384
  // HUMAN-audience too: a config the user wrote is being ignored. Routed
@@ -19,6 +19,7 @@ import { findLocalBinsAt, VENV_BIN_DIRS } from "./package-manager.js";
19
19
  import { findNearestMarkerRoot } from "./path-utils.js";
20
20
  import { getScratchTreeFnmatchPatterns } from "./scratch-tree-policy.js";
21
21
  import { safeSpawnAsync } from "./safe-spawn.js";
22
+ import { probeToolAsync } from "./tool-probe.js";
22
23
  import { classifyProbeFailure, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
23
24
  import { firstOutputLine, spawnFailedWithNoOutput, } from "./dispatch/runners/utils/spawn-outcome.js";
24
25
  import { formatToolFailure } from "./dispatch/runners/utils/tool-failure.js";
@@ -229,7 +230,7 @@ export class PythonDeadCodeClient {
229
230
  let probe;
230
231
  let hostStallMs;
231
232
  try {
232
- probe = await safeSpawnAsync(c.cmd, [...c.prefix, "--version"], {
233
+ probe = await probeToolAsync(c.cmd, [...c.prefix, "--version"], {
233
234
  timeout: 5000,
234
235
  });
235
236
  }
@@ -415,9 +416,13 @@ export class PythonDeadCodeClient {
415
416
  durationMs,
416
417
  };
417
418
  }
419
+ // vulture's verified exit-code table (above): exit 0 with empty
420
+ // stdout IS the clean-run signal, so this ran over the root
421
+ // (#2154) — unlike knip, whose clean run always prints JSON.
418
422
  return {
419
423
  ...emptyResult(this.language),
420
424
  success: true,
425
+ analyzed: true,
421
426
  summary: "No dead code found",
422
427
  durationMs,
423
428
  };
@@ -430,6 +435,8 @@ export class PythonDeadCodeClient {
430
435
  return {
431
436
  ...emptyResult(this.language),
432
437
  success: true,
438
+ // #2154: parsed vulture output for this root.
439
+ analyzed: true,
433
440
  unusedExports,
434
441
  summary: total === 0
435
442
  ? "No dead code found"
@@ -815,7 +815,11 @@ export class DependencyChecker {
815
815
  }
816
816
  }
817
817
  this.publishState(gen, circular, circularFiles);
818
- return { circular, count: circular.length };
818
+ // #2154: the one madge site that parsed a graph for this root. Every
819
+ // other return here (missing root, no top-level source file, madge
820
+ // unavailable, spawn error, parse throw) is the SAME empty shape and
821
+ // must not be read as "no cycles in this project".
822
+ return { circular, count: circular.length, analyzed: true };
819
823
  }
820
824
  catch (err) {
821
825
  this.log(`Scan error: ${err.message}`);
@@ -29,7 +29,7 @@ import { isSpawnableCommand } from "../installer/index.js";
29
29
  import { isAbsoluteFilePath, normalizeEphemeralMapKey, normalizeMapKey, } from "../path-utils.js";
30
30
  import { loadPiLensProjectConfig } from "../project-lens-config.js";
31
31
  import { RUNTIME_CONFIG, getRunnerTimeoutFloorMs } from "../runtime-config.js";
32
- import { safeSpawnAsync } from "../safe-spawn.js";
32
+ import { probeToolAsync } from "../tool-probe.js";
33
33
  import { classifyDiagnostic } from "./diagnostic-taxonomy.js";
34
34
  import { classifyProbeFailure, logAvailabilityDecision, startHostStallSampler, transientRetryDelayMs, } from "./runners/utils/availability-policy.js";
35
35
  import { recordAvailabilityProbeOverrun, getDispatchAvailabilityGeneration, } from "./runners/utils/runner-helpers.js";
@@ -147,7 +147,7 @@ export async function checkToolAvailability(command, facts) {
147
147
  let hostStallMs;
148
148
  let probeJoined = false;
149
149
  try {
150
- const shared = dispatcherProbeFlights.run(`dispatcher:${key}`, () => safeSpawnAsync(command, ["--version"], {
150
+ const shared = dispatcherProbeFlights.run(`dispatcher:${key}`, () => probeToolAsync(command, ["--version"], {
151
151
  timeout: TOOL_PROBE_TIMEOUT_MS,
152
152
  }));
153
153
  probeJoined = shared.joined;
@@ -285,11 +285,13 @@ telemetryModel, telemetryProvider) {
285
285
  async hasTool(command) {
286
286
  return checkToolAvailability(command, facts);
287
287
  },
288
- log(message) {
288
+ log(message, level) {
289
289
  // #1333: pi owns the terminal — a runner advisory must never be a raw
290
290
  // write. Every DispatchContext.log line lands in extension.log instead.
291
291
  logExtension({
292
292
  subsystem: "dispatch",
293
+ // exactOptionalPropertyTypes: an omitted level must stay omitted.
294
+ ...(level !== undefined && { level }),
293
295
  message,
294
296
  metadata: { filePath: normalizedFilePath, kind },
295
297
  });
@@ -446,31 +448,49 @@ function buildCoverageNotice(ctx, runnerLatencies) {
446
448
  ...new Set(relevant.flatMap((r) => r.unconfirmedServerIds ?? [])),
447
449
  ];
448
450
  if (unconfirmedServerIds.length > 0) {
449
- // The marker describes this exact silent-scanner set. A scanner can
450
- // recover while another goes dark on the same file, so the set belongs
451
- // in the session dedupe identity rather than only kind and path.
451
+ const deferredIds = new Set(relevant.flatMap((r) => r.deferredServerIds ?? []));
452
+ const markerFor = (ids) => {
453
+ const shown = ids.slice(0, 4);
454
+ const remainder = ids.length - shown.length;
455
+ return `${shown.join(", ")}${remainder > 0 ? ` +${remainder}` : ""}`;
456
+ };
457
+ const deferredServerIds = unconfirmedServerIds.filter((id) => deferredIds.has(id));
458
+ const silentServerIds = unconfirmedServerIds.filter((id) => !deferredIds.has(id));
459
+ // The marker describes this exact scanner set. A scanner can recover
460
+ // while another goes dark on the same file, so the set belongs in the
461
+ // session dedupe identity rather than only kind and path.
452
462
  // #2016: these are SCANNER IDS, not filesystem paths. `normalizeMapKey`
453
463
  // would realpath each one; on Windows that fails, falls through to
454
464
  // `resolveNonExisting`, and resolves the id against the CURRENT process
455
465
  // cwd, so the dedupe key differed by platform and by cwd (the #2219
456
466
  // non-path-sentinel class). The cheap syntactic fold is what this
457
- // session-scoped dedupe key actually needs.
458
- const silentScannerSet = [...new Set(unconfirmedServerIds)]
467
+ // session-scoped dedupe key actually needs. Code-unit comparator: the
468
+ // sorted set is a KEY, so ordering must be deterministic across locales
469
+ // — localeCompare is not.
470
+ const dedupeSet = (ids) => [...new Set(ids)]
459
471
  .map(normalizeEphemeralMapKey)
460
- // Code-unit comparator: the sorted set is a dedupe KEY, so ordering
461
- // must be deterministic across locales — localeCompare is not.
462
472
  .sort((a, b) => Number(a > b) - Number(a < b))
463
473
  .join(",");
464
- const onceKey = `${ctx.kind}:${ctx.filePath}:${silentScannerSet}`;
474
+ // #2810 round 4: the PARTITION is part of the identity, not just the
475
+ // scanner set. The same scanner can be silent on one edit and marked for
476
+ // late delivery on the next; keying on the unconfirmed set alone showed
477
+ // the session whichever message came first and suppressed the other, so a
478
+ // scanner that recovered a delivery path (or lost one) kept the stale
479
+ // wording for the rest of the session.
480
+ const onceKey = `${ctx.kind}:${ctx.filePath}:${dedupeSet(silentServerIds)}|${dedupeSet(deferredServerIds)}`;
465
481
  if (coverageNoticeSeen.has(onceKey))
466
482
  return undefined;
467
483
  coverageNoticeSeen.add(onceKey);
468
- const shown = unconfirmedServerIds.slice(0, 4);
469
- const remainder = unconfirmedServerIds.length - shown.length;
470
- const marker = `${shown.join(", ")}${remainder > 0 ? ` +${remainder}` : ""}`;
484
+ const coverageParts = [];
485
+ if (deferredServerIds.length > 0) {
486
+ coverageParts.push(`coverage: ${markerFor(deferredServerIds)} deferred diagnostics are incomplete; findings arrive at turn end if the scan lands.`);
487
+ }
488
+ if (silentServerIds.length > 0) {
489
+ coverageParts.push(`coverage: ${markerFor(silentServerIds)} silent — diagnostics are incomplete (not a clean result).`);
490
+ }
471
491
  return {
472
492
  id: `coverage-partial:${ctx.kind}:${path.basename(ctx.filePath)}`,
473
- message: `coverage: ${marker} silent — diagnostics are incomplete (not a clean result).`,
493
+ message: coverageParts.join("\n"),
474
494
  filePath: ctx.filePath,
475
495
  severity: "warning",
476
496
  semantic: "warning",
@@ -837,6 +857,9 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
837
857
  ...(result.unconfirmedServerIds !== undefined && {
838
858
  unconfirmedServerIds: result.unconfirmedServerIds,
839
859
  }),
860
+ ...(result.deferredServerIds !== undefined && {
861
+ deferredServerIds: result.deferredServerIds,
862
+ }),
840
863
  });
841
864
  logLatency({
842
865
  type: "runner",
@@ -135,7 +135,7 @@ function isCallPassThrough(stmt, paramNames) {
135
135
  }
136
136
  return { pass: true, target: (expr.children ?? [])[0]?.text };
137
137
  }
138
- function calcCyclomaticComplexity(body) {
138
+ export function calcCyclomaticComplexity(body) {
139
139
  let cc = 1;
140
140
  walk(body, (node) => {
141
141
  if (COMPLEXITY_TYPES.has(node.type)) {
@@ -1,6 +1,10 @@
1
1
  import { logLatency } from "../../latency-logger.js";
2
2
  import { isJstsFactFile } from "../../file-kinds.js";
3
3
  import { childrenOfType, firstChildOfType, withFactTree, walk, } from "./tree-sitter-facts.js";
4
+ /** Whether the import-facts provider applies to a source path. */
5
+ export function importFactsApplyTo(filePath) {
6
+ return isJstsFactFile(filePath);
7
+ }
4
8
  function stripQuotes(raw) {
5
9
  return raw.replace(/^["'`]+|["'`]+$/g, "");
6
10
  }
@@ -69,7 +73,7 @@ export const importFactProvider = {
69
73
  provides: ["file.imports", "file.reexports", "file.importFactsCoverage"],
70
74
  requires: ["file.content"],
71
75
  appliesTo(ctx) {
72
- return isJstsFactFile(ctx.filePath);
76
+ return importFactsApplyTo(ctx.filePath);
73
77
  },
74
78
  async run(ctx, store) {
75
79
  const content = store.getFileFact(ctx.filePath, "file.content");
@@ -1,5 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { safeSpawnAsync } from "../../safe-spawn.js";
3
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
3
4
  import { PRIORITY } from "../priorities.js";
4
5
  import { createAvailabilityChecker, resolveToolCommandWithInstallFallback, } from "./utils/runner-helpers.js";
5
6
  const actionlint = createAvailabilityChecker("actionlint", ".exe");
@@ -66,7 +67,7 @@ const actionlintRunner = {
66
67
  return isGitHubWorkflowFile(ctx.filePath);
67
68
  },
68
69
  async run(ctx) {
69
- const cwd = ctx.cwd || process.cwd();
70
+ const cwd = resolveRunnerCwd(ctx, "actionlint");
70
71
  let cmd = null;
71
72
  if (await actionlint.isAvailableAsync(cwd)) {
72
73
  cmd = actionlint.getCommand(cwd);
@@ -5,13 +5,12 @@
5
5
  * Autofix is handled earlier by the post-write pipeline to avoid
6
6
  * mutating files mid-dispatch after LSP sync has already happened.
7
7
  */
8
- import * as path from "node:path";
9
8
  import { incrementDegradationCount } from "../../degradation-ledger.js";
10
9
  import { mapWithConcurrency } from "../../dependency-checker.js";
11
10
  import { safeSpawnAsync } from "../../safe-spawn.js";
12
11
  import { biomeConfigArgs, getAutofixCapability, getJstsLintPolicyForCwd, } from "../../tool-policy.js";
13
12
  import { PRIORITY } from "../priorities.js";
14
- import { resolveToolCommandWithInstallFallback } from "./utils/runner-helpers.js";
13
+ import { resolveRunnerCwd, resolveToolCommandWithInstallFallback, } from "./utils/runner-helpers.js";
15
14
  import { finishParsedRun } from "./utils/tool-failure.js";
16
15
  /**
17
16
  * Map biome's own severity vocabulary onto the four-tier `Diagnostic.severity`
@@ -198,7 +197,7 @@ const biomeCheckJsonRunner = {
198
197
  appliesTo: ["jsts"],
199
198
  priority: PRIORITY.FORMAT_AND_LINT_PRIMARY,
200
199
  async run(ctx) {
201
- const cwd = ctx.cwd || path.dirname(ctx.filePath);
200
+ const cwd = resolveRunnerCwd(ctx, "biome");
202
201
  const policy = getJstsLintPolicyForCwd(cwd);
203
202
  // Defer to ESLint/oxlint if the project has explicitly configured one —
204
203
  // biome runs as the default linter only when no alternative is present.
@@ -1,6 +1,8 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { safeSpawnAsync } from "../../safe-spawn.js";
4
+ import { probeToolAsync } from "../../tool-probe.js";
5
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
4
6
  import { PRIORITY } from "../priorities.js";
5
7
  import { createAvailabilityChecker } from "./utils/runner-helpers.js";
6
8
  // Per-compiler availability checkers — each one caches per-cwd and
@@ -100,8 +102,7 @@ async function resolveCompiler(absPath, cwd) {
100
102
  // Mirrors the deliberate global-PATH probes in utils/runner-helpers.ts:
101
103
  // this never resolves a config file or a target path, so there is no cwd
102
104
  // for it to get wrong.
103
- // cwd-exempt: presence probe only -- `cl` with no args and no target file, used solely to detect whether MSVC is on PATH
104
- const clProbe = await safeSpawnAsync("cl", [], { timeout: 5000 });
105
+ const clProbe = await probeToolAsync("cl", [], { timeout: 5000 });
105
106
  if (!clProbe.error && clProbe.status !== null) {
106
107
  return {
107
108
  command: "cl",
@@ -181,7 +182,7 @@ const cppCheckRunner = {
181
182
  priority: PRIORITY.GENERAL_ANALYSIS,
182
183
  skipTestFiles: false,
183
184
  async run(ctx) {
184
- const cwd = ctx.cwd || process.cwd();
185
+ const cwd = resolveRunnerCwd(ctx, "cpp-check");
185
186
  const absPath = path.resolve(cwd, ctx.filePath);
186
187
  const compiler = await resolveCompiler(absPath, cwd);
187
188
  if (!compiler) {
@@ -1,6 +1,7 @@
1
1
  import * as path from "node:path";
2
2
  import * as fs from "node:fs";
3
3
  import { safeSpawnAsync } from "../../safe-spawn.js";
4
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
4
5
  import { PRIORITY } from "../priorities.js";
5
6
  import { createCwdCachedProbe } from "./utils/runner-helpers.js";
6
7
  const CREDO_PROBE_BUDGET_MS = 10_000;
@@ -50,7 +51,7 @@ const credoRunner = {
50
51
  priority: PRIORITY.GENERAL_ANALYSIS,
51
52
  skipTestFiles: false,
52
53
  async run(ctx) {
53
- const cwd = ctx.cwd || process.cwd();
54
+ const cwd = resolveRunnerCwd(ctx, "credo");
54
55
  if (!hasMixExs(cwd)) {
55
56
  return { status: "skipped", diagnostics: [], semantic: "none" };
56
57
  }
@@ -104,6 +104,7 @@
104
104
  import * as fs from "node:fs";
105
105
  import * as path from "node:path";
106
106
  import { safeSpawnAsync } from "../../safe-spawn.js";
107
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
107
108
  import { createAvailabilityChecker, resolveAvailableOrInstall, } from "./utils/runner-helpers.js";
108
109
  import { spawnFailedWithNoOutput } from "./utils/spawn-outcome.js";
109
110
  import { PRIORITY } from "../priorities.js";
@@ -281,7 +282,7 @@ const cueVetRunner = {
281
282
  skipTestFiles: false,
282
283
  timeoutMs: 30_000,
283
284
  async run(ctx) {
284
- const cwd = ctx.cwd || process.cwd();
285
+ const cwd = resolveRunnerCwd(ctx, "cue-vet");
285
286
  let cmd = null;
286
287
  if (await cue.isAvailableAsync(cwd)) {
287
288
  cmd = cue.getCommand(cwd);
@@ -1,5 +1,6 @@
1
1
  import * as path from "node:path";
2
2
  import { safeSpawnAsync } from "../../safe-spawn.js";
3
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
3
4
  import { createAvailabilityChecker } from "./utils/runner-helpers.js";
4
5
  import { PRIORITY } from "../priorities.js";
5
6
  const dart = createAvailabilityChecker("dart", ".exe");
@@ -147,7 +148,7 @@ const dartAnalyzeRunner = {
147
148
  priority: PRIORITY.GENERAL_ANALYSIS,
148
149
  skipTestFiles: false,
149
150
  async run(ctx) {
150
- const cwd = ctx.cwd || process.cwd();
151
+ const cwd = resolveRunnerCwd(ctx, "dart-analyze");
151
152
  const absPath = path.resolve(cwd, ctx.filePath);
152
153
  const dartAvailable = await dart.isAvailableAsync(cwd);
153
154
  const flutterAvailable = !dartAvailable && (await flutter.isAvailableAsync(cwd));
@@ -1,6 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { safeSpawnAsync } from "../../safe-spawn.js";
4
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
4
5
  import { getLinterPolicyForCwd } from "../../tool-policy.js";
5
6
  import { PRIORITY } from "../priorities.js";
6
7
  import { createAvailabilityChecker } from "./utils/runner-helpers.js";
@@ -129,7 +130,7 @@ const detektRunner = {
129
130
  timeoutMs: 90_000,
130
131
  skipTestFiles: false,
131
132
  async run(ctx) {
132
- const cwd = ctx.cwd || process.cwd();
133
+ const cwd = resolveRunnerCwd(ctx, "detekt");
133
134
  const policy = getLinterPolicyForCwd(ctx.filePath, cwd);
134
135
  if (policy && !policy.preferredRunners.includes("detekt")) {
135
136
  return { status: "skipped", diagnostics: [], semantic: "none" };
@@ -1,6 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { safeSpawnAsync } from "../../safe-spawn.js";
4
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
4
5
  import { PRIORITY } from "../priorities.js";
5
6
  import { createAvailabilityChecker } from "./utils/runner-helpers.js";
6
7
  const dotnet = createAvailabilityChecker("dotnet", ".exe");
@@ -118,7 +119,7 @@ const dotnetBuildRunner = {
118
119
  timeoutMs: 90_000,
119
120
  skipTestFiles: false,
120
121
  async run(ctx) {
121
- const cwd = ctx.cwd || process.cwd();
122
+ const cwd = resolveRunnerCwd(ctx, "dotnet-build");
122
123
  if (!(await dotnet.isAvailableAsync(cwd))) {
123
124
  return { status: "skipped", diagnostics: [], semantic: "none" };
124
125
  }
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { getProjectDataDir } from "../../file-utils.js";
4
4
  import { safeSpawnAsync } from "../../safe-spawn.js";
5
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
5
6
  import { PRIORITY } from "../priorities.js";
6
7
  import { createAvailabilityChecker } from "./utils/runner-helpers.js";
7
8
  // Per-cwd cached `--version` probes (#120). Before this, each dispatch
@@ -141,7 +142,7 @@ const elixirCheckRunner = {
141
142
  priority: PRIORITY.GENERAL_ANALYSIS,
142
143
  skipTestFiles: false,
143
144
  async run(ctx) {
144
- const cwd = ctx.cwd || process.cwd();
145
+ const cwd = resolveRunnerCwd(ctx, "elixir-check");
145
146
  const absPath = path.resolve(cwd, ctx.filePath);
146
147
  let command;
147
148
  let args = [];
@@ -7,6 +7,7 @@
7
7
  * Gate: skips when no ESLint config is detected (project uses Biome/OxLint instead).
8
8
  */
9
9
  import { safeSpawnAsync } from "../../safe-spawn.js";
10
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
10
11
  import { getAutofixCapability, hasEslintConfig } from "../../tool-policy.js";
11
12
  import { PRIORITY } from "../priorities.js";
12
13
  import { createCwdCachedProbe, resolveToolCommand, } from "./utils/runner-helpers.js";
@@ -79,7 +80,7 @@ const eslintRunner = {
79
80
  appliesTo: ["jsts"],
80
81
  priority: PRIORITY.LINT_SECONDARY,
81
82
  async run(ctx) {
82
- const cwd = ctx.cwd || process.cwd();
83
+ const cwd = resolveRunnerCwd(ctx, "eslint");
83
84
  const userHasConfig = hasEslintConfig(cwd);
84
85
  // Only run if project has an ESLint config.
85
86
  if (!userHasConfig) {
@@ -1,4 +1,5 @@
1
1
  import { safeSpawnAsync } from "../../safe-spawn.js";
2
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
2
3
  import { PRIORITY } from "../priorities.js";
3
4
  import { createAvailabilityChecker } from "./utils/runner-helpers.js";
4
5
  // fish_indent ships with fish — not separately installable, no managed fallback
@@ -9,7 +10,7 @@ const fishIndentRunner = {
9
10
  priority: PRIORITY.FORMAT_AND_LINT_PRIMARY,
10
11
  skipTestFiles: false,
11
12
  async run(ctx) {
12
- const cwd = ctx.cwd || process.cwd();
13
+ const cwd = resolveRunnerCwd(ctx, "fish-indent");
13
14
  const available = await fishIndent.isAvailableAsync(cwd);
14
15
  if (!available)
15
16
  return { status: "skipped", diagnostics: [], semantic: "none" };
@@ -1,4 +1,5 @@
1
1
  import { safeSpawnAsync } from "../../safe-spawn.js";
2
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
2
3
  import { createAvailabilityChecker } from "./utils/runner-helpers.js";
3
4
  import { PRIORITY } from "../priorities.js";
4
5
  const gleam = createAvailabilityChecker("gleam", ".exe");
@@ -41,7 +42,7 @@ const gleamCheckRunner = {
41
42
  priority: PRIORITY.GENERAL_ANALYSIS,
42
43
  skipTestFiles: false,
43
44
  async run(ctx) {
44
- const cwd = ctx.cwd || process.cwd();
45
+ const cwd = resolveRunnerCwd(ctx, "gleam-check");
45
46
  if (!(await gleam.isAvailableAsync(cwd))) {
46
47
  return { status: "skipped", diagnostics: [], semantic: "none" };
47
48
  }
@@ -6,6 +6,7 @@
6
6
  import { relative, resolve, sep, posix } from "node:path";
7
7
  import { goClient } from "../../go-client.js";
8
8
  import { safeSpawnAsync } from "../../safe-spawn.js";
9
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
9
10
  import { stripAnsi } from "../../sanitize.js";
10
11
  import { skipUnlessToolRan } from "./utils/tool-failure.js";
11
12
  import { parseGoVetOutput } from "./utils/diagnostic-parsers.js";
@@ -29,7 +30,7 @@ const goVetRunner = {
29
30
  // `go.mod file not found` (#263). ctx.cwd is the go.mod module root
30
31
  // (resolveLanguageRootForFile, markers ["go.mod"]), so vet the file's
31
32
  // package from there.
32
- const cwd = ctx.cwd || process.cwd();
33
+ const cwd = resolveRunnerCwd(ctx, "go-vet");
33
34
  const fileRel = relative(cwd, ctx.filePath).split(sep).join(posix.sep);
34
35
  const pkgPath = fileRel.startsWith("../")
35
36
  ? // File isn't under ctx.cwd (unexpected — ctx.cwd is the go.mod root).
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import * as path from "node:path";
13
13
  import { safeSpawnAsync } from "../../safe-spawn.js";
14
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
14
15
  import { getLinterPolicyForCwd, hasGolangciConfig } from "../../tool-policy.js";
15
16
  import { PRIORITY } from "../priorities.js";
16
17
  import { createAvailabilityChecker, resolveAvailableOrInstall, } from "./utils/runner-helpers.js";
@@ -80,7 +81,7 @@ const golangciRunner = {
80
81
  priority: PRIORITY.GENERAL_ANALYSIS,
81
82
  timeoutMs: 90_000,
82
83
  async run(ctx) {
83
- const cwd = ctx.cwd || process.cwd();
84
+ const cwd = resolveRunnerCwd(ctx, "golangci-lint");
84
85
  const policy = getLinterPolicyForCwd(ctx.filePath, cwd);
85
86
  if (policy && !policy.preferredRunners.includes("golangci-lint")) {
86
87
  return { status: "skipped", diagnostics: [], semantic: "none" };
@@ -1,5 +1,6 @@
1
1
  import * as path from "node:path";
2
2
  import { safeSpawnAsync } from "../../safe-spawn.js";
3
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
3
4
  import { getLinterPolicyForCwd } from "../../tool-policy.js";
4
5
  import { PRIORITY } from "../priorities.js";
5
6
  import { createAvailabilityChecker, resolveToolCommandWithInstallFallback, } from "./utils/runner-helpers.js";
@@ -37,7 +38,7 @@ const hadolintRunner = {
37
38
  priority: PRIORITY.GENERAL_ANALYSIS,
38
39
  skipTestFiles: false,
39
40
  async run(ctx) {
40
- const cwd = ctx.cwd || process.cwd();
41
+ const cwd = resolveRunnerCwd(ctx, "hadolint");
41
42
  const policy = getLinterPolicyForCwd(ctx.filePath, cwd);
42
43
  if (policy && !policy.preferredRunners.includes("hadolint")) {
43
44
  return { status: "skipped", diagnostics: [], semantic: "none" };
@@ -3,6 +3,7 @@ import { PathKeyedMap } from "../../path-keyed-map.js";
3
3
  import { normalizeMapKey } from "../../path-utils.js";
4
4
  import { safeSpawnAsync } from "../../safe-spawn.js";
5
5
  import { truncatedByOutputCap } from "../../spawn-output-cap.js";
6
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
6
7
  import { findNearestDirWithMarker } from "../../workspace-topology.js";
7
8
  import { PRIORITY } from "../priorities.js";
8
9
  import { createAvailabilityChecker, resolveAvailableOrInstall, } from "./utils/runner-helpers.js";
@@ -147,6 +148,7 @@ const helmLintRunner = {
147
148
  skipTestFiles: false,
148
149
  timeoutMs: 35_000,
149
150
  async run(ctx) {
151
+ const cwd = resolveRunnerCwd(ctx, "helm");
150
152
  const workspaceRoot = path.resolve(ctx.projectRoot ?? ctx.cwd);
151
153
  const startDir = path.dirname(path.resolve(ctx.filePath));
152
154
  const discovered = findNearestDirWithMarker(startDir, "chartYamlPath");
@@ -158,7 +160,7 @@ const helmLintRunner = {
158
160
  const existing = inFlightByChartRoot.get(chartRoot);
159
161
  if (existing)
160
162
  return existing;
161
- const promise = lintChart(chartRoot, ctx.cwd).finally(() => {
163
+ const promise = lintChart(chartRoot, cwd).finally(() => {
162
164
  if (inFlightByChartRoot.get(chartRoot) === promise)
163
165
  inFlightByChartRoot.delete(chartRoot);
164
166
  });
@@ -78,6 +78,7 @@ import { normalizeMapKey } from "../../path-utils.js";
78
78
  import { loadPiLensProjectConfig } from "../../project-lens-config.js";
79
79
  import { getProjectTrustState, projectTrustDenialReason, } from "../../project-trust.js";
80
80
  import { safeSpawnAsync } from "../../safe-spawn.js";
81
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
81
82
  import { killedForOutputCap, truncatedByOutputCap, } from "../../spawn-output-cap.js";
82
83
  import { isTrivyEnabled, resolveSeverityFloor } from "../../trivy-client.js";
83
84
  import { findNearestDirWithMarker } from "../../workspace-topology.js";
@@ -896,7 +897,7 @@ const helmRenderRunner = {
896
897
  skipTestFiles: false,
897
898
  timeoutMs: RENDER_TIMEOUT_MS + TRIVY_TIMEOUT_MS + 10_000,
898
899
  async run(ctx) {
899
- const cwd = ctx.cwd || process.cwd();
900
+ const cwd = resolveRunnerCwd(ctx, "helm-render");
900
901
  const workspaceRoot = path.resolve(ctx.projectRoot ?? cwd);
901
902
  // Gate 1 — consent, read from the project whose chart would run. Keying
902
903
  // this on `cwd` let an opt-in in one directory authorize a DIFFERENT
@@ -1,5 +1,6 @@
1
1
  import * as path from "node:path";
2
2
  import { safeSpawnAsync } from "../../safe-spawn.js";
3
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
3
4
  import { getLinterPolicyForCwd } from "../../tool-policy.js";
4
5
  import { PRIORITY } from "../priorities.js";
5
6
  import { createAvailabilityChecker, resolveToolCommandWithInstallFallback, } from "./utils/runner-helpers.js";
@@ -49,7 +50,7 @@ const htmlhintRunner = {
49
50
  priority: PRIORITY.GENERAL_ANALYSIS,
50
51
  skipTestFiles: false,
51
52
  async run(ctx) {
52
- const cwd = ctx.cwd || process.cwd();
53
+ const cwd = resolveRunnerCwd(ctx, "htmlhint");
53
54
  const policy = getLinterPolicyForCwd(ctx.filePath, cwd);
54
55
  if (policy && !policy.preferredRunners.includes("htmlhint")) {
55
56
  return { status: "skipped", diagnostics: [], semantic: "none" };
@@ -1,5 +1,6 @@
1
1
  import * as path from "node:path";
2
2
  import { safeSpawnAsync } from "../../safe-spawn.js";
3
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
3
4
  import { hasJavaBuildDescriptor } from "../../tool-policy.js";
4
5
  import { PRIORITY } from "../priorities.js";
5
6
  import { createAvailabilityChecker } from "./utils/runner-helpers.js";
@@ -41,7 +42,7 @@ const javacRunner = {
41
42
  priority: PRIORITY.GENERAL_ANALYSIS,
42
43
  skipTestFiles: false,
43
44
  async run(ctx) {
44
- const cwd = ctx.cwd || process.cwd();
45
+ const cwd = resolveRunnerCwd(ctx, "javac");
45
46
  const absPath = path.resolve(cwd, ctx.filePath);
46
47
  // Inside a Maven/Gradle project a classpath-less single-file compile
47
48
  // cannot produce a valid verdict: every non-JDK import becomes a
@@ -1,5 +1,6 @@
1
1
  import * as path from "node:path";
2
2
  import { safeSpawnAsync } from "../../safe-spawn.js";
3
+ import { resolveRunnerCwd } from "../../tool-cwd.js";
3
4
  import { getAutofixCapability, getLinterPolicyForCwd, } from "../../tool-policy.js";
4
5
  import { PRIORITY } from "../priorities.js";
5
6
  import { createAvailabilityChecker, resolveToolCommandWithInstallFallback, } from "./utils/runner-helpers.js";
@@ -58,7 +59,7 @@ const ktlintRunner = {
58
59
  priority: PRIORITY.FORMAT_AND_LINT_PRIMARY,
59
60
  skipTestFiles: false,
60
61
  async run(ctx) {
61
- const cwd = ctx.cwd || process.cwd();
62
+ const cwd = resolveRunnerCwd(ctx, "ktlint");
62
63
  const policy = getLinterPolicyForCwd(ctx.filePath, cwd);
63
64
  if (policy && !policy.preferredRunners.includes("ktlint")) {
64
65
  return { status: "skipped", diagnostics: [], semantic: "none" };