@herbertgao/pi-extensions 2026.8.4 → 2026.8.6

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 (360) hide show
  1. package/README.md +5 -5
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +11 -3
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +11 -3
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +53 -14
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
  14. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  15. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +18 -0
  16. package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
  17. package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
  18. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
  19. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +20 -4
  21. package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
  22. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
  23. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +15 -6
  25. package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
  26. package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
  27. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +9 -5
  28. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +2 -0
  29. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +20 -0
  30. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +6 -0
  31. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
  32. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  33. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
  34. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
  35. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
  36. package/node_modules/@narumitw/pi-btw/README.md +32 -24
  37. package/node_modules/@narumitw/pi-btw/package.json +6 -6
  38. package/node_modules/@narumitw/pi-btw/src/btw.ts +4 -2
  39. package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -13
  40. package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
  41. package/node_modules/pi-lens/CHANGELOG.md +2535 -954
  42. package/node_modules/pi-lens/README.md +13 -0
  43. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
  44. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +314 -0
  45. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
  46. package/node_modules/pi-lens/dist/clients/agent-nudge.js +14 -7
  47. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
  48. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
  49. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
  50. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
  51. package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
  52. package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
  53. package/node_modules/pi-lens/dist/clients/biome-client.js +136 -24
  54. package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
  55. package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
  56. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
  57. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
  58. package/node_modules/pi-lens/dist/clients/bus-publish.js +19 -4
  59. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
  60. package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
  61. package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
  62. package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
  63. package/node_modules/pi-lens/dist/clients/cascade-format.js +57 -2
  64. package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
  65. package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
  66. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
  67. package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
  68. package/node_modules/pi-lens/dist/clients/console-guard-install.js +26 -0
  69. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
  70. package/node_modules/pi-lens/dist/clients/dead-code-client.js +137 -31
  71. package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
  72. package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
  73. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
  74. package/node_modules/pi-lens/dist/clients/dependency-checker.js +361 -77
  75. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +65 -13
  76. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +18 -4
  77. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +176 -29
  78. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
  79. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
  80. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
  81. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
  82. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
  83. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1172 -683
  84. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
  85. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
  86. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
  87. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +47 -6
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +226 -0
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +69 -0
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +473 -42
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +97 -0
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  114. package/node_modules/pi-lens/dist/clients/disposition-publish.js +18 -4
  115. package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
  116. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
  117. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
  118. package/node_modules/pi-lens/dist/clients/extension-log.js +432 -0
  119. package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
  120. package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
  121. package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
  122. package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
  123. package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
  124. package/node_modules/pi-lens/dist/clients/format-events-publish.js +51 -7
  125. package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
  126. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
  127. package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
  128. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
  129. package/node_modules/pi-lens/dist/clients/git-guard.js +678 -11
  130. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
  131. package/node_modules/pi-lens/dist/clients/go-client.js +23 -40
  132. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +124 -7
  133. package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
  134. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
  135. package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
  136. package/node_modules/pi-lens/dist/clients/installer/index.js +395 -100
  137. package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
  138. package/node_modules/pi-lens/dist/clients/jscpd-client.js +27 -55
  139. package/node_modules/pi-lens/dist/clients/knip-client.js +58 -42
  140. package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
  141. package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
  142. package/node_modules/pi-lens/dist/clients/latency-logger.js +72 -1
  143. package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
  144. package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
  145. package/node_modules/pi-lens/dist/clients/lens-events.js +63 -9
  146. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
  147. package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
  148. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +73 -0
  149. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
  150. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
  151. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +59 -14
  152. package/node_modules/pi-lens/dist/clients/lsp/client.js +662 -56
  153. package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
  154. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +115 -0
  155. package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
  156. package/node_modules/pi-lens/dist/clients/lsp/index.js +2025 -800
  157. package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
  158. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  159. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  160. package/node_modules/pi-lens/dist/clients/lsp/server.js +437 -86
  161. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +96 -0
  162. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
  163. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +18 -3
  164. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  165. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
  166. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
  167. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +127 -32
  168. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
  169. package/node_modules/pi-lens/dist/clients/mcp/session.js +235 -14
  170. package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
  171. package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
  172. package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
  173. package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
  174. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
  175. package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
  176. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
  177. package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
  178. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
  179. package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
  180. package/node_modules/pi-lens/dist/clients/pipeline.js +76 -15
  181. package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
  182. package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
  183. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
  184. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +1 -1
  185. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
  186. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
  187. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
  188. package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
  189. package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
  190. package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
  191. package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
  192. package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
  193. package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
  194. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
  195. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
  196. package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
  197. package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
  198. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
  199. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
  200. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
  201. package/node_modules/pi-lens/dist/clients/review-graph/query.js +24 -0
  202. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
  203. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
  204. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
  205. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
  206. package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
  207. package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
  208. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +257 -77
  209. package/node_modules/pi-lens/dist/clients/runtime-context.js +129 -12
  210. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +242 -26
  211. package/node_modules/pi-lens/dist/clients/runtime-session.js +428 -117
  212. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
  213. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +342 -33
  214. package/node_modules/pi-lens/dist/clients/runtime-turn.js +619 -93
  215. package/node_modules/pi-lens/dist/clients/rust-client.js +23 -38
  216. package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
  217. package/node_modules/pi-lens/dist/clients/security-scan-client.js +90 -6
  218. package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
  219. package/node_modules/pi-lens/dist/clients/sg-runner.js +424 -196
  220. package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
  221. package/node_modules/pi-lens/dist/clients/smells-rollup.js +231 -0
  222. package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
  223. package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
  224. package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
  225. package/node_modules/pi-lens/dist/clients/startup-timing.js +7 -1
  226. package/node_modules/pi-lens/dist/clients/test-runner-client.js +469 -32
  227. package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
  228. package/node_modules/pi-lens/dist/clients/tool-policy.js +390 -39
  229. package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
  230. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
  231. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
  232. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
  233. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
  234. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
  235. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
  236. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
  237. package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
  238. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
  239. package/node_modules/pi-lens/dist/clients/warm-attach.js +50 -13
  240. package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
  241. package/node_modules/pi-lens/dist/clients/word-index.js +680 -81
  242. package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
  243. package/node_modules/pi-lens/dist/index.js +51785 -36118
  244. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
  245. package/node_modules/pi-lens/dist/mcp/server.js +219 -46
  246. package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
  247. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
  248. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
  249. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
  250. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +203 -38
  251. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +210 -42
  252. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
  253. package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
  254. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
  255. package/node_modules/pi-lens/docs/agent-guide.md +40 -15
  256. package/node_modules/pi-lens/docs/agent-tools.md +21 -10
  257. package/node_modules/pi-lens/docs/analysisall.md +192 -0
  258. package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
  259. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +14 -3
  260. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
  261. package/node_modules/pi-lens/docs/environment-variables.md +44 -0
  262. package/node_modules/pi-lens/docs/fable.md +27 -0
  263. package/node_modules/pi-lens/docs/features.md +45 -3
  264. package/node_modules/pi-lens/docs/globalconfig.md +56 -1
  265. package/node_modules/pi-lens/docs/language-coverage.md +4 -3
  266. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
  267. package/node_modules/pi-lens/docs/mcp.md +37 -4
  268. package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
  269. package/node_modules/pi-lens/docs/settings.md +18 -1
  270. package/node_modules/pi-lens/docs/tools.md +4 -1
  271. package/node_modules/pi-lens/docs/usage.md +41 -5
  272. package/node_modules/pi-lens/docs/word-index.md +35 -0
  273. package/node_modules/pi-lens/package.json +10 -10
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +8 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +7 -0
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +7 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +8 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +8 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +21 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +9 -0
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +9 -0
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +13 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +16 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +27 -0
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  300. package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  301. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
  302. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +134 -2
  303. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +10 -11
  304. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
  305. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +8 -114
  306. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +129 -0
  307. package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
  308. package/node_modules/pi-mcp-adapter/CHANGELOG.md +38 -0
  309. package/node_modules/pi-mcp-adapter/README.md +38 -4
  310. package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
  311. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
  312. package/node_modules/pi-mcp-adapter/cli.js +25 -4
  313. package/node_modules/pi-mcp-adapter/commands.ts +15 -7
  314. package/node_modules/pi-mcp-adapter/config.ts +4 -4
  315. package/node_modules/pi-mcp-adapter/direct-tools.ts +58 -8
  316. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
  317. package/node_modules/pi-mcp-adapter/index.ts +8 -3
  318. package/node_modules/pi-mcp-adapter/init.ts +29 -5
  319. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
  320. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
  321. package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
  322. package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
  323. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
  324. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
  325. package/node_modules/pi-mcp-adapter/metadata-cache.ts +37 -4
  326. package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
  327. package/node_modules/pi-mcp-adapter/package.json +4 -3
  328. package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
  329. package/node_modules/pi-mcp-adapter/request-headers-command.ts +336 -0
  330. package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
  331. package/node_modules/pi-mcp-adapter/server-manager.ts +41 -7
  332. package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
  333. package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
  334. package/node_modules/pi-mcp-adapter/tool-metadata.ts +62 -4
  335. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
  336. package/node_modules/pi-mcp-adapter/types.ts +176 -27
  337. package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
  338. package/node_modules/pi-web-access/CHANGELOG.md +14 -0
  339. package/node_modules/pi-web-access/README.md +18 -12
  340. package/node_modules/pi-web-access/auth-fetch.ts +148 -0
  341. package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
  342. package/node_modules/pi-web-access/curator-page.ts +5 -3
  343. package/node_modules/pi-web-access/curator-server.ts +2 -1
  344. package/node_modules/pi-web-access/extract.ts +106 -34
  345. package/node_modules/pi-web-access/fetch-params.ts +17 -3
  346. package/node_modules/pi-web-access/firecrawl.ts +172 -12
  347. package/node_modules/pi-web-access/gemini-search.ts +18 -4
  348. package/node_modules/pi-web-access/index.ts +120 -48
  349. package/node_modules/pi-web-access/package.json +2 -2
  350. package/node_modules/pi-web-access/summary-review.ts +11 -5
  351. package/node_modules/pi-web-access/youtube-extract.ts +2 -2
  352. package/package.json +12 -13
  353. package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
  354. package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
  355. package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
  356. package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
  357. package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
  358. package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
  359. package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
  360. package/node_modules/pi-lens/dist/clients/types.js +0 -11
