@herbertgao/pi-extensions 2026.8.14 → 2026.8.15

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 (231) hide show
  1. package/README.md +16 -13
  2. package/THIRD_PARTY_NOTICES.md +0 -1
  3. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +8 -1
  4. package/node_modules/@czottmann/pi-automode/README.md +4 -0
  5. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +71 -9
  6. package/node_modules/@czottmann/pi-automode/package.json +1 -1
  7. package/node_modules/@herbertgao/pi-bark/LICENSE +21 -0
  8. package/node_modules/@herbertgao/pi-bark/README.md +44 -0
  9. package/node_modules/@herbertgao/pi-bark/assets/pi-icon.png +0 -0
  10. package/node_modules/@herbertgao/pi-bark/package.json +53 -0
  11. package/node_modules/@herbertgao/pi-bark/src/index.ts +178 -0
  12. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  13. package/node_modules/@narumitw/pi-btw/README.md +125 -88
  14. package/node_modules/@narumitw/pi-btw/dist/index.ts +140 -45
  15. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +2 -2
  16. package/node_modules/@narumitw/pi-btw/package.json +5 -8
  17. package/node_modules/@narumitw/pi-btw/src/btw.ts +31 -26
  18. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +106 -17
  19. package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -2
  20. package/node_modules/@narumitw/pi-btw/src/settings.ts +16 -0
  21. package/node_modules/@tifan/pi-handoff/README.md +36 -13
  22. package/node_modules/@tifan/pi-handoff/package.json +2 -2
  23. package/node_modules/@tifan/pi-handoff/src/index.ts +160 -4
  24. package/node_modules/@tifan/pi-recap/README.md +1 -1
  25. package/node_modules/@tifan/pi-recap/package.json +2 -2
  26. package/node_modules/@tifan/pi-recap/src/index.ts +15 -0
  27. package/node_modules/@tifan/pi-rename/README.md +42 -21
  28. package/node_modules/@tifan/pi-rename/package.json +1 -1
  29. package/node_modules/@tifan/pi-rename/src/index.ts +62 -11
  30. package/node_modules/@tifan/pi-rename/src/language.ts +18 -0
  31. package/node_modules/@tifan/pi-rename/src/models.ts +69 -13
  32. package/node_modules/@tifan/pi-rename/src/naming.ts +36 -7
  33. package/node_modules/@tifan/pi-rename/src/sanitize.ts +22 -14
  34. package/node_modules/pi-lens/CHANGELOG.md +320 -0
  35. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +79 -0
  36. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +16 -1
  37. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +13 -6
  38. package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +12 -0
  39. package/node_modules/pi-lens/dist/clients/biome-client.js +7 -0
  40. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +20 -0
  41. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +56 -19
  42. package/node_modules/pi-lens/dist/clients/build-identity.js +95 -0
  43. package/node_modules/pi-lens/dist/clients/bus-publish.js +3 -0
  44. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +2 -1
  45. package/node_modules/pi-lens/dist/clients/cache-observability.js +2 -1
  46. package/node_modules/pi-lens/dist/clients/cascade-logger.js +15 -1
  47. package/node_modules/pi-lens/dist/clients/dead-code-client.js +14 -11
  48. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +56 -4
  49. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +14 -0
  50. package/node_modules/pi-lens/dist/clients/dependency-checker.js +20 -3
  51. package/node_modules/pi-lens/dist/clients/deps/minimatch.js +1 -1
  52. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +39 -0
  53. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +150 -9
  54. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +231 -9
  55. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +205 -100
  56. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +87 -0
  57. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +21 -5
  58. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +174 -55
  59. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +5 -1
  60. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +16 -3
  61. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +54 -9
  62. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +1 -3
  63. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +37 -1
  64. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -1
  65. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +11 -4
  66. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +4 -0
  67. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +8 -3
  68. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +76 -25
  69. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +4 -0
  70. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +19 -7
  71. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +7 -13
  72. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +51 -17
  73. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +66 -1
  74. package/node_modules/pi-lens/dist/clients/file-utils.js +157 -7
  75. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +5 -0
  76. package/node_modules/pi-lens/dist/clients/formatters.js +28 -3
  77. package/node_modules/pi-lens/dist/clients/git-guard.js +197 -47
  78. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +32 -1
  79. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +5 -1
  80. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +7 -0
  81. package/node_modules/pi-lens/dist/clients/installer/index.js +131 -34
  82. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +3 -1
  83. package/node_modules/pi-lens/dist/clients/instance-reaper.js +4 -13
  84. package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +145 -0
  85. package/node_modules/pi-lens/dist/clients/instance-registry.js +432 -122
  86. package/node_modules/pi-lens/dist/clients/jscpd-client.js +6 -1
  87. package/node_modules/pi-lens/dist/clients/language-profile.js +4 -2
  88. package/node_modules/pi-lens/dist/clients/latency-logger.js +19 -1
  89. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  90. package/node_modules/pi-lens/dist/clients/lens-map.js +3 -1
  91. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +4 -0
  92. package/node_modules/pi-lens/dist/clients/lsp/client.js +516 -31
  93. package/node_modules/pi-lens/dist/clients/lsp/config.js +25 -2
  94. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +9 -4
  95. package/node_modules/pi-lens/dist/clients/lsp/index.js +509 -182
  96. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +5 -0
  97. package/node_modules/pi-lens/dist/clients/lsp/launch.js +36 -8
  98. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +116 -4
  99. package/node_modules/pi-lens/dist/clients/lsp/server.js +142 -13
  100. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +99 -0
  101. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +16 -6
  102. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +32 -23
  103. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +16 -2
  104. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +12 -6
  105. package/node_modules/pi-lens/dist/clients/mcp/session.js +14 -1
  106. package/node_modules/pi-lens/dist/clients/memory-sampler.js +23 -1
  107. package/node_modules/pi-lens/dist/clients/message-end-attribution.js +30 -0
  108. package/node_modules/pi-lens/dist/clients/metrics-history.js +21 -4
  109. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +232 -18
  110. package/node_modules/pi-lens/dist/clients/package-manager.js +6 -18
  111. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +14 -1
  112. package/node_modules/pi-lens/dist/clients/path-utils.js +32 -0
  113. package/node_modules/pi-lens/dist/clients/pipeline.js +0 -8
  114. package/node_modules/pi-lens/dist/clients/process-singletons.js +210 -0
  115. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +2 -1
  116. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +10 -4
  117. package/node_modules/pi-lens/dist/clients/project-snapshot.js +33 -11
  118. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +24 -2
  119. package/node_modules/pi-lens/dist/clients/read-guard.js +74 -7
  120. package/node_modules/pi-lens/dist/clients/recent-touches.js +17 -5
  121. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +639 -62
  122. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +5 -1
  123. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +21 -1
  124. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +68 -0
  125. package/node_modules/pi-lens/dist/clients/runtime-session.js +30 -12
  126. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +15 -0
  127. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +106 -7
  128. package/node_modules/pi-lens/dist/clients/runtime-turn.js +242 -27
  129. package/node_modules/pi-lens/dist/clients/safe-spawn.js +158 -18
  130. package/node_modules/pi-lens/dist/clients/security-scan-client.js +48 -17
  131. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +238 -42
  132. package/node_modules/pi-lens/dist/clients/sg-runner.js +27 -15
  133. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +338 -0
  134. package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +37 -0
  135. package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +15 -1
  136. package/node_modules/pi-lens/dist/clients/startup-scan.js +6 -7
  137. package/node_modules/pi-lens/dist/clients/startup-timing.js +69 -4
  138. package/node_modules/pi-lens/dist/clients/string-utils.js +20 -0
  139. package/node_modules/pi-lens/dist/clients/test-runner-client.js +419 -88
  140. package/node_modules/pi-lens/dist/clients/tool-policy.js +1 -1
  141. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +11 -5
  142. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +17 -1
  143. package/node_modules/pi-lens/dist/clients/warm-attach.js +8 -10
  144. package/node_modules/pi-lens/dist/clients/word-index-logger.js +13 -1
  145. package/node_modules/pi-lens/dist/clients/word-index-store.js +448 -0
  146. package/node_modules/pi-lens/dist/clients/word-index.js +582 -104
  147. package/node_modules/pi-lens/dist/clients/workspace-topology.js +17 -0
  148. package/node_modules/pi-lens/dist/clients/zizmor-config.js +26 -6
  149. package/node_modules/pi-lens/dist/index.js +11243 -6651
  150. package/node_modules/pi-lens/dist/mcp/build-staleness.js +8 -7
  151. package/node_modules/pi-lens/dist/mcp/cli.js +3 -1
  152. package/node_modules/pi-lens/dist/mcp/server.js +6 -1
  153. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +4 -0
  154. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +14 -5
  155. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +3 -1
  156. package/node_modules/pi-lens/docs/agent-guide.md +4 -2
  157. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +41 -10
  158. package/node_modules/pi-lens/docs/astplayground.md +26 -17
  159. package/node_modules/pi-lens/package.json +5 -1
  160. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/c-no-malloc-free-test.yml +20 -0
  161. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-malloc-free-test.yml +25 -0
  162. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-printf-test.yml +19 -0
  163. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-fmt-println-test.yml +19 -0
  164. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-panic-in-lib-test.yml +17 -0
  165. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-underscore-func-name-test.yml +18 -0
  166. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-errors-is-test.yml +15 -0
  167. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-string-builder-test.yml +21 -0
  168. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/hyphenated-svg-attribute-test.yml +11 -0
  169. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-lateinit-test.yml +17 -0
  170. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-nullable-boolean-test.yml +15 -0
  171. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-println-test.yml +19 -0
  172. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unnecessary-let-test.yml +7 -0
  173. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unsafe-cast-test.yml +7 -0
  174. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-var-test.yml +7 -0
  175. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-data-class-test.yml +25 -0
  176. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-is-empty-test.yml +11 -0
  177. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-important-test.yml +21 -0
  178. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-types-test.yml +30 -0
  179. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-string-concat-in-loop-test.yml +100 -0
  180. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-system-out-println-test.yml +7 -0
  181. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/prefer-string-is-empty-test.yml +7 -0
  182. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/python-optional-type-test.yml +9 -0
  183. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/reducible-list-comprehension-test.yml +11 -0
  184. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ruby-symbol-to-proc-candidate-test.yml +10 -0
  185. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/rust-no-chars-enumerate-test.yml +7 -0
  186. package/node_modules/pi-lens/rules/ast-grep-rules/rules/c-no-malloc-free.yml +39 -0
  187. package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-malloc-free.yml +45 -0
  188. package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-printf.yml +41 -0
  189. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-fmt-println.yml +43 -0
  190. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-panic-in-lib.yml +41 -0
  191. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-underscore-func-name.yml +35 -0
  192. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-errors-is.yml +40 -0
  193. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-string-builder.yml +43 -0
  194. package/node_modules/pi-lens/rules/ast-grep-rules/rules/hyphenated-svg-attribute.yml +32 -0
  195. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-lateinit.yml +37 -0
  196. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-nullable-boolean.yml +33 -0
  197. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-println.yml +42 -0
  198. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unnecessary-let.yml +28 -0
  199. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unsafe-cast.yml +28 -0
  200. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-var.yml +35 -0
  201. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-data-class.yml +66 -0
  202. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-is-empty.yml +35 -0
  203. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-important.yml +21 -0
  204. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-types.yml +33 -0
  205. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-string-concat-in-loop.yml +90 -0
  206. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-system-out-println.yml +16 -0
  207. package/node_modules/pi-lens/rules/ast-grep-rules/rules/prefer-string-is-empty.yml +11 -0
  208. package/node_modules/pi-lens/rules/ast-grep-rules/rules/python-optional-type.yml +22 -0
  209. package/node_modules/pi-lens/rules/ast-grep-rules/rules/reducible-list-comprehension.yml +29 -0
  210. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ruby-symbol-to-proc-candidate.yml +25 -0
  211. package/node_modules/pi-lens/rules/ast-grep-rules/rules/rust-no-chars-enumerate.yml +19 -0
  212. package/node_modules/pi-mcp-adapter/CHANGELOG.md +15 -0
  213. package/node_modules/pi-mcp-adapter/README.md +2 -2
  214. package/node_modules/pi-mcp-adapter/commands.ts +1 -1
  215. package/node_modules/pi-mcp-adapter/dist/types.d.ts +1 -1
  216. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +105 -32
  217. package/node_modules/pi-mcp-adapter/package.json +1 -1
  218. package/node_modules/pi-mcp-adapter/proxy-modes.ts +22 -12
  219. package/node_modules/pi-mcp-adapter/server-manager.ts +6 -0
  220. package/node_modules/pi-mcp-adapter/types.ts +1 -1
  221. package/node_modules/pi-web-access/CHANGELOG.md +20 -0
  222. package/node_modules/pi-web-access/README.md +15 -2
  223. package/node_modules/pi-web-access/extract.ts +57 -12
  224. package/node_modules/pi-web-access/github-extract.ts +47 -3
  225. package/node_modules/pi-web-access/index.ts +1 -1
  226. package/node_modules/pi-web-access/package.json +1 -1
  227. package/node_modules/pi-web-access/page-query.ts +61 -8
  228. package/package.json +16 -16
  229. package/node_modules/@tifan/pi-titlebar-spinner/README.md +0 -24
  230. package/node_modules/@tifan/pi-titlebar-spinner/package.json +0 -39
  231. package/node_modules/@tifan/pi-titlebar-spinner/src/index.ts +0 -49
