@herbertgao/pi-extensions 2026.9.5 → 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 (276) hide show
  1. package/README.md +5 -22
  2. package/THIRD_PARTY_NOTICES.md +0 -4
  3. package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +1 -1
  4. package/node_modules/@herbertgao/sol-pi/package.json +1 -1
  5. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +0 -5
  6. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +3 -1
  7. package/node_modules/@narumitw/pi-btw/README.md +2 -0
  8. package/node_modules/@narumitw/pi-btw/dist/index.ts +284 -365
  9. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
  10. package/node_modules/@narumitw/pi-btw/package.json +52 -53
  11. package/node_modules/@narumitw/pi-btw/src/bring-to-main.ts +494 -547
  12. package/node_modules/@narumitw/pi-btw/src/btw.ts +814 -856
  13. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +673 -674
  14. package/node_modules/@narumitw/pi-btw/src/keybindings.ts +228 -270
  15. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +309 -318
  16. package/node_modules/@narumitw/pi-btw/src/menu.ts +416 -454
  17. package/node_modules/@narumitw/pi-btw/src/settings.ts +203 -219
  18. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +173 -201
  19. package/node_modules/@narumitw/pi-btw/src/text.ts +21 -23
  20. package/node_modules/@narumitw/pi-btw/src/transcript-markdown.ts +65 -0
  21. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +611 -662
  22. package/node_modules/pi-lens/CHANGELOG.md +239 -0
  23. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -0
  24. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +30 -4
  25. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +2 -0
  26. package/node_modules/pi-lens/dist/clients/bash-file-access.js +26 -0
  27. package/node_modules/pi-lens/dist/clients/biome-client.js +1 -1
  28. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +403 -35
  29. package/node_modules/pi-lens/dist/clients/bootstrap.js +6 -1
  30. package/node_modules/pi-lens/dist/clients/cascade-format.js +200 -4
  31. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +10 -2
  32. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +16 -1
  33. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +12 -1
  34. package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +65 -0
  35. package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +28 -0
  36. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +9 -2
  37. package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +222 -0
  38. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +389 -13
  39. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +175 -25
  40. package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +62 -0
  41. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +21 -41
  42. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +6 -1
  43. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +7 -7
  44. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -12
  45. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +2 -21
  46. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +1 -1
  47. package/node_modules/pi-lens/dist/clients/dispatch/types.js +4 -1
  48. package/node_modules/pi-lens/dist/clients/disposition-publish.js +1 -0
  49. package/node_modules/pi-lens/dist/clients/effective-config.js +1 -1
  50. package/node_modules/pi-lens/dist/clients/file-kinds.js +26 -1
  51. package/node_modules/pi-lens/dist/clients/file-role.js +21 -21
  52. package/node_modules/pi-lens/dist/clients/file-utils.js +156 -10
  53. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +56 -9
  54. package/node_modules/pi-lens/dist/clients/format-service.js +39 -21
  55. package/node_modules/pi-lens/dist/clients/formatters.js +60 -13
  56. package/node_modules/pi-lens/dist/clients/generation-guard.js +2 -2
  57. package/node_modules/pi-lens/dist/clients/git-guard.js +11 -3
  58. package/node_modules/pi-lens/dist/clients/indent-retarget.js +78 -8
  59. package/node_modules/pi-lens/dist/clients/installer/index.js +333 -114
  60. package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +7 -0
  61. package/node_modules/pi-lens/dist/clients/instance-reaper.js +20 -10
  62. package/node_modules/pi-lens/dist/clients/language-profile.js +23 -1
  63. package/node_modules/pi-lens/dist/clients/language-registry.js +5 -1
  64. package/node_modules/pi-lens/dist/clients/latency-logger.js +4 -1
  65. package/node_modules/pi-lens/dist/clients/lens-engine.js +12 -3
  66. package/node_modules/pi-lens/dist/clients/lens-events.js +2 -1
  67. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +79 -0
  68. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +2 -1
  69. package/node_modules/pi-lens/dist/clients/lsp/client.js +25 -21
  70. package/node_modules/pi-lens/dist/clients/lsp/config.js +2 -2
  71. package/node_modules/pi-lens/dist/clients/lsp/index.js +7 -0
  72. package/node_modules/pi-lens/dist/clients/lsp/launch.js +16 -4
  73. package/node_modules/pi-lens/dist/clients/lsp/server.js +19 -2
  74. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +2 -1
  75. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +5 -3
  76. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +12 -8
  77. package/node_modules/pi-lens/dist/clients/mcp/session.js +36 -4
  78. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +24 -7
  79. package/node_modules/pi-lens/dist/clients/observed-mutation.js +40 -2
  80. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +23 -3
  81. package/node_modules/pi-lens/dist/clients/opengrep-client.js +110 -15
  82. package/node_modules/pi-lens/dist/clients/path-utils.js +123 -3
  83. package/node_modules/pi-lens/dist/clients/performance-report.js +5 -1
  84. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +1 -1
  85. package/node_modules/pi-lens/dist/clients/pipeline.js +74 -5
  86. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +37 -5
  87. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +145 -18
  88. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +4 -1
  89. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +59 -3
  90. package/node_modules/pi-lens/dist/clients/project-lens-config.js +45 -2
  91. package/node_modules/pi-lens/dist/clients/read-guard.js +70 -11
  92. package/node_modules/pi-lens/dist/clients/resource-sampler.js +147 -9
  93. package/node_modules/pi-lens/dist/clients/ruff-client.js +1 -1
  94. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +33 -5
  95. package/node_modules/pi-lens/dist/clients/runtime-context.js +6 -2
  96. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +77 -7
  97. package/node_modules/pi-lens/dist/clients/runtime-session.js +21 -2
  98. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +43 -12
  99. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +330 -85
  100. package/node_modules/pi-lens/dist/clients/runtime-turn.js +147 -17
  101. package/node_modules/pi-lens/dist/clients/safe-spawn.js +258 -5
  102. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +6 -0
  103. package/node_modules/pi-lens/dist/clients/session-event-guard.js +50 -5
  104. package/node_modules/pi-lens/dist/clients/sg-runner.js +23 -11
  105. package/node_modules/pi-lens/dist/clients/sgconfig.js +79 -2
  106. package/node_modules/pi-lens/dist/clients/test-runner-client.js +6 -41
  107. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +43 -0
  108. package/node_modules/pi-lens/dist/clients/tool-agreement.js +423 -0
  109. package/node_modules/pi-lens/dist/clients/tool-config.js +50 -0
  110. package/node_modules/pi-lens/dist/clients/tool-cwd.js +47 -76
  111. package/node_modules/pi-lens/dist/clients/tool-policy.js +163 -7
  112. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +120 -0
  113. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +127 -189
  114. package/node_modules/pi-lens/dist/clients/widget-state.js +98 -0
  115. package/node_modules/pi-lens/dist/clients/word-index.js +5 -2
  116. package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +7 -0
  117. package/node_modules/pi-lens/dist/index.js +27416 -24153
  118. package/node_modules/pi-lens/dist/mcp/server.js +5 -2
  119. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +7 -1
  120. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +168 -53
  121. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +244 -72
  122. package/node_modules/pi-lens/docs/audit1.md +5 -5
  123. package/node_modules/pi-lens/docs/dependencies.md +11 -0
  124. package/node_modules/pi-lens/docs/dispositions.md +52 -0
  125. package/node_modules/pi-lens/docs/environment-variables.md +2 -2
  126. package/node_modules/pi-lens/docs/features.md +4 -4
  127. package/node_modules/pi-lens/docs/globalconfig.md +3 -1
  128. package/node_modules/pi-lens/docs/language-coverage.md +1 -0
  129. package/node_modules/pi-lens/docs/pi-lens-fixer.md +53 -94
  130. package/node_modules/pi-lens/docs/pi-lens-investigator.md +43 -38
  131. package/node_modules/pi-lens/docs/pi-lens-retro.md +97 -0
  132. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +53 -66
  133. package/node_modules/pi-lens/docs/release-qa-baseline.md +22 -0
  134. package/node_modules/pi-lens/docs/settings.md +7 -4
  135. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
  136. package/node_modules/pi-lens/docs/usage.md +2 -0
  137. package/node_modules/pi-lens/package.json +1 -1
  138. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +24 -12
  139. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +40 -0
  140. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +15 -0
  141. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +7 -0
  142. package/package.json +6 -17
  143. package/examples/pi-footer.json +0 -275
  144. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +0 -70
  145. package/node_modules/@czottmann/pi-automode/LICENSE.md +0 -21
  146. package/node_modules/@czottmann/pi-automode/README.md +0 -215
  147. package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +0 -39
  148. package/node_modules/@czottmann/pi-automode/docs/adr/ADR-001-permission-precedence-and-trust-boundaries.md +0 -46
  149. package/node_modules/@czottmann/pi-automode/docs/adr/ADR-002-global-config-in-extension-data-directory.md +0 -60
  150. package/node_modules/@czottmann/pi-automode/docs/adr/INDEX.md +0 -6
  151. package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +0 -428
  152. package/node_modules/@czottmann/pi-automode/docs/configuration.md +0 -171
  153. package/node_modules/@czottmann/pi-automode/docs/defaults.md +0 -178
  154. package/node_modules/@czottmann/pi-automode/docs/diagnostics.md +0 -90
  155. package/node_modules/@czottmann/pi-automode/docs/observability-logging.md +0 -160
  156. package/node_modules/@czottmann/pi-automode/examples/automode.local.json +0 -44
  157. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/bash.ts +0 -692
  158. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +0 -852
  159. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +0 -904
  160. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +0 -228
  161. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +0 -954
  162. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +0 -429
  163. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/log.ts +0 -173
  164. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/model-selector.ts +0 -113
  165. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/model.ts +0 -13
  166. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/paths.ts +0 -297
  167. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/permissions.ts +0 -623
  168. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/state.ts +0 -102
  169. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/transcript.ts +0 -236
  170. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/types.ts +0 -205
  171. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/utils.ts +0 -54
  172. package/node_modules/@czottmann/pi-automode/extensions/auto-mode.ts +0 -26
  173. package/node_modules/@czottmann/pi-automode/package.json +0 -61
  174. package/node_modules/@czottmann/pi-automode/skills/automode-diagnostics/SKILL.md +0 -63
  175. package/node_modules/@luxusai/pi-hindsight/CHANGELOG.md +0 -376
  176. package/node_modules/@luxusai/pi-hindsight/README.md +0 -92
  177. package/node_modules/@luxusai/pi-hindsight/docs/adr/001-memory-lifecycle-and-scope.md +0 -56
  178. package/node_modules/@luxusai/pi-hindsight/docs/adr/002-explicit-routing-strategy-seam.md +0 -169
  179. package/node_modules/@luxusai/pi-hindsight/docs/adr/003-tui-memory-mode-vocabulary.md +0 -107
  180. package/node_modules/@luxusai/pi-hindsight/docs/adr/004-lifeos-dual-bank-design.md +0 -89
  181. package/node_modules/@luxusai/pi-hindsight/docs/adr/005-domain-banks-and-agent-first-surface.md +0 -189
  182. package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark.webp +0 -0
  183. package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark@2x.webp +0 -0
  184. package/node_modules/@luxusai/pi-hindsight/docs/coding-memory-evaluation.md +0 -54
  185. package/node_modules/@luxusai/pi-hindsight/docs/compatibility.md +0 -75
  186. package/node_modules/@luxusai/pi-hindsight/docs/hindsight-core-functions.md +0 -300
  187. package/node_modules/@luxusai/pi-hindsight/docs/mission-and-mental-model-quality.md +0 -158
  188. package/node_modules/@luxusai/pi-hindsight/docs/next-opt-out-design.md +0 -164
  189. package/node_modules/@luxusai/pi-hindsight/docs/risky-memory-modes.md +0 -139
  190. package/node_modules/@luxusai/pi-hindsight/docs/starter-mental-model-suggestions.md +0 -74
  191. package/node_modules/@luxusai/pi-hindsight/docs/surface-reference.md +0 -261
  192. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-operations.ts +0 -151
  193. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-selection.ts +0 -18
  194. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-settings-presenter.ts +0 -46
  195. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-templates.ts +0 -383
  196. package/node_modules/@luxusai/pi-hindsight/extensions/banks/banking.ts +0 -240
  197. package/node_modules/@luxusai/pi-hindsight/extensions/banks/knowledge-page-seed.ts +0 -176
  198. package/node_modules/@luxusai/pi-hindsight/extensions/banks/retain-strategies.ts +0 -178
  199. package/node_modules/@luxusai/pi-hindsight/extensions/client/client-retry.ts +0 -41
  200. package/node_modules/@luxusai/pi-hindsight/extensions/client/client.ts +0 -386
  201. package/node_modules/@luxusai/pi-hindsight/extensions/client/fetch-compat.ts +0 -39
  202. package/node_modules/@luxusai/pi-hindsight/extensions/client/timeout.ts +0 -34
  203. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-defaults.ts +0 -140
  204. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-model.ts +0 -61
  205. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-registry.ts +0 -925
  206. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-field-paths.ts +0 -247
  207. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-normalize.ts +0 -547
  208. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-writer.ts +0 -480
  209. package/node_modules/@luxusai/pi-hindsight/extensions/config/config.ts +0 -182
  210. package/node_modules/@luxusai/pi-hindsight/extensions/config/setup-gate.ts +0 -86
  211. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-execute.ts +0 -1010
  212. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-parse.ts +0 -175
  213. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-plan.ts +0 -425
  214. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-presentation.ts +0 -210
  215. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-sessions.ts +0 -817
  216. package/node_modules/@luxusai/pi-hindsight/extensions/index.ts +0 -25
  217. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/git-seed.ts +0 -319
  218. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-recall.ts +0 -196
  219. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-retain.ts +0 -189
  220. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-runtime.ts +0 -65
  221. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle.ts +0 -260
  222. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/mental-models.ts +0 -244
  223. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/observation-scopes.ts +0 -59
  224. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-cleanup.ts +0 -82
  225. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-visibility.ts +0 -45
  226. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall.ts +0 -331
  227. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-cursor.ts +0 -354
  228. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-job-builder.ts +0 -66
  229. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-receipts.ts +0 -132
  230. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain.ts +0 -228
  231. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-bank-template-operations.ts +0 -74
  232. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-config-operations.ts +0 -16
  233. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-control-operations.ts +0 -648
  234. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-diagnostics-operations.ts +0 -105
  235. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-identity.ts +0 -68
  236. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-service.ts +0 -71
  237. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-types.ts +0 -13
  238. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-recall-operations.ts +0 -162
  239. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-retain-operations.ts +0 -131
  240. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-scope.ts +0 -104
  241. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-session-operations.ts +0 -37
  242. package/node_modules/@luxusai/pi-hindsight/extensions/operations/operation-catalog.ts +0 -893
  243. package/node_modules/@luxusai/pi-hindsight/extensions/operations/reflect-presenter.ts +0 -35
  244. package/node_modules/@luxusai/pi-hindsight/extensions/operations/scope-migrate.ts +0 -177
  245. package/node_modules/@luxusai/pi-hindsight/extensions/operations/tools.ts +0 -7
  246. package/node_modules/@luxusai/pi-hindsight/extensions/queue/flush-presenter.ts +0 -21
  247. package/node_modules/@luxusai/pi-hindsight/extensions/queue/jsonl-queue-store.ts +0 -112
  248. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-delivery.ts +0 -111
  249. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-lock.ts +0 -214
  250. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-operations.ts +0 -70
  251. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue.ts +0 -392
  252. package/node_modules/@luxusai/pi-hindsight/extensions/tui/bank-template-presentation.ts +0 -45
  253. package/node_modules/@luxusai/pi-hindsight/extensions/tui/commands.ts +0 -9
  254. package/node_modules/@luxusai/pi-hindsight/extensions/tui/guided-setup.ts +0 -860
  255. package/node_modules/@luxusai/pi-hindsight/extensions/tui/prefill-input.ts +0 -105
  256. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-flow.ts +0 -189
  257. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-server-probe.ts +0 -299
  258. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-actions.ts +0 -201
  259. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-facts.ts +0 -47
  260. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-render.ts +0 -239
  261. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-types.ts +0 -50
  262. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui.ts +0 -259
  263. package/node_modules/@luxusai/pi-hindsight/extensions/tui/tool-presenters.ts +0 -77
  264. package/node_modules/@luxusai/pi-hindsight/extensions/types.ts +0 -534
  265. package/node_modules/@luxusai/pi-hindsight/extensions/utils/diagnostics.ts +0 -319
  266. package/node_modules/@luxusai/pi-hindsight/extensions/utils/messages.ts +0 -325
  267. package/node_modules/@luxusai/pi-hindsight/extensions/utils/sanitize.ts +0 -42
  268. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-memory-meta.ts +0 -244
  269. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-operations.ts +0 -56
  270. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session.ts +0 -50
  271. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-fields.ts +0 -167
  272. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-health.ts +0 -207
  273. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status.ts +0 -120
  274. package/node_modules/@luxusai/pi-hindsight/extensions/version.ts +0 -8
  275. package/node_modules/@luxusai/pi-hindsight/package.json +0 -118
  276. package/node_modules/@luxusai/pi-hindsight/skills/hindsight-memory-doctor/SKILL.md +0 -75