@@ -13,14 +13,63 @@
13
13
  import { existsSync, realpathSync } from "node:fs";
14
14
  import * as os from "node:os";
15
15
  import * as path from "node:path";
16
- import { dirname, win32 } from "node:path";
16
+ import { win32 } from "node:path";
17
17
  import { fileURLToPath, pathToFileURL } from "node:url";
18
18
  import { minimatch } from "./deps/minimatch.js";
19
19
  /**
20
- * Detect if a path is a Windows path (has drive letter or UNC prefix).
20
+ * Detect a positively Windows-shaped path, regardless of the host OS.
21
+ *
22
+ * A backslash anywhere in a path is not enough: it is a legal character in a
23
+ * POSIX filename. Only a drive-letter prefix (`X:`), a UNC root (`\\`), or a
24
+ * rooted backslash at position zero (`\`) selects Windows parsing.
25
+ */
26
+ export function isWindowsPath(filePath) {
27
+ return /^[A-Za-z]:/.test(filePath) || filePath.startsWith("\\");
28
+ }
29
+ /**
30
+ * Canonical backslash→forward-slash fold — the single sanctioned form of the
31
+ * `p.replace(/\\/g, "/")` idiom otherwise hand-rolled across the codebase
32
+ * (~138 sites, #1193). PURE separator normalization: it does NOT resolve,
33
+ * canonicalize, lowercase, or collapse repeated slashes — reach for
34
+ * `normalizeFilePath`/`normalizeMapKey`/`normalizeEphemeralMapKey` when a
35
+ * canonical map *key* (case-fold / realpath) is what you need. Consolidating on
36
+ * this funnels the scattered transform and makes a shape-2 lint/ast-grep rule
37
+ * possible for the first time: today a bare inline `.replace(/\\/g, "/")` is
38
+ * byte-identical to the sanctioned use so it can't be ruled (#1158); once
39
+ * everything routes through `toPosix`, an *un-migrated* inline `.replace`
40
+ * becomes detectable.
41
+ */
42
+ export function toPosix(filePath) {
43
+ return filePath.replace(/\\/g, "/");
44
+ }
45
+ /** Return whether `filePath` is fully qualified under Windows semantics. */
46
+ export function isFullyQualifiedWin32(filePath) {
47
+ return win32.isAbsolute(filePath) && win32.parse(filePath).root.length > 1;
48
+ }
49
+ /** Return whether `filePath` is fully qualified under POSIX semantics. */
50
+ export function isFullyQualifiedPosix(filePath) {
51
+ return path.posix.isAbsolute(filePath) && !isWindowsPath(filePath);
52
+ }
53
+ /**
54
+ * Return whether `filePath` is fully qualified under the host's semantics.
55
+ *
56
+ * In particular, `/foo` is rooted-relative under Win32 (ambient-drive
57
+ * dependent) but fully qualified under POSIX.
58
+ */
59
+ export function isFullyQualified(filePath) {
60
+ return process.platform === "win32"
61
+ ? isFullyQualifiedWin32(filePath)
62
+ : isFullyQualifiedPosix(filePath);
63
+ }
64
+ /**
65
+ * Split a path into its non-empty segments on EITHER separator (`\` or `/`),
66
+ * regardless of the running OS — the shape-safe form of `p.split(path.sep)` /
67
+ * an inline `p.split(/[\\/]+/)`, which #1161/#1163 showed must not assume the
68
+ * host separator for a possibly-cross-shaped path. Drops empty segments
69
+ * (leading slash, drive-root, doubled separators).
21
70
  */
