pi-lens 4.1.2 → 4.1.4

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 (359) hide show
  1. package/CHANGELOG.md +1308 -0
  2. package/README.md +3 -0
  3. package/config/biome/core.jsonc +11 -2
  4. package/config/dependency-cruiser-eager-allowlist.json +91 -0
  5. package/dist/clients/actionable-warnings-logger.js +18 -3
  6. package/dist/clients/actionable-warnings.js +927 -80
  7. package/dist/clients/agent-behavior-client.js +13 -4
  8. package/dist/clients/ast-grep-client.js +136 -9
  9. package/dist/clients/ast-grep-rule-manager.js +60 -5
  10. package/dist/clients/ast-grep-tool-logger.js +2 -2
  11. package/dist/clients/availability-probe-flight.js +12 -0
  12. package/dist/clients/bash-file-access.js +1 -3
  13. package/dist/clients/biome-client.js +16 -2
  14. package/dist/clients/blocker-freshness.js +34 -0
  15. package/dist/clients/bootstrap.js +509 -73
  16. package/dist/clients/bounded-cache.js +152 -12
  17. package/dist/clients/bounded-telemetry.js +115 -23
  18. package/dist/clients/build-identity.js +95 -0
  19. package/dist/clients/bundled-resource-health.js +113 -0
  20. package/dist/clients/bus-events-logger.js +2 -2
  21. package/dist/clients/bus-publish.js +3 -0
  22. package/dist/clients/cache/rule-cache.js +33 -5
  23. package/dist/clients/cache-manager.js +105 -6
  24. package/dist/clients/cache-observability.js +46 -19
  25. package/dist/clients/cargo-manifest.js +422 -0
  26. package/dist/clients/cascade-logger.js +17 -3
  27. package/dist/clients/code-quality-warnings.js +13 -3
  28. package/dist/clients/complexity-client.js +16 -5
  29. package/dist/clients/config-core/deny.js +221 -0
  30. package/dist/clients/config-core/index.js +47 -0
  31. package/dist/clients/config-core/merge.js +357 -0
  32. package/dist/clients/config-core/normalize.js +340 -0
  33. package/dist/clients/config-core/process-spec.js +248 -0
  34. package/dist/clients/config-core/provenance.js +171 -0
  35. package/dist/clients/config-core/records.js +218 -0
  36. package/dist/clients/config-core/resolve.js +125 -0
  37. package/dist/clients/config-core/safe-object.js +78 -0
  38. package/dist/clients/config-core/schema.js +167 -0
  39. package/dist/clients/config-diagnostic-codes.js +281 -0
  40. package/dist/clients/config-locations.js +160 -0
  41. package/dist/clients/config-resolve.js +789 -0
  42. package/dist/clients/config-schema.js +197 -0
  43. package/dist/clients/config-warn.js +407 -0
  44. package/dist/clients/dead-code-client.js +33 -25
  45. package/dist/clients/dead-code-logger.js +2 -2
  46. package/dist/clients/deadline-utils.js +178 -0
  47. package/dist/clients/debug-handles.js +2 -2
  48. package/dist/clients/debug-heap.js +3 -3
  49. package/dist/clients/deferred-lsp-work.js +106 -0
  50. package/dist/clients/degradation-ledger.js +166 -8
  51. package/dist/clients/demoted-finding-render.js +14 -0
  52. package/dist/clients/dependency-checker.js +28 -22
  53. package/dist/clients/deps/minimatch.js +1 -1
  54. package/dist/clients/diagnostic-line-freshness.js +12 -9
  55. package/dist/clients/diagnostic-logger.js +14 -4
  56. package/dist/clients/dispatch/collect-later-tier.js +39 -0
  57. package/dist/clients/dispatch/dispatcher.js +304 -19
  58. package/dist/clients/dispatch/fact-store.js +328 -9
  59. package/dist/clients/dispatch/integration.js +214 -107
  60. package/dist/clients/dispatch/pending-runner-findings.js +87 -0
  61. package/dist/clients/dispatch/plan.js +21 -5
  62. package/dist/clients/dispatch/runners/actionlint.js +0 -1
  63. package/dist/clients/dispatch/runners/ast-grep-napi.js +550 -77
  64. package/dist/clients/dispatch/runners/biome-check.js +0 -1
  65. package/dist/clients/dispatch/runners/cpp-check.js +0 -1
  66. package/dist/clients/dispatch/runners/credo.js +0 -1
  67. package/dist/clients/dispatch/runners/cue-vet.js +0 -1
  68. package/dist/clients/dispatch/runners/dart-analyze.js +0 -1
  69. package/dist/clients/dispatch/runners/detekt.js +0 -1
  70. package/dist/clients/dispatch/runners/dotnet-build.js +0 -1
  71. package/dist/clients/dispatch/runners/elixir-check.js +0 -1
  72. package/dist/clients/dispatch/runners/eslint.js +5 -2
  73. package/dist/clients/dispatch/runners/fact-rules.js +0 -1
  74. package/dist/clients/dispatch/runners/fish-indent.js +0 -1
  75. package/dist/clients/dispatch/runners/gleam-check.js +0 -1
  76. package/dist/clients/dispatch/runners/go-vet.js +1 -3
  77. package/dist/clients/dispatch/runners/golangci-lint.js +0 -1
  78. package/dist/clients/dispatch/runners/hadolint.js +0 -1
  79. package/dist/clients/dispatch/runners/helm-lint.js +16 -4
  80. package/dist/clients/dispatch/runners/helm-render.js +54 -10
  81. package/dist/clients/dispatch/runners/htmlhint.js +0 -1
  82. package/dist/clients/dispatch/runners/index.js +1 -3
  83. package/dist/clients/dispatch/runners/javac.js +0 -1
  84. package/dist/clients/dispatch/runners/ktlint.js +0 -1
  85. package/dist/clients/dispatch/runners/lsp.js +37 -2
  86. package/dist/clients/dispatch/runners/markdownlint.js +4 -2
  87. package/dist/clients/dispatch/runners/mypy.js +0 -1
  88. package/dist/clients/dispatch/runners/oxlint.js +24 -22
  89. package/dist/clients/dispatch/runners/php-lint.js +0 -1
  90. package/dist/clients/dispatch/runners/phpstan.js +0 -1
  91. package/dist/clients/dispatch/runners/prisma-validate.js +0 -1
  92. package/dist/clients/dispatch/runners/psscriptanalyzer.js +4 -1
  93. package/dist/clients/dispatch/runners/pyright.js +7 -4
  94. package/dist/clients/dispatch/runners/rubocop.js +0 -1
  95. package/dist/clients/dispatch/runners/ruff.js +0 -1
  96. package/dist/clients/dispatch/runners/rust-clippy.js +9 -6
  97. package/dist/clients/dispatch/runners/shellcheck.js +0 -1
  98. package/dist/clients/dispatch/runners/shfmt.js +0 -1
  99. package/dist/clients/dispatch/runners/spellcheck.js +0 -1
  100. package/dist/clients/dispatch/runners/spotbugs.js +0 -1
  101. package/dist/clients/dispatch/runners/sqlfluff.js +0 -1
  102. package/dist/clients/dispatch/runners/stylelint.js +0 -1
  103. package/dist/clients/dispatch/runners/swiftlint.js +0 -1
  104. package/dist/clients/dispatch/runners/taplo.js +0 -1
  105. package/dist/clients/dispatch/runners/terragrunt.js +0 -1
  106. package/dist/clients/dispatch/runners/tflint.js +0 -1
  107. package/dist/clients/dispatch/runners/tree-sitter.js +0 -1
  108. package/dist/clients/dispatch/runners/trivy-config.js +0 -1
  109. package/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
  110. package/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -4
  111. package/dist/clients/dispatch/runners/utils/runner-helpers.js +270 -105
  112. package/dist/clients/dispatch/runners/utils/spawn-outcome.js +4 -0
  113. package/dist/clients/dispatch/runners/utils/tool-failure.js +19 -7
  114. package/dist/clients/dispatch/runners/utils/toolchain-availability.js +89 -26
  115. package/dist/clients/dispatch/runners/vale.js +0 -1
  116. package/dist/clients/dispatch/runners/yaml-rule-parser.js +51 -17
  117. package/dist/clients/dispatch/runners/yamllint.js +0 -1
  118. package/dist/clients/dispatch/runners/zig-check.js +0 -1
  119. package/dist/clients/disposition-logger.js +3 -3
  120. package/dist/clients/effective-config.js +403 -0
  121. package/dist/clients/error-class.js +23 -0
  122. package/dist/clients/event-loop-hold.js +274 -0
  123. package/dist/clients/event-loop-monitor.js +66 -1
  124. package/dist/clients/extension-log.js +2 -2
  125. package/dist/clients/feature-hints.js +2 -1
  126. package/dist/clients/file-time.js +6 -1
  127. package/dist/clients/file-utils.js +266 -13
  128. package/dist/clients/finding-delivery-gate.js +55 -13
  129. package/dist/clients/format-service.js +6 -1
  130. package/dist/clients/formatters.js +303 -104
  131. package/dist/clients/freshness-cadence.js +17 -0
  132. package/dist/clients/generated-artifacts.js +114 -19
  133. package/dist/clients/generation-guard.js +4 -9
  134. package/dist/clients/git-guard.js +198 -48
  135. package/dist/clients/git-tracked-ignore.js +32 -1
  136. package/dist/clients/go-client.js +39 -0
  137. package/dist/clients/govulncheck-client.js +5 -1
  138. package/dist/clients/gradle-ktfmt-style.js +252 -0
  139. package/dist/clients/gzip-stage-write.js +7 -0
  140. package/dist/clients/hashline-anchor.js +424 -0
  141. package/dist/clients/hook-budgets.js +76 -0
  142. package/dist/clients/host-edit-normalize.js +5 -2
  143. package/dist/clients/host-ports.js +1 -1
  144. package/dist/clients/installer/index.js +200 -84
  145. package/dist/clients/installer/managed-tool-refresh.js +3 -1
  146. package/dist/clients/instance-reaper.js +108 -243
  147. package/dist/clients/instance-registry-lock.js +145 -0
  148. package/dist/clients/instance-registry.js +435 -122
  149. package/dist/clients/jscpd-client.js +6 -1
  150. package/dist/clients/language-profile.js +4 -2
  151. package/dist/clients/language-registry.js +595 -0
  152. package/dist/clients/latency-logger.js +179 -3
  153. package/dist/clients/ledger-bounds.js +13 -0
  154. package/dist/clients/lens-config.js +141 -30
  155. package/dist/clients/lens-engine.js +30 -37
  156. package/dist/clients/lens-flag-registry.js +67 -6
  157. package/dist/clients/lens-map.js +3 -1
  158. package/dist/clients/log-cleanup.js +2 -2
  159. package/dist/clients/lsp/aggregation.js +3 -1
  160. package/dist/clients/lsp/cascade-tier.js +4 -0
  161. package/dist/clients/lsp/client.js +672 -49
  162. package/dist/clients/lsp/config.js +504 -156
  163. package/dist/clients/lsp/diagnostic-binding.js +10 -5
  164. package/dist/clients/lsp/edits.js +15 -2
  165. package/dist/clients/lsp/index.js +1179 -247
  166. package/dist/clients/lsp/jvm-runtime.js +5 -0
  167. package/dist/clients/lsp/language.js +18 -169
  168. package/dist/clients/lsp/launch.js +38 -9
  169. package/dist/clients/lsp/pending-aux-coverage.js +113 -12
  170. package/dist/clients/lsp/server.js +426 -160
  171. package/dist/clients/lsp/session-roots.js +192 -0
  172. package/dist/clients/lsp/spawn-history.js +2 -7
  173. package/dist/clients/lsp/tsserver-sync.js +9 -2
  174. package/dist/clients/lsp/workspace-diagnostics-cache.js +17 -7
  175. package/dist/clients/lsp/workspace-sweep-hold.js +32 -23
  176. package/dist/clients/lsp-mutation.js +179 -23
  177. package/dist/clients/map-with-concurrency.js +36 -0
  178. package/dist/clients/mcp/analyze.js +45 -10
  179. package/dist/clients/mcp/session.js +19 -17
  180. package/dist/clients/memory-sampler.js +28 -1
  181. package/dist/clients/message-end-attribution.js +30 -0
  182. package/dist/clients/metrics-history.js +21 -4
  183. package/dist/clients/middle-man-analysis.js +2 -4
  184. package/dist/clients/module-report.js +20 -45
  185. package/dist/clients/mutating-tool.js +651 -0
  186. package/dist/clients/mutation-attribution.js +368 -0
  187. package/dist/clients/mutation-bridge.js +240 -0
  188. package/dist/clients/ndjson-logger.js +247 -20
  189. package/dist/clients/observed-mutation-sources.js +101 -0
  190. package/dist/clients/observed-mutation.js +1215 -0
  191. package/dist/clients/opaque-mutation-scan.js +300 -40
  192. package/dist/clients/opengrep-config.js +4 -0
  193. package/dist/clients/package-manager.js +201 -44
  194. package/dist/clients/partial-edit-apply.js +359 -80
  195. package/dist/clients/path-attribution-telemetry.js +23 -7
  196. package/dist/clients/path-keyed-map.js +35 -3
  197. package/dist/clients/path-utils.js +460 -13
  198. package/dist/clients/persist-debounce.js +8 -1
  199. package/dist/clients/php-cs-fixer-config.js +114 -0
  200. package/dist/clients/pipeline.js +77 -24
  201. package/dist/clients/probe-home-state.js +228 -0
  202. package/dist/clients/process-bridge.js +66 -0
  203. package/dist/clients/process-singletons.js +210 -0
  204. package/dist/clients/process-snapshot.js +68 -0
  205. package/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
  206. package/dist/clients/project-diagnostics/runner-adapters/madge.js +2 -1
  207. package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +27 -3
  208. package/dist/clients/project-diagnostics/scanner.js +61 -24
  209. package/dist/clients/project-lens-config.js +393 -122
  210. package/dist/clients/project-snapshot.js +85 -35
  211. package/dist/clients/python-environment.js +217 -0
  212. package/dist/clients/python-provenance.js +639 -0
  213. package/dist/clients/quiet-window.js +6 -1
  214. package/dist/clients/read-bridge.js +13 -21
  215. package/dist/clients/read-expansion.js +9 -46
  216. package/dist/clients/read-guard-logger.js +39 -6
  217. package/dist/clients/read-guard-tool-lines.js +364 -212
  218. package/dist/clients/read-guard.js +95 -7
  219. package/dist/clients/recent-touches.js +17 -5
  220. package/dist/clients/resource-sampler.js +195 -96
  221. package/dist/clients/review-graph/builder.js +752 -132
  222. package/dist/clients/review-graph/service.js +55 -3
  223. package/dist/clients/review-graph/shared-extraction-ir.js +7 -15
  224. package/dist/clients/review-graph/tsconfig-paths.js +5 -1
  225. package/dist/clients/review-graph/workspace-modules.js +10 -74
  226. package/dist/clients/review-graph-logger.js +30 -4
  227. package/dist/clients/runtime-agent-end.js +18 -2
  228. package/dist/clients/runtime-context.js +49 -16
  229. package/dist/clients/runtime-coordinator.js +93 -6
  230. package/dist/clients/runtime-session.js +169 -34
  231. package/dist/clients/runtime-tool-call.js +183 -35
  232. package/dist/clients/runtime-tool-result.js +894 -241
  233. package/dist/clients/runtime-turn.js +1264 -74
  234. package/dist/clients/rust-client.js +31 -0
  235. package/dist/clients/safe-spawn.js +191 -28
  236. package/dist/clients/sanitize.js +7 -1
  237. package/dist/clients/scratch-tree-policy.js +1 -3
  238. package/dist/clients/security-scan-client.js +51 -17
  239. package/dist/clients/session-lifecycle.js +238 -42
  240. package/dist/clients/session-start-observability.js +79 -0
  241. package/dist/clients/sessionstart-logger.js +12 -3
  242. package/dist/clients/sg-runner.js +27 -15
  243. package/dist/clients/sgconfig.js +6 -1
  244. package/dist/clients/shared-checkout-guard.js +338 -0
  245. package/dist/clients/skills-resolver.js +105 -0
  246. package/dist/clients/smells-rollup.js +2 -2
  247. package/dist/clients/spawn-output-cap.js +37 -0
  248. package/dist/clients/spawn-timeout-cooldown.js +15 -1
  249. package/dist/clients/startup-scan.js +6 -7
  250. package/dist/clients/startup-timing.js +69 -4
  251. package/dist/clients/string-utils.js +33 -0
  252. package/dist/clients/subagent-mode.js +17 -4
  253. package/dist/clients/test-runner-client.js +586 -118
  254. package/dist/clients/test-runner-delivery.js +239 -0
  255. package/dist/clients/tool-definition.js +41 -1
  256. package/dist/clients/tool-policy.js +92 -22
  257. package/dist/clients/tree-sitter-cache.js +40 -40
  258. package/dist/clients/tree-sitter-client.js +97 -53
  259. package/dist/clients/tree-sitter-logger.js +19 -3
  260. package/dist/clients/tree-sitter-query-loader.js +81 -1
  261. package/dist/clients/tree-sitter-shared.js +51 -46
  262. package/dist/clients/tree-sitter-symbol-extractor.js +28 -0
  263. package/dist/clients/typos-config.js +5 -0
  264. package/dist/clients/user-notify.js +6 -2
  265. package/dist/clients/warm-attach.js +8 -10
  266. package/dist/clients/widget-state.js +191 -26
  267. package/dist/clients/word-index-logger.js +15 -3
  268. package/dist/clients/word-index-store.js +463 -0
  269. package/dist/clients/word-index.js +815 -125
  270. package/dist/clients/workspace-topology.js +25 -1
  271. package/dist/clients/zizmor-config.js +29 -6
  272. package/dist/index.js +68577 -53950
  273. package/dist/mcp/analyze-cli.js +3 -1
  274. package/dist/mcp/build-staleness.js +8 -7
  275. package/dist/mcp/cli.js +3 -1
  276. package/dist/mcp/server.js +133 -6
  277. package/dist/scripts/lib/process-scan.mjs +583 -0
  278. package/dist/scripts/lib/skills-predicate.mjs +129 -0
  279. package/dist/tools/effective-config.js +89 -0
  280. package/dist/tools/lens-diagnostics.js +99 -24
  281. package/dist/tools/lsp-diagnostics.js +81 -85
  282. package/dist/tools/lsp-navigation.js +40 -17
  283. package/dist/tools/shared.js +0 -1
  284. package/docs/agent-guide.md +68 -3
  285. package/docs/ast-grep_rules_catalog.md +41 -10
  286. package/docs/astplayground.md +26 -17
  287. package/docs/configuration.md +222 -0
  288. package/docs/dependencies.md +3 -3
  289. package/docs/features.md +44 -5
  290. package/docs/language-coverage.md +2 -2
  291. package/docs/pi-lens-fixer.md +25 -0
  292. package/docs/pi-lens-investigator.md +25 -0
  293. package/docs/pi-lens-reviewer.md +27 -0
  294. package/docs/pi-lens-subagent.md +38 -0
  295. package/docs/pi-lens-warden.md +55 -0
  296. package/docs/public-api-stability.md +359 -0
  297. package/docs/release-qa-baseline.md +182 -0
  298. package/docs/subagent-compat.md +110 -30
  299. package/docs/tree-sitter_rules_catalog.md +2 -2
  300. package/package.json +17 -21
  301. package/rules/ast-grep-rules/rule-tests/c-no-malloc-free-test.yml +20 -0
  302. package/rules/ast-grep-rules/rule-tests/cpp-no-malloc-free-test.yml +25 -0
  303. package/rules/ast-grep-rules/rule-tests/cpp-no-printf-test.yml +19 -0
  304. package/rules/ast-grep-rules/rule-tests/go-no-fmt-println-test.yml +19 -0
  305. package/rules/ast-grep-rules/rule-tests/go-no-panic-in-lib-test.yml +17 -0
  306. package/rules/ast-grep-rules/rule-tests/go-no-underscore-func-name-test.yml +18 -0
  307. package/rules/ast-grep-rules/rule-tests/go-prefer-errors-is-test.yml +15 -0
  308. package/rules/ast-grep-rules/rule-tests/go-prefer-string-builder-test.yml +21 -0
  309. package/rules/ast-grep-rules/rule-tests/hyphenated-svg-attribute-test.yml +11 -0
  310. package/rules/ast-grep-rules/rule-tests/kotlin-no-lateinit-test.yml +17 -0
  311. package/rules/ast-grep-rules/rule-tests/kotlin-no-nullable-boolean-test.yml +15 -0
  312. package/rules/ast-grep-rules/rule-tests/kotlin-no-println-test.yml +19 -0
  313. package/rules/ast-grep-rules/rule-tests/kotlin-no-unnecessary-let-test.yml +7 -0
  314. package/rules/ast-grep-rules/rule-tests/kotlin-no-unsafe-cast-test.yml +7 -0
  315. package/rules/ast-grep-rules/rule-tests/kotlin-no-var-test.yml +7 -0
  316. package/rules/ast-grep-rules/rule-tests/kotlin-prefer-data-class-test.yml +25 -0
  317. package/rules/ast-grep-rules/rule-tests/kotlin-prefer-is-empty-test.yml +11 -0
  318. package/rules/ast-grep-rules/rule-tests/no-important-test.yml +21 -0
  319. package/rules/ast-grep-rules/rule-tests/no-raw-types-test.yml +30 -0
  320. package/rules/ast-grep-rules/rule-tests/no-string-concat-in-loop-test.yml +100 -0
  321. package/rules/ast-grep-rules/rule-tests/no-system-out-println-test.yml +7 -0
  322. package/rules/ast-grep-rules/rule-tests/prefer-string-is-empty-test.yml +7 -0
  323. package/rules/ast-grep-rules/rule-tests/python-optional-type-test.yml +9 -0
  324. package/rules/ast-grep-rules/rule-tests/reducible-list-comprehension-test.yml +11 -0
  325. package/rules/ast-grep-rules/rule-tests/ruby-symbol-to-proc-candidate-test.yml +10 -0
  326. package/rules/ast-grep-rules/rule-tests/rust-no-chars-enumerate-test.yml +7 -0
  327. package/rules/ast-grep-rules/rules/c-no-malloc-free.yml +39 -0
  328. package/rules/ast-grep-rules/rules/cpp-no-malloc-free.yml +45 -0
  329. package/rules/ast-grep-rules/rules/cpp-no-printf.yml +41 -0
  330. package/rules/ast-grep-rules/rules/go-no-fmt-println.yml +43 -0
  331. package/rules/ast-grep-rules/rules/go-no-panic-in-lib.yml +41 -0
  332. package/rules/ast-grep-rules/rules/go-no-underscore-func-name.yml +35 -0
  333. package/rules/ast-grep-rules/rules/go-prefer-errors-is.yml +40 -0
  334. package/rules/ast-grep-rules/rules/go-prefer-string-builder.yml +43 -0
  335. package/rules/ast-grep-rules/rules/hyphenated-svg-attribute.yml +32 -0
  336. package/rules/ast-grep-rules/rules/kotlin-no-lateinit.yml +37 -0
  337. package/rules/ast-grep-rules/rules/kotlin-no-nullable-boolean.yml +33 -0
  338. package/rules/ast-grep-rules/rules/kotlin-no-println.yml +42 -0
  339. package/rules/ast-grep-rules/rules/kotlin-no-unnecessary-let.yml +28 -0
  340. package/rules/ast-grep-rules/rules/kotlin-no-unsafe-cast.yml +28 -0
  341. package/rules/ast-grep-rules/rules/kotlin-no-var.yml +35 -0
  342. package/rules/ast-grep-rules/rules/kotlin-prefer-data-class.yml +66 -0
  343. package/rules/ast-grep-rules/rules/kotlin-prefer-is-empty.yml +35 -0
  344. package/rules/ast-grep-rules/rules/no-important.yml +21 -0
  345. package/rules/ast-grep-rules/rules/no-raw-types.yml +33 -0
  346. package/rules/ast-grep-rules/rules/no-string-concat-in-loop.yml +90 -0
  347. package/rules/ast-grep-rules/rules/no-system-out-println.yml +16 -0
  348. package/rules/ast-grep-rules/rules/prefer-string-is-empty.yml +11 -0
  349. package/rules/ast-grep-rules/rules/python-optional-type.yml +22 -0
  350. package/rules/ast-grep-rules/rules/reducible-list-comprehension.yml +29 -0
  351. package/rules/ast-grep-rules/rules/ruby-symbol-to-proc-candidate.yml +25 -0
  352. package/rules/ast-grep-rules/rules/rust-no-chars-enumerate.yml +19 -0
  353. package/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
  354. package/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
  355. package/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
  356. package/scripts/analyze-pi-lens-logs.mjs +192 -1
  357. package/scripts/install-selftest.mjs +58 -2
  358. package/scripts/lib/skills-predicate.mjs +129 -0
  359. package/scripts/rpc-load-check.mjs +3 -1
