@herbertgao/pi-extensions 2026.9.6 → 2026.9.8

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 (304) 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/grok-mermaid/CHANGELOG.md +46 -0
  19. package/node_modules/grok-mermaid/LICENSE +205 -0
  20. package/node_modules/grok-mermaid/README.md +191 -0
  21. package/node_modules/grok-mermaid/dist/ansi.d.ts +16 -0
  22. package/node_modules/grok-mermaid/dist/ansi.d.ts.map +1 -0
  23. package/node_modules/grok-mermaid/dist/ansi.js +23 -0
  24. package/node_modules/grok-mermaid/dist/ansi.js.map +1 -0
  25. package/node_modules/grok-mermaid/dist/canvas.d.ts +87 -0
  26. package/node_modules/grok-mermaid/dist/canvas.d.ts.map +1 -0
  27. package/node_modules/grok-mermaid/dist/canvas.js +366 -0
  28. package/node_modules/grok-mermaid/dist/canvas.js.map +1 -0
  29. package/node_modules/grok-mermaid/dist/graph.d.ts +74 -0
  30. package/node_modules/grok-mermaid/dist/graph.d.ts.map +1 -0
  31. package/node_modules/grok-mermaid/dist/graph.js +91 -0
  32. package/node_modules/grok-mermaid/dist/graph.js.map +1 -0
  33. package/node_modules/grok-mermaid/dist/index.d.ts +32 -0
  34. package/node_modules/grok-mermaid/dist/index.d.ts.map +1 -0
  35. package/node_modules/grok-mermaid/dist/index.js +100 -0
  36. package/node_modules/grok-mermaid/dist/index.js.map +1 -0
  37. package/node_modules/grok-mermaid/dist/labels.d.ts +62 -0
  38. package/node_modules/grok-mermaid/dist/labels.d.ts.map +1 -0
  39. package/node_modules/grok-mermaid/dist/labels.js +324 -0
  40. package/node_modules/grok-mermaid/dist/labels.js.map +1 -0
  41. package/node_modules/grok-mermaid/dist/layout-seq.d.ts +12 -0
  42. package/node_modules/grok-mermaid/dist/layout-seq.d.ts.map +1 -0
  43. package/node_modules/grok-mermaid/dist/layout-seq.js +194 -0
  44. package/node_modules/grok-mermaid/dist/layout-seq.js.map +1 -0
  45. package/node_modules/grok-mermaid/dist/layout.d.ts +87 -0
  46. package/node_modules/grok-mermaid/dist/layout.d.ts.map +1 -0
  47. package/node_modules/grok-mermaid/dist/layout.js +881 -0
  48. package/node_modules/grok-mermaid/dist/layout.js.map +1 -0
  49. package/node_modules/grok-mermaid/dist/parse.d.ts +83 -0
  50. package/node_modules/grok-mermaid/dist/parse.d.ts.map +1 -0
  51. package/node_modules/grok-mermaid/dist/parse.js +1151 -0
  52. package/node_modules/grok-mermaid/dist/parse.js.map +1 -0
  53. package/node_modules/grok-mermaid/dist/source-box.d.ts +18 -0
  54. package/node_modules/grok-mermaid/dist/source-box.d.ts.map +1 -0
  55. package/node_modules/grok-mermaid/dist/source-box.js +78 -0
  56. package/node_modules/grok-mermaid/dist/source-box.js.map +1 -0
  57. package/node_modules/grok-mermaid/dist/types.d.ts +42 -0
  58. package/node_modules/grok-mermaid/dist/types.d.ts.map +1 -0
  59. package/node_modules/grok-mermaid/dist/types.js +1 -0
  60. package/node_modules/grok-mermaid/dist/types.js.map +1 -0
  61. package/node_modules/grok-mermaid/dist/width-data.d.ts +2 -0
  62. package/node_modules/grok-mermaid/dist/width-data.d.ts.map +1 -0
  63. package/node_modules/grok-mermaid/dist/width-data.js +994 -0
  64. package/node_modules/grok-mermaid/dist/width-data.js.map +1 -0
  65. package/node_modules/grok-mermaid/dist/width.d.ts +18 -0
  66. package/node_modules/grok-mermaid/dist/width.d.ts.map +1 -0
  67. package/node_modules/grok-mermaid/dist/width.js +76 -0
  68. package/node_modules/grok-mermaid/dist/width.js.map +1 -0
  69. package/node_modules/grok-mermaid/package.json +49 -0
  70. package/node_modules/grok-mermaid/src/ansi.ts +34 -0
  71. package/node_modules/grok-mermaid/src/canvas.ts +373 -0
  72. package/node_modules/grok-mermaid/src/graph.ts +142 -0
  73. package/node_modules/grok-mermaid/src/index.ts +104 -0
  74. package/node_modules/grok-mermaid/src/labels.ts +326 -0
  75. package/node_modules/grok-mermaid/src/layout-seq.ts +203 -0
  76. package/node_modules/grok-mermaid/src/layout.ts +1015 -0
  77. package/node_modules/grok-mermaid/src/parse.ts +1189 -0
  78. package/node_modules/grok-mermaid/src/source-box.ts +89 -0
  79. package/node_modules/grok-mermaid/src/types.ts +43 -0
  80. package/node_modules/grok-mermaid/src/width-data.ts +993 -0
  81. package/node_modules/grok-mermaid/src/width.ts +74 -0
  82. package/node_modules/pi-lens/CHANGELOG.md +239 -0
  83. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -0
  84. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +30 -4
  85. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +2 -0
  86. package/node_modules/pi-lens/dist/clients/bash-file-access.js +26 -0
  87. package/node_modules/pi-lens/dist/clients/biome-client.js +1 -1
  88. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +403 -35
  89. package/node_modules/pi-lens/dist/clients/bootstrap.js +6 -1
  90. package/node_modules/pi-lens/dist/clients/cascade-format.js +200 -4
  91. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +10 -2
  92. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +16 -1
  93. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +12 -1
  94. package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +65 -0
  95. package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +28 -0
  96. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +9 -2
  97. package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +222 -0
  98. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +389 -13
  99. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +175 -25
  100. package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +62 -0
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +21 -41
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +6 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +7 -7
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -12
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +2 -21
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +1 -1
  107. package/node_modules/pi-lens/dist/clients/dispatch/types.js +4 -1
  108. package/node_modules/pi-lens/dist/clients/disposition-publish.js +1 -0
  109. package/node_modules/pi-lens/dist/clients/effective-config.js +1 -1
  110. package/node_modules/pi-lens/dist/clients/file-kinds.js +26 -1
  111. package/node_modules/pi-lens/dist/clients/file-role.js +21 -21
  112. package/node_modules/pi-lens/dist/clients/file-utils.js +156 -10
  113. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +56 -9
  114. package/node_modules/pi-lens/dist/clients/format-service.js +39 -21
  115. package/node_modules/pi-lens/dist/clients/formatters.js +60 -13
  116. package/node_modules/pi-lens/dist/clients/generation-guard.js +2 -2
  117. package/node_modules/pi-lens/dist/clients/git-guard.js +11 -3
  118. package/node_modules/pi-lens/dist/clients/indent-retarget.js +78 -8
  119. package/node_modules/pi-lens/dist/clients/installer/index.js +333 -114
  120. package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +7 -0
  121. package/node_modules/pi-lens/dist/clients/instance-reaper.js +20 -10
  122. package/node_modules/pi-lens/dist/clients/language-profile.js +23 -1
  123. package/node_modules/pi-lens/dist/clients/language-registry.js +5 -1
  124. package/node_modules/pi-lens/dist/clients/latency-logger.js +4 -1
  125. package/node_modules/pi-lens/dist/clients/lens-engine.js +12 -3
  126. package/node_modules/pi-lens/dist/clients/lens-events.js +2 -1
  127. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +79 -0
  128. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +2 -1
  129. package/node_modules/pi-lens/dist/clients/lsp/client.js +25 -21
  130. package/node_modules/pi-lens/dist/clients/lsp/config.js +2 -2
  131. package/node_modules/pi-lens/dist/clients/lsp/index.js +7 -0
  132. package/node_modules/pi-lens/dist/clients/lsp/launch.js +16 -4
  133. package/node_modules/pi-lens/dist/clients/lsp/server.js +19 -2
  134. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +2 -1
  135. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +5 -3
  136. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +12 -8
  137. package/node_modules/pi-lens/dist/clients/mcp/session.js +36 -4
  138. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +24 -7
  139. package/node_modules/pi-lens/dist/clients/observed-mutation.js +40 -2
  140. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +23 -3
  141. package/node_modules/pi-lens/dist/clients/opengrep-client.js +110 -15
  142. package/node_modules/pi-lens/dist/clients/path-utils.js +123 -3
  143. package/node_modules/pi-lens/dist/clients/performance-report.js +5 -1
  144. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +1 -1
  145. package/node_modules/pi-lens/dist/clients/pipeline.js +74 -5
  146. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +37 -5
  147. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +145 -18
  148. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +4 -1
  149. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +59 -3
  150. package/node_modules/pi-lens/dist/clients/project-lens-config.js +45 -2
  151. package/node_modules/pi-lens/dist/clients/read-guard.js +70 -11
  152. package/node_modules/pi-lens/dist/clients/resource-sampler.js +147 -9
  153. package/node_modules/pi-lens/dist/clients/ruff-client.js +1 -1
  154. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +33 -5
  155. package/node_modules/pi-lens/dist/clients/runtime-context.js +6 -2
  156. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +77 -7
  157. package/node_modules/pi-lens/dist/clients/runtime-session.js +21 -2
  158. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +43 -12
  159. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +330 -85
  160. package/node_modules/pi-lens/dist/clients/runtime-turn.js +147 -17
  161. package/node_modules/pi-lens/dist/clients/safe-spawn.js +258 -5
  162. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +6 -0
  163. package/node_modules/pi-lens/dist/clients/session-event-guard.js +50 -5
  164. package/node_modules/pi-lens/dist/clients/sg-runner.js +23 -11
  165. package/node_modules/pi-lens/dist/clients/sgconfig.js +79 -2
  166. package/node_modules/pi-lens/dist/clients/test-runner-client.js +6 -41
  167. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +43 -0
  168. package/node_modules/pi-lens/dist/clients/tool-agreement.js +423 -0
  169. package/node_modules/pi-lens/dist/clients/tool-config.js +50 -0
  170. package/node_modules/pi-lens/dist/clients/tool-cwd.js +47 -76
  171. package/node_modules/pi-lens/dist/clients/tool-policy.js +163 -7
  172. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +120 -0
  173. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +127 -189
  174. package/node_modules/pi-lens/dist/clients/widget-state.js +98 -0
  175. package/node_modules/pi-lens/dist/clients/word-index.js +5 -2
  176. package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +7 -0
  177. package/node_modules/pi-lens/dist/index.js +27416 -24153
  178. package/node_modules/pi-lens/dist/mcp/server.js +5 -2
  179. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +7 -1
  180. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +168 -53
  181. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +244 -72
  182. package/node_modules/pi-lens/docs/audit1.md +5 -5
  183. package/node_modules/pi-lens/docs/dependencies.md +11 -0
  184. package/node_modules/pi-lens/docs/dispositions.md +52 -0
  185. package/node_modules/pi-lens/docs/environment-variables.md +2 -2
  186. package/node_modules/pi-lens/docs/features.md +4 -4
  187. package/node_modules/pi-lens/docs/globalconfig.md +3 -1
  188. package/node_modules/pi-lens/docs/language-coverage.md +1 -0
  189. package/node_modules/pi-lens/docs/pi-lens-fixer.md +53 -94
  190. package/node_modules/pi-lens/docs/pi-lens-investigator.md +43 -38
  191. package/node_modules/pi-lens/docs/pi-lens-retro.md +97 -0
  192. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +53 -66
  193. package/node_modules/pi-lens/docs/release-qa-baseline.md +22 -0
  194. package/node_modules/pi-lens/docs/settings.md +7 -4
  195. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
  196. package/node_modules/pi-lens/docs/usage.md +2 -0
  197. package/node_modules/pi-lens/package.json +1 -1
  198. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +24 -12
  199. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +40 -0
  200. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +15 -0
  201. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +7 -0
  202. package/package.json +6 -11
  203. package/node_modules/@luxusai/pi-hindsight/CHANGELOG.md +0 -376
  204. package/node_modules/@luxusai/pi-hindsight/README.md +0 -92
  205. package/node_modules/@luxusai/pi-hindsight/docs/adr/001-memory-lifecycle-and-scope.md +0 -56
  206. package/node_modules/@luxusai/pi-hindsight/docs/adr/002-explicit-routing-strategy-seam.md +0 -169
  207. package/node_modules/@luxusai/pi-hindsight/docs/adr/003-tui-memory-mode-vocabulary.md +0 -107
  208. package/node_modules/@luxusai/pi-hindsight/docs/adr/004-lifeos-dual-bank-design.md +0 -89
  209. package/node_modules/@luxusai/pi-hindsight/docs/adr/005-domain-banks-and-agent-first-surface.md +0 -189
  210. package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark.webp +0 -0
  211. package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark@2x.webp +0 -0
  212. package/node_modules/@luxusai/pi-hindsight/docs/coding-memory-evaluation.md +0 -54
  213. package/node_modules/@luxusai/pi-hindsight/docs/compatibility.md +0 -75
  214. package/node_modules/@luxusai/pi-hindsight/docs/hindsight-core-functions.md +0 -300
  215. package/node_modules/@luxusai/pi-hindsight/docs/mission-and-mental-model-quality.md +0 -158
  216. package/node_modules/@luxusai/pi-hindsight/docs/next-opt-out-design.md +0 -164
  217. package/node_modules/@luxusai/pi-hindsight/docs/risky-memory-modes.md +0 -139
  218. package/node_modules/@luxusai/pi-hindsight/docs/starter-mental-model-suggestions.md +0 -74
  219. package/node_modules/@luxusai/pi-hindsight/docs/surface-reference.md +0 -261
  220. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-operations.ts +0 -151
  221. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-selection.ts +0 -18
  222. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-settings-presenter.ts +0 -46
  223. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-templates.ts +0 -383
  224. package/node_modules/@luxusai/pi-hindsight/extensions/banks/banking.ts +0 -240
  225. package/node_modules/@luxusai/pi-hindsight/extensions/banks/knowledge-page-seed.ts +0 -176
  226. package/node_modules/@luxusai/pi-hindsight/extensions/banks/retain-strategies.ts +0 -178
  227. package/node_modules/@luxusai/pi-hindsight/extensions/client/client-retry.ts +0 -41
  228. package/node_modules/@luxusai/pi-hindsight/extensions/client/client.ts +0 -386
  229. package/node_modules/@luxusai/pi-hindsight/extensions/client/fetch-compat.ts +0 -39
  230. package/node_modules/@luxusai/pi-hindsight/extensions/client/timeout.ts +0 -34
  231. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-defaults.ts +0 -140
  232. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-model.ts +0 -61
  233. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-registry.ts +0 -925
  234. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-field-paths.ts +0 -247
  235. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-normalize.ts +0 -547
  236. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-writer.ts +0 -480
  237. package/node_modules/@luxusai/pi-hindsight/extensions/config/config.ts +0 -182
  238. package/node_modules/@luxusai/pi-hindsight/extensions/config/setup-gate.ts +0 -86
  239. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-execute.ts +0 -1010
  240. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-parse.ts +0 -175
  241. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-plan.ts +0 -425
  242. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-presentation.ts +0 -210
  243. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-sessions.ts +0 -817
  244. package/node_modules/@luxusai/pi-hindsight/extensions/index.ts +0 -25
  245. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/git-seed.ts +0 -319
  246. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-recall.ts +0 -196
  247. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-retain.ts +0 -189
  248. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-runtime.ts +0 -65
  249. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle.ts +0 -260
  250. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/mental-models.ts +0 -244
  251. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/observation-scopes.ts +0 -59
  252. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-cleanup.ts +0 -82
  253. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-visibility.ts +0 -45
  254. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall.ts +0 -331
  255. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-cursor.ts +0 -354
  256. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-job-builder.ts +0 -66
  257. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-receipts.ts +0 -132
  258. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain.ts +0 -228
  259. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-bank-template-operations.ts +0 -74
  260. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-config-operations.ts +0 -16
  261. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-control-operations.ts +0 -648
  262. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-diagnostics-operations.ts +0 -105
  263. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-identity.ts +0 -68
  264. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-service.ts +0 -71
  265. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-types.ts +0 -13
  266. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-recall-operations.ts +0 -162
  267. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-retain-operations.ts +0 -131
  268. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-scope.ts +0 -104
  269. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-session-operations.ts +0 -37
  270. package/node_modules/@luxusai/pi-hindsight/extensions/operations/operation-catalog.ts +0 -893
  271. package/node_modules/@luxusai/pi-hindsight/extensions/operations/reflect-presenter.ts +0 -35
  272. package/node_modules/@luxusai/pi-hindsight/extensions/operations/scope-migrate.ts +0 -177
  273. package/node_modules/@luxusai/pi-hindsight/extensions/operations/tools.ts +0 -7
  274. package/node_modules/@luxusai/pi-hindsight/extensions/queue/flush-presenter.ts +0 -21
  275. package/node_modules/@luxusai/pi-hindsight/extensions/queue/jsonl-queue-store.ts +0 -112
  276. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-delivery.ts +0 -111
  277. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-lock.ts +0 -214
  278. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-operations.ts +0 -70
  279. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue.ts +0 -392
  280. package/node_modules/@luxusai/pi-hindsight/extensions/tui/bank-template-presentation.ts +0 -45
  281. package/node_modules/@luxusai/pi-hindsight/extensions/tui/commands.ts +0 -9
  282. package/node_modules/@luxusai/pi-hindsight/extensions/tui/guided-setup.ts +0 -860
  283. package/node_modules/@luxusai/pi-hindsight/extensions/tui/prefill-input.ts +0 -105
  284. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-flow.ts +0 -189
  285. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-server-probe.ts +0 -299
  286. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-actions.ts +0 -201
  287. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-facts.ts +0 -47
  288. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-render.ts +0 -239
  289. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-types.ts +0 -50
  290. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui.ts +0 -259
  291. package/node_modules/@luxusai/pi-hindsight/extensions/tui/tool-presenters.ts +0 -77
  292. package/node_modules/@luxusai/pi-hindsight/extensions/types.ts +0 -534
  293. package/node_modules/@luxusai/pi-hindsight/extensions/utils/diagnostics.ts +0 -319
  294. package/node_modules/@luxusai/pi-hindsight/extensions/utils/messages.ts +0 -325
  295. package/node_modules/@luxusai/pi-hindsight/extensions/utils/sanitize.ts +0 -42
  296. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-memory-meta.ts +0 -244
  297. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-operations.ts +0 -56
  298. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session.ts +0 -50
  299. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-fields.ts +0 -167
  300. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-health.ts +0 -207
  301. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status.ts +0 -120
  302. package/node_modules/@luxusai/pi-hindsight/extensions/version.ts +0 -8
  303. package/node_modules/@luxusai/pi-hindsight/package.json +0 -118
  304. 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
- }