22
- function isWindowsPath(filePath) {
23
- return /^[A-Za-z]:/.test(filePath) || filePath.startsWith("\\\\");
71
+ export function splitPathSegments(filePath) {
72
+ return filePath.split(/[\\/]+/).filter(Boolean);
24
73
  }
25
74
  /**
26
75
  * Normalize a file path for consistent Map key usage.
@@ -87,7 +136,15 @@ function resolveNonExisting(filePath) {
87
136
  const base = canonical.replace(/\\/g, "/");
88
137
  return base.endsWith("/") ? base + tail : `${base}/${tail}`;
89
138
  }
90
- const parent = dirname(current);
139
+ // Use win32.dirname (not the platform-default dirname) so a
140
+ // Windows-shaped path is parsed with win32 semantics regardless of the
141
+ // running OS — consistent with the win32.resolve/win32.normalize this
142
+ // branch already commits to. The platform-default POSIX dirname would
143
+ // find no separator in a win32-resolved "C:\repo\..." path (its only
144
+ // separators are backslashes), collapse to ".", stop the upward walk at
145
+ // cwd, and mangle the key on Linux CI (refs #1150, the #1024
146
+ // OS-divergence class).
147
+ const parent = win32.dirname(current);
91
148
  if (parent === current) {
92
149
  // Reached filesystem root without finding existing dir
93
150
  // Fall back to full lowercase
@@ -111,6 +168,29 @@ export function uriToPath(uri) {
111
168
  return normalizeFilePath(uri);
112
169
  }
113
170
  }
171
+ /**
172
+ * Decode a file:// URI to an on-disk path WITHOUT map-key normalization.
173
+ *
174
+ * `uriToPath` runs its result through `normalizeFilePath`, which on win32
175
+ * lowercases the nonexistent tail of a path (see `resolveNonExisting`) and
176
+ * canonicalizes an existing path to its real casing. That is correct for Map
177
+ * keys, but DESTRUCTIVE for a real create/rename target: creating `NewFile.txt`
178
+ * would write `newfile.txt`, and a legitimate case-only rename would collapse
179
+ * to a no-op ("source and destination must differ"). Disk mutations must honor
180
+ * the caller's intended casing, so they resolve their target through this
181
+ * decode-only path while confinement/validation keep using the normalized
182
+ * `uriToPath`. Non-win32 is unaffected either way (normalizeFilePath is a
183
+ * near-identity there).
184
+ */
185
+ export function uriToDiskPath(uri) {
186
+ try {
187
+ return fileURLToPath(uri);
188
+ }
189
+ catch {
190
+ // Not a valid file:// URI — treat as a plain path (matches uriToPath).
191
+ return uri;
192
+ }
193
+ }
114
194
  /**
115
195
  * Convert a path to a file:// URI.
116
196
  * Does NOT normalize the path - URIs preserve original casing.
@@ -125,12 +205,28 @@ export function pathToUri(filePath) {
125
205
  export function normalizeMapKey(filePath) {
126
206
  return normalizeFilePath(filePath);
127
207
  }
128
- /** Human-facing path relative to a project root when the file is inside it. */
208
+ /**
209
+ * Human-facing path relative to a project root when the file is inside it.
210
+ *
211
+ * Parses by path SHAPE, not host OS (refs #1150/#1152, shape-2 class #1163):
212
+ * a Windows-shaped `filePath` (drive-letter/UNC — e.g. a persisted call-graph
213
+ * symbol-key path `C:\repo\src\x.ts` rehydrated on a Linux CI run) is split
214
+ * with `win32.*` regardless of `process.platform`. The host-default
215
+ * `isAbsolute`/`relative` find no drive-letter anchor in a win32 path on POSIX:
216
+ * `path.isAbsolute("C:\\repo\\x.ts")` returns FALSE on Linux, short-circuiting
217
+ * to the raw absolute path instead of ever relativizing it — so a file that IS
218
+ * under the project root renders as a full absolute path on Linux but the
219
+ * expected `src/x.ts` on Windows (green-locally / wrong-on-CI, the #1024
220
+ * divergence class). `win32.*` on a native POSIX path (Windows never sees one;
221
+ * Linux native paths aren't Windows-shaped) is never selected, so same-OS
222
+ * native paths are unchanged either way.
223
+ */
129
224
  export function toProjectRelativePath(filePath, projectRoot) {
130
- if (!path.isAbsolute(filePath))
225
+ const p = isWindowsPath(filePath) ? win32 : path;
226
+ if (!p.isAbsolute(filePath))
131
227
  return filePath.replace(/\\/g, "/");
132
- const relative = path.relative(path.resolve(projectRoot), filePath);
133
- return relative && !relative.startsWith("..") && !path.isAbsolute(relative)
228
+ const relative = p.relative(p.resolve(projectRoot), filePath);
229
+ return relative && !relative.startsWith("..") && !p.isAbsolute(relative)
134
230
  ? relative.replace(/\\/g, "/")
135
231
  : filePath.replace(/\\/g, "/");
136
232
  }
@@ -156,6 +252,11 @@ export function toProjectRelativePath(filePath, projectRoot) {
156
252
  * real-casing resolution actually matters.
157
253
  */
158
254
  export function normalizeEphemeralMapKey(filePath) {
255
+ // Most hot-path keys on POSIX are already canonical slash-separated strings.
256
+ // Preserve that identity instead of allocating a replacement string for each
257
+ // file in a large diagnostics reconciliation.
258
+ if (process.platform !== "win32" && !filePath.includes("\\"))
259
+ return filePath;
159
260
  const slashed = filePath.replace(/\\/g, "/");
160
261
  return process.platform === "win32" ? slashed.toLowerCase() : slashed;
161
262
  }
@@ -19,7 +19,7 @@ import { recordFromCodeQualityDiagnostic, } from "./code-quality-warnings.js";
19
19
  import { recordDiagnostics } from "./widget-state.js";
20
20
  import { getDiagnosticLogger } from "./diagnostic-logger.js";
21
21
  import { getDiagnosticTracker } from "./diagnostic-tracker.js";
22
- import { computeCascadeForFile, dispatchLintWithResult, } from "./dispatch/integration.js";
22
+ import { loadDispatchIntegration } from "./dispatch/lazy.js";
23
23
  import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
24
24
  import { createAvailabilityChecker, resolveAvailableOrInstall, resolveCommandArgsWithInstallFallback, resolveToolCommand, resolveToolCommandWithInstallFallback, } from "./dispatch/runners/utils/runner-helpers.js";
25
25
  import { findDetektConfig } from "./dispatch/runners/detekt.js";
@@ -29,12 +29,14 @@ import { logLatency } from "./latency-logger.js";
29
29
  import { emitLensAnalysisComplete } from "./lens-events.js";
30
30
  import { publishFilesTouched } from "./bus-publish.js";
31
31
  import { publishDiagnostics, wasPreviouslyReportedDirty, } from "./diagnostics-publish.js";
32
- import { getLSPService } from "./lsp/index.js";
32
+ import { loadLspService } from "./lsp-lazy.js";
33
33
  import { clearGraphCache } from "./review-graph/builder.js";
34
+ import { BoundedLruCache } from "./bounded-cache.js";
34
35
  import { RUNTIME_CONFIG } from "./runtime-config.js";
35
36
  import { getAmbientAbortSignal, safeSpawnAsync } from "./safe-spawn.js";
36
37
  import { combineAbortSignals } from "./deadline-utils.js";
37
- import { getAutofixPolicyForFile, getPreferredAutofixTools, getRubocopCommand, hasBiomeConfig, hasDetektConfig, hasEslintConfig, hasGolangciConfig, hasKtfmtConfig, hasOxlintConfig, hasRubocopConfig, hasSqlfluffConfig, hasStylelintConfig, } from "./tool-policy.js";
38
+ import { recordDegradationOnce } from "./degradation-ledger.js";
39
+ import { getAutofixPolicyForFile, getPreferredAutofixTools, getRubocopCommand, hasBiomeConfig, hasDetektConfig, hasEslintConfig, hasGolangciConfig, hasKtfmtConfig, hasKtlintConfig, hasOxlintConfig, hasRubocopConfig, hasSqlfluffConfig, hasStylelintConfig, markdownlintConfigArgs, } from "./tool-policy.js";
38
40
  const LSP_MAX_FILE_BYTES = RUNTIME_CONFIG.pipeline.lspMaxFileBytes;
39
41
  const LSP_MAX_FILE_LINES = RUNTIME_CONFIG.pipeline.lspMaxFileLines;
40
42
  const LSP_SPAWN_BUDGET_MS = RUNTIME_CONFIG.pipeline.lspSpawnBudgetMs;
@@ -66,7 +68,10 @@ const SNAPSHOT_YIELD_EVERY = 500;
66
68
  async function snapshotDirInto(dir, ignoreMatcher, stack, snapshot, counter) {
67
69
  let entries;
68
70
  try {
69
- entries = nodeFs.readdirSync(dir, { withFileTypes: true });
71
+ // Async (#1137): a synchronous per-directory read blocks the loop for the
72
+ // whole stall on a slow cloud-backed (OneDrive) directory; fs.promises
73
+ // keeps this tool_result-path walk off the event loop.
74
+ entries = await nodeFs.promises.readdir(dir, { withFileTypes: true });
70
75
  }
71
76
  catch {
72
77
  return;
@@ -85,7 +90,7 @@ async function snapshotDirInto(dir, ignoreMatcher, stack, snapshot, counter) {
85
90
  if (ignoreMatcher.isIgnored(fullPath, false))
86
91
  continue;
87
92
  try {
88
- const stat = nodeFs.statSync(fullPath);
93
+ const stat = await nodeFs.promises.stat(fullPath);
89
94
  snapshot.set(path.resolve(fullPath), {
90
95
  mtimeMs: stat.mtimeMs,
91
96
  size: stat.size,
@@ -173,7 +178,7 @@ function createPhaseTracker(toolName, filePath) {
173
178
  }
174
179
  // --- ESLint autofix helpers ---
175
180
  export { hasEslintConfig, hasRubocopConfig, hasSqlfluffConfig, hasStylelintConfig, };
176
- const _eslintCache = new Map();
181
+ const _eslintCache = new BoundedLruCache(32);
177
182
  /**
178
183
  * Run eslint --fix on a file. Runs a single spawn and diffs the file before/after,
179
184
  * same idiom as the other autofix helpers below. Exit code 1 (unfixable problems
@@ -186,7 +191,9 @@ async function tryEslintFix(filePath, cwd) {
186
191
  const userHasConfig = hasEslintConfig(cwd);
187
192
  if (!userHasConfig)
188
193
  return 0;
189
- const cacheKey = path.resolve(cwd);
194
+ // PATH is part of command resolution; include it so an install or PATH
195
+ // refresh cannot leave a negative eslint result live for the session.
196
+ const cacheKey = `${path.resolve(cwd)}|${process.env.PATH ?? ""}`;
190
197
  let cached = _eslintCache.get(cacheKey);
191
198
  if (!cached) {
192
199
  const candidate = resolveToolCommand(cwd, "eslint") ?? "eslint";
@@ -274,8 +281,12 @@ async function tryMarkdownlintFix(filePath, cwd) {
274
281
  const cmd = await resolveToolCommandWithInstallFallback(cwd, "markdownlint");
275
282
  if (!cmd)
276
283
  return 0;
284
+ // Shared config-args seam (#1247): the lint runner consumes the same
285
+ // builder, so the bare --fix here can never fall back to markdownlint's
286
+ // default all-rules-on config again (the whole-file CHANGELOG/AGENTS
287
+ // reformat + `#946` → `# 946` corruption).
277
288
  // markdownlint-cli2 --fix exits non-zero when unfixable violations remain.
278
- return detectFileChangedAfterCommand(filePath, cmd, ["--fix", filePath], cwd, [1]);
289
+ return detectFileChangedAfterCommand(filePath, cmd, [...markdownlintConfigArgs(cwd), "--fix", filePath], cwd, [1]);
279
290
  }
280
291
  async function tryOxlintFix(filePath, cwd) {
281
292
  const cmd = await resolveToolCommandWithInstallFallback(cwd, "oxlint");
@@ -356,6 +367,7 @@ export async function runAutofix(filePath, cwd, getFlag, dbg, deps, getFlagSourc
356
367
  hasGolangciConfig: hasGolangciConfig(cwd),
357
368
  hasDetektConfig: hasDetektConfig(cwd),
358
369
  hasKtfmtConfig: hasKtfmtConfig(cwd),
370
+ hasKtlintConfig: hasKtlintConfig(cwd),
359
371
  hasOxlintConfig: hasOxlintConfig(cwd),
360
372
  };
361
373
  const autofixPolicy = getAutofixPolicyForFile(filePath, autofixContext);
@@ -396,7 +408,7 @@ export async function runAutofix(filePath, cwd, getFlag, dbg, deps, getFlagSourc
396
408
  dbg(`autofix: ruff unavailable for ${filePath}`);
397
409
  continue;
398
410
  }
399
- const result = await ruffClient.fixFileAsync(filePath);
411
+ const result = await ruffClient.fixFileAsync(filePath, cwd);
400
412
  if (result.success && result.fixed > 0) {
401
413
  fixedCount += result.fixed;
402
414
  autofixTools.push(`ruff:${result.fixed}`);
@@ -415,7 +427,7 @@ export async function runAutofix(filePath, cwd, getFlag, dbg, deps, getFlagSourc
415
427
  dbg(`autofix: biome unavailable or unsupported for ${filePath}`);
416
428
  continue;
417
429
  }
418
- const result = await biomeClient.fixFileAsync(filePath);
430
+ const result = await biomeClient.fixFileAsync(filePath, cwd);
419
431
  if (result.success && result.fixed > 0) {
420
432
  fixedCount += result.fixed;
421
433
  autofixTools.push(`biome:${result.fixed}`);
@@ -570,7 +582,6 @@ export async function runAutofix(filePath, cwd, getFlag, dbg, deps, getFlagSourc
570
582
  dbg(`autofix: oxlint --fix fixed ${filePath}`);
571
583
  needsContentRefresh = true;
572
584
  }
573
- continue;
574
585
  }
575
586
  }
576
587
  if (attemptedTools.length > 0 && autofixTools.length === 0) {
@@ -593,7 +604,7 @@ export async function resyncLspFile(filePath, fileContent, needsContentRefresh,
593
604
  if (limitCheck.tooLarge)
594
605
  return;
595
606
  try {
596
- const lspService = getLSPService();
607
+ const lspService = (await loadLspService()).getLSPService();
597
608
  if (lspService.supportsLSP(filePath)) {
598
609
  // Push the final post-format/post-fix content through touchFile (not the
599
610
  // bare openFile) so it registers in the touch-debounce map via
@@ -704,6 +715,13 @@ export async function runFormatPhase(filePath, getFormatService, dbg) {
704
715
  }
705
716
  if (!result.allSucceeded) {
706
717
  const failures = result.formatters.filter((f) => !f.success);
718
+ for (const failure of failures) {
719
+ recordDegradationOnce({
720
+ kind: "formatter-failure",
721
+ subject: `${failure.name}:${path.basename(filePath)}`,
722
+ reason: failure.error ?? "unknown error",
723
+ });
724
+ }
707
725
  formatFailures.push(...failures.map((f) => `${f.name}: ${f.error ?? "unknown error"}`));
708
726
  dbg("autoformat: " +
709
727
  failures
@@ -713,6 +731,11 @@ export async function runFormatPhase(filePath, getFormatService, dbg) {
713
731
  }
714
732
  catch (err) {
715
733
  const message = err instanceof Error ? err.message : String(err);
734
+ recordDegradationOnce({
735
+ kind: "formatter-failure",
736
+ subject: `format-service:${path.basename(filePath)}`,
737
+ reason: message,
738
+ });
716
739
  formatFailures.push(message);
717
740
  dbg(`autoformat error: ${err}`);
718
741
  }
@@ -820,7 +843,25 @@ export async function runPipeline(ctx, deps) {
820
843
  });
821
844
  // --- 3. Auto-fix ---
822
845
  phase.start("autofix");
823
- const { fixedCount, autofixTools, attemptedTools, changedFiles: autofixChangedFiles, needsContentRefresh: fixRefresh, skipReason: autofixSkipReason, } = await runAutofix(filePath, cwd, getFlag, dbg, deps, getFlagSource);
846
+ let fixedCount = 0;
847
+ let autofixTools = [];
848
+ let attemptedTools = [];
849
+ let autofixChangedFiles = [];
850
+ let fixRefresh = false;
851
+ let autofixSkipReason;
852
+ if (ctx.autofixMode === "deferred") {
853
+ autofixSkipReason = "deferred_to_agent_end";
854
+ dbg(`autofix: deferred until agent_end for ${filePath}`);
855
+ }
856
+ else
857
+ ({
858
+ fixedCount,
859
+ autofixTools,
860
+ attemptedTools,
861
+ changedFiles: autofixChangedFiles,
862
+ needsContentRefresh: fixRefresh,
863
+ skipReason: autofixSkipReason,
864
+ } = await runAutofix(filePath, cwd, getFlag, dbg, deps, getFlagSource));
824
865
  for (const changedFile of autofixChangedFiles) {
825
866
  piChangedFiles.add(path.resolve(changedFile));
826
867
  }
@@ -871,11 +912,17 @@ export async function runPipeline(ctx, deps) {
871
912
  const piApi = {
872
913
  getFlag: getFlag,
873
914
  };
915
+ const { dispatchLintWithResult, computeCascadeForFile } = await loadDispatchIntegration();
874
916
  const dispatchResult = await dispatchLintWithResult(filePath, cwd, piApi, ctx.modifiedRanges, {
875
917
  model: ctx.telemetry?.model ?? "unknown",
876
918
  sessionId: ctx.telemetry?.sessionId ?? "unknown",
877
919
  turnIndex: ctx.telemetry?.turnIndex ?? 0,
878
920
  writeIndex: ctx.telemetry?.writeIndex ?? 0,
921
+ }, {
922
+ projectRoot: ctx.projectRoot,
923
+ writeIndex: ctx.telemetry?.writeIndex,
924
+ telemetryModel: ctx.telemetry?.modelId,
925
+ telemetryProvider: ctx.telemetry?.provider,
879
926
  });
880
927
  recordDiagnostics(filePath, dispatchResult.diagnostics, ctx.telemetry?.writeIndex);
881
928
  // #502: emit the write batch's FINAL diagnostic state immediately after
@@ -972,7 +1019,11 @@ export async function runPipeline(ctx, deps) {
972
1019
  const fileList = changedList.length
973
1020
  ? "\nModified files:\n" + topFiles + overflow
974
1021
  : "";
975
- output += `\n\n⚠️ **File was modified by auto-format/fix. You MUST re-read modified file(s) before making any further edits — the content on disk has changed (whitespace, indentation, quotes, or code). Editing from memory will produce mismatches.**${fileList}`;
1022
+ const targetHasAuthoritativeAttachment = ctx.autofixMode !== "deferred" &&
1023
+ autofixChangedFiles.some((changedFile) => path.resolve(changedFile) === path.resolve(filePath));
1024
+ output += targetHasAuthoritativeAttachment
1025
+ ? `\n\n⚠️ **The attached full content for ${toRunnerDisplayPath(cwd, filePath)} is authoritative after autofix. You MUST re-read any other modified side-effect files before editing them.**${fileList}`
1026
+ : `\n\n⚠️ **File was modified by auto-format/fix. You MUST re-read modified file(s) before making any further edits — the content on disk has changed (whitespace, indentation, quotes, or code). Editing from memory will produce mismatches.**${fileList}`;
976
1027
  }
977
1028
  phase.end("dispatch_lint", {
978
1029
  hasOutput: !!dispatchResult.output,
@@ -984,6 +1035,11 @@ export async function runPipeline(ctx, deps) {
984
1035
  // result is never shown inline — settled (bounded) and surfaced at turn_end.
985
1036
  // The stored promise must never reject: an unhandled rejection is fatal, so a
986
1037
  // failing compute resolves to an "error" skip-run instead.
1038
+ const cascadeOrigin = {
1039
+ turnSeq: ctx.telemetry?.turnIndex,
1040
+ writeSeq: ctx.telemetry?.writeIndex,
1041
+ projectSeq: ctx.seqState?.projectSeq(),
1042
+ };
987
1043
  const cascadePromise = getFlag("no-lsp")
988
1044
  ? undefined
989
1045
  : computeCascadeForFile(filePath, cwd, {
@@ -995,10 +1051,12 @@ export async function runPipeline(ctx, deps) {
995
1051
  fileContent,
996
1052
  wordIndex: ctx.wordIndex,
997
1053
  onWordIndexUpdated: ctx.onWordIndexUpdated,
998
- }).catch((err) => {
1054
+ }).then((run) => ({ ...run, origin: cascadeOrigin }))
1055
+ .catch((err) => {
999
1056
  dbg(`cascade compute failed for ${filePath}: ${err}`);
1000
1057
  return {
1001
1058
  filePath,
1059
+ origin: cascadeOrigin,
1002
1060
  result: undefined,
1003
1061
  neighborCount: 0,
1004
1062
  diagnosticCount: 0,
@@ -1054,5 +1112,8 @@ export async function runPipeline(ctx, deps) {
1054
1112
  formattersUsed,
1055
1113
  fixedCount,
1056
1114
  autofixTools,
1115
+ postMutation: ctx.autofixMode !== "deferred" && autofixChangedFiles.some((f) => path.resolve(f) === path.resolve(filePath)) && fileContent !== undefined
1116
+ ? { filePath: path.resolve(filePath), content: fileContent, source: "autofix" }
1117
+ : undefined,
1057
1118
  };
1058
1119
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * One-shot invocation detection, shared across the session_start-scheduled
3
+ * background work that must NOT run — or retain the process — in print mode
4
+ * (#1154 quick-mode warmup, #1153 orphan-reaper).
5
+ *
6
+ * `pi -p` / `pi --print` is a one-shot: the host computes a single turn and
7
+ * exits. Work scheduled to warm caches / clean up state for a *future* session
8
+ * in the same process is pure waste in this mode AND — when it arms a
9
+ * referenced libuv handle (a non-`unref`'d timer, an LSP child, an fs walk)
10
+ * with no session_shutdown teardown — keeps the already-settled one-shot
11
+ * process alive. That is the referenced-handle retention class of
12
+ * #1097/#1110/#1148/#1149 and the located #1122 hypothesis-A concern.
13
+ *
14
+ * argv is read live (not captured at module load) so tests can drive it; the
15
+ * flag never changes within a real process, and there is no hot-path caller,
16
+ * so recomputing is free. This mirrors the print detection in
17
+ * `runtime-session.ts`'s `resolveStartupMode` — keep them a single source.
18
+ */
19
+ export function isPrintMode(argv = process.argv) {
20
+ return argv.includes("--print") || argv.includes("-p");
21
+ }
@@ -44,6 +44,31 @@ export function readProjectChanges(cwd) {
44
44
  return [];
45
45
  }
46
46
  }
47
+ /**
48
+ * Async twin of `readProjectChanges` (#1162). `fs.promises.readFile` — unlike
49
+ * the sync `readFileSync` above — YIELDS to the event loop, which is what lets
50
+ * a caller bound it with a `Promise.race` timeout: a `setTimeout` can never
51
+ * preempt a synchronous blocking read (the thread doesn't return to the event
52
+ * loop until the OS does), so a bounded read on the session_start hot path
53
+ * requires the async form. Same parse/error-swallow semantics as the sync
54
+ * version — a missing/corrupt log is the normal cold-start case, not a
55
+ * caller-visible error.
56
+ */
57
+ export async function readProjectChangesAsync(cwd) {
58
+ const logPath = getProjectChangeLogPath(cwd);
59
+ try {
60
+ const content = await fs.promises.readFile(logPath, "utf-8");
61
+ return content
62
+ .split(/\r?\n/)
63
+ .map((line) => line.trim())
64
+ .filter(Boolean)
65
+ .map(parseChangeLine)
66
+ .filter((entry) => Boolean(entry));
67
+ }
68
+ catch {
69
+ return [];
70
+ }
71
+ }
47
72
  export function readChangesSince(cwd, seq, maxEntries = 200) {
48
73
  const limit = Math.max(1, maxEntries);
49
74
  return readProjectChanges(cwd)
@@ -134,6 +159,21 @@ export function readLatestProjectSequence(cwd, base) {
134
159
  }
135
160
  return fullReplay(entries);
136
161
  }
162
+ /**
163
+ * Async twin of `readLatestProjectSequence` (#1162) — same fold logic, but
164
+ * reads the log via `readProjectChangesAsync` so a caller can bound the wait
165
+ * with a `Promise.race` timeout (see that function's doc comment for why the
166
+ * sync form can't be bounded at all).
167
+ */
168
+ export async function readLatestProjectSequenceAsync(cwd, base) {
169
+ const entries = await readProjectChangesAsync(cwd);
170
+ if (base) {
171
+ const partial = partialReplay(entries, base);
172
+ if (partial)
173
+ return partial;
174
+ }
175
+ return fullReplay(entries);
176
+ }
137
177
  export function appendProjectChange(cwd, entry) {
138
178
  const logPath = getProjectChangeLogPath(cwd);
139
179
  fs.mkdirSync(path.dirname(logPath), { recursive: true });
@@ -1,6 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { getProjectDataDir } from "../file-utils.js";
4
+ import { writeFileAtomic } from "../atomic-write.js";
4
5
  import { readJsonCache } from "../json-cache-read.js";
5
6
  // v2: cheap-tier scan now also runs ast-grep-napi (#308); invalidate older
6
7
  // snapshots so a pre-ast-grep cache isn't served as complete via refreshRunners=cached.
@@ -25,7 +26,7 @@ export function loadProjectDiagnosticsSnapshot(cwd) {
25
26
  export function saveProjectDiagnosticsSnapshot(cwd, snapshot) {
26
27
  const filePath = cachePath(cwd, SNAPSHOT_CACHE_FILE);
27
28
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
28
- fs.writeFileSync(filePath, JSON.stringify(snapshot, null, 2));
29
+ writeFileAtomic(filePath, JSON.stringify(snapshot, null, 2));
29
30
  }
30
31
  export function loadProjectDiagnosticsDeltaReport(cwd) {
31
32
  return readJsonCache(cachePath(cwd, DELTA_CACHE_FILE), (parsed) => {
@@ -42,7 +43,7 @@ export function loadProjectDiagnosticsDeltaReport(cwd) {
42
43
  export function writeProjectDiagnosticsDeltaReport(cwd, report) {
43
44
  const filePath = cachePath(cwd, DELTA_CACHE_FILE);
44
45
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
45
- fs.writeFileSync(filePath, JSON.stringify(report, null, 2));
46
+ writeFileAtomic(filePath, JSON.stringify(report, null, 2));
46
47
  }
47
48
  /**
48
49
  * Drop diagnostics whose underlying file changed on disk after the snapshot was
@@ -383,7 +383,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
383
383
  cold.push("test-runner");
384
384
  return;
385
385
  }
386
- record("test-runner", testRunnerFindingsToProjectDiagnostics(cached.data), Date.now() - startMs);
386
+ record("test-runner", testRunnerFindingsToProjectDiagnostics(cached.data, analysisRoot, options.runtime), Date.now() - startMs);
387
387
  }),
388
388
  ];
389
389
  // Swallow any later rejection so an aborted-and-abandoned task can never
@@ -1,5 +1,6 @@
1
1
  import * as path from "node:path";
2
2
  import { parseSymbolKey, } from "../../call-graph.js";
3
+ import { isTestRoleCollateral } from "../../collateral-test-role.js";
3
4
  function resolveImpactFile(cwd, filePath) {
4
5
  return path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
5
6
  }
@@ -87,6 +88,13 @@ export function callGraphImpactToProjectDiagnostics(cwd, findings) {
87
88
  if (!filePath)
88
89
  continue; // Nothing real to attribute this to.
89
90
  const resolvedFile = resolveImpactFile(cwd, filePath);
91
+ // #1080: the call graph is derived from the tests-free review graph and is
92
+ // normally tests-free, but this adapter accepts arbitrary caller
93
+ // symbol keys — an old/fixture/expanded graph could supply a test-file
94
+ // caller. A KNOWN test-role caller must not be persisted as collateral
95
+ // call-graph impact. Fail-open: a classifier error retains the finding.
96
+ if (isTestRoleCollateral(resolvedFile))
97
+ continue;
90
98
  const dedupeKey = `${resolvedFile}:${result.symbolKey}`;
91
99
  const existing = byKey.get(dedupeKey);
92
100
  if (existing && SEVERITY_RANK[existing.severity] >= SEVERITY_RANK[mapped.severity]) {
@@ -1,3 +1,6 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { advisoryPathKey, validateAdvisoryProvenance, } from "../../advisory-provenance.js";
1
4
  function failureMessage(failure) {
2
5
  const firstLine = failure.message.split("\n")[0]?.slice(0, 300) ?? "";
3
6
  return firstLine ? `${failure.name}: ${firstLine}` : failure.name;
@@ -71,8 +74,26 @@ export function testResultToProjectDiagnostics(result, stale = false) {
71
74
  },
72
75
  ];
73
76
  }
74
- export function testRunnerFindingsToProjectDiagnostics(cache) {
77
+ export function testRunnerFindingsToProjectDiagnostics(cache, cwd, runtime) {
75
78
  if (!cache.results || cache.results.length === 0)
76
79
  return [];
77
- return cache.results.flatMap((r) => testResultToProjectDiagnostics(r, cache.stale));
80
+ const validation = cwd
81
+ ? validateAdvisoryProvenance(cache, cwd, runtime)
82
+ : { status: "unknown", reasons: ["validation-context-missing"] };
83
+ const root = cwd ?? process.cwd();
84
+ const missingKeys = new Set((cache.provenance?.files ?? [])
85
+ .filter((file) => !fs.existsSync(path.resolve(root, file.path)))
86
+ .map((file) => advisoryPathKey(file.path, root)));
87
+ const historical = cache.superseded === true || cache.stale === true || validation.status !== "current";
88
+ return cache.results
89
+ .filter((result) => !missingKeys.has(advisoryPathKey(result.file, root)))
90
+ .flatMap((result) => testResultToProjectDiagnostics(result, historical))
91
+ .map((diagnostic) => historical ? {
92
+ ...diagnostic,
93
+ severity: "info",
94
+ semantic: "none",
95
+ message: diagnostic.message.startsWith("[stale")
96
+ ? diagnostic.message
97
+ : `[historical — re-run to confirm] ${diagnostic.message}`,
98
+ } : diagnostic);
78
99
  }
@@ -10,7 +10,7 @@ import { isAtOrAboveHomeDir } from "../path-utils.js";
10
10
  import { getProjectDiagnosticsScannerMaxFiles } from "../project-scale.js";
11
11
  import { captureReviewGraphStructuralIr } from "../review-graph/builder.js";
12
12
  import { publishReviewGraphFileIr, reviewGraphIrContentHash, } from "../review-graph/shared-extraction-ir.js";
13
- import { collectSourceFilesWithBudgetAsync } from "../source-filter.js";
13
+ import { collectSourceFilesWithBudgetAsync, } from "../source-filter.js";
14
14
  import { logTreeSitter, logTreeSitterCacheStats, } from "../tree-sitter-logger.js";
15
15
  import { queriesForLanguage, queryLoader, } from "../tree-sitter-query-loader.js";
16
16
  import { EXT_TO_LANG, getSharedTreeSitterClient, isTreeSitterWasmAborted, } from "../tree-sitter-shared.js";
@@ -322,11 +322,19 @@ export async function scanProjectDiagnostics(options) {
322
322
  // trips `maxFiles`. Unlike `unsafeRoot` this is not a refusal: when the
323
323
  // budget trips we scan the truncated best-effort list and flag the snapshot
324
324
  // so callers don't read the partial result as a complete sweep.
325
+ // #1107 phase 2: typed as the full `SourceCollectionResult` (not just its
326
+ // `{files, entryBudgetExceeded}` shape) so the generated-skip counters
327
+ // below are readable through the ternary — the `options.files` branch
328
+ // legitimately never walked, so it leaves them `undefined`, same
329
+ // convention as `entryBudgetExceeded: false` above it.
325
330
  const collected = options.files
326
331
  ? { files: options.files.slice(0, maxFiles), entryBudgetExceeded: false }
327
332
  : await collectSourceFilesWithBudgetAsync(cwd, {
328
333
  maxFiles,
329
334
  maxScanEntries: options.maxScanEntries,
335
+ // #1107 phase 2 review: the actionable opt-out generatedSkipNotice
336
+ // points a user at.
337
+ includeGenerated: options.includeGenerated === true,
330
338
  });
331
339
  const files = collected.files;
332
340
  // Check cancellation before and during the file-major pass so a full-mode
@@ -361,6 +369,18 @@ export async function scanProjectDiagnostics(options) {
361
369
  // byte-identical for the untruncated (normal) case.
362
370
  if (collected.entryBudgetExceeded)
363
371
  snapshot.scanTruncated = true;
372
+ // #1107 phase 2: only present when nonzero, same convention as the
373
+ // truncation flag above — a healthy scan with no generated-name skips
374
+ // produces a byte-identical snapshot to before this change.
375
+ if (collected.generatedOrArtifactSkips) {
376
+ snapshot.generatedFileSkips = collected.generatedOrArtifactSkips;
377
+ }
378
+ if (collected.generatedNameOnlySkips) {
379
+ snapshot.generatedNameOnlySkips = collected.generatedNameOnlySkips;
380
+ }
381
+ if (collected.generatedDirSkips) {
382
+ snapshot.generatedDirSkips = collected.generatedDirSkips;
383
+ }
364
384
  if (wasmAborted) {
365
385
  snapshot.scanTruncated = true;
366
386
  snapshot.treeSitterStatus = "wasm_aborted_restart_required";