@@ -0,0 +1,79 @@
1
+ [
2
+ "./clients/agent-nudge.js",
3
+ "./clients/ast-grep-client.js",
4
+ "./clients/bootstrap.js",
5
+ "./clients/bounded-telemetry.js",
6
+ "./clients/build-identity.js",
7
+ "./clients/bus-events-logger.js",
8
+ "./clients/bus-publish.js",
9
+ "./clients/cache-manager.js",
10
+ "./clients/cache-observability.js",
11
+ "./clients/cascade-format.js",
12
+ "./clients/console-guard-install.js",
13
+ "./clients/debug-handles.js",
14
+ "./clients/debug-heap.js",
15
+ "./clients/degradation-ledger.js",
16
+ "./clients/diagnostic-tracker.js",
17
+ "./clients/diagnostics-publish.js",
18
+ "./clients/dispatch/lazy.js",
19
+ "./clients/dispatch/runner-context.js",
20
+ "./clients/disposition-publish.js",
21
+ "./clients/event-loop-monitor.js",
22
+ "./clients/extension-log.js",
23
+ "./clients/extension-mode.js",
24
+ "./clients/file-utils.js",
25
+ "./clients/format-events-publish.js",
26
+ "./clients/format-service.js",
27
+ "./clients/formatters-lazy.js",
28
+ "./clients/git-guard.js",
29
+ "./clients/host-ports.js",
30
+ "./clients/installer/index.js",
31
+ "./clients/instance-reaper.js",
32
+ "./clients/instance-registry.js",
33
+ "./clients/latency-logger.js",
34
+ "./clients/lens-config.js",
35
+ "./clients/lens-events.js",
36
+ "./clients/lens-flag-registry.js",
37
+ "./clients/lsp-budget.js",
38
+ "./clients/lsp-lazy.js",
39
+ "./clients/lsp-status.js",
40
+ "./clients/lsp/cascade-tier.js",
41
+ "./clients/lsp/config.js",
42
+ "./clients/lsp/index.js",
43
+ "./clients/memory-sampler.js",
44
+ "./clients/message-end-attribution.js",
45
+ "./clients/package-root.js",
46
+ "./clients/path-attribution-telemetry.js",
47
+ "./clients/path-utils.js",
48
+ "./clients/project-lens-config.js",
49
+ "./clients/project-trust.js",
50
+ "./clients/quiet-window.js",
51
+ "./clients/read-bridge.js",
52
+ // index.ts:56 is type-only, but dependency-cruiser parses it as an edge.
53
+ "./clients/read-guard.js",
54
+ "./clients/recent-touches.js",
55
+ "./clients/runtime-agent-end.js",
56
+ "./clients/runtime-context.js",
57
+ "./clients/runtime-coordinator.js",
58
+ "./clients/runtime-session.js",
59
+ "./clients/runtime-tool-call.js",
60
+ "./clients/runtime-tool-result.js",
61
+ "./clients/runtime-turn.js",
62
+ "./clients/safe-spawn.js",
63
+ "./clients/session-event-guard.js",
64
+ "./clients/session-lifecycle.js",
65
+ "./clients/session-start-observability.js",
66
+ "./clients/session-state-store.js",
67
+ "./clients/sessionstart-logger.js",
68
+ "./clients/smells-rollup.js",
69
+ "./clients/startup-timing.js",
70
+ "./clients/tool-definition.js",
71
+ "./clients/tool-render.js",
72
+ "./clients/tool-set-policy.js",
73
+ "./clients/turn-summary-render.js",
74
+ "./clients/turn-summary.js",
75
+ "./clients/user-notify.js",
76
+ "./clients/vanished-instance-marker.js",
77
+ "./clients/warm-attach.js",
78
+ "./clients/widget-state.js"
79
+ ]
@@ -2,6 +2,7 @@ import * as path from "node:path";
2
2
  import { isTestMode } from "./env-utils.js";