@@ -1,6 +1,7 @@
1
1
  import * as nodeFs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { resetBoundedTelemetry } from "./bounded-telemetry.js";
4
+ import { rotateMessageEndAttribution } from "./message-end-attribution.js";
4
5
  import { createDeadline, yieldIfOverBudget } from "./cooperative-budget.js";
5
6
  import { deadCodeIssueCount } from "./dead-code-client.js";
6
7
  import { logDeadCodeScan } from "./dead-code-logger.js";
@@ -10,9 +11,12 @@ import { resetPsScriptAnalyzerAvailability } from "./dispatch/runners/psscriptan
10
11
  import { resetInstallRetryLatches } from "./dispatch/runners/utils/availability-policy.js";
11
12
  import { resetLazyInstallAttempts } from "./dispatch/runners/utils/lazy-installer.js";
12
13
  import { resetDispatchAvailabilityState } from "./dispatch/runners/utils/runner-helpers.js";
14
+ import { resetObservedRunnerLatency } from "./dispatch/collect-later-tier.js";
15
+ import { resetPendingRunnerFindings } from "./dispatch/pending-runner-findings.js";
13
16
  import { clearAllSessions as clearFileTimeSessions } from "./file-time.js";
14
17
  import { getGlobalPiLensDir, getKnipIgnorePatterns, getProjectDataDir, } from "./file-utils.js";