@@ -1,35 +0,0 @@
1
- function summarizeReflectTrace(trace: unknown): string {
2
- if (!trace || typeof trace !== "object") return "";
3
- const record = trace as Record<string, unknown>;
4
- const toolCalls = Array.isArray(record.tool_calls) ? record.tool_calls : [];
5
- const llmCalls = Array.isArray(record.llm_calls) ? record.llm_calls : [];
6
- if (toolCalls.length === 0 && llmCalls.length === 0) return "";
7
- const toolNames = toolCalls
8
- .map((call) =>
9
- call && typeof call === "object" ? (call as Record<string, unknown>).tool : undefined,
10
- )
11
- .filter((name): name is string => typeof name === "string");
12
- const toolPart = toolCalls.length
13
- ? `${toolCalls.length} tool call${toolCalls.length === 1 ? "" : "s"}${
14
- toolNames.length ? ` (${[...new Set(toolNames)].join(", ")})` : ""
15
- }`
16
- : "";
17
- const llmPart = llmCalls.length
18
- ? `${llmCalls.length} LLM call${llmCalls.length === 1 ? "" : "s"}`
19
- : "";
20
- return `Trace: ${[toolPart, llmPart].filter(Boolean).join("; ")}`;
21
- }
22
-
23
- export function formatReflectResult(result: unknown): string {
24
- if (typeof result === "string") return result;
25
- if (!result || typeof result !== "object") return JSON.stringify(result, null, 2);
26
- const record = result as Record<string, unknown>;
27
- const parts: string[] = [];
28
- if (typeof record.text === "string" && record.text.trim()) parts.push(record.text.trim());
29
- if (record.structured_output && typeof record.structured_output === "object") {
30
- parts.push(`Structured output:\n${JSON.stringify(record.structured_output, null, 2)}`);
31
- }
32
- const traceSummary = summarizeReflectTrace(record.trace);
33
- if (traceSummary) parts.push(traceSummary);
34
- return parts.length ? parts.join("\n\n") : JSON.stringify(result, null, 2);
35
- }
@@ -1,177 +0,0 @@
1
- import { mkdir, writeFile } from "node:fs/promises";
2
- import { dirname, join } from "node:path";
3
- import {
4
- deriveProjectBankId,
5
- legacyRepoScopeTag,
6
- projectScopeTag,
7
- resolveProjectIdentity,
8
- } from "../banks/banking.js";
9
- import type { ResolvedConfig } from "../types.js";
10
-
11
- export interface ScopeMigratePlan {
12
- dryRun: true;
13
- /** Never rewrites banks/tags from this plan alone. */
14
- rewrite: "none";
15
- bankId: string;
16
- pathDerivedBank: boolean;
17
- scopeMode: ResolvedConfig["scope"]["mode"];
18
- projectTag: string;
19
- legacyRepoTag: string;
20
- projectId: string;
21
- projectIdBasis: "pin" | "remote" | "basename";
22
- projectIdSource: string;
23
- dualTagWindow: true;
24
- /**
25
- * True while retain still stamps legacy repo:<slug>-<path-hash>.
26
- * That legacy tag always depends on absolute path; dual-tag project: is the stable recovery path.
27
- */
28
- legacyPathHashTag: true;
29
- /**
30
- * True when project id itself is weak (basename only). Remote/pin identities are stable;
31
- * do not conflate them with path-hash legacy tags.
32
- */
33
- identityBasisWeak: boolean;
34
- findings: string[];
35
- guidance: string[];
36
- /** Optional remote tag sample counts when listTags results were provided. */
37
- remoteTagCounts?: {
38
- projectTagHits: number;
39
- legacyRepoTagHits: number;
40
- otherRepoTags: string[];
41
- sampleSize: number;
42
- };
43
- createdAt: string;
44
- }
45
-
46
- export interface ScopeMigrateReceipt extends ScopeMigratePlan {
47
- receiptPath: string;
48
- }
49
-
50
- /**
51
- * Build a dry-run scope migration plan. Never mutates Hindsight banks or tags.
52
- * Dual-tag retain/recall already covers the live window; this plan documents risk and next steps.
53
- */
54
- export function buildScopeMigratePlan(args: {
55
- cwd: string;
56
- config: ResolvedConfig;
57
- projectBankId?: string;
58
- bankTags?: string[];
59
- now?: Date;
60
- }): ScopeMigratePlan {
61
- const identity = resolveProjectIdentity(args.cwd, args.config);
62
- const bankId = args.projectBankId ?? deriveProjectBankId(args.cwd, args.config);
63
- const pathDerivedBank = !args.config.banks.project.bankId;
64
- const projectTag = projectScopeTag(identity.projectId);
65
- const legacyRepoTag = legacyRepoScopeTag(identity.legacyRepoKey);
66
- const identityBasisWeak = identity.basis === "basename";
67
-
68
- const findings: string[] = [];
69
- const guidance: string[] = [];
70
-
71
- findings.push(
72
- `Active dual-tag window: retain writes ${projectTag} and ${legacyRepoTag}; recall any_strict matches either.`,
73
- );
74
- findings.push(`Project id basis=${identity.basis} source=${identity.source} → ${projectTag}.`);
75
- findings.push(
76
- `Legacy tag ${legacyRepoTag} is path-hash based (machine/path moves change the hash). Stable project: tag is the recovery path for new writes.`,
77
- );
78
-
79
- if (pathDerivedBank) {
80
- findings.push(
81
- `Coding bank is path-derived (${bankId}); domain-tagged mode requires banks.project.bankId for a shared coding bank.`,
82
- );
83
- guidance.push(
84
- "Set banks.project.bankId (and setupComplete) so repos share one coding bank; do not rely on path-hash bank ids.",
85
- );
86
- }
87
-
88
- if (identityBasisWeak) {
89
- findings.push(
90
- "Project id is basename-derived; remotes or pins are more stable across clones and renames.",
91
- );
92
- guidance.push(
93
- 'Prefer scope.projectId pin or projectIdStrategy "remote" when a git origin exists.',
94
- );
95
- } else {
96
- findings.push(
97
- `Project id basis is ${identity.basis} (stable across absolute path moves when dual-tag project: is present).`,
98
- );
99
- }
100
-
101
- let remoteTagCounts: ScopeMigratePlan["remoteTagCounts"];
102
- if (args.bankTags?.length) {
103
- const otherRepoTags = [
104
- ...new Set(args.bankTags.filter((t) => t.startsWith("repo:") && t !== legacyRepoTag)),
105
- ].sort();
106
- const projectTagHits = args.bankTags.filter((t) => t === projectTag).length;
107
- const legacyRepoTagHits = args.bankTags.filter((t) => t === legacyRepoTag).length;
108
- remoteTagCounts = {
109
- projectTagHits,
110
- legacyRepoTagHits,
111
- otherRepoTags,
112
- sampleSize: args.bankTags.length,
113
- };
114
- findings.push(
115
- `Remote tag sample (${args.bankTags.length}): project hits=${projectTagHits}, legacy hits=${legacyRepoTagHits}, other repo:* tags=${otherRepoTags.length}.`,
116
- );
117
- if (legacyRepoTagHits > 0 && projectTagHits === 0) {
118
- findings.push(
119
- "Sample has legacy repo tags without the stable project tag — reimport or dual-tag new retains before dropping dual-tag.",
120
- );
121
- }
122
- if (otherRepoTags.length > 0) {
123
- findings.push(
124
- `Other path-hash repo tags present (likely other machines/paths): ${otherRepoTags.slice(0, 5).join(", ")}${otherRepoTags.length > 5 ? "…" : ""}.`,
125
- );
126
- guidance.push(
127
- "Other repo:* path-hashes are expected after path moves; dual-tag project: is the recovery path for new retains.",
128
- );
129
- }
130
- } else {
131
- guidance.push(
132
- "Optional: pass bank tag inventory (listTags) into dry-run for remote counts; this local plan never rewrites.",
133
- );
134
- }
135
-
136
- guidance.push(
137
- "No silent rewrite: this dry-run only writes a local receipt under .pi/hindsight/.",
138
- );
139
- guidance.push(
140
- "When dual-tag can end: every active project memory carries project:<id> (or you completed export/import rebuild).",
141
- );
142
-
143
- return {
144
- dryRun: true,
145
- rewrite: "none",
146
- bankId,
147
- pathDerivedBank,
148
- scopeMode: args.config.scope.mode,
149
- projectTag,
150
- legacyRepoTag,
151
- projectId: identity.projectId,
152
- projectIdBasis: identity.basis,
153
- projectIdSource: identity.source,
154
- dualTagWindow: true,
155
- legacyPathHashTag: true,
156
- identityBasisWeak,
157
- findings,
158
- guidance,
159
- ...(remoteTagCounts ? { remoteTagCounts } : {}),
160
- createdAt: (args.now ?? new Date()).toISOString(),
161
- };
162
- }
163
-
164
- export function resolveScopeMigrateReceiptPath(cwd: string): string {
165
- return join(cwd, ".pi", "hindsight", "scope-migrate-receipt.json");
166
- }
167
-
168
- export async function writeScopeMigrateReceipt(
169
- cwd: string,
170
- plan: ScopeMigratePlan,
171
- ): Promise<ScopeMigrateReceipt> {
172
- const receiptPath = resolveScopeMigrateReceiptPath(cwd);
173
- await mkdir(dirname(receiptPath), { recursive: true });
174
- const receipt: ScopeMigrateReceipt = { ...plan, receiptPath };
175
- await writeFile(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`, "utf8");
176
- return receipt;
177
- }
@@ -1,7 +0,0 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- import type { MemoryOperationsDeps } from "./memory-operation-service.js";
3
- import { createOperationCatalog } from "./operation-catalog.js";
4
-
5
- export function registerTools(pi: ExtensionAPI, deps: MemoryOperationsDeps) {
6
- for (const tool of createOperationCatalog(deps).tools) pi.registerTool(tool);
7
- }
@@ -1,21 +0,0 @@
1
- import type { RetainOutcome } from "../types.js";
2
- import type { FlushRetainQueueResult } from "./queue.js";
3
-
4
- export function formatRetainOutcome(outcome: RetainOutcome | undefined): string {
5
- if (!outcome) return "";
6
- const parts: string[] = [];
7
- if (outcome.itemsCount !== undefined) parts.push(`${outcome.itemsCount} items`);
8
- if (outcome.operations !== undefined) parts.push(`${outcome.operations} operations`);
9
- if (outcome.tokens !== undefined) parts.push(`${outcome.tokens} tokens`);
10
- return parts.join(", ");
11
- }
12
-
13
- export function formatFlushRetainQueueResult(result: FlushRetainQueueResult): string {
14
- const base = `Hindsight flushed ${result.sent}; dead-lettered ${result.deadLettered}; remaining ${result.remaining}`;
15
- const outcome = formatRetainOutcome(result.outcome);
16
- return outcome ? `${base}; retained ${outcome}` : base;
17
- }
18
-
19
- export function flushRetainQueueNotifyLevel(result: FlushRetainQueueResult): "info" | "warning" {
20
- return result.remaining || result.deadLettered || result.malformed ? "warning" : "info";
21
- }
@@ -1,112 +0,0 @@
1
- import { appendFile, mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
- import { dirname } from "node:path";
3
-
4
- export interface JsonlQueueFileSummary {
5
- path: string;
6
- valid: number;
7
- malformed: number;
8
- error: string | null;
9
- }
10
-
11
- export interface ParsedJsonlQueue<T> {
12
- jobs: T[];
13
- malformedLines: string[];
14
- }
15
-
16
- export class JsonlQueueStore<T> {
17
- constructor(
18
- readonly path: string,
19
- private readonly isItem: (value: unknown) => value is T,
20
- ) {}
21
-
22
- async count(): Promise<number> {
23
- try {
24
- return (await readFile(this.path, "utf8")).split("\n").filter(Boolean).length;
25
- } catch (error) {
26
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return 0;
27
- throw error;
28
- }
29
- }
30
-
31
- async append(items: T[]): Promise<void> {
32
- if (items.length === 0) return;
33
- await mkdir(dirname(this.path), { recursive: true });
34
- await appendFile(
35
- this.path,
36
- items.map((item) => JSON.stringify(item)).join("\n") + "\n",
37
- "utf8",
38
- );
39
- }
40
-
41
- async readStrict(): Promise<T[]> {
42
- const parsed = await this.readTolerant();
43
- if (parsed.malformedLines.length > 0) {
44
- throw new Error(
45
- `Malformed queue file ${this.path}: ${parsed.malformedLines.length} invalid line(s)`,
46
- );
47
- }
48
- return parsed.jobs;
49
- }
50
-
51
- async readTolerant(): Promise<ParsedJsonlQueue<T>> {
52
- try {
53
- const text = await readFile(this.path, "utf8");
54
- const jobs: T[] = [];
55
- const malformedLines: string[] = [];
56
- for (const line of text.split("\n").filter(Boolean)) {
57
- try {
58
- const parsed = JSON.parse(line) as unknown;
59
- if (this.isItem(parsed)) jobs.push(parsed);
60
- else malformedLines.push(line);
61
- } catch {
62
- malformedLines.push(line);
63
- }
64
- }
65
- return { jobs, malformedLines };
66
- } catch (error) {
67
- if ((error as NodeJS.ErrnoException).code === "ENOENT") {
68
- return { jobs: [], malformedLines: [] };
69
- }
70
- throw error;
71
- }
72
- }
73
-
74
- async summarize(): Promise<JsonlQueueFileSummary> {
75
- try {
76
- const text = await readFile(this.path, "utf8");
77
- let valid = 0;
78
- let malformed = 0;
79
- for (const line of text.split("\n").filter(Boolean)) {
80
- try {
81
- const parsed = JSON.parse(line) as unknown;
82
- if (this.isItem(parsed)) valid += 1;
83
- else malformed += 1;
84
- } catch {
85
- malformed += 1;
86
- }
87
- }
88
- return { path: this.path, valid, malformed, error: null };
89
- } catch (error) {
90
- if ((error as NodeJS.ErrnoException).code === "ENOENT") {
91
- return { path: this.path, valid: 0, malformed: 0, error: null };
92
- }
93
- return {
94
- path: this.path,
95
- valid: 0,
96
- malformed: 0,
97
- error: error instanceof Error ? error.message : String(error),
98
- };
99
- }
100
- }
101
-
102
- async replace(items: T[]): Promise<void> {
103
- await mkdir(dirname(this.path), { recursive: true });
104
- const tmp = `${this.path}.tmp`;
105
- await writeFile(
106
- tmp,
107
- items.map((item) => JSON.stringify(item)).join("\n") + (items.length ? "\n" : ""),
108
- "utf8",
109
- );
110
- await rename(tmp, this.path);
111
- }
112
- }
@@ -1,111 +0,0 @@
1
- import type { HindsightLikeClient, RetainJob } from "../types.js";
2
-
3
- export interface RetainDeliveryOutcome {
4
- itemsCount?: number;
5
- operationIds: string[];
6
- tokens?: number;
7
- }
8
-
9
- export function redactQueueError(error: unknown): string {
10
- const message = error instanceof Error ? error.message : String(error);
11
- const text = message
12
- .replace(/\b(Bearer\s+)[A-Za-z0-9._~+/=-]{12,}/gi, "$1[REDACTED]")
13
- .replace(/\b(sk-[A-Za-z0-9_-]{12,})\b/g, "[REDACTED_API_KEY]")
14
- .replace(/\b(ghp_[A-Za-z0-9_]{12,})\b/g, "[REDACTED_GITHUB_TOKEN]")
15
- .replace(
16
- /\b([A-Z0-9_]*(?:API_KEY|TOKEN|SECRET|PASSWORD)[A-Z0-9_]*\s*=\s*)[^\s\n"',}]+/gi,
17
- "$1[REDACTED]",
18
- )
19
- .replace(
20
- /\b((?:api[_-]?key|token|secret|password|authorization)\s*:\s*)["']?[^\s\n"',}]+["']?/gi,
21
- "$1[REDACTED]",
22
- )
23
- .replace(
24
- /(["'](?:api[_-]?key|token|secret|password|authorization)["']\s*:\s*)["'][^"']+["']/gi,
25
- '$1"[REDACTED]"',
26
- )
27
- .replace(/\b((?:Cookie|Set-Cookie)\s*:\s*)[^\r\n]+/gi, "$1[REDACTED]")
28
- .replace(/(https?:\/\/)([^\s/@]+):([^\s/@]+)@/gi, "$1[REDACTED]@");
29
- return text.replace(/https?:\/\/[^\s"'<>]+/gi, (url) =>
30
- url.replace(/([?&])([^=&#\s]+)=([^&#\s]+)/g, (match, separator, key) => {
31
- if (!/(?:token|key|secret|password|api[_-]?key|apikey)/i.test(String(key))) {
32
- return String(match);
33
- }
34
- return `${String(separator)}${String(key)}=[REDACTED]`;
35
- }),
36
- );
37
- }
38
-
39
- export function retainOptionsForJob(job: RetainJob) {
40
- const async = job.item.async;
41
- return {
42
- context: job.item.context,
43
- ...(job.item.timestamp ? { timestamp: job.item.timestamp } : {}),
44
- ...(job.item.metadata ? { metadata: job.item.metadata } : {}),
45
- ...(async !== undefined ? { async } : {}),
46
- // Stable per queue job so async retain retries are idempotent (Hindsight operation_id).
47
- ...(async === true ? { operationId: job.id } : {}),
48
- ...(job.item.entities?.length ? { entities: job.item.entities } : {}),
49
- ...(job.item.tags ? { tags: job.item.tags } : {}),
50
- ...(job.item.observationScopes ? { observationScopes: job.item.observationScopes } : {}),
51
- ...(job.item.documentTags ? { documentTags: job.item.documentTags } : {}),
52
- ...(job.item.strategy ? { strategy: job.item.strategy } : {}),
53
- documentId: job.documentId,
54
- updateMode: job.updateMode,
55
- };
56
- }
57
-
58
- export function operationIdsFromResponse(response: unknown): string[] {
59
- if (!response || typeof response !== "object") return [];
60
- const record = response as Record<string, unknown>;
61
- const candidates = [record.operation_id, record.operationId, record.id];
62
- const operationIds = candidates.filter(
63
- (candidate): candidate is string => typeof candidate === "string" && candidate.length > 0,
64
- );
65
- const nested = record.operations;
66
- if (Array.isArray(nested)) {
67
- for (const item of nested) {
68
- if (typeof item === "string") operationIds.push(item);
69
- else if (item && typeof item === "object") {
70
- const id =
71
- (item as Record<string, unknown>).id ?? (item as Record<string, unknown>).operation_id;
72
- if (typeof id === "string") operationIds.push(id);
73
- }
74
- }
75
- }
76
- const operationIdsArray = record.operation_ids ?? record.operationIds;
77
- if (Array.isArray(operationIdsArray)) {
78
- for (const item of operationIdsArray) {
79
- if (typeof item === "string" && item.length > 0) operationIds.push(item);
80
- }
81
- }
82
- return [...new Set(operationIds)];
83
- }
84
-
85
- function positiveNumber(value: unknown): number | undefined {
86
- return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
87
- }
88
-
89
- export function parseRetainOutcome(response: unknown): RetainDeliveryOutcome {
90
- const operationIds = operationIdsFromResponse(response);
91
- if (!response || typeof response !== "object") return { operationIds };
92
- const record = response as Record<string, unknown>;
93
- const usage =
94
- record.usage && typeof record.usage === "object"
95
- ? (record.usage as Record<string, unknown>)
96
- : undefined;
97
- const itemsCount = positiveNumber(record.items_count ?? record.itemsCount);
98
- const tokens = usage ? positiveNumber(usage.total_tokens) : undefined;
99
- return {
100
- operationIds,
101
- ...(itemsCount !== undefined ? { itemsCount } : {}),
102
- ...(tokens !== undefined ? { tokens } : {}),
103
- };
104
- }
105
-
106
- export async function deliverRetainJob(
107
- client: HindsightLikeClient,
108
- job: RetainJob,
109
- ): Promise<unknown> {
110
- return client.retain(job.bankId, job.item.content, retainOptionsForJob(job));
111
- }
@@ -1,214 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
3
- import { dirname } from "node:path";
4
-
5
- const queueLocks = new Map<string, Promise<void>>();
6
-
7
- const LOCK_RETRY_MS = 25;
8
- const LOCK_TIMEOUT_MS = 5_000;
9
- const LOCK_STALE_MS = 30_000;
10
-
11
- export const RETAIN_QUEUE_LOCK = {
12
- retryMs: LOCK_RETRY_MS,
13
- timeoutMs: LOCK_TIMEOUT_MS,
14
- staleMs: LOCK_STALE_MS,
15
- };
16
-
17
- export interface QueueLockOwner {
18
- pid?: number;
19
- acquiredAt?: string;
20
- token?: string;
21
- }
22
-
23
- function sleep(ms: number): Promise<void> {
24
- return new Promise((resolve) => setTimeout(resolve, ms));
25
- }
26
-
27
- async function removeDirectory(path: string): Promise<void> {
28
- await rm(path, { recursive: true, force: true, maxRetries: 3, retryDelay: 10 });
29
- }
30
-
31
- async function writeQueueHeartbeat(lockPath: string, token: string): Promise<void> {
32
- const heartbeatPath = `${lockPath}/heartbeat-${token}`;
33
- const tmpPath = `${heartbeatPath}.${process.pid}.tmp`;
34
- await writeFile(tmpPath, new Date().toISOString(), "utf8");
35
- await rename(tmpPath, heartbeatPath);
36
- }
37
-
38
- async function writeQueueLockOwner(lockPath: string, token: string): Promise<void> {
39
- const ownerPath = `${lockPath}/owner`;
40
- const tmpPath = `${ownerPath}.${process.pid}.${token}.tmp`;
41
- await writeFile(
42
- tmpPath,
43
- JSON.stringify({ pid: process.pid, acquiredAt: new Date().toISOString(), token }),
44
- "utf8",
45
- );
46
- await rename(tmpPath, ownerPath);
47
- await writeQueueHeartbeat(lockPath, token);
48
- }
49
-
50
- function startQueueLockHeartbeat(lockPath: string, token: string): NodeJS.Timeout {
51
- const heartbeatMs = Math.max(1, Math.min(5_000, Math.floor(RETAIN_QUEUE_LOCK.staleMs / 2)));
52
- const heartbeat = setInterval(() => {
53
- void writeQueueHeartbeat(lockPath, token).catch(() => undefined);
54
- }, heartbeatMs);
55
- heartbeat.unref?.();
56
- return heartbeat;
57
- }
58
-
59
- export function isQueueLockOwnerStale(
60
- owner: QueueLockOwner | undefined,
61
- now = Date.now(),
62
- staleMs = RETAIN_QUEUE_LOCK.staleMs,
63
- ): boolean {
64
- if (!owner) return true;
65
- const acquiredAt = owner.acquiredAt ? Date.parse(owner.acquiredAt) : Number.NaN;
66
- return !Number.isFinite(acquiredAt) || now - acquiredAt > staleMs;
67
- }
68
-
69
- async function readQueueLockOwner(lockPath: string): Promise<QueueLockOwner | undefined> {
70
- try {
71
- return JSON.parse(await readFile(`${lockPath}/owner`, "utf8")) as QueueLockOwner;
72
- } catch {
73
- return undefined;
74
- }
75
- }
76
-
77
- async function isOwnerlessLockDirectoryStale(lockPath: string): Promise<boolean> {
78
- try {
79
- const info = await stat(lockPath);
80
- return Date.now() - info.mtimeMs > RETAIN_QUEUE_LOCK.staleMs;
81
- } catch (error) {
82
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return false;
83
- return true;
84
- }
85
- }
86
-
87
- async function isQueueLockStale(lockPath: string, owner: QueueLockOwner): Promise<boolean> {
88
- if (!owner.token) return isQueueLockOwnerStale(owner);
89
- try {
90
- const heartbeat = await stat(`${lockPath}/heartbeat-${owner.token}`);
91
- return Date.now() - heartbeat.mtimeMs > RETAIN_QUEUE_LOCK.staleMs;
92
- } catch {
93
- return isQueueLockOwnerStale(owner);
94
- }
95
- }
96
-
97
- async function removeLockIfOwned(lockPath: string, token: string): Promise<void> {
98
- const owner = await readQueueLockOwner(lockPath);
99
- if (owner?.token === token) await removeDirectory(lockPath);
100
- }
101
-
102
- export function isQueueLockRaceError(error: unknown): boolean {
103
- const code = (error as NodeJS.ErrnoException).code;
104
- return code === "EEXIST" || code === "EPERM" || code === "ENOTEMPTY" || code === "EBUSY";
105
- }
106
-
107
- function staleClaimPath(lockPath: string): string {
108
- return `${lockPath}.stale-claim`;
109
- }
110
-
111
- async function isStaleCleanupClaimActive(lockPath: string): Promise<boolean> {
112
- const claimPath = staleClaimPath(lockPath);
113
- try {
114
- const info = await stat(claimPath);
115
- if (Date.now() - info.mtimeMs <= RETAIN_QUEUE_LOCK.staleMs) return true;
116
- await removeDirectory(claimPath);
117
- return false;
118
- } catch (error) {
119
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return false;
120
- throw error;
121
- }
122
- }
123
-
124
- async function withStaleCleanupClaim<T>(
125
- lockPath: string,
126
- fn: () => Promise<T>,
127
- ): Promise<T | undefined> {
128
- const claimPath = staleClaimPath(lockPath);
129
- try {
130
- await mkdir(claimPath, { recursive: false });
131
- } catch (error) {
132
- if ((error as NodeJS.ErrnoException).code === "EEXIST") return undefined;
133
- throw error;
134
- }
135
- try {
136
- return await fn();
137
- } finally {
138
- await removeDirectory(claimPath);
139
- }
140
- }
141
-
142
- async function removeLockIfStillStale(lockPath: string): Promise<boolean> {
143
- return (
144
- (await withStaleCleanupClaim(lockPath, async () => {
145
- const owner = await readQueueLockOwner(lockPath);
146
- const stale = owner
147
- ? await isQueueLockStale(lockPath, owner)
148
- : await isOwnerlessLockDirectoryStale(lockPath);
149
- if (!stale) return false;
150
- await removeDirectory(lockPath);
151
- return true;
152
- })) ?? false
153
- );
154
- }
155
-
156
- async function acquireFileLock(path: string): Promise<() => Promise<void>> {
157
- const lockPath = `${path}.lock`;
158
- const started = Date.now();
159
- await mkdir(dirname(path), { recursive: true });
160
- while (true) {
161
- if (await isStaleCleanupClaimActive(lockPath)) {
162
- if (Date.now() - started > RETAIN_QUEUE_LOCK.timeoutMs)
163
- throw new Error(`Timed out waiting for retain queue lock ${lockPath}`);
164
- await sleep(RETAIN_QUEUE_LOCK.retryMs);
165
- continue;
166
- }
167
- const token = randomUUID();
168
- try {
169
- await mkdir(lockPath, { recursive: false });
170
- try {
171
- await writeQueueLockOwner(lockPath, token);
172
- } catch (error) {
173
- await removeDirectory(lockPath);
174
- if (["ENOENT", "EINVAL"].includes((error as NodeJS.ErrnoException).code ?? "")) continue;
175
- throw error;
176
- }
177
- const heartbeat = startQueueLockHeartbeat(lockPath, token);
178
- return async () => {
179
- clearInterval(heartbeat);
180
- await removeLockIfOwned(lockPath, token);
181
- };
182
- } catch (error) {
183
- const code = (error as NodeJS.ErrnoException).code;
184
- if (!isQueueLockRaceError(error)) throw error;
185
- if (code === "EEXIST" && (await removeLockIfStillStale(lockPath))) continue;
186
- if (Date.now() - started > RETAIN_QUEUE_LOCK.timeoutMs)
187
- throw new Error(`Timed out waiting for retain queue lock ${lockPath}`);
188
- await sleep(RETAIN_QUEUE_LOCK.retryMs);
189
- }
190
- }
191
- }
192
-
193
- export async function withQueueLock<T>(path: string, fn: () => Promise<T>): Promise<T> {
194
- const previous = queueLocks.get(path) ?? Promise.resolve();
195
- let release!: () => void;
196
- const next = new Promise<void>((resolve) => {
197
- release = resolve;
198
- });
199
- const lock = previous.catch(() => undefined).then(() => next);
200
- queueLocks.set(path, lock);
201
- await previous.catch(() => undefined);
202
- let releaseFileLock: (() => Promise<void>) | undefined;
203
- try {
204
- releaseFileLock = await acquireFileLock(path);
205
- return await fn();
206
- } finally {
207
- try {
208
- if (releaseFileLock) await releaseFileLock();
209
- } finally {
210
- release();
211
- if (queueLocks.get(path) === lock) queueLocks.delete(path);
212
- }
213
- }
214
- }