3
3
  import { getGlobalPiLensDir } from "./file-utils.js";
4
4
  import { createNdjsonLogger } from "./ndjson-logger.js";
5
+ import { normalizeFilePath } from "./path-utils.js";
5
6
  const AW_LOG_DIR = getGlobalPiLensDir();
6
7
  const AW_LOG_FILE = path.join(AW_LOG_DIR, "actionable-warnings.log");
7
8
  const AW_LOG_BACKUP_FILE = path.join(AW_LOG_DIR, "actionable-warnings.log.1");
@@ -12,11 +13,25 @@ const writer = createNdjsonLogger({
12
13
  maxBytes: MAX_LOG_BYTES,
13
14
  backupPath: AW_LOG_BACKUP_FILE,
14
15
  });
16
+ /**
17
+ * #2219 (the #2141 class): `filePath` reaches here from
18
+ * `actionable-warnings.ts`'s raw `path.resolve(cwd, file)` — the file
19
+ * imports `normalizeMapKey` and uses it for map lookups on this same value,
20
+ * but never normalizes what gets logged. `filePath` is optional (several
21
+ * events, e.g. `report_started`/`report_complete`, carry none), so only
22
+ * normalize when present.
23
+ */
15
24
  export function logActionableWarningsEvent(entry) {
16
25
  if (isTestMode()) {
17
26
  return;
18
27
  }
19
- writer.log({ ts: new Date().toISOString(), ...entry });
28
+ writer.log({
29
+ ts: new Date().toISOString(),
30
+ ...entry,
31
+ ...(entry.filePath !== undefined
32
+ ? { filePath: normalizeFilePath(entry.filePath) }
33
+ : {}),
34
+ });
20
35
  }