15
18
  import { GitleaksClient } from "./gitleaks-client.js";
19
+ import { resetGoAvailability } from "./go-client.js";
16
20
  import { GovulncheckClient, } from "./govulncheck-client.js";
17
21
  import { sweepAtomicWriteStages } from "./instance-reaper.js";
18
22
  import { canRunStartupHeavyScans } from "./language-policy.js";
@@ -22,7 +26,7 @@ import { runLogCleanup } from "./log-cleanup.js";
22
26
  import { resetCascadeTierSessionState } from "./lsp/cascade-tier.js";
23
27
  import { initLSPConfig, loadLSPConfig } from "./lsp/config.js";
24
28
  import { resetWorkspaceDiagnosticsCacheSession } from "./lsp/workspace-diagnostics-session.js";
25
- import { resetDirectLspCommandAvailability } from "./lsp/server.js";
29
+ import { resetDirectLspCommandAvailability, resetLSPCaseSensitivityState, } from "./lsp/server.js";
26
30
  import { loadLspService } from "./lsp-lazy.js";
27
31
  import { resetManagedToolRefreshSession } from "./installer/managed-tool-refresh-session.js";
28
32
  import { resetResolvedPathCache } from "./installer/index.js";
@@ -32,9 +36,10 @@ import { isAtOrAboveHomeDir } from "./path-utils.js";
32
36
  import { isPrintMode } from "./print-mode.js";
