@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
@@ -12,25 +12,29 @@
12
12
  *
13
13
  * Same shape as `ledger-bounds.ts` (#2426) for the degradation ledger's bound.
14
14
  *
15
- * NOT consolidated here: `lsp/client.ts` keeps its own file-local copy on
16
- * purpose (see its comment) that is a documented decision, not drift.
15
+ * Callers that need results use the result-returning overload; callers that
16
+ * only need completion use the void overload. Keeping both forms here prevents
17
+ * per-caller worker-pool copies from drifting.
17
18
  */
18
- /**
19
- * Run `mapper` over `items` with at most `concurrency` in flight at once.
20
- */
21
- export async function mapWithConcurrency(items, concurrency, mapper) {
19
+ export async function mapWithConcurrency(items, concurrency, mapper, signal) {
22
20
  if (items.length === 0)
23
- return;
21
+ return [];
24
22
  let nextIndex = 0;
25
23
  const workerCount = Math.max(1, Math.min(concurrency, items.length));
24
+ const results = new Map();
26
25
  const worker = async () => {
27
26
  while (true) {
27
+ if (signal?.aborted)
28
+ return;
28
29
  const index = nextIndex++;
29
30
  if (index >= items.length)
30
31
  return;
31
- await mapper(items[index]);
32
+ results.set(index, (await mapper(items[index])));
32
33
  }
33
34
  };
34
35
  const workers = Array.from({ length: workerCount }, () => worker());
35
36
  await Promise.all(workers);
37
+ return [...results.entries()]
38
+ .sort(([left], [right]) => left - right)
39
+ .map(([, result]) => result);
36
40
  }
@@ -31,6 +31,8 @@ import { handleSessionStart } from "../runtime-session.js";
31
31
  import { handleTurnEnd } from "../runtime-turn.js";
32
32
  import { createMcpHost } from "./host-shim.js";
33
33
  import { startSituationalToolTelemetrySession } from "../situational-tool-telemetry.js";
34
+ import { bounded } from "../deadline-utils.js";
35
+ import { HOOK_WALL_BUDGET_MS } from "../hook-budgets.js";
34
36
  let contextPromise;
35
37
  let resolvedRuntime;
36
38
  /**
@@ -73,17 +75,20 @@ function joinMessages(consumed) {
73
75
  return undefined;
74
76
  return consumed.messages.map((message) => message.content).join("\n\n");
75
77
  }
78
+ const degradedSessionStart = {
79
+ aliveLspClients: 0,
80
+ };
76
81
  /**
77
82
  * Run pi-lens's real session_start. Much of the work (scans, baseline, LSP warm)
78
83
  * runs in the background, so the immediate return carries the synchronous
79
84
  * guidance + whatever baseline/LSP state is ready; query `pilens_diagnostics` /
80
85
  * `pilens_health` afterwards for the scan results as they land.
81
86
  */
82
- export async function runSessionStart(cwd) {
87
+ async function runSessionStartImpl(cwd) {
83
88
  startSituationalToolTelemetrySession("mcp");
84
89
  const ctx = await getMcpSessionContext();
85
90
  const host = createMcpHost(undefined, cwd);
86
- await handleSessionStart({
91
+ await bounded(handleSessionStart({
87
92
  ctxCwd: cwd,
88
93
  // The MCP server has no TUI keystroke latency to protect, so the
89
94
  // first-call-quick heuristic must not apply. Force "full" mode so
@@ -108,16 +113,33 @@ export async function runSessionStart(cwd) {
108
113
  cleanStaleTsBuildInfo: () => [],
109
114
  resetDispatchBaselines: (cwdArg) => resetDispatchBaselines(cwdArg ?? cwd),
110
115
  resetLSPService,
116
+ }), {
117
+ ms: HOOK_WALL_BUDGET_MS.session_start,
118
+ signal: undefined,
119
+ hook: "session_start",
120
+ label: "handleSessionStart",
111
121
  });
112
122
  const baseline = ctx.runtime.errorDebtBaseline;
113
123
  return {
114
- guidance: joinMessages(consumeSessionStartGuidance(ctx.cacheManager, cwd)),
124
+ guidance: joinMessages(consumeSessionStartGuidance(ctx.cacheManager, cwd, "mcp")),
115
125
  errorDebtBaseline: baseline
116
126
  ? { testsPassed: baseline.testsPassed, buildPassed: baseline.buildPassed }
117
127
  : undefined,
118
128
  aliveLspClients: getLSPService().getAliveClientCount(),
119
129
  };
120
130
  }
131
+ export function runSessionStart(cwd) {
132
+ return bounded(runSessionStartImpl(cwd), {
133
+ ms: HOOK_WALL_BUDGET_MS.session_start,
134
+ signal: undefined,
135
+ hook: "session_start",
136
+ label: "mcp-session-start",
137
+ }).then((outcome) => outcome ?? degradedSessionStart);
138
+ }
139
+ const degradedTurnEnd = {
140
+ outcome: { filesRegistered: 0 },
141
+ commit: () => { },
142
+ };
121
143
  /**
122
144
  * #1274: the delivery map used to be unbounded — every timed-out client and
123
145
  * every distinct raw-cwd alias left an entry for the life of the process.
@@ -193,7 +215,7 @@ const inFlightIpcTurnEnds = new Map();
193
215
  * reads edited files from turn-state, so we register the caller-supplied files
194
216
  * first (a full-file range, importsChanged=true so dep/knip re-check broadly).
195
217
  */
196
- async function runTurnEndNow(cwd, files = [], deferredDelivery = false) {
218
+ async function runTurnEndNowImpl(cwd, files = [], deferredDelivery = false) {
197
219
  const ctx = await getMcpSessionContext();
198
220
  const host = createMcpHost(undefined, cwd);
199
221
  let registered = 0;
@@ -216,6 +238,7 @@ async function runTurnEndNow(cwd, files = [], deferredDelivery = false) {
216
238
  lastSeen: new Date().toISOString(),
217
239
  };
218
240
  await handleTurnEnd({
241
+ signal: undefined,
219
242
  ctxCwd: cwd,
220
243
  getFlag: host.getFlag,
221
244
  dbg: noop,
@@ -225,6 +248,7 @@ async function runTurnEndNow(cwd, files = [], deferredDelivery = false) {
225
248
  deadCodeClients: ctx.clients.deadCodeClients,
226
249
  depChecker: ctx.clients.depChecker,
227
250
  testRunnerClient: ctx.clients.testRunnerClient,
251
+ host: "mcp",
228
252
  owner,
229
253
  resetLSPService,
230
254
  resetFormatService,
@@ -250,6 +274,14 @@ async function runTurnEndNow(cwd, files = [], deferredDelivery = false) {
250
274
  },
251
275
  };
252
276
  }
277
+ function runTurnEndNow(cwd, files = [], deferredDelivery = false) {
278
+ return bounded(runTurnEndNowImpl(cwd, files, deferredDelivery), {
279
+ ms: HOOK_WALL_BUDGET_MS.turn_end,
280
+ signal: undefined,
281
+ hook: "turn_end",
282
+ label: "mcp-turn-end",
283
+ }).then((transaction) => transaction ?? degradedTurnEnd);
284
+ }
253
285
  /**
254
286
  * Run a normal MCP turn_end. Its caller owns the tool response, so the
255
287
  * historical consume-on-return behavior remains unchanged.
@@ -189,8 +189,17 @@ export function resetSinkRotations() {
189
189
  state.rotationCount = 0;
190
190
  state.rotationFailures = 0;
191
191
  state.rotationRetryAfterMs = 0;
192
+ state.optionConflictRecorded = false;
192
193
  }
193
194
  }
195
+ /** Pure read-time view for the degradation ledger; never logs through this sink. */
196
+ export function getSinkOptionConflicts() {
197
+ if (!ndjsonGlobalState)
198
+ return [];
199
+ return [...ndjsonGlobalState.writers.values()]
200
+ .filter((state) => state.optionConflictRecorded)
201
+ .map((state) => ({ file: state.file }));
202
+ }
194
203
  function requireCurrentGlobalState() {
195
204
  if (ndjsonGlobalState)
196
205
  return ndjsonGlobalState;
@@ -213,12 +222,6 @@ function registerWriter(state) {
213
222
  function normalizeLogPath(file) {
214
223
  return normalizeFilePath(path.resolve(file));
215
224
  }
216
- function assertCompatibleWriterOptions(existing, maxBytes, backupPath) {
217
- if (existing.maxBytes === maxBytes && existing.backupPath === backupPath)
218
- return;
219
- throw new Error(`createNdjsonLogger: incompatible options for shared path ${existing.file}; ` +
220
- `the first writer's maxBytes/backupPath must be reused`);
221
- }
222
225
  function writeQueueItemSync(state, item) {
223
226
  if (item.kind === "truncate") {
224
227
  fs.writeFileSync(state.file, "");
@@ -298,7 +301,6 @@ function createWriterState(file, maxBytes, backupPath) {
298
301
  if (existing) {
299
302
  // A partially initialized global state from another graph still needs to
300
303
  // be enrolled, but it must never get a second queue or exit flusher.
301
- assertCompatibleWriterOptions(existing, maxBytes, backupPath);
302
304
  if (!exitFlushers.has(existing.exitFlusher))
303
305
  registerWriter(existing);
304
306
  // A state adopted from a pre-#1970 module graph predates this field.
@@ -318,6 +320,20 @@ function createWriterState(file, maxBytes, backupPath) {
318
320
  existing.rotationFailures = 0;
319
321
  if (typeof existing.rotationRetryAfterMs !== "number")
320
322
  existing.rotationRetryAfterMs = 0;
323
+ if (typeof existing.optionConflictRecorded !== "boolean")
324
+ existing.optionConflictRecorded = false;
325
+ const optionsDiffer = existing.maxBytes !== maxBytes || existing.backupPath !== backupPath;
326
+ if (optionsDiffer) {
327
+ // A pre-#2505 graph can leave this writer unbounded. Adopt the incoming
328
+ // bound so reload restores rotation instead of preserving an unsafe gap.
329
+ if (existing.maxBytes === undefined && maxBytes !== undefined)
330
+ existing.maxBytes = maxBytes;
331
+ if (existing.backupPath === undefined && backupPath !== undefined)
332
+ existing.backupPath = backupPath;
333
+ // A rotation-policy disagreement must never abort extension loading.
334
+ // The ledger folds this bounded, per-path fact into health at read time.
335
+ existing.optionConflictRecorded = true;
336
+ }
321
337
  return existing;
322
338
  }
323
339
  const state = {};
@@ -337,6 +353,7 @@ function createWriterState(file, maxBytes, backupPath) {
337
353
  state.rotationCount = 0;
338
354
  state.rotationFailures = 0;
339
355
  state.rotationRetryAfterMs = 0;
356
+ state.optionConflictRecorded = false;
340
357
  globalState.writers.set(file, state);
341
358
  registerWriter(state);
342
359
  return state;
@@ -717,6 +717,7 @@ export async function settleObservedMutation(args) {
717
717
  return {
718
718
  settled: false,
719
719
  changedPaths: [],
720
+ unverifiablePaths: [],
720
721
  replayed: 0,
721
722
  scanned: 0,
722
723
  stoppedEarly: false,
@@ -730,6 +731,7 @@ export async function settleObservedMutation(args) {
730
731
  return {
731
732
  settled: false,
732
733
  changedPaths: [],
734
+ unverifiablePaths: [],
733
735
  replayed: 0,
734
736
  scanned: 0,
735
737
  stoppedEarly: false,
@@ -744,6 +746,7 @@ export async function settleObservedMutation(args) {
744
746
  return {
745
747
  settled: false,
746
748
  changedPaths: [],
749
+ unverifiablePaths: [],
747
750
  replayed: 0,
748
751
  scanned: 0,
749
752
  stoppedEarly: false,
@@ -771,6 +774,7 @@ export async function settleObservedMutation(args) {
771
774
  return {
772
775
  settled: false,
773
776
  changedPaths: [],
777
+ unverifiablePaths: [],
774
778
  replayed: 0,
775
779
  scanned: 0,
776
780
  stoppedEarly: true,
@@ -779,7 +783,9 @@ export async function settleObservedMutation(args) {
779
783
  }
780
784
  const captured = capture.value;
781
785
  seedLedger(captured.snapshot);
782
- const changed = diffFileStats(pending.stats, captured.snapshot).filter((candidate) => args.isRecordable?.(candidate) !== false);
786
+ const diff = diffObservedStats(pending.stats, captured.snapshot);
787
+ const changed = diff.changed.filter((candidate) => args.isRecordable?.(candidate) !== false);
788
+ const unverifiablePaths = diff.unverifiable.filter((candidate) => args.isRecordable?.(candidate) !== false);
783
789
  // Two different ways the observation can fall short of what the tool named:
784
790
  // the ARM already truncated a wide directory (#2449 round 3, S3), or the
785
791
  // re-capture's own deadline cut its tail. Both mean the same thing to the
@@ -790,6 +796,19 @@ export async function settleObservedMutation(args) {
790
796
  : captured.stoppedEarly
791
797
  ? "capture-cut-short"
792
798
  : undefined;
799
+ if (unverifiablePaths.length > 0) {
800
+ // Hashless evidence is a bounded coverage gap, not a mutation. Keep it
801
+ // visible through the existing degradation record without replaying it.
802
+ noteObservedUnverifiable(args.toolName);
803
+ logLatency({
804
+ type: "phase",
805
+ toolName: args.toolName,
806
+ phase: "observed_mutation_coverage_unknown",
807
+ filePath: unverifiablePaths.slice(0, 5).join(","),
808
+ durationMs: Date.now() - started,
809
+ result: `unverifiable:${unverifiablePaths.length}`,
810
+ });
811
+ }
793
812
  if (changed.length === 0) {
794
813
  // An INCOMPLETE observation is not evidence of cleanliness — it is
795
814
  // evidence we stopped looking. Advancing the clean latch on it would
@@ -797,11 +816,12 @@ export async function settleObservedMutation(args) {
797
816
  // with a directory wider than the cap that is every single call.
798
817
  if (truncated)
799
818
  noteObservedUnverifiable(args.toolName);
800
- else
819
+ else if (unverifiablePaths.length === 0)
801
820
  noteObservedClean(args.toolName);
802
821
  return {
803
822
  settled: true,
804
823
  changedPaths: [],
824
+ unverifiablePaths,
805
825
  replayed: 0,
806
826
  scanned: captured.snapshot.size,
807
827
  stoppedEarly: truncated,
@@ -860,12 +880,30 @@ export async function settleObservedMutation(args) {
860
880
  return {
861
881
  settled: true,
862
882
  changedPaths: changed,
883
+ unverifiablePaths,
863
884
  replayed,
864
885
  scanned: captured.snapshot.size,
865
886
  stoppedEarly: truncated,
866
887
  reason: cutReason,
867
888
  };
868
889
  }
890
+ /**
891
+ * The observational net asks whether to look harder. An absent content hash
892
+ * therefore remains unverifiable even when cheap stat fields report a change.
893
+ * #2984: stat-only evidence must not grant ownership or replay a mutation.
894
+ */
895
+ function diffObservedStats(before, after) {
896
+ const changed = new Set(diffFileStats(before, after));
897
+ const unverifiable = new Set();
898
+ for (const [key, stat] of after) {
899
+ const previous = before.get(key);
900
+ if (previous && (previous.hash === undefined || stat.hash === undefined)) {
901
+ changed.delete(key);
902
+ unverifiable.add(key);
903
+ }
904
+ }
905
+ return { changed: [...changed], unverifiable: [...unverifiable] };
906
+ }
869
907
  async function readBytesSafe(filePath) {
870
908
  try {
871
909
  return await fs.promises.readFile(filePath);
@@ -124,6 +124,9 @@ export async function captureFileStatsForPaths(files, options = {}) {
124
124
  return { snapshot, stoppedEarly };
125
125
  }
126
126
  export async function captureFileStats(root, options = {}) {
127
+ if (options.forcedUnknownReason !== undefined) {
128
+ return { unknownReason: options.forcedUnknownReason, scannedCount: 0 };
129
+ }
127
130
  const budgetMs = options.budgetMs ?? 50;
128
131
  try {
129
132
  const walk = await collectSourceFilesWithBudgetAsync(root, {
@@ -150,13 +153,12 @@ export async function captureFileStats(root, options = {}) {
150
153
  }
151
154
  /** Paths added or whose size/mtime changed. Deletions are NOT reported. */
152
155
  export function diffFileStats(before, after) {
156
+ const contentChanged = new Set(diffFileContent(before, after));
153
157
  const changed = [];
154
158
  for (const [key, stat] of after) {
155
159
  const prev = before.get(key);
156
160
  // Content confirm: same mtime tick + same size but different bytes.
157
- const contentConfirm = prev?.hash !== undefined &&
158
- stat.hash !== undefined &&
159
- prev.hash !== stat.hash;
161
+ const contentConfirm = contentChanged.has(key);
160
162
  if (!prev ||
161
163
  prev.mtimeMs !== stat.mtimeMs ||
162
164
  prev.size !== stat.size ||
@@ -166,6 +168,24 @@ export function diffFileStats(before, after) {
166
168
  }
167
169
  return changed;
168
170
  }
171
+ /**
172
+ * Return paths confirmed different by hashes, plus files absent before and
173
+ * present after. A missing baseline entry is evidence of creation; a missing
174
+ * hash on an existing entry remains unknown.
175
+ */
176
+ export function diffFileContent(before, after) {
177
+ const changed = [];
178
+ for (const [key, stat] of after) {
179
+ const previous = before.get(key);
180
+ if (previous === undefined ||
181
+ (previous.hash !== undefined &&
182
+ stat.hash !== undefined &&
183
+ previous.hash !== stat.hash)) {
184
+ changed.push(key);
185
+ }
186
+ }
187
+ return changed;
188
+ }
169
189
  export class OpaqueBaselineStore {
170
190
  byCwd = new Map();
171
191
  evictions = 0;
@@ -54,7 +54,9 @@ import * as os from "node:os";
54
54
  import * as path from "node:path";
55
55
  import { mkdtempSync } from "node:fs";
56
56
  import { resolveOpengrepConfig } from "./opengrep-config.js";
57
+ import { recordDegradationOnce } from "./degradation-ledger.js";
57
58
  import { getScratchTreeDirNames } from "./scratch-tree-policy.js";
59
+ import { realpathOrResolve } from "./path-utils.js";
58
60
  import { safeSpawnAsync } from "./safe-spawn.js";
59
61
  import { SecurityScanClient } from "./security-scan-client.js";
60
62
  const EMPTY_RESULT = {
@@ -92,12 +94,13 @@ export class OpengrepClient extends SecurityScanClient {
92
94
  * opengrep process (mirrors `GitleaksClient`/`JscpdClient`).
93
95
  */
94
96
  async scan(cwd) {
95
- const targetDir = path.resolve(cwd);
97
+ const targetDir = realpathOrResolve(cwd);
96
98
  const scannedAt = new Date().toISOString();
97
99
  if (!(await this.ensureAvailable())) {
98
100
  return {
99
101
  ...EMPTY_RESULT,
100
102
  scannedAt,
103
+ reason: "not-installed",
101
104
  summary: "opengrep not installed",
102
105
  };
103
106
  }
@@ -134,33 +137,76 @@ export class OpengrepClient extends SecurityScanClient {
134
137
  ], { cwd, timeout: SCAN_TIMEOUT_MS });
135
138
  if (result.error) {
136
139
  this.log(`Scan error: ${result.error.message}`);
140
+ const reason = `${result.failure ?? "spawn-failed"}: ${result.error.message}`;
141
+ this.recordRefusal(cwd, reason, result.status);
137
142
  return {
138
143
  ...EMPTY_RESULT,
139
144
  scannedAt,
140
- summary: result.error.message.slice(0, 200),
145
+ reason: "spawn-failed",
146
+ summary: reason.slice(0, 200),
141
147
  };
142
148
  }
143
149
  if (!fs.existsSync(reportPath)) {
150
+ const reason = (result.stderr ?? "").trim().split("\n")[0] || "no report produced";
151
+ this.recordRefusal(cwd, reason, result.status);
144
152
  return {
145
153
  ...EMPTY_RESULT,
146
154
  scannedAt,
147
- summary: (result.stderr ?? "").trim().split("\n")[0] || "no report produced",
155
+ reason: "no-report",
156
+ summary: reason,
148
157
  };
149
158
  }
150
- const findings = parseOpengrepReport(fs.readFileSync(reportPath, "utf-8"));
159
+ const raw = fs.readFileSync(reportPath, "utf-8");
160
+ const report = readOpengrepReport(raw);
161
+ if (report.verdict === "refused" || result.status !== 0) {
162
+ const reason = report.verdict === "refused"
163
+ ? report.reason
164
+ : `opengrep exited with status ${result.status}`;
165
+ this.recordRefusal(cwd, reason, result.status);
166
+ return {
167
+ ...EMPTY_RESULT,
168
+ scannedAt,
169
+ reason: "refused",
170
+ summary: reason.slice(0, 200),
171
+ };
172
+ }
173
+ if (report.partial) {
174
+ recordDegradationOnce({
175
+ kind: "opengrep-partial-scan",
176
+ subject: cwd,
177
+ reason: report.partial.reason,
178
+ metadata: { status: result.status },
179
+ });
180
+ }
151
181
  // #2154: the one opengrep site that parsed a scan of this root.
152
182
  return {
153
183
  success: true,
154
- analyzed: true,
155
- findings,
184
+ // A warning-level partial report with no scanned paths proves that
185
+ // the producer went cold. A complete empty report still proves a
186
+ // genuine scan, so it remains authoritative for the id-only fallback.
187
+ analyzed: !report.partial || report.scanned.length > 0,
188
+ ...(report.scanned.length > 0
189
+ ? {
190
+ analyzedFiles: report.scanned.map((file) => realpathOrResolve(path.resolve(cwd, file))),
191
+ }
192
+ : {}),
193
+ ...(report.partial ? { partial: true } : {}),
194
+ ...(report.partial ? { summary: report.partial.reason } : {}),
195
+ ...(report.partial && report.scanned.length === 0
196
+ ? { reason: "partial-no-paths" }
197
+ : {}),
198
+ findings: report.findings,
156
199
  scannedAt,
157
200
  };
158
201
  }
159
202
  catch (err) {
203
+ const reason = err instanceof Error ? err.message : String(err);
204
+ this.recordRefusal(cwd, reason, undefined);
160
205
  return {
161
206
  ...EMPTY_RESULT,
162
207
  scannedAt,
163
- summary: err instanceof Error ? err.message.slice(0, 200) : String(err),
208
+ reason: "crashed",
209
+ summary: reason.slice(0, 200),
164
210
  };
165
211
  }
166
212
  finally {
@@ -172,6 +218,53 @@ export class OpengrepClient extends SecurityScanClient {
172
218
  }
173
219
  }
174
220
  }
221
+ recordRefusal(cwd, reason, status) {
222
+ recordDegradationOnce({
223
+ kind: "opengrep-scan-refused",
224
+ subject: cwd,
225
+ reason,
226
+ metadata: { status },
227
+ });
228
+ }
229
+ }
230
+ function readOpengrepReport(raw) {
231
+ let parsed;
232
+ try {
233
+ parsed = JSON.parse(raw);
234
+ }
235
+ catch {
236
+ return { verdict: "refused", reason: "unparseable opengrep report" };
237
+ }
238
+ if (!parsed || typeof parsed !== "object")
239
+ return { verdict: "refused", reason: "unparseable opengrep report" };
240
+ const report = parsed;
241
+ if (!Array.isArray(report.results) && !Array.isArray(report.errors))
242
+ return { verdict: "refused", reason: "unparseable opengrep report" };
243
+ const errors = (Array.isArray(report.errors) ? report.errors : []).map((error) => {
244
+ if (typeof error === "string")
245
+ return { message: error };
246
+ if (!error || typeof error !== "object")
247
+ return { message: "unrecognised opengrep error entry" };
248
+ const entry = error;
249
+ const level = typeof entry.level === "string" ? entry.level : undefined;
250
+ const message = typeof entry.message === "string" ? entry.message : undefined;
251
+ return {
252
+ ...(level ? { level } : {}),
253
+ message: message ?? level ?? "unrecognised opengrep error entry",
254
+ };
255
+ });
256
+ const refusal = errors.find((error) => error.level !== "warn");
257
+ if (refusal)
258
+ return { verdict: "refused", reason: refusal.message };
259
+ const scanned = Array.isArray(report.paths?.scanned)
260
+ ? report.paths.scanned.filter((file) => typeof file === "string")
261
+ : [];
262
+ return {
263
+ verdict: "usable",
264
+ ...(errors[0] ? { partial: { reason: errors[0].message } } : {}),
265
+ findings: parseOpengrepReport(parsed),
266
+ scanned,
267
+ };
175
268
  }
176
269
  // --- Parser ---
177
270
  /**
@@ -185,15 +278,17 @@ export class OpengrepClient extends SecurityScanClient {
185
278
  * CLI surface has drifted in places, e.g. `--files-with-matches` requires
186
279
  * `--experimental` where semgrep's doesn't).
187
280
  */
188
- export function parseOpengrepReport(raw) {
189
- if (!raw.trim())
190
- return [];
191
- let parsed;
192
- try {
193
- parsed = JSON.parse(raw);
194
- }
195
- catch {
281
+ export function parseOpengrepReport(rawOrParsed) {
282
+ if (typeof rawOrParsed === "string" && !rawOrParsed.trim())
196
283
  return [];
284
+ let parsed = rawOrParsed;
285
+ if (typeof rawOrParsed === "string") {
286
+ try {
287
+ parsed = JSON.parse(rawOrParsed);
288
+ }
289
+ catch {
290
+ return [];
291
+ }
197
292
  }
198
293
  if (!parsed || typeof parsed !== "object")
199
294
  return [];