21
36
  export function getActionableWarningsLogPath() {
22
37
  return AW_LOG_FILE;
@@ -13,6 +13,7 @@ import * as os from "node:os";
13
13
  import * as path from "node:path";
14
14
  import { AstGrepRuleManager } from "./ast-grep-rule-manager.js";
15
15
  import { resolvePackagePath } from "./package-root.js";
16
+ import { truncatedByOutputCap } from "./spawn-output-cap.js";
16
17
  import { SgRunner, } from "./sg-runner.js";
17
18
  // --- Client ---
18
19
  function extractDebugAst(raw) {
@@ -263,6 +264,18 @@ export class AstGrepClient {
263
264
  const result = await this.runner.execRaw(args, 10_000, options);
264
265
  const stderr = result.stderr.trim();
265
266
  const stdout = result.stdout.trim();
267
+ // FIRST (#2100): hitting `execRaw`'s cap SIGTERMs ast-grep, so a
268
+ // truncated run also carries the kill's error message. Read after the
269
+ // two checks below, this guard could only ever describe a run that
270
+ // exited before the signal reached it. `execRaw` re-spells `failure`
271
+ // in its own vocabulary but keeps the timeout/aborted spellings, so
272
+ // those runs still fall through to the error branch below.
273
+ if (truncatedByOutputCap(result)) {
274
+ return {
275
+ valid: false,
276
+ error: "ast-grep validation output was truncated",
277
+ };
278
+ }
266
279
  if (result.error)
267
280
  return { valid: false, error: result.error };
268
281
  if (result.status !== 0 && !(result.status === 1 && !stderr)) {
@@ -272,12 +285,6 @@ export class AstGrepClient {
272
285
  `ast-grep validation failed with exit code ${result.status}`,
273
286
  };
274
287
  }
275
- if (result.outputTruncated) {
276
- return {
277
- valid: false,
278
- error: "ast-grep validation output was truncated",
279
- };
280
- }
281
288
  if (stderrHasError(stderr))
282
289
  return { valid: false, error: stderr };
283
290
  const warning = stderr || stdout || undefined;
@@ -0,0 +1,12 @@
1
+ import { createSingleFlight } from "./single-flight.js";
2
+ /** Create a flight registry whose lifetime belongs to the caller. */
3
+ export function createAvailabilityProbeFlight(options = {}) {
4
+ const flights = createSingleFlight(options);
5
+ return {
6
+ run(key, probe) {
7
+ const joined = flights.has(key);
8
+ return { promise: flights.run(key, probe), joined };
9
+ },
10
+ clear: () => flights.clear(),
11
+ };
12
+ }
@@ -179,6 +179,7 @@ export class BiomeClient {
179
179
  latched: true,
180
180
  hostStallMs,
181
181
  budgetMs: PROBE_TIMEOUT_MS,
182
+ classifiedBy: "probe",
182
183
  });
183
184
  return { outcome: "success", value: true };
184
185
  }
@@ -225,6 +226,12 @@ export class BiomeClient {
225
226
  latched: true,
226
227
  hostStallMs: this.lastProbeHostStallMs,
227
228
  budgetMs: PROBE_TIMEOUT_MS,
229
+ classifiedBy: "caller",
230
+ evidence: {
231
+ install: "succeeded",
232
+ binary: "biome",
233
+ source: "managed-dir",
234
+ },
228
235
  });
