@herbertgao/pi-extensions 2026.9.6 → 2026.9.7

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 (240) hide show
  1. package/README.md +3 -8
  2. package/THIRD_PARTY_NOTICES.md +0 -4
  3. package/node_modules/@narumitw/pi-btw/README.md +2 -0
  4. package/node_modules/@narumitw/pi-btw/dist/index.ts +284 -365
  5. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
  6. package/node_modules/@narumitw/pi-btw/package.json +52 -53
  7. package/node_modules/@narumitw/pi-btw/src/bring-to-main.ts +494 -547
  8. package/node_modules/@narumitw/pi-btw/src/btw.ts +814 -856
  9. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +673 -674
  10. package/node_modules/@narumitw/pi-btw/src/keybindings.ts +228 -270
  11. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +309 -318
  12. package/node_modules/@narumitw/pi-btw/src/menu.ts +416 -454
  13. package/node_modules/@narumitw/pi-btw/src/settings.ts +203 -219
  14. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +173 -201
  15. package/node_modules/@narumitw/pi-btw/src/text.ts +21 -23
  16. package/node_modules/@narumitw/pi-btw/src/transcript-markdown.ts +65 -0
  17. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +611 -662
  18. package/node_modules/pi-lens/CHANGELOG.md +239 -0
  19. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -0
  20. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +30 -4
  21. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +2 -0
  22. package/node_modules/pi-lens/dist/clients/bash-file-access.js +26 -0
  23. package/node_modules/pi-lens/dist/clients/biome-client.js +1 -1
  24. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +403 -35
  25. package/node_modules/pi-lens/dist/clients/bootstrap.js +6 -1
  26. package/node_modules/pi-lens/dist/clients/cascade-format.js +200 -4
  27. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +10 -2
  28. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +16 -1
  29. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +12 -1
  30. package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +65 -0
  31. package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +28 -0
  32. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +9 -2
  33. package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +222 -0
  34. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +389 -13
  35. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +175 -25
  36. package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +62 -0
  37. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +21 -41
  38. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +6 -1
  39. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +7 -7
  40. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -12
  41. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +2 -21
  42. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +1 -1
  43. package/node_modules/pi-lens/dist/clients/dispatch/types.js +4 -1
  44. package/node_modules/pi-lens/dist/clients/disposition-publish.js +1 -0
  45. package/node_modules/pi-lens/dist/clients/effective-config.js +1 -1
  46. package/node_modules/pi-lens/dist/clients/file-kinds.js +26 -1
  47. package/node_modules/pi-lens/dist/clients/file-role.js +21 -21
  48. package/node_modules/pi-lens/dist/clients/file-utils.js +156 -10
  49. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +56 -9
  50. package/node_modules/pi-lens/dist/clients/format-service.js +39 -21
  51. package/node_modules/pi-lens/dist/clients/formatters.js +60 -13
  52. package/node_modules/pi-lens/dist/clients/generation-guard.js +2 -2
  53. package/node_modules/pi-lens/dist/clients/git-guard.js +11 -3
  54. package/node_modules/pi-lens/dist/clients/indent-retarget.js +78 -8
  55. package/node_modules/pi-lens/dist/clients/installer/index.js +333 -114
  56. package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +7 -0
  57. package/node_modules/pi-lens/dist/clients/instance-reaper.js +20 -10
  58. package/node_modules/pi-lens/dist/clients/language-profile.js +23 -1
  59. package/node_modules/pi-lens/dist/clients/language-registry.js +5 -1
  60. package/node_modules/pi-lens/dist/clients/latency-logger.js +4 -1
  61. package/node_modules/pi-lens/dist/clients/lens-engine.js +12 -3
  62. package/node_modules/pi-lens/dist/clients/lens-events.js +2 -1
  63. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +79 -0
  64. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +2 -1
  65. package/node_modules/pi-lens/dist/clients/lsp/client.js +25 -21
  66. package/node_modules/pi-lens/dist/clients/lsp/config.js +2 -2
  67. package/node_modules/pi-lens/dist/clients/lsp/index.js +7 -0
  68. package/node_modules/pi-lens/dist/clients/lsp/launch.js +16 -4
  69. package/node_modules/pi-lens/dist/clients/lsp/server.js +19 -2
  70. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +2 -1
  71. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +5 -3
  72. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +12 -8
  73. package/node_modules/pi-lens/dist/clients/mcp/session.js +36 -4
  74. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +24 -7
  75. package/node_modules/pi-lens/dist/clients/observed-mutation.js +40 -2
  76. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +23 -3
  77. package/node_modules/pi-lens/dist/clients/opengrep-client.js +110 -15
  78. package/node_modules/pi-lens/dist/clients/path-utils.js +123 -3
  79. package/node_modules/pi-lens/dist/clients/performance-report.js +5 -1
  80. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +1 -1
  81. package/node_modules/pi-lens/dist/clients/pipeline.js +74 -5
  82. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +37 -5
  83. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +145 -18
  84. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +4 -1
  85. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +59 -3
  86. package/node_modules/pi-lens/dist/clients/project-lens-config.js +45 -2
  87. package/node_modules/pi-lens/dist/clients/read-guard.js +70 -11
  88. package/node_modules/pi-lens/dist/clients/resource-sampler.js +147 -9
  89. package/node_modules/pi-lens/dist/clients/ruff-client.js +1 -1
  90. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +33 -5
  91. package/node_modules/pi-lens/dist/clients/runtime-context.js +6 -2
  92. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +77 -7
  93. package/node_modules/pi-lens/dist/clients/runtime-session.js +21 -2
  94. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +43 -12
  95. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +330 -85
  96. package/node_modules/pi-lens/dist/clients/runtime-turn.js +147 -17
  97. package/node_modules/pi-lens/dist/clients/safe-spawn.js +258 -5
  98. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +6 -0
  99. package/node_modules/pi-lens/dist/clients/session-event-guard.js +50 -5
  100. package/node_modules/pi-lens/dist/clients/sg-runner.js +23 -11
  101. package/node_modules/pi-lens/dist/clients/sgconfig.js +79 -2
  102. package/node_modules/pi-lens/dist/clients/test-runner-client.js +6 -41
  103. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +43 -0
  104. package/node_modules/pi-lens/dist/clients/tool-agreement.js +423 -0
  105. package/node_modules/pi-lens/dist/clients/tool-config.js +50 -0
  106. package/node_modules/pi-lens/dist/clients/tool-cwd.js +47 -76
  107. package/node_modules/pi-lens/dist/clients/tool-policy.js +163 -7
  108. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +120 -0
  109. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +127 -189
  110. package/node_modules/pi-lens/dist/clients/widget-state.js +98 -0
  111. package/node_modules/pi-lens/dist/clients/word-index.js +5 -2
  112. package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +7 -0
  113. package/node_modules/pi-lens/dist/index.js +27416 -24153
  114. package/node_modules/pi-lens/dist/mcp/server.js +5 -2
  115. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +7 -1
  116. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +168 -53
  117. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +244 -72
  118. package/node_modules/pi-lens/docs/audit1.md +5 -5
  119. package/node_modules/pi-lens/docs/dependencies.md +11 -0
  120. package/node_modules/pi-lens/docs/dispositions.md +52 -0
  121. package/node_modules/pi-lens/docs/environment-variables.md +2 -2
  122. package/node_modules/pi-lens/docs/features.md +4 -4
  123. package/node_modules/pi-lens/docs/globalconfig.md +3 -1
  124. package/node_modules/pi-lens/docs/language-coverage.md +1 -0
  125. package/node_modules/pi-lens/docs/pi-lens-fixer.md +53 -94
  126. package/node_modules/pi-lens/docs/pi-lens-investigator.md +43 -38
  127. package/node_modules/pi-lens/docs/pi-lens-retro.md +97 -0
  128. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +53 -66
  129. package/node_modules/pi-lens/docs/release-qa-baseline.md +22 -0
  130. package/node_modules/pi-lens/docs/settings.md +7 -4
  131. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
  132. package/node_modules/pi-lens/docs/usage.md +2 -0
  133. package/node_modules/pi-lens/package.json +1 -1
  134. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +24 -12
  135. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +40 -0
  136. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +15 -0
  137. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +7 -0
  138. package/package.json +5 -11
  139. package/node_modules/@luxusai/pi-hindsight/CHANGELOG.md +0 -376
  140. package/node_modules/@luxusai/pi-hindsight/README.md +0 -92
  141. package/node_modules/@luxusai/pi-hindsight/docs/adr/001-memory-lifecycle-and-scope.md +0 -56
  142. package/node_modules/@luxusai/pi-hindsight/docs/adr/002-explicit-routing-strategy-seam.md +0 -169
  143. package/node_modules/@luxusai/pi-hindsight/docs/adr/003-tui-memory-mode-vocabulary.md +0 -107
  144. package/node_modules/@luxusai/pi-hindsight/docs/adr/004-lifeos-dual-bank-design.md +0 -89
  145. package/node_modules/@luxusai/pi-hindsight/docs/adr/005-domain-banks-and-agent-first-surface.md +0 -189
  146. package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark.webp +0 -0
  147. package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark@2x.webp +0 -0
  148. package/node_modules/@luxusai/pi-hindsight/docs/coding-memory-evaluation.md +0 -54
  149. package/node_modules/@luxusai/pi-hindsight/docs/compatibility.md +0 -75
  150. package/node_modules/@luxusai/pi-hindsight/docs/hindsight-core-functions.md +0 -300
  151. package/node_modules/@luxusai/pi-hindsight/docs/mission-and-mental-model-quality.md +0 -158
  152. package/node_modules/@luxusai/pi-hindsight/docs/next-opt-out-design.md +0 -164
  153. package/node_modules/@luxusai/pi-hindsight/docs/risky-memory-modes.md +0 -139
  154. package/node_modules/@luxusai/pi-hindsight/docs/starter-mental-model-suggestions.md +0 -74
  155. package/node_modules/@luxusai/pi-hindsight/docs/surface-reference.md +0 -261
  156. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-operations.ts +0 -151
  157. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-selection.ts +0 -18
  158. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-settings-presenter.ts +0 -46
  159. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-templates.ts +0 -383
  160. package/node_modules/@luxusai/pi-hindsight/extensions/banks/banking.ts +0 -240
  161. package/node_modules/@luxusai/pi-hindsight/extensions/banks/knowledge-page-seed.ts +0 -176
  162. package/node_modules/@luxusai/pi-hindsight/extensions/banks/retain-strategies.ts +0 -178
  163. package/node_modules/@luxusai/pi-hindsight/extensions/client/client-retry.ts +0 -41
  164. package/node_modules/@luxusai/pi-hindsight/extensions/client/client.ts +0 -386
  165. package/node_modules/@luxusai/pi-hindsight/extensions/client/fetch-compat.ts +0 -39
  166. package/node_modules/@luxusai/pi-hindsight/extensions/client/timeout.ts +0 -34
  167. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-defaults.ts +0 -140
  168. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-model.ts +0 -61
  169. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-registry.ts +0 -925
  170. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-field-paths.ts +0 -247
  171. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-normalize.ts +0 -547
  172. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-writer.ts +0 -480
  173. package/node_modules/@luxusai/pi-hindsight/extensions/config/config.ts +0 -182
  174. package/node_modules/@luxusai/pi-hindsight/extensions/config/setup-gate.ts +0 -86
  175. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-execute.ts +0 -1010
  176. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-parse.ts +0 -175
  177. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-plan.ts +0 -425
  178. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-presentation.ts +0 -210
  179. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-sessions.ts +0 -817
  180. package/node_modules/@luxusai/pi-hindsight/extensions/index.ts +0 -25
  181. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/git-seed.ts +0 -319
  182. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-recall.ts +0 -196
  183. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-retain.ts +0 -189
  184. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-runtime.ts +0 -65
  185. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle.ts +0 -260
  186. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/mental-models.ts +0 -244
  187. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/observation-scopes.ts +0 -59
  188. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-cleanup.ts +0 -82
  189. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-visibility.ts +0 -45
  190. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall.ts +0 -331
  191. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-cursor.ts +0 -354
  192. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-job-builder.ts +0 -66
  193. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-receipts.ts +0 -132
  194. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain.ts +0 -228
  195. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-bank-template-operations.ts +0 -74
  196. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-config-operations.ts +0 -16
  197. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-control-operations.ts +0 -648
  198. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-diagnostics-operations.ts +0 -105
  199. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-identity.ts +0 -68
  200. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-service.ts +0 -71
  201. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-types.ts +0 -13
  202. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-recall-operations.ts +0 -162
  203. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-retain-operations.ts +0 -131
  204. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-scope.ts +0 -104
  205. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-session-operations.ts +0 -37
  206. package/node_modules/@luxusai/pi-hindsight/extensions/operations/operation-catalog.ts +0 -893
  207. package/node_modules/@luxusai/pi-hindsight/extensions/operations/reflect-presenter.ts +0 -35
  208. package/node_modules/@luxusai/pi-hindsight/extensions/operations/scope-migrate.ts +0 -177
  209. package/node_modules/@luxusai/pi-hindsight/extensions/operations/tools.ts +0 -7
  210. package/node_modules/@luxusai/pi-hindsight/extensions/queue/flush-presenter.ts +0 -21
  211. package/node_modules/@luxusai/pi-hindsight/extensions/queue/jsonl-queue-store.ts +0 -112
  212. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-delivery.ts +0 -111
  213. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-lock.ts +0 -214
  214. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-operations.ts +0 -70
  215. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue.ts +0 -392
  216. package/node_modules/@luxusai/pi-hindsight/extensions/tui/bank-template-presentation.ts +0 -45
  217. package/node_modules/@luxusai/pi-hindsight/extensions/tui/commands.ts +0 -9
  218. package/node_modules/@luxusai/pi-hindsight/extensions/tui/guided-setup.ts +0 -860
  219. package/node_modules/@luxusai/pi-hindsight/extensions/tui/prefill-input.ts +0 -105
  220. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-flow.ts +0 -189
  221. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-server-probe.ts +0 -299
  222. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-actions.ts +0 -201
  223. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-facts.ts +0 -47
  224. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-render.ts +0 -239
  225. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-types.ts +0 -50
  226. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui.ts +0 -259
  227. package/node_modules/@luxusai/pi-hindsight/extensions/tui/tool-presenters.ts +0 -77
  228. package/node_modules/@luxusai/pi-hindsight/extensions/types.ts +0 -534
  229. package/node_modules/@luxusai/pi-hindsight/extensions/utils/diagnostics.ts +0 -319
  230. package/node_modules/@luxusai/pi-hindsight/extensions/utils/messages.ts +0 -325
  231. package/node_modules/@luxusai/pi-hindsight/extensions/utils/sanitize.ts +0 -42
  232. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-memory-meta.ts +0 -244
  233. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-operations.ts +0 -56
  234. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session.ts +0 -50
  235. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-fields.ts +0 -167
  236. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-health.ts +0 -207
  237. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status.ts +0 -120
  238. package/node_modules/@luxusai/pi-hindsight/extensions/version.ts +0 -8
  239. package/node_modules/@luxusai/pi-hindsight/package.json +0 -118
  240. package/node_modules/@luxusai/pi-hindsight/skills/hindsight-memory-doctor/SKILL.md +0 -75