33
37
  import { readLatestProjectSequence, readLatestProjectSequenceAsync, } from "./project-changes.js";
34
38
  import { getProjectSnapshotPath, getProjectSnapshotLegacyPath, hydrateRuntimeFromProjectSnapshot, hydrateRuntimeFromProjectSnapshotIfIdle, isProjectSnapshotFresh, isProjectSnapshotMetaStale, loadProjectSnapshot, loadProjectSnapshotExportsAndRules, PROJECT_SNAPSHOT_VERSION, readProjectSnapshotMeta, saveRuntimeProjectSnapshot, } from "./project-snapshot.js";
35
- import { clearTsconfigPathsCache } from "./review-graph/tsconfig-paths.js";
36
39
  import { scanProjectRules } from "./rules-scanner.js";
40
+ import { resetRustAvailability } from "./rust-client.js";
37
41
  import { resetSafeSpawnWindowsCommandCache } from "./safe-spawn.js";
42
+ import { resetAnalyzerBootstrapSessionState, } from "./bootstrap.js";
38
43
  import { getSlowFsVerdict, isSlowFs, slowFsDegradationNotice, } from "./slow-fs.js";
39
44
  import { countRecentSmells, formatSmellsSessionStartLine, resetSmellsSessionState, } from "./smells-rollup.js";
40
45
  import { findNearestProjectRoot, getStartupScanMaxEntries, isStartupScanVerdictFresh, resolveStartupScanContext, } from "./startup-scan.js";
@@ -44,10 +49,29 @@ import { isWarmAttached } from "./warm-attach.js";
44
49
  import { setSessionLanguages } from "./widget-state.js";
45
50
  import { logWordIndex } from "./word-index-logger.js";
46
51
  import { resetOpaqueMutationState } from "./opaque-mutation-scan.js";
52
+ import { primePersistedMutationAttribution, resetMutationAttribution, } from "./mutation-attribution.js";
53
+ import { resetObservedMutationNet } from "./observed-mutation.js";
47
54
  import { resetPendingAuxiliaryCoverage } from "./lsp/pending-aux-coverage.js";
48
55
  import { resetWorkspaceTopology } from "./workspace-topology.js";
49
56
  import { resetZizmorTokenAvailability } from "./zizmor-config.js";
50
57
  import { resetSpawnTimeoutCooldowns } from "./spawn-timeout-cooldown.js";
58
+ import { resetTestRunnerDelivery } from "./test-runner-delivery.js";
59
+ import { resetLspMutationNoBridgeDbgLatch } from "./lsp-mutation.js";
60
+ /**
61
+ * `deps` with resolved analyzer clients merged in, or `null` when the
62
+ * bootstrap could not be served and the caller must proceed without them.
63
+ *
64
+ * No cast and no "is one representative field present?" probe: the seam
65
+ * answers with all seventeen clients or with `null`, so the two outcomes are
66
+ * the two branches. A skipped consumer is already counted in the ledger under
67
+ * `analyzer-bootstrap-unavailable`, so it is never mistaken for a clean one.
68
+ */
69
+ async function demandBootstrapDeps(deps, reason) {
70
+ const clients = await deps.bootstrap.request(reason);
71
+ if (!clients)
72
+ return null;
73
+ return { ...deps, ...clients };
74
+ }
51
75
  const HOST_STALL_THRESHOLD_MS = 30_000;
52
76
  function logHostReadyDelay(deps, cwd) {
53
77
  if (!deps.emitHostReadyDelay ||
@@ -623,7 +647,7 @@ async function buildOrRefreshWordIndex(args) {
623
647
  const snapshot = loadProjectSnapshot(snapshotRoot);
624
648
  const snapshotLoadMs = Date.now() - snapshotLoadStartMs;
625
649
  if (snapshot?.wordIndex) {
626
- const { deserializeWordIndex, refreshWordIndexIncrementally } = await import("./word-index.js");
650
+ const { deserializeWordIndex, refreshWordIndexIncrementally, countWordIndexPostingEntries, estimateWordIndexResidentBytes, } = await import("./word-index.js");
627
651
  const deserializeStartMs = Date.now();
628
652
  const index = deserializeWordIndex(snapshot.wordIndex);
629
653
  const deserializeMs = Date.now() - deserializeStartMs;
@@ -675,6 +699,8 @@ async function buildOrRefreshWordIndex(args) {
675
699
  durationMs: Date.now() - startMs,
676
700
  indexedFileCount: index.docCount,
677
701
  tokens: index.postings.size,
702
+ postingEntries: countWordIndexPostingEntries(index),
703
+ residentBytes: estimateWordIndexResidentBytes(index),
678
704
  truncated: index.truncated,
679
705
  phaseDurationsMs: {
680
706
  snapshotLoadMs,
@@ -714,7 +740,7 @@ async function buildOrRefreshWordIndex(args) {
714
740
  // implementation backs this task, the quick-mode warmup call below, AND the
715
741
  // stateless cold-query background trigger in word-index.ts — a bound/skip
716
742
  // -rule change lands once, not in three copies.
717
- const { buildWordIndexAsync, collectWordIndexDocs } = await import("./word-index.js");
743
+ const { buildWordIndexAsync, collectWordIndexDocs, countWordIndexPostingEntries, estimateWordIndexResidentBytes, } = await import("./word-index.js");
718
744
  const docs = await collectWordIndexDocs(analysisRoot, () => runtime.isCurrentSession(sessionGeneration), rebuildPreflightFiles);
719
745
  if (!runtime.isCurrentSession(sessionGeneration))
720
746
  return;
@@ -751,6 +777,8 @@ async function buildOrRefreshWordIndex(args) {
751
777
  durationMs: Date.now() - startMs,
752
778
  indexedFileCount: runtime.wordIndex.docCount,
753
779
  tokens: runtime.wordIndex.postings.size,
780
+ postingEntries: countWordIndexPostingEntries(runtime.wordIndex),
781
+ residentBytes: estimateWordIndexResidentBytes(runtime.wordIndex),
754
782
  truncated: runtime.wordIndex.truncated,
755
783
  skipped: docs.skipped,
756
784
  });
@@ -766,6 +794,24 @@ async function buildOrRefreshWordIndex(args) {
766
794
  // Each consumer already handles the "not ready yet" case gracefully
767
795
  // (cachedExports.size > 0, cache miss paths).
768
796
  function scheduleStartupScans(deps, runtime, sessionGeneration, analysisRoot, snapshotRoot, languageProfile, dbg) {
797
+ // #2467: the analyzers these scans drive are loaded HERE, on the deferred
798
+ // background path, instead of before the handler ran. This function was
799
+ // already fire-and-forget ("don't block session start"), so resolving them
800
+ // across one await adds no wait to the interactive path and changes no
801
+ // ordering its caller depends on — the caller never awaited it.
802
+ void (async () => {
803
+ const resolved = await demandBootstrapDeps(deps, "session-start-scans");
804
+ // Fail open: no analyzers means no scans, said out loud. The demand is
805
+ // already counted in the ledger, so this is not a silent skip.
806
+ if (!resolved) {
807
+ dbg("session_start scans: analyzer bootstrap unavailable — skipped");
808
+ return;
809
+ }
810
+ scheduleStartupScansWithClients(resolved, runtime, sessionGeneration, analysisRoot, snapshotRoot, languageProfile, dbg);
811
+ })();
812
+ }
813
+ /** The scan bodies, once the analyzers they need are resolved. */
814
+ function scheduleStartupScansWithClients(deps, runtime, sessionGeneration, analysisRoot, snapshotRoot, languageProfile, dbg) {
769
815
  const { todoScanner, cacheManager, knipClient, jscpdClient, deadCodeClients, govulncheckClient, gitleaksClient, trivyClient, opengrepClient, astGrepClient, depChecker, } = deps;
770
816
  // Some background scans are CPU-heavy and arrive on the event loop
771
817
  // just as the user is most likely typing (right after /new). Defer
@@ -1150,7 +1196,12 @@ function scheduleStartupScans(deps, runtime, sessionGeneration, analysisRoot, sn
1150
1196
  // Build (or hydrate) the canonical review graph first. The call graph is a
1151
1197
  // derived projection of that graph, so its freshness is the review graph's
1152
1198
  // version/signature—not a second source walk and mtime policy.
1153
- const sessionFacts = new FactStore();
1199
+ // Subject labels this store's capacity-eviction telemetry distinctly
1200
+ // from the other five production FactStore instances (#2243 review
1201
+ // round 3, F1) — this session-start walk runs BEFORE any dispatch and
1202
+ // can visit every file in the project, so a shared subject would let
1203
+ // it consume the dispatch store's once-per-session ledger slot first.
1204
+ const sessionFacts = new FactStore("runtime-session-call-graph");
1154
1205
  const graph = await buildOrUpdateGraph(analysisRoot, [], sessionFacts);
1155
1206
  if (!runtime.isCurrentSession(sessionGeneration))
1156
1207
  return;
@@ -1235,6 +1286,20 @@ function scheduleStartupScans(deps, runtime, sessionGeneration, analysisRoot, sn
1235
1286
  });
1236
1287
  }
1237
1288
  function scheduleDeferredToolProbes(deps, languageProfile, startupDefaults, startupScansWillRun, dbg) {
1289
+ // #2467: same deferral as `scheduleStartupScans` — the probes were already
1290
+ // fire-and-forget, so the analyzers they probe load here rather than before
1291
+ // the handler.
1292
+ void (async () => {
1293
+ const resolved = await demandBootstrapDeps(deps, "session-start-tool-probes");
1294
+ if (!resolved) {
1295
+ dbg("session_start tools: analyzer bootstrap unavailable — no probes");
1296
+ return;
1297
+ }
1298
+ scheduleDeferredToolProbesWithClients(resolved, languageProfile, startupDefaults, startupScansWillRun, dbg);
1299
+ })();
1300
+ }
1301
+ /** The probe bodies, once the analyzers they probe are resolved. */
1302
+ function scheduleDeferredToolProbesWithClients(deps, languageProfile, startupDefaults, startupScansWillRun, dbg) {
1238
1303
  const { biomeClient, ruffClient, depChecker } = deps;
1239
1304
  const defaultTools = new Set(startupDefaults);
1240
1305
  const probes = [];
@@ -1288,29 +1353,40 @@ export const SESSION_START_GUIDANCE = [
1288
1353
  ];
1289
1354
  export async function handleSessionStart(deps) {
1290
1355
  resetDegradationLedger();
1356
+ // #2467: re-arm the analyzer bootstrap's shutdown gate. The gate is a
1357
+ // per-SESSION claim ("this session is over") held in process-lived storage,
1358
+ // so without this a replacement session in the same process would find
1359
+ // every analyzer refused for the rest of the process — AGENTS.md defect
1360
+ // shape 17. The resident clients themselves are deliberately kept.
1361
+ resetAnalyzerBootstrapSessionState();
1362
+ resetTestRunnerDelivery();
1363
+ // #2450 fix round 3, catalog shape 17: the "bridge unavailable" dbg latch
1364
+ // (`clients/lsp-mutation.ts`) is a process-lifetime once-per-session flag,
1365
+ // same shape as the ledger's own once-per-subject bookkeeping above, but
1366
+ // kept as its own reset (not folded into `resetDegradationLedger`) to
1367
+ // avoid that module reaching back into this one — see the latch's own
1368
+ // comment for why.
1369
+ resetLspMutationNoBridgeDbgLatch();
1291
1370
  // #1743: the bounded-telemetry per-turn counters. The rising-edge state is
1292
1371
  // NOT here — it is the ledger's own tally, reset on the line above. These
1293
1372
  // counters are keyed by turn index, and a new session restarts turn
1294
1373
  // numbering at 0, so without this a stale count could survive a session
1295
1374
  // boundary that happened to land on the same index.
1296
1375
  resetBoundedTelemetry();
1376
+ // #1956 R3: stale message_end events drain after replacement. Rotate the
1377
+ // live anchor into one bounded previous slot so the queued event keeps the
1378
+ // replaced session's attribution until a newer live message_end is seen.
1379
+ rotateMessageEndAttribution();
1380
+ // #2526: the once-per-session `config_resolved` claim is NOT re-armed here.
1381
+ // index.ts resolves this session's config in `ensureLSPConfigInitialized`
1382
+ // BEFORE it calls this handler, so a re-arm on this line would fire between
1383
+ // the session's own two resolutions and the deferred `loadLSPConfig` below
1384
+ // would write a second row for one session (review round 2, F1). The re-arm
1385
+ // lives in index.ts's `session_start` closure, ahead of that ensure and
1386
+ // behind the #473 gate — see `resetOncePerSessionPhases`'s doc comment.
1297
1387
  const handlerEnteredAt = Date.now();
1298
1388
  const sessionStartMs = deps.sessionStartFiredAt ?? handlerEnteredAt;
1299
1389
  const cwdForTelemetry = deps.ctxCwd ?? process.cwd();
1300
- if (deps.bootstrapClientsStartedAt !== undefined &&
1301
- deps.bootstrapClientsDurationMs !== undefined) {
1302
- logLatency({
1303
- type: "phase",
1304
- filePath: cwdForTelemetry,
1305
- phase: "bootstrap_clients_load",
1306
- startedAt: new Date(deps.bootstrapClientsStartedAt).toISOString(),
1307
- durationMs: deps.bootstrapClientsDurationMs,
1308
- metadata: {
1309
- parent: "session_start_prehandler",
1310
- reason: deps.sessionReason,
1311
- },
1312
- });
1313
- }
1314
1390
  if (deps.sessionStartFiredAt !== undefined) {
1315
1391
  logLatency({
1316
1392
  type: "phase",
@@ -1590,7 +1666,7 @@ export async function handleSessionStart(deps) {
1590
1666
  }
1591
1667
  const allowBootstrapTasks = startupMode === "full";
1592
1668
  const quickMode = startupMode === "quick";
1593
- const { ctxCwd, getFlag, notify, dbg, log, runtime, metricsClient, knipClient, cacheManager, testRunnerClient, goClient, rustClient, ensureTool, cleanStaleTsBuildInfo, resetDispatchBaselines, resetLSPService, } = deps;
1669
+ const { ctxCwd, getFlag, notify, dbg, log, runtime, cacheManager, ensureTool, cleanStaleTsBuildInfo, resetDispatchBaselines, resetLSPService, } = deps;
1594
1670
  // Lightweight phase timer — resets after each call so each log line shows
1595
1671
  // the cost of that phase alone, not cumulative time from session start.
1596
1672
  let _phaseT = Date.now();
@@ -1598,23 +1674,31 @@ export async function handleSessionStart(deps) {
1598
1674
  dbg(`session_start phase ${name}: ${Date.now() - _phaseT}ms`);
1599
1675
  _phaseT = Date.now();
1600
1676
  };
1601
- metricsClient.reset();
1677
+ // #2467: `peek`, never a load. A metrics client that was never constructed
1678
+ // holds no per-session state to re-arm, so the reset is vacuous — and
1679
+ // loading the analyzer graph in order to reset nothing is the interactive-
1680
+ // path cost this issue removes.
1681
+ deps.bootstrap.peek()?.metricsClient.reset();
1602
1682
  getDiagnosticTracker().reset();
1603
1683
  clearFileTimeSessions();
1604
1684
  runtime.complexityBaselines.clear();
1605
1685
  resetDispatchBaselines(ctxCwd);
1606
- // #806: drop the shared per-directory marker index (and any consumer
1607
- // cache layered on top, e.g. tsconfig-paths' matcher cache) so a
1608
- // `.pi-lens.json`/`tsconfig.json`/workspace-manifest edit made between
1609
- // sessions is picked up fresh instead of only on process restart — this
1610
- // also fixes #805's mid-session tsconfig staleness (the matcher cache was
1611
- // previously session-lived with no reset hook at all).
1686
+ // #806: clear the shared per-directory marker index and registered
1687
+ // topology-derived caches only at session start. Mid-session edits are NOT
1688
+ // detected; #805's tsconfig matcher cache follows the same registry reset.
1612
1689
  resetWorkspaceTopology();
1613
- clearTsconfigPathsCache();
1614
1690
  // #2000: opaque-recovery baselines are keyed cwd:generation (unreachable
1615
1691
  // after reset) and the git-worktree memo must re-probe after a session
1616
1692
  // that may have seen a non-git dir become one.
1617
1693
  resetOpaqueMutationState();
1694
+ // #2430: the observational net's pending baselines are keyed by tool-call
1695
+ // id and its content ledger describes the previous session's files; both
1696
+ // mis-answer forever without this. The learned attribution map is cleared
1697
+ // and then RE-PRIMED from this project's persisted file, so a tool an
1698
+ // earlier session observed is classified by name with no snapshot at all.
1699
+ resetObservedMutationNet();
1700
+ resetMutationAttribution();
1701
+ primePersistedMutationAttribution(ctxCwd);
1618
1702
  // #2026: pending auxiliary baselines are unreachable after generation bump.
1619
1703
  resetPendingAuxiliaryCoverage();
1620
1704
  // #817/#1199: Windows command resolution is cached per (command, canonical
@@ -1630,6 +1714,10 @@ export async function handleSessionStart(deps) {
1630
1714
  // the tool for the rest of the process lifetime. Clear it here, same
1631
1715
  // boundary as the other per-session caches on this line.
1632
1716
  resetDispatchAvailabilityState();
1717
+ // Runner collect-later observations and their late findings are session
1718
+ // scoped. A new session must re-probe rather than inherit a process latch.
1719
+ resetObservedRunnerLatency();
1720
+ resetPendingRunnerFindings();
1633
1721
  // #1995: a command that TIMED OUT (not merely failed a probe) cools down
1634
1722
  // for the rest of the session so a hot loop of edits cannot hand the same
1635
1723
  // wedged .cmd shim a second budget. A new session may retry: the executable
@@ -1650,6 +1738,18 @@ export async function handleSessionStart(deps) {
1650
1738
  // login` and starts a fresh session still reads the previous session's
1651
1739
  // stale "no token" verdict until the cooldown (if any) happens to expire.
1652
1740
  resetZizmorTokenAvailability();
1741
+ // #2455: same #1496/#1535 process-lifetime-latch shape, one caller later —
1742
+ // the `goClient`/`rustClient` singletons each hold their own
1743
+ // `createAvailabilityLatch()` outside the dispatch generation counter above
1744
+ // (they predate `createCwdCachedProbe`). A "missing" verdict never expires
1745
+ // on its own, so without these a go/cargo install between sessions stayed
1746
+ // unobserved for the rest of the process's life. Found by hand while
1747
+ // auditing #2455's detector widening, NOT surfaced by it: the sweep skips
1748
+ // any file exporting no reset at all, and neither client module exported
1749
+ // one, so no container predicate could have yielded them (#2455 fix round 4,
1750
+ // F5 — an earlier draft of this comment had the causality backwards).
1751
+ resetGoAvailability();
1752
+ resetRustAvailability();
1653
1753
  // psscriptanalyzer's three latches (interpreter, module, -File exec) are
1654
1754
  // module-local, so the generation counter above does not reach them
1655
1755
  // (#1490, #1540).
@@ -1670,6 +1770,9 @@ export async function handleSessionStart(deps) {
1670
1770
  // #1897: direct-LSP negative availability and bare installer paths are
1671
1771
  // session facts. A command or PATH entry can appear between sessions.
1672
1772
  resetDirectLspCommandAvailability();
1773
+ // #2052: a root may not exist when the case-sensitivity probe first sees it.
1774
+ // Re-probe it at the next session boundary after the workspace is created.
1775
+ resetLSPCaseSensitivityState();
1673
1776
  resetResolvedPathCache();
1674
1777
  // #1653: pnpm/yarn/bun/npm's availability latches (package-manager.ts) are
1675
1778
  // module-local, same #1490/#1535 shape as the two lines above — the
@@ -1694,7 +1797,9 @@ export async function handleSessionStart(deps) {
1694
1797
  // Some embedders inject a capability-shaped Knip client rather than the
1695
1798
  // concrete KnipClient. Session reset is an optional lifecycle capability;
1696
1799
  // its absence must not make session_start fail.
1697
- knipClient.resetSessionState?.();
1800
+ // #2467: `peek` for the same reason as `metricsClient.reset()` above —
1801
+ // an unloaded knip client has no session state to clear.
1802
+ deps.bootstrap.peek()?.knipClient.resetSessionState?.();
1698
1803
  // #1910: the tier-3 cascade outstanding-touch registry and its
1699
1804
  // sweep-scoped expired/evicted counters (clients/lsp/cascade-tier.ts) are
1700
1805
  // a per-SESSION claim about touches THIS session fired. #1899 bounded the
@@ -1751,6 +1856,12 @@ export async function handleSessionStart(deps) {
1751
1856
  }
1752
1857
  const hasWorkspaceCwd = typeof ctxCwd === "string" && ctxCwd.length > 0;
1753
1858
  const cwd = ctxCwd ?? process.cwd();
1859
+ // #2526 review round 3, S1: this handler no longer predicts whether config
1860
+ // will resolve. `loadLSPConfig` itself publishes a `config_resolution_pending`
1861
+ // mark, from inside the function, at the instant a resolution is actually
1862
+ // attempted — see that function's doc comment (`clients/lsp/config.ts`) for
1863
+ // why a prediction mirrored against this handler's own gates drifted from
1864
+ // what index.ts and this handler's callers actually schedule.
1754
1865
  // #1228: generic atomic-write stages are shared by several project stores,
1755
1866
  // so the review-graph-specific sweep cannot own this namespace. Sweep the
1756
1867
  // project data roots and machine-global registry root once per session start;
@@ -1920,7 +2031,18 @@ export async function handleSessionStart(deps) {
1920
2031
  filePath: cwd,
1921
2032
  startedAt: new Date(sessionStartMs).toISOString(),
1922
2033
  durationMs: totalDurationMs,
1923
- metadata: { mode: startupMode, reason: deps.sessionReason },
2034
+ metadata: {
2035
+ mode: startupMode,
2036
+ reason: deps.sessionReason,
2037
+ // #2129: the root-identity input decideSessionStart consulted,
2038
+ // alongside `mode` — every start reaching this line already
2039
+ // classified `primary`/`sequential-replacement` (a `secondary-root`
2040
+ // start returns before `handleSessionStart` is ever called).
2041
+ classification: deps.sessionStartClassification,
2042
+ // `undefined` is omitted by JSON.stringify. Keep unknown explicit so
2043
+ // strict log readers can distinguish it from legacy omission.
2044
+ sameRoot: deps.sessionStartSameRoot ?? "unknown",
2045
+ },
1924
2046
  });
1925
2047
  logHostReadyDelay(deps, cwd);
1926
2048
  emitSmellsSessionStartLine(dbg, sessionStartMs);
@@ -2162,13 +2284,19 @@ export async function handleSessionStart(deps) {
2162
2284
  else {
2163
2285
  dbg("session_start: skipping prettier preinstall probe (startup mode)");
2164
2286
  }
2165
- const detectedRunner = testRunnerClient.detectRunner(analysisRoot);
2287
+ // #2467: the first point on the FULL-mode path that genuinely needs the
2288
+ // analyzer clients, so it is where the load is paid. Quick mode — the
2289
+ // process's first session, the one the user is waiting on — returns above
2290
+ // and never reaches here. Fail open: an unavailable bootstrap drops these
2291
+ // three entries from the "Active tools" line rather than failing the start.
2292
+ const summaryClients = await deps.bootstrap.request("session-start-tools");
2293
+ const detectedRunner = summaryClients?.testRunnerClient.detectRunner(analysisRoot);
2166
2294
  phase("test-runner-detect");
2167
2295
  if (detectedRunner)
2168
2296
  tools.push(`Test runner (${detectedRunner.runner})`);
2169
- if (await goClient.isGoAvailableAsync())
2297
+ if (await summaryClients?.goClient.isGoAvailableAsync())
2170
2298
  tools.push("Go (go vet)");
2171
- if (await rustClient.isAvailableAsync())
2299
+ if (await summaryClients?.rustClient.isAvailableAsync())
2172
2300
  tools.push("Rust (cargo)");
2173
2301
  log(`Active tools: ${tools.join(", ")}`);
2174
2302
  dbg(`session_start tools: ${tools.join(", ")}`);
@@ -2264,7 +2392,14 @@ export async function handleSessionStart(deps) {
2264
2392
  filePath: cwd,
2265
2393
  startedAt: new Date(sessionStartMs).toISOString(),
2266
2394
  durationMs: totalDurationMs,
2267
- metadata: { mode: startupMode, reason: deps.sessionReason },
2395
+ metadata: {
2396
+ mode: startupMode,
2397
+ reason: deps.sessionReason,
2398
+ // #2129: see the quick-mode session_start_total record above.
2399
+ classification: deps.sessionStartClassification,
2400
+ // Keep the full path's durable shape identical to quick mode.
2401
+ sameRoot: deps.sessionStartSameRoot ?? "unknown",
2402
+ },
2268
2403
  });
2269
2404
  logHostReadyDelay(deps, cwd);
2270
2405
  emitSmellsSessionStartLine(dbg, sessionStartMs);