229
236
  }
230
237
  return true;
@@ -43,12 +43,32 @@
43
43
  * runtime condition — that edge is invisible to this gate, so a blocker whose only
44
44
  * stale dependency arrives that way survives a replay. This is an honest boundary,
45
45
  * not a silently-closed one.
46
+ *
47
+ * Delivery cap (#1950). A demoted-but-confirmable entry (`alreadyStale` above)
48
+ * is deliberately NOT retired after its past-EOF sibling's one-delivery rule
49
+ * (#1944, `blocker-past-eof.ts`) — its coordinates are still in bounds, so a
50
+ * fresh dispatch can genuinely confirm or clear it, and retiring on delivery
51
+ * one would discard a recoverable finding. But nothing capped how many times
52
+ * the SAME demoted record re-serves: incident data (#1944's lane sweep) showed
53
+ * 18 `alreadyStale` re-serves in one window, with repeat deliveries carrying
54
+ * near-zero information after the first. `runtime-turn.ts` counts each
55
+ * delivery (`InlineBlockerRecord.staleDeliveryCount`) and retires the record
56
+ * via `RuntimeCoordinator.retireDemotedDependencyDriftBlocker` once it reaches
57
+ * `DEPENDENCY_DRIFT_MAX_DELIVERIES` — a count, not a TTL or a session
58
+ * boundary, because "how many times has the agent already been told" is the
59
+ * quantity that actually went to zero information, not "how much time has
60
+ * passed". The ledger reason says "capped, re-run can still confirm" so it
61
+ * reads distinctly from #1944's "retired, unrecoverable" — a capped record
62
+ * COULD still resolve a fresh dispatch; it just stopped being handed one for
63
+ * free.
46
64
  */
47
65
  import * as fs from "node:fs";
48
66
  import { normalizeEphemeralMapKey } from "./path-utils.js";
49
67
  import { resolveImportToFiles } from "./review-graph/import-resolvers.js";
50
68
  import { getSharedTreeSitterClient, resolveTreeSitterLanguage, } from "./tree-sitter-shared.js";
51
69
  import { TreeSitterSymbolExtractor } from "./tree-sitter-symbol-extractor.js";
70
+ /** See the module doc's "Delivery cap (#1950)" section above. */
71
+ export const DEPENDENCY_DRIFT_MAX_DELIVERIES = 3;
52
72
  /**
53
73
  * Upper bound on the number of forward imports stat-checked for a single blocker.
54
74
  * Blocking entries are rare, but one file can import many modules; the sweep must
@@ -24,10 +24,9 @@
24
24
  * that must re-arm at `session_start` and will eventually be forgotten
25
25
  * (the #1266–#1625 defect class).
26
26
  * 2. **A per-turn cap names its turn.** `capPerTurn` carries the caller's
27
- * `turnIndex` in the same object as the limit, so there is no separate
28
- * reset to wire and forget: the counters clear when the observed turn
29
- * changes. This is the same lazy clear-on-transition shape as
30
- * `noteImportResolutionMemoTurn` (`clients/blocker-freshness.ts`).
27
+ * `turnIndex` in the same object as the limit. A bounded 64-turn window
28
+ * keeps overlapping async turns independent; results older than that window
29
+ * fail closed. Session start clears the window when numbering restarts.
31
30
  * 3. **Identity always survives.** Every emitted record carries the
32
31
  * discriminating identity in `filePath` and in `metadata.identity`, so
33
32
  * aggregation can still answer "which file, which method, which record is
@@ -85,6 +84,14 @@ export const BOUNDED_TELEMETRY_PHASES = [
85
84
  * exact count.
86
85
  */
87
86
  "lsp_warm_client_missing",
87
+ /** #2052: a file's nearest LSP root is outside the session ceiling. */
88
+ "lsp_capability_skip",
89
+ /**
90
+ * #2044: failed-first test state was retired after a confirmed missing path,
91
+ * retained because the filesystem verdict was indeterminate, or evicted at
92
+ * the state cap. Selection checks and detailed rows are both capped per turn.
93
+ */
94
+ "test_runner_failed_target_state",
88
95
  /**
89
96
  * #1723: an event-loop block at or above the floor. Not a degradation, so
90
97
  * no ledger kind; bounded by call cadence (one `turn_end` runs it once per
@@ -97,6 +104,17 @@ export const BOUNDED_TELEMETRY_PHASES = [
97
104
  * queue of them, so it is rising-edge per event name.
98
105
  */
99
106
  "session_event_stale_ctx_skip",
107
+ /**
108
+ * #2007: a worktree-mutating git command declined because another live
109
+ * session shares this dirty checkout. Rising edge per checkout root; the
110
+ * ledger keeps the exact repeat count.
111
+ */
112
+ "shared_checkout_switch_blocked",
113
+ /**
114
+ * #2007: `git status` could not report the working-tree state, so the same
115
+ * command was declined on an UNKNOWN rather than assumed clean.
116
+ */
117
+ "shared_checkout_probe_failed",
100
118
  ];
101
119
  const REGISTERED_PHASES = new Set(BOUNDED_TELEMETRY_PHASES);
102
120
  /** Report whether a phase name is in the registry (used by the sweep). */
@@ -104,14 +122,15 @@ export function isBoundedTelemetryPhase(phase) {
104
122
  return REGISTERED_PHASES.has(phase);
105
123
  }
106
124
  /**
107
- * Per-turn admission counters, keyed by phase. Cleared lazily when a call
108
- * observes a turn index different from the one the counters were built for,
109
- * and eagerly by `resetBoundedTelemetry` at `session_start` (a new session
110
- * restarts turn numbering at 0, so the lazy check alone could carry a stale
111
- * count across a session boundary that happened to land on the same index).
125
+ * Per-turn admission counters, keyed by turn and phase. Multiple turns remain
126
+ * live because async work can finish T1 after T2 has already emitted. The map
127
+ * retains a bounded recent window; work older than that window fails closed
128
+ * rather than reopening an exhausted budget. Session start clears all state.
112
129
  */
130
+ const MAX_TRACKED_TURN_CAPS = 64;
113
131
  let turnCounts = new Map();
114
- let countedTurnIndex;
132
+ let latestCountedTurnIndex;
133
+ let retiredThroughTurnIndex = Number.NEGATIVE_INFINITY;
115
134
  /**
116
135
  * Decide whether this occurrence earns a detailed record, and count it in the
117
136
  * ledger either way. Returns `true` when the caller should write the record.
@@ -180,14 +199,25 @@ export function emitBounded(phase, identity, payload, options = {}) {
180
199
  function admitAgainstTurnCap(phase, cap) {
181
200
  if (cap === undefined)
182
201
  return true;
183
- if (countedTurnIndex !== cap.turnIndex) {
184
- turnCounts = new Map();
185
- countedTurnIndex = cap.turnIndex;
202
+ if (cap.turnIndex <= retiredThroughTurnIndex)
203
+ return false;
204
+ let counts = turnCounts.get(cap.turnIndex);
205
+ if (!counts) {
206
+ if (turnCounts.size >= MAX_TRACKED_TURN_CAPS) {
207
+ const oldestTrackedTurn = Math.min(...turnCounts.keys());
208
+ if (cap.turnIndex < oldestTrackedTurn)
209
+ return false;
210
+ turnCounts.delete(oldestTrackedTurn);
211
+ retiredThroughTurnIndex = Math.max(retiredThroughTurnIndex, oldestTrackedTurn);
212
+ }
213
+ counts = new Map();
214
+ turnCounts.set(cap.turnIndex, counts);
186
215
  }
187
- const used = turnCounts.get(phase) ?? 0;
216
+ latestCountedTurnIndex = Math.max(latestCountedTurnIndex ?? cap.turnIndex, cap.turnIndex);
217
+ const used = counts.get(phase) ?? 0;
188
218
  if (used >= cap.limit)
189
219
  return false;
190
- turnCounts.set(phase, used + 1);
220
+ counts.set(phase, used + 1);
191
221
  return true;
192
222
  }
193
223
  /**
@@ -198,9 +228,16 @@ function admitAgainstTurnCap(phase, cap) {
198
228
  */
199
229
  export function resetBoundedTelemetry() {
200
230
  turnCounts = new Map();
201
- countedTurnIndex = undefined;
231
+ latestCountedTurnIndex = undefined;
232
+ retiredThroughTurnIndex = Number.NEGATIVE_INFINITY;
233
+ }
234
+ /** Test-only: one turn's live admission count for a phase. */
235
+ export function _boundedTurnCountForTest(phase, turnIndex = latestCountedTurnIndex) {
236
+ if (turnIndex === undefined)
237
+ return 0;
238
+ return turnCounts.get(turnIndex)?.get(phase) ?? 0;
202
239
  }
203
- /** Test-only: the live per-turn admission count for a phase. */
204
- export function _boundedTurnCountForTest(phase) {
205
- return turnCounts.get(phase) ?? 0;
240
+ /** Test-only: number of turn buckets retained by the bounded counter. */
241
+ export function _boundedTrackedTurnsForTest() {
242
+ return turnCounts.size;
206
243
  }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * #1775: `sessionstart.log` recorded no build identity — no commit hash, no
3
+ * build timestamp. Dogfood forensics ("does this session include PR #1727?")
4
+ * had to reconstruct that by hand: run `git merge-base --is-ancestor` in the
5
+ * serving checkout and compare `dist/` mtimes against the session-start time.
6
+ *
7
+ * The running extension can be a different checkout than the repo an
8
+ * investigator is looking at, so identity is derived from the RUNNING
9
+ * build's own files — `getPackageRoot` (already used by grammar-source.ts to
10
+ * find pi-lens's own assets under both the unbundled dev layout and a
11
+ * packaged install) plus the mtime of the very entry file that is executing
12
+ * right now — never from `process.cwd()` and never assumed to be a git
13
+ * checkout.
14
+ *
15
+ * Commit resolution reuses `review-graph/git-identity.ts`'s
16
+ * `resolveGitIdentity` — a PURE filesystem read, no `git` subprocess. This
17
+ * runs inside the `session_start` handler, ahead of the #473
18
+ * concurrent-secondary classification, so every in-process subagent pays
19
+ * whatever this costs; a synchronous `git rev-parse`/`git status` spawn pair
20
+ * (fix round 1's approach, reverted here) measured 150-300ms typical and up
21
+ * to ~10s worst case on two 5s timeouts. `resolveGitIdentity` is a handful of
22
+ * `fs.readFileSync` calls, LRU-cached per process, so this path never spawns.
23
+ *
24
+ * The dirty working-tree flag genuinely needs `git status`, which has no
25
+ * spawn-free equivalent — so it is dropped from this record rather than
26
+ * reintroducing a spawn on the session-start hot path (refs #1775 followup
27
+ * comment: dirty flag deferred, commit + entryMtime + version shipped).
28
+ */
29
+ import * as fs from "node:fs";
30
+ import * as path from "node:path";
31
+ import { fileURLToPath } from "node:url";
32
+ import { isTestMode } from "./env-utils.js";
33
+ import { getPackageRoot } from "./package-root.js";
34
+ import { normalizeFilePath } from "./path-utils.js";
35
+ import { resolveGitIdentity } from "./review-graph/git-identity.js";
36
+ /**
37
+ * A `.git` found by walking up from `root` only counts as owning `root` when
38
+ * its resolved worktree top-level IS `root` — not merely an ancestor of it.
39
+ * Without this check, a packaged install nested anywhere under an unrelated
40
+ * repo (a dotfiles checkout in $HOME is the common case) would confidently
41
+ * report that repo's HEAD as the running build's identity.
42
+ */
43
+ function resolveOwnedCommit(root) {
44
+ const identity = resolveGitIdentity(root);
45
+ if (!identity)
46
+ return "unknown";
47
+ const normalizedRoot = normalizeFilePath(path.resolve(root));
48
+ if (identity.worktreeRoot !== normalizedRoot)
49
+ return "unknown";
50
+ return identity.headCommit.slice(0, 8);
51
+ }
52
+ function readPackageVersion(root) {
53
+ try {
54
+ const raw = fs.readFileSync(path.join(root, "package.json"), "utf-8");
55
+ const pkg = JSON.parse(raw);
56
+ return pkg.version ?? "unknown";
57
+ }
58
+ catch {
59
+ return "unknown";
60
+ }
61
+ }
62
+ function statEntryMtime(entryImportMetaUrl) {
63
+ try {
64
+ return fs.statSync(fileURLToPath(entryImportMetaUrl)).mtime.toISOString();
65
+ }
66
+ catch {
67
+ // Entry path unresolvable (e.g. a non-file:// URL) — "unknown" rather
68
+ // than throw out of a session-start observability line.
69
+ return "unknown";
70
+ }
71
+ }
72
+ /**
73
+ * Resolve the identity of the build currently running — pure filesystem
74
+ * reads, no subprocess. `entryImportMetaUrl` must be the ENTRY module's own
75
+ * `import.meta.url` (index.ts) — passing a different module's URL would
76
+ * report that module's mtime instead of the running build's.
77
+ *
78
+ * Returns `undefined` inside the test runner: nothing computed, nothing
79
+ * logged (`sessionstart-logger`'s writer is a no-op there too), so a caller
80
+ * on the session-start hot path never pays this cost during tests.
81
+ */
82
+ export function getBuildIdentity(entryImportMetaUrl) {
83
+ if (isTestMode())
84
+ return undefined;
85
+ const root = getPackageRoot(entryImportMetaUrl);
86
+ return {
87
+ commit: resolveOwnedCommit(root),
88
+ entryMtime: statEntryMtime(entryImportMetaUrl),
89
+ version: readPackageVersion(root),
90
+ };
91
+ }
92
+ /** One bounded, human-readable line for sessionstart.log's free-text writer. */
93
+ export function formatBuildIdentity(identity) {
94
+ return `session_start: build identity — commit=${identity.commit} entryMtime=${identity.entryMtime} version=${identity.version}`;
95
+ }
@@ -23,6 +23,7 @@ import { appendRecentTouches } from "./recent-touches.js";
23
23
  import { createLiveBusEmitter, recordStaleBusFailure, resolveLiveBusEmitter, } from "./live-bus-emitter.js";
24
24
  export const BUS_FILES_TOUCHED_EVENT = "pilens:files:touched";
25
25
  export const BUS_FILES_TOUCHED_VERSION = 1;
26
+ const BUS_LOG_PATH_CAP = 64;
26
27
  const liveEmitter = createLiveBusEmitter();
27
28
  let hasLoggedFailure = false;
28
29
  // Log-once-per-process guards for the two structural-no-op outcomes below
@@ -148,6 +149,8 @@ export function publishFilesTouched(args) {
148
149
  cwd: payload.cwd,
149
150
  reason: args.reason,
150
151
  fileCount: payload.paths.length,
152
+ writer: "pi-lens",
153
+ paths: payload.paths.slice(0, BUS_LOG_PATH_CAP),
151
154
  });
152
155
  }
153
156
  catch (err) {
@@ -11,6 +11,7 @@ import { getProjectDataDir } from "../file-utils.js";
11
11
  import { readJsonCache } from "../json-cache-read.js";
12
12
  import { resolvePackagePath } from "../package-root.js";
13
13
  import { writeFileAtomic } from "../atomic-write.js";
14
+ import { compareOrdinal } from "../string-utils.js";
14
15
  // v4: cache skip_test_files + fix_action — v3 entries silently dropped them,
15
16
  // and ruleHash (rule-file mtimes) never invalidates on a code-only fix.
16
17
  // v5 (#675): rule SELECTION changed in code — javascript no longer inherits the
@@ -69,7 +70,7 @@ export class RuleCache {
69
70
  }
70
71
  computeRuleHash(ruleFiles) {
71
72
  const hash = crypto.createHash("sha256");
72
- for (const file of ruleFiles.sort((a, b) => a.localeCompare(b))) {
73
+ for (const file of ruleFiles.sort(compareOrdinal)) {
73
74
  const resolved = path.resolve(file);
74
75
  if (!fs.existsSync(resolved))
75
76
  continue;
@@ -50,6 +50,7 @@
50
50
  import { createHash } from "node:crypto";
51
51
  import { lazyEnvNumber } from "./env-utils.js";
52
52
  import { logLatency } from "./latency-logger.js";
53
+ import { compareOrdinal } from "./string-utils.js";
53
54
  /** RuntimeCoordinator's counter is process-global when sessions share a host. */
54
55
  const PROCESS_TURN_SCOPE = "process-global-runtime";
55
56
  const SECONDARY_TURN_SCOPE = "unavailable-concurrent-secondary";
@@ -490,7 +491,7 @@ function boundedHashArray(value, depth, seen, state) {
490
491
  return `array:${value.length}:[${items.join(",")}]`;
491
492
  }
492
493
  function boundedHashObject(value, depth, seen, state) {
493
- const keys = Object.keys(value).sort((left, right) => left.localeCompare(right));
494
+ const keys = Object.keys(value).sort((left, right) => compareOrdinal(left, right));
494
495
  if (keys.length > 24)
495
496
  state.contentTruncated = true;
496
497
  const fields = keys
@@ -3,17 +3,31 @@ import { isTestMode } from "./env-utils.js";
3
3
  import { getGlobalPiLensDir } from "./file-utils.js";
4
4
  import { createNdjsonLogger } from "./ndjson-logger.js";
5
5
  import { getMaxLogSizeMB } from "./log-cleanup.js";
6
+ import { normalizeLoggedPath } from "./path-utils.js";
6
7
  const CASCADE_LOG_DIR = getGlobalPiLensDir();
7
8
  const CASCADE_LOG_FILE = path.join(CASCADE_LOG_DIR, "cascade.log");
8
9
  const writer = createNdjsonLogger({
9
10
  filePath: CASCADE_LOG_FILE,
10
11
  maxBytes: getMaxLogSizeMB() * 1024 * 1024,
11
12
  });
13
+ /**
14
+ * #2219 (the #2141 class): call sites across `dispatch/integration.ts`,
15
+ * `runtime-turn.ts`, and `runtime-coordinator.ts` feed a mix of raw
16
+ * `filePath`/`cwd` params and (for `lsp/cascade-tier.ts`) the
17
+ * `"<quiet-window>"` sentinel. Normalize once here, the single emit seam —
18
+ * same pattern as `review-graph-logger.ts`'s `logReviewGraph`, guarded via
19
+ * `normalizeLoggedPath` so the non-path sentinel passes through unchanged
20
+ * instead of being resolved against the process cwd.
21
+ */
12
22
  export function logCascade(entry) {
13
23
  if (isTestMode()) {
14
24
  return;
15
25
  }
16
- writer.log({ ts: new Date().toISOString(), ...entry });
26
+ writer.log({
27
+ ts: new Date().toISOString(),
28
+ ...entry,
29
+ filePath: normalizeLoggedPath(entry.filePath),
30
+ });
17
31
  }
18
32
  export function getCascadeLogPath() {
19
33
  return CASCADE_LOG_FILE;
@@ -101,9 +101,9 @@ export function parseVultureOutput(output, root) {
101
101
  *
102
102
  * vulture finds unused functions, classes, methods, imports, variables and
103
103
  * attributes by static analysis of the whole tree. It has no JSON reporter, so
104
- * we parse its stable one-line-per-finding text output. It exits 1 when it
105
- * FINDS dead code (linter convention), so a non-zero exit with parseable
106
- * output is success, not failure.
104
+ * we parse its stable one-line-per-finding text output. A clean run exits 0;
105
+ * found dead code exits 3 with findings on stdout. A non-zero exit with
106
+ * parseable output is success, not failure (see runAnalyze for the table).
107
107
  */
108
108
  export class PythonDeadCodeClient {
109
109
  id = "python";
@@ -245,6 +245,7 @@ export class PythonDeadCodeClient {
245
245
  latched: true,
246
246
  hostStallMs,
247
247
  budgetMs: 5000,
248
+ classifiedBy: "probe",
248
249
  });
249
250
  return true;
250
251
  }
@@ -370,14 +371,16 @@ export class PythonDeadCodeClient {
370
371
  durationMs,
371
372
  };
372
373
  }
373
- // vulture writes parse/usage errors to stderr and exits 1 with no
374
- // stdout findings; distinguish that from "found dead code" (exit 1 WITH
375
- // findings on stdout). #1736 sweep: the ORIGINAL guard here required
376
- // non-empty stderr to call it an error, so a nonzero exit with BOTH
377
- // empty stdout and empty stderr (a silent crash) still fell through to
378
- // "No dead code found" -- the same empty-distinguishes-clean-from-errored
379
- // gap the knip fix closes. A nonzero exit with no findings on stdout is
380
- // never clean now, regardless of whether stderr said anything.
374
+ // Verified exit-code table (vulture 2.16, probed live during the #1758
375
+ // review): a clean run exits 0 with empty stdout; dead code found exits
376
+ // 3 with findings on stdout; invalid input or a parse error exits 1
377
+ // with empty stdout and the error on stderr. #1736 sweep: the ORIGINAL
378
+ // guard here required non-empty stderr to call it an error, so a
379
+ // nonzero exit with BOTH empty stdout and empty stderr (a silent crash)
380
+ // still fell through to "No dead code found" -- the same
381
+ // empty-distinguishes-clean-from-errored gap the knip fix closes. A
382
+ // nonzero exit with no findings on stdout is never clean now,
383
+ // regardless of whether stderr said anything.
381
384
  const output = result.stdout || "";
382
385
  if (!output.trim()) {
383
386
  const stderr = (result.stderr || "").trim();