@@ -65,6 +65,8 @@
65
65
  */
66
66
  import { emitBounded } from "./bounded-telemetry.js";
67
67
  import { recordDegradationOnce } from "./degradation-ledger.js";
68
+ import { bounded } from "./deadline-utils.js";
69
+ import { HOOK_WALL_BUDGET_MS } from "./hook-budgets.js";
68
70
  import { probeCtxActive } from "./session-lifecycle.js";
69
71
  import { runWithTurnContext } from "./turn-context.js";
70
72
  function stableSessionId(ctx) {
@@ -176,6 +178,16 @@ function isThenable(value) {
176
178
  * never drift apart between a void handler and a value-returning one.
177
179
  */
178
180
  function guardSessionEvent(eventName, handler, onStaleResult, options) {
181
+ const defaultBudgetKey = (eventName) => {
182
+ if (eventName === "tool_result")
183
+ return "tool_result_edit";
184
+ if (eventName === "session_start" ||
185
+ eventName === "turn_end" ||
186
+ eventName === "agent_end" ||
187
+ eventName === "agent_settled")
188
+ return eventName;
189
+ return undefined;
190
+ };
179
191
  const skip = (event, detectedAt) => {
180
192
  recordStaleSkip(eventName, detectedAt);
181
193
  try {
@@ -199,17 +211,50 @@ function guardSessionEvent(eventName, handler, onStaleResult, options) {
199
211
  // below.
200
212
  return skip(event, "pre-dispatch");
201
213
  try {
214
+ let signal;
215
+ try {
216
+ signal = ctx?.signal;
217
+ }
218
+ catch (err) {
219
+ if (isStaleExtensionCtxError(err))
220
+ return Promise.resolve(skip(event, "mid-handler"));
221
+ // The signal read moved out of each handler's own try/catch and
222
+ // into the guard (#2523 hook budgets), so a ctx whose `signal`
223
+ // accessor throws for a NON-stale reason is a crashed handler and
224
+ // must leave the same record the handler's own catch used to (#2884).
225
+ // This preserves the old catch behavior for lifecycle handlers. The
226
+ // `tool_result` and `context` handlers never caught this signal read,
227
+ // so their non-stale accessor errors are surfaced as a swallowed
228
+ // handler crash instead (#2939 F5). `rethrow` is intentionally not
229
+ // forwarded: wrappers do not pass it, and only `surfaceHandlerCrash`
230
+ // honors that option (#2939 F4).
231
+ const crashOptions = {};
232
+ if (options.dbg !== undefined)
233
+ crashOptions.dbg = options.dbg;
234
+ surfaceHandlerCrash(eventName, err, crashOptions);
235
+ return Promise.resolve(onStaleResult(event));
236
+ }
202
237
  const result = runWithTurnContext(stableSessionId(ctx), () => handler(event, ctx));
203
238
  if (isThenable(result)) {
204
- // Recover the rejection in place. The host awaits the same promise
205
- // it would have awaited anyway; it just resolves instead.
206
- // SAFETY: the recovered promise settles to `Awaited<R>`, which the
207
- // host consumes exactly as it would an `R` — see the note above.
208
- return Promise.resolve(result).catch((err) => {
239
+ const recovered = Promise.resolve(result).catch((err) => {
209
240
  if (isStaleExtensionCtxError(err))
210
241
  return skip(event, "mid-handler");
211
242
  throw err;
212
243
  });
244
+ const budget = typeof options.budgetKey === "function"
245
+ ? options.budgetKey(event, ctx)
246
+ : (options.budgetKey ?? defaultBudgetKey(eventName));
247
+ if (budget === undefined)
248
+ return recovered;
249
+ return bounded(recovered, {
250
+ ms: HOOK_WALL_BUDGET_MS[budget],
251
+ // The settled drain must observe an aborted signal and requeue
252
+ // before its promise is released; its own workers read the same
253
+ // signal and remain bounded at their seams.
254
+ signal: budget === "agent_settled" ? undefined : signal,
255
+ hook: budget,
256
+ label: "registered-handler",
257
+ });
213
258
  }
214
259
  return result;
215
260
  }
@@ -652,13 +652,19 @@ export class SgRunner {
652
652
  }
653
653
  // --- Shared helpers for temp-dir rule scans ---
654
654
  prepareTempScan(ruleId, ruleYaml) {
655
- const sessionDir = path.join(os.tmpdir(), `pi-lens-temp-${ruleId}-${Date.now()}`);
656
- const rulesSubdir = path.join(sessionDir, "rules");
657
- const configFile = path.join(sessionDir, ".sgconfig.yml");
658
- fs.mkdirSync(rulesSubdir, { recursive: true });
659
- fs.writeFileSync(configFile, `ruleDirs:\n - ./rules\n`);
660
- fs.writeFileSync(path.join(rulesSubdir, `${ruleId}.yml`), ruleYaml);
661
- return { sessionDir, configFile };
655
+ const sessionDir = fs.mkdtempSync(path.join(os.tmpdir(), `pi-lens-temp-${ruleId}-`));
656
+ try {
657
+ const rulesSubdir = path.join(sessionDir, "rules");
658
+ const configFile = path.join(sessionDir, ".sgconfig.yml");
659
+ fs.mkdirSync(rulesSubdir, { recursive: true });
660
+ fs.writeFileSync(configFile, `ruleDirs:\n - ./rules\n`);
661
+ fs.writeFileSync(path.join(rulesSubdir, `${ruleId}.yml`), ruleYaml);
662
+ return { sessionDir, configFile };
663
+ }
664
+ catch (error) {
665
+ this.cleanupTempScan(sessionDir);
666
+ throw error;
667
+ }
662
668
  }
663
669
  cleanupTempScan(sessionDir) {
664
670
  try {
@@ -732,8 +738,10 @@ export class SgRunner {
732
738
  }
733
739
  }
734
740
  async tempScanDetailedAsync(dir, ruleId, ruleYaml, timeout = DEFAULT_EXEC_TIMEOUT_MS, options = {}) {
735
- const { sessionDir, configFile } = this.prepareTempScan(ruleId, ruleYaml);
741
+ let sessionDir;
736
742
  try {
743
+ ({ sessionDir } = this.prepareTempScan(ruleId, ruleYaml));
744
+ const configFile = path.join(sessionDir, ".sgconfig.yml");
737
745
  const { cmd: sgCmd, args: sgPre } = getSgCommand();
738
746
  const result = await safeSpawnAsync(sgCmd, [
739
747
  ...sgPre,
@@ -752,7 +760,8 @@ export class SgRunner {
752
760
  return this.interpretScanResult(result, ["scan"]);
753
761
  }
754
762
  finally {
755
- this.cleanupTempScan(sessionDir);
763
+ if (sessionDir)
764
+ this.cleanupTempScan(sessionDir);
756
765
  }
757
766
  }
758
767
  /** Backwards-compatible match-only wrapper for existing session scanners. */
@@ -766,8 +775,10 @@ export class SgRunner {
766
775
  * Apply: --update-all (writes fixes defined in the YAML `fix:` field).
767
776
  */
768
777
  async tempScanWithFixAsync(dir, ruleId, ruleYaml, applyFixes, timeout = DEFAULT_EXEC_TIMEOUT_MS, options = {}) {
769
- const { sessionDir, configFile } = this.prepareTempScan(ruleId, ruleYaml);
778
+ let sessionDir;
770
779
  try {
780
+ ({ sessionDir } = this.prepareTempScan(ruleId, ruleYaml));
781
+ const configFile = path.join(sessionDir, ".sgconfig.yml");
771
782
  const { cmd: sgCmd, args: sgPre } = getSgCommand();
772
783
  const scanArgs = [
773
784
  ...sgPre,
@@ -823,7 +834,8 @@ export class SgRunner {
823
834
  return { matches: [], error: String(err) };
824
835
  }
825
836
  finally {
826
- this.cleanupTempScan(sessionDir);
837
+ if (sessionDir)
838
+ this.cleanupTempScan(sessionDir);
827
839
  }
828
840
  }
829
841
  /**
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import * as fs from "node:fs";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
+ import { recordDegradationOnce } from "./degradation-ledger.js";
5
6
  import { load as loadYaml } from "./deps/js-yaml.js";
6
7
  import { resolvePackagePath } from "./package-root.js";
7
8
  import { findLocalToolConfig } from "./path-utils.js";
@@ -236,8 +237,83 @@ export function resolveBaselineSgconfig(projectRoot = process.cwd()) {
236
237
  const ruleDirForYaml = mergedDir.split(path.sep).join("/");
237
238
  fs.writeFileSync(file, `ruleDirs:\n - ${JSON.stringify(ruleDirForYaml)}\n`);
238
239
  cachedBaselines.set(root, { fingerprint, path: file, mergedDir });
240
+ // Only the current pair is protected: every other baseline this process
241
+ // resolved is an eviction candidate too, or the keep-set would grow with
242
+ // the root count and the cap could never fire. An evicted sibling
243
+ // re-materializes on its next resolve through the existsSync check above.
244
+ enforceBaselineEntryCap(dir, new Set([file, mergedDir]));
239
245
  return file;
240
246
  }
247
+ /**
248
+ * Hard bound on the shared scratch dir (#2912: it held ~703,000 entries after
249
+ * one merge train). An entry cap, not a per-session subdirectory: no
250
+ * session-end seam owns this module, and a cap is race-safe across concurrent
251
+ * processes — a sibling whose baseline is evicted re-materializes on its next
252
+ * resolve (the existsSync check above self-heals). Each root baseline is two
253
+ * entries (config + merged rules dir); 24 entries ≈ 12 live roots, ample for
254
+ * concurrent agents times worktrees. Eviction is oldest-mtime-first and emits
255
+ * one bounded record per session.
256
+ */
257
+ const SG_BASELINE_RETAINED_ENTRIES = 24;
258
+ function enforceBaselineEntryCap(dir, keep) {
259
+ let names;
260
+ try {
261
+ names = fs.readdirSync(dir);
262
+ }
263
+ catch {
264
+ return;
265
+ }
266
+ const candidates = [];
267
+ for (const name of names) {
268
+ const isConfig = /^baseline(?:-\d+(?:-[a-f0-9]+)?)?\.sgconfig\.yml$/.test(name);
269
+ const isMergedDir = /^baseline-\d+(?:-[a-f0-9]+)?\.rules$/.test(name);
270
+ if (!isConfig && !isMergedDir)
271
+ continue;
272
+ const full = path.join(dir, name);
273
+ if (keep.has(full))
274
+ continue;
275
+ try {
276
+ candidates.push({
277
+ full,
278
+ mtimeMs: fs.statSync(full).mtimeMs,
279
+ recursive: isMergedDir,
280
+ });
281
+ }
282
+ catch {
283
+ // Racing another session; leave its artifact alone.
284
+ }
285
+ }
286
+ const over = candidates.length + keep.size - SG_BASELINE_RETAINED_ENTRIES;
287
+ if (over <= 0)
288
+ return;
289
+ candidates.sort((a, b) => a.mtimeMs - b.mtimeMs);
290
+ let evicted = 0;
291
+ for (const candidate of candidates.slice(0, over)) {
292
+ try {
293
+ fs.rmSync(candidate.full, {
294
+ recursive: candidate.recursive,
295
+ force: true,
296
+ });
297
+ evicted += 1;
298
+ for (const [root, baseline] of cachedBaselines) {
299
+ if (baseline.path === candidate.full ||
300
+ baseline.mergedDir === candidate.full) {
301
+ cachedBaselines.delete(root);
302
+ }
303
+ }
304
+ }
305
+ catch {
306
+ // Racing another session; leave its artifact alone.
307
+ }
308
+ }
309
+ if (evicted > 0) {
310
+ recordDegradationOnce({
311
+ kind: "sgconfig-baseline-cap-evict",
312
+ subject: dir,
313
+ reason: `evicted ${evicted} oldest baseline entries over the ${SG_BASELINE_RETAINED_ENTRIES}-entry cap`,
314
+ });
315
+ }
316
+ }
241
317
  function cleanupStaleBaselines(dir, keep) {
242
318
  try {
243
319
  const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;
@@ -250,8 +326,9 @@ function cleanupStaleBaselines(dir, keep) {
250
326
  if (keep.has(full))
251
327
  continue;
252
328
  try {
253
- if (fs.statSync(full).mtimeMs < cutoff) {
254
- fs.rmSync(full, { recursive: isMergedDir, force: true });
329
+ const stat = fs.statSync(full);
330
+ if (stat.mtimeMs < cutoff) {
331
+ fs.rmSync(full, { recursive: stat.isDirectory(), force: true });
255
332
  }
256
333
  }
257
334
  catch {
@@ -65,8 +65,8 @@ import { resolveToolCwd } from "./tool-cwd.js";
65
65
  export function isRunnerErrorResult(result) {
66
66
  return result.failed === 0 && !!result.error;
67
67
  }
68
- // Source file test file patterns (reverse lookup)
69
- const SOURCE_TO_TEST_PATTERNS = [
68
+ // Discovery table; exported so the #2928 parity test enumerates its conventions.
69
+ export const SOURCE_TO_TEST_PATTERNS = [
70
70
  {
71
71
  ext: ".ts",
72
72
  testExts: [".test.ts", ".spec.ts"],
@@ -281,7 +281,6 @@ export const RUNNERS = {
281
281
  kinds: ["java", "kotlin"],
282
282
  configFiles: ["pom.xml"],
283
283
  command: "mvn",
284
- spawnCwdMarkers: ["mvnw", "mvnw.cmd"],
285
284
  args: (_testFile, _cwd) => ["test", "-q"],
286
285
  parseJson: false,
287
286
  },
@@ -1162,43 +1161,6 @@ export class TestRunnerClient {
1162
1161
  strategy: "related",
1163
1162
  };
1164
1163
  }
1165
- /**
1166
- * The working directory the test-runner CHILD is spawned in (#2871).
1167
- *
1168
- * AGENTS.md defect shape 40: `resolveToolCwd` is the one seam for a child
1169
- * process's cwd, and this file resolved its own — it handed
1170
- * `safeSpawnAsync` the dispatch root, so `RUNNERS.go.args` built
1171
- * `./tools/tapctl/internal/lightning` relative to a root module that does
1172
- * not own that package. Marker discovery, the `.git` fallback, the
1173
- * dispatch-root fallback, the `$HOME` ceiling, the once-per-key `tool-cwd`
1174
- * log line and the bounded `tool-cwd-resolution` degradation now all come
1175
- * from the seam, with the runner's own table as the markers.
1176
- *
1177
- * This is a SECOND value, never a reassignment of `cwd`: the failed-target
1178
- * ledger (`recordResult` → `getFailedTargets`, read back by
1179
- * `getTestRunTarget`) is keyed by the DISPATCH root, and re-keying it per
1180
- * module would make a recorded failure unfindable from the turn that
1181
- * selects targets.
1182
- *
1183
- * Review round 2, F6: there is deliberately NO clamp on a resolution that
1184
- * lands outside the dispatch root. The seam only answers outside it when
1185
- * the FILE is outside it (`tool-cwd.ts`: every in-tree branch is
1186
- * `isUnderDir`-checked), and the one production caller —
1187
- * `runtime-turn.ts`'s turn-end batch, on both the fresh and the deferred
1188
- * path — filters every target through `isExcludedTestTarget`, which fails
1189
- * CLOSED out of tree (#2522). Round 1 shipped that clamp plus a
1190
- * `tool-cwd-resolution` ledger row for it; neither could fire in a live
1191
- * session, so the row was a record nothing could observe and the guard was
1192
- * defence against a caller that does not exist. Both are gone. If a second
1193
- * caller is ever added that can pass an out-of-tree file, it needs this
1194
- * decision made where that caller is, with a test that reaches it.
1195
- */
1196
- resolveSpawnCwd(runner, config, testFile, dispatchRoot) {
1197
- return resolveToolCwd("runner", runner, testFile, {
1198
- cwd: path.resolve(dispatchRoot),
1199
- rootMarkers: config.spawnCwdMarkers ?? config.configFiles,
1200
- });
1201
- }
1202
1164
  async runTestFileAsync(testFile, cwd, runnerOrRequest, legacyConfig) {
1203
1165
  const absoluteTestFile = path.resolve(testFile);
1204
1166
  let request;
@@ -1216,7 +1178,10 @@ export class TestRunnerClient {
1216
1178
  return this.emptyResult(absoluteTestFile, "", runner, "Test file not found");
1217
1179
  }
1218
1180
  try {
1219
- const spawnCwd = this.resolveSpawnCwd(runner, config, absoluteTestFile, cwd);
1181
+ const spawnCwd = resolveToolCwd("runner", runner, absoluteTestFile, {
1182
+ cwd: path.resolve(cwd),
1183
+ rootMarkers: config.spawnCwdMarkers ?? config.configFiles,
1184
+ }).cwd;
1220
1185
  const { command, args, env } = await this.resolveExec(runner, config, absoluteTestFile, cwd, spawnCwd);
1221
1186
  this.log(`Running (async): ${command} ${args.join(" ")} (cwd ${spawnCwd})`);
1222
1187
  const result = await safeSpawnAsync(command, args, {
@@ -8,9 +8,51 @@
8
8
  * delivery gate for this cache.
9
9
  */
10
10
  import { emitBounded } from "./bounded-telemetry.js";
11
+ import { recordDegradationOnce } from "./degradation-ledger.js";
12
+ import { logLatency } from "./latency-logger.js";
11
13
  import { consumeTestFindings, peekTestFindings } from "./runtime-context.js";
12
14
  const MAX_PENDING_DELIVERIES = 32;
13
15
  const pending = new Map();
16
+ function logDeliveredVerdicts(cacheManager, cwd, runtime, delivery) {
17
+ const verdicts = cacheManager.readCache("test-runner-findings", cwd)?.data?.verdicts;
18
+ let knownCount = 0;
19
+ let staleCount = 0;
20
+ let unknownCount = 0;
21
+ for (const verdict of verdicts ?? []) {
22
+ const evidence = verdict.fileSeq ?? {
23
+ state: "unknown",
24
+ reason: "legacy-cache-record",
25
+ };
26
+ if (evidence.state === "unknown") {
27
+ unknownCount += 1;
28
+ recordDegradationOnce({
29
+ kind: "test-runner-delivery",
30
+ subject: `${delivery.sessionId}:${verdict.sourceFile}`,
31
+ reason: `missing file sequence evidence (${evidence.reason})`,
32
+ });
33
+ continue;
34
+ }
35
+ knownCount += 1;
36
+ const currentFileSeq = runtime.getFileSeq(verdict.sourceFile);
37
+ if (currentFileSeq > evidence.value)
38
+ staleCount += 1;
39
+ }
40
+ if (knownCount === 0 && unknownCount === 0)
41
+ return;
42
+ logLatency({
43
+ type: "phase",
44
+ phase: "test_runner_verdict_delivery",
45
+ filePath: cwd,
46
+ durationMs: 0,
47
+ metadata: {
48
+ sessionId: delivery.sessionId,
49
+ generation: delivery.generation,
50
+ verdictCount: knownCount,
51
+ staleCount,
52
+ unknownCount,
53
+ },
54
+ });
55
+ }
14
56
  function key(cwd, sessionId, ownerId) {
15
57
  // `cwd` comes from the same host session field at turn_end and settle. Keep
16
58
  // that canonical value unchanged so cache reads use the identical workspace
@@ -221,6 +263,7 @@ export function consumeStagedTestRunnerFindings(args) {
221
263
  record(deliveryKey, "superseded", delivery, { currentGeneration });
222
264
  return undefined;
223
265
  }
266
+ logDeliveredVerdicts(args.cacheManager, args.cwd, args.runtime, delivery);
224
267
  const findings = consumeTestFindings(args.cacheManager, args.cwd, args.runtime);
225
268
  if (!findings) {
226
269
  pending.delete(deliveryKey);