@herbertgao/pi-extensions 2026.9.5 → 2026.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/README.md +5 -22
  2. package/THIRD_PARTY_NOTICES.md +0 -4
  3. package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +1 -1
  4. package/node_modules/@herbertgao/sol-pi/package.json +1 -1
  5. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +0 -5
  6. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +3 -1
  7. package/node_modules/@narumitw/pi-btw/README.md +2 -0
  8. package/node_modules/@narumitw/pi-btw/dist/index.ts +284 -365
  9. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
  10. package/node_modules/@narumitw/pi-btw/package.json +52 -53
  11. package/node_modules/@narumitw/pi-btw/src/bring-to-main.ts +494 -547
  12. package/node_modules/@narumitw/pi-btw/src/btw.ts +814 -856
  13. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +673 -674
  14. package/node_modules/@narumitw/pi-btw/src/keybindings.ts +228 -270
  15. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +309 -318
  16. package/node_modules/@narumitw/pi-btw/src/menu.ts +416 -454
  17. package/node_modules/@narumitw/pi-btw/src/settings.ts +203 -219
  18. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +173 -201
  19. package/node_modules/@narumitw/pi-btw/src/text.ts +21 -23
  20. package/node_modules/@narumitw/pi-btw/src/transcript-markdown.ts +65 -0
  21. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +611 -662
  22. package/node_modules/pi-lens/CHANGELOG.md +239 -0
  23. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -0
  24. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +30 -4
  25. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +2 -0
  26. package/node_modules/pi-lens/dist/clients/bash-file-access.js +26 -0
  27. package/node_modules/pi-lens/dist/clients/biome-client.js +1 -1
  28. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +403 -35
  29. package/node_modules/pi-lens/dist/clients/bootstrap.js +6 -1
  30. package/node_modules/pi-lens/dist/clients/cascade-format.js +200 -4
  31. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +10 -2
  32. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +16 -1
  33. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +12 -1
  34. package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +65 -0
  35. package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +28 -0
  36. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +9 -2
  37. package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +222 -0
  38. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +389 -13
  39. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +175 -25
  40. package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +62 -0
  41. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +21 -41
  42. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +6 -1
  43. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +7 -7
  44. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -12
  45. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +2 -21
  46. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +1 -1
  47. package/node_modules/pi-lens/dist/clients/dispatch/types.js +4 -1
  48. package/node_modules/pi-lens/dist/clients/disposition-publish.js +1 -0
  49. package/node_modules/pi-lens/dist/clients/effective-config.js +1 -1
  50. package/node_modules/pi-lens/dist/clients/file-kinds.js +26 -1
  51. package/node_modules/pi-lens/dist/clients/file-role.js +21 -21
  52. package/node_modules/pi-lens/dist/clients/file-utils.js +156 -10
  53. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +56 -9
  54. package/node_modules/pi-lens/dist/clients/format-service.js +39 -21
  55. package/node_modules/pi-lens/dist/clients/formatters.js +60 -13
  56. package/node_modules/pi-lens/dist/clients/generation-guard.js +2 -2
  57. package/node_modules/pi-lens/dist/clients/git-guard.js +11 -3
  58. package/node_modules/pi-lens/dist/clients/indent-retarget.js +78 -8
  59. package/node_modules/pi-lens/dist/clients/installer/index.js +333 -114
  60. package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +7 -0
  61. package/node_modules/pi-lens/dist/clients/instance-reaper.js +20 -10
  62. package/node_modules/pi-lens/dist/clients/language-profile.js +23 -1
  63. package/node_modules/pi-lens/dist/clients/language-registry.js +5 -1
  64. package/node_modules/pi-lens/dist/clients/latency-logger.js +4 -1
  65. package/node_modules/pi-lens/dist/clients/lens-engine.js +12 -3
  66. package/node_modules/pi-lens/dist/clients/lens-events.js +2 -1
  67. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +79 -0
  68. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +2 -1
  69. package/node_modules/pi-lens/dist/clients/lsp/client.js +25 -21
  70. package/node_modules/pi-lens/dist/clients/lsp/config.js +2 -2
  71. package/node_modules/pi-lens/dist/clients/lsp/index.js +7 -0
  72. package/node_modules/pi-lens/dist/clients/lsp/launch.js +16 -4
  73. package/node_modules/pi-lens/dist/clients/lsp/server.js +19 -2
  74. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +2 -1
  75. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +5 -3
  76. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +12 -8
  77. package/node_modules/pi-lens/dist/clients/mcp/session.js +36 -4
  78. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +24 -7
  79. package/node_modules/pi-lens/dist/clients/observed-mutation.js +40 -2
  80. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +23 -3
  81. package/node_modules/pi-lens/dist/clients/opengrep-client.js +110 -15
  82. package/node_modules/pi-lens/dist/clients/path-utils.js +123 -3
  83. package/node_modules/pi-lens/dist/clients/performance-report.js +5 -1
  84. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +1 -1
  85. package/node_modules/pi-lens/dist/clients/pipeline.js +74 -5
  86. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +37 -5
  87. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +145 -18
  88. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +4 -1
  89. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +59 -3
  90. package/node_modules/pi-lens/dist/clients/project-lens-config.js +45 -2
  91. package/node_modules/pi-lens/dist/clients/read-guard.js +70 -11
  92. package/node_modules/pi-lens/dist/clients/resource-sampler.js +147 -9
  93. package/node_modules/pi-lens/dist/clients/ruff-client.js +1 -1
  94. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +33 -5
  95. package/node_modules/pi-lens/dist/clients/runtime-context.js +6 -2
  96. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +77 -7
  97. package/node_modules/pi-lens/dist/clients/runtime-session.js +21 -2
  98. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +43 -12
  99. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +330 -85
  100. package/node_modules/pi-lens/dist/clients/runtime-turn.js +147 -17
  101. package/node_modules/pi-lens/dist/clients/safe-spawn.js +258 -5
  102. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +6 -0
  103. package/node_modules/pi-lens/dist/clients/session-event-guard.js +50 -5
  104. package/node_modules/pi-lens/dist/clients/sg-runner.js +23 -11
  105. package/node_modules/pi-lens/dist/clients/sgconfig.js +79 -2
  106. package/node_modules/pi-lens/dist/clients/test-runner-client.js +6 -41
  107. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +43 -0
  108. package/node_modules/pi-lens/dist/clients/tool-agreement.js +423 -0
  109. package/node_modules/pi-lens/dist/clients/tool-config.js +50 -0
  110. package/node_modules/pi-lens/dist/clients/tool-cwd.js +47 -76
  111. package/node_modules/pi-lens/dist/clients/tool-policy.js +163 -7
  112. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +120 -0
  113. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +127 -189
  114. package/node_modules/pi-lens/dist/clients/widget-state.js +98 -0
  115. package/node_modules/pi-lens/dist/clients/word-index.js +5 -2
  116. package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +7 -0
  117. package/node_modules/pi-lens/dist/index.js +27416 -24153
  118. package/node_modules/pi-lens/dist/mcp/server.js +5 -2
  119. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +7 -1
  120. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +168 -53
  121. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +244 -72
  122. package/node_modules/pi-lens/docs/audit1.md +5 -5
  123. package/node_modules/pi-lens/docs/dependencies.md +11 -0
  124. package/node_modules/pi-lens/docs/dispositions.md +52 -0
  125. package/node_modules/pi-lens/docs/environment-variables.md +2 -2
  126. package/node_modules/pi-lens/docs/features.md +4 -4
  127. package/node_modules/pi-lens/docs/globalconfig.md +3 -1
  128. package/node_modules/pi-lens/docs/language-coverage.md +1 -0
  129. package/node_modules/pi-lens/docs/pi-lens-fixer.md +53 -94
  130. package/node_modules/pi-lens/docs/pi-lens-investigator.md +43 -38
  131. package/node_modules/pi-lens/docs/pi-lens-retro.md +97 -0
  132. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +53 -66
  133. package/node_modules/pi-lens/docs/release-qa-baseline.md +22 -0
  134. package/node_modules/pi-lens/docs/settings.md +7 -4
  135. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
  136. package/node_modules/pi-lens/docs/usage.md +2 -0
  137. package/node_modules/pi-lens/package.json +1 -1
  138. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +24 -12
  139. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +40 -0
  140. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +15 -0
  141. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +7 -0
  142. package/package.json +6 -17
  143. package/examples/pi-footer.json +0 -275
  144. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +0 -70
  145. package/node_modules/@czottmann/pi-automode/LICENSE.md +0 -21
  146. package/node_modules/@czottmann/pi-automode/README.md +0 -215
  147. package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +0 -39
  148. package/node_modules/@czottmann/pi-automode/docs/adr/ADR-001-permission-precedence-and-trust-boundaries.md +0 -46
  149. package/node_modules/@czottmann/pi-automode/docs/adr/ADR-002-global-config-in-extension-data-directory.md +0 -60
  150. package/node_modules/@czottmann/pi-automode/docs/adr/INDEX.md +0 -6
  151. package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +0 -428
  152. package/node_modules/@czottmann/pi-automode/docs/configuration.md +0 -171
  153. package/node_modules/@czottmann/pi-automode/docs/defaults.md +0 -178
  154. package/node_modules/@czottmann/pi-automode/docs/diagnostics.md +0 -90
  155. package/node_modules/@czottmann/pi-automode/docs/observability-logging.md +0 -160
  156. package/node_modules/@czottmann/pi-automode/examples/automode.local.json +0 -44
  157. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/bash.ts +0 -692
  158. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +0 -852
  159. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +0 -904
  160. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +0 -228
  161. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +0 -954
  162. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +0 -429
  163. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/log.ts +0 -173
  164. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/model-selector.ts +0 -113
  165. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/model.ts +0 -13
  166. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/paths.ts +0 -297
  167. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/permissions.ts +0 -623
  168. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/state.ts +0 -102
  169. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/transcript.ts +0 -236
  170. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/types.ts +0 -205
  171. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/utils.ts +0 -54
  172. package/node_modules/@czottmann/pi-automode/extensions/auto-mode.ts +0 -26
  173. package/node_modules/@czottmann/pi-automode/package.json +0 -61
  174. package/node_modules/@czottmann/pi-automode/skills/automode-diagnostics/SKILL.md +0 -63
  175. package/node_modules/@luxusai/pi-hindsight/CHANGELOG.md +0 -376
  176. package/node_modules/@luxusai/pi-hindsight/README.md +0 -92
  177. package/node_modules/@luxusai/pi-hindsight/docs/adr/001-memory-lifecycle-and-scope.md +0 -56
  178. package/node_modules/@luxusai/pi-hindsight/docs/adr/002-explicit-routing-strategy-seam.md +0 -169
  179. package/node_modules/@luxusai/pi-hindsight/docs/adr/003-tui-memory-mode-vocabulary.md +0 -107
  180. package/node_modules/@luxusai/pi-hindsight/docs/adr/004-lifeos-dual-bank-design.md +0 -89
  181. package/node_modules/@luxusai/pi-hindsight/docs/adr/005-domain-banks-and-agent-first-surface.md +0 -189
  182. package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark.webp +0 -0
  183. package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark@2x.webp +0 -0
  184. package/node_modules/@luxusai/pi-hindsight/docs/coding-memory-evaluation.md +0 -54
  185. package/node_modules/@luxusai/pi-hindsight/docs/compatibility.md +0 -75
  186. package/node_modules/@luxusai/pi-hindsight/docs/hindsight-core-functions.md +0 -300
  187. package/node_modules/@luxusai/pi-hindsight/docs/mission-and-mental-model-quality.md +0 -158
  188. package/node_modules/@luxusai/pi-hindsight/docs/next-opt-out-design.md +0 -164
  189. package/node_modules/@luxusai/pi-hindsight/docs/risky-memory-modes.md +0 -139
  190. package/node_modules/@luxusai/pi-hindsight/docs/starter-mental-model-suggestions.md +0 -74
  191. package/node_modules/@luxusai/pi-hindsight/docs/surface-reference.md +0 -261
  192. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-operations.ts +0 -151
  193. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-selection.ts +0 -18
  194. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-settings-presenter.ts +0 -46
  195. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-templates.ts +0 -383
  196. package/node_modules/@luxusai/pi-hindsight/extensions/banks/banking.ts +0 -240
  197. package/node_modules/@luxusai/pi-hindsight/extensions/banks/knowledge-page-seed.ts +0 -176
  198. package/node_modules/@luxusai/pi-hindsight/extensions/banks/retain-strategies.ts +0 -178
  199. package/node_modules/@luxusai/pi-hindsight/extensions/client/client-retry.ts +0 -41
  200. package/node_modules/@luxusai/pi-hindsight/extensions/client/client.ts +0 -386
  201. package/node_modules/@luxusai/pi-hindsight/extensions/client/fetch-compat.ts +0 -39
  202. package/node_modules/@luxusai/pi-hindsight/extensions/client/timeout.ts +0 -34
  203. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-defaults.ts +0 -140
  204. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-model.ts +0 -61
  205. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-registry.ts +0 -925
  206. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-field-paths.ts +0 -247
  207. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-normalize.ts +0 -547
  208. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-writer.ts +0 -480
  209. package/node_modules/@luxusai/pi-hindsight/extensions/config/config.ts +0 -182
  210. package/node_modules/@luxusai/pi-hindsight/extensions/config/setup-gate.ts +0 -86
  211. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-execute.ts +0 -1010
  212. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-parse.ts +0 -175
  213. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-plan.ts +0 -425
  214. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-presentation.ts +0 -210
  215. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-sessions.ts +0 -817
  216. package/node_modules/@luxusai/pi-hindsight/extensions/index.ts +0 -25
  217. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/git-seed.ts +0 -319
  218. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-recall.ts +0 -196
  219. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-retain.ts +0 -189
  220. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-runtime.ts +0 -65
  221. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle.ts +0 -260
  222. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/mental-models.ts +0 -244
  223. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/observation-scopes.ts +0 -59
  224. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-cleanup.ts +0 -82
  225. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-visibility.ts +0 -45
  226. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall.ts +0 -331
  227. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-cursor.ts +0 -354
  228. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-job-builder.ts +0 -66
  229. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-receipts.ts +0 -132
  230. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain.ts +0 -228
  231. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-bank-template-operations.ts +0 -74
  232. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-config-operations.ts +0 -16
  233. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-control-operations.ts +0 -648
  234. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-diagnostics-operations.ts +0 -105
  235. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-identity.ts +0 -68
  236. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-service.ts +0 -71
  237. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-types.ts +0 -13
  238. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-recall-operations.ts +0 -162
  239. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-retain-operations.ts +0 -131
  240. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-scope.ts +0 -104
  241. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-session-operations.ts +0 -37
  242. package/node_modules/@luxusai/pi-hindsight/extensions/operations/operation-catalog.ts +0 -893
  243. package/node_modules/@luxusai/pi-hindsight/extensions/operations/reflect-presenter.ts +0 -35
  244. package/node_modules/@luxusai/pi-hindsight/extensions/operations/scope-migrate.ts +0 -177
  245. package/node_modules/@luxusai/pi-hindsight/extensions/operations/tools.ts +0 -7
  246. package/node_modules/@luxusai/pi-hindsight/extensions/queue/flush-presenter.ts +0 -21
  247. package/node_modules/@luxusai/pi-hindsight/extensions/queue/jsonl-queue-store.ts +0 -112
  248. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-delivery.ts +0 -111
  249. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-lock.ts +0 -214
  250. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-operations.ts +0 -70
  251. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue.ts +0 -392
  252. package/node_modules/@luxusai/pi-hindsight/extensions/tui/bank-template-presentation.ts +0 -45
  253. package/node_modules/@luxusai/pi-hindsight/extensions/tui/commands.ts +0 -9
  254. package/node_modules/@luxusai/pi-hindsight/extensions/tui/guided-setup.ts +0 -860
  255. package/node_modules/@luxusai/pi-hindsight/extensions/tui/prefill-input.ts +0 -105
  256. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-flow.ts +0 -189
  257. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-server-probe.ts +0 -299
  258. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-actions.ts +0 -201
  259. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-facts.ts +0 -47
  260. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-render.ts +0 -239
  261. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-types.ts +0 -50
  262. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui.ts +0 -259
  263. package/node_modules/@luxusai/pi-hindsight/extensions/tui/tool-presenters.ts +0 -77
  264. package/node_modules/@luxusai/pi-hindsight/extensions/types.ts +0 -534
  265. package/node_modules/@luxusai/pi-hindsight/extensions/utils/diagnostics.ts +0 -319
  266. package/node_modules/@luxusai/pi-hindsight/extensions/utils/messages.ts +0 -325
  267. package/node_modules/@luxusai/pi-hindsight/extensions/utils/sanitize.ts +0 -42
  268. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-memory-meta.ts +0 -244
  269. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-operations.ts +0 -56
  270. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session.ts +0 -50
  271. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-fields.ts +0 -167
  272. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-health.ts +0 -207
  273. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status.ts +0 -120
  274. package/node_modules/@luxusai/pi-hindsight/extensions/version.ts +0 -8
  275. package/node_modules/@luxusai/pi-hindsight/package.json +0 -118
  276. package/node_modules/@luxusai/pi-hindsight/skills/hindsight-memory-doctor/SKILL.md +0 -75
@@ -1,954 +0,0 @@
1
- import { realpathSync, statSync } from "node:fs";
2
- import { dirname, resolve } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import type {
5
- ExtensionAPI,
6
- ExtensionCommandContext,
7
- ExtensionContext,
8
- } from "@earendil-works/pi-coding-agent";
9
- import { StringEnum } from "@earendil-works/pi-ai";
10
- import { Type } from "typebox";
11
- import {
12
- classifierReasoningForConfig,
13
- defaultClassifyAction,
14
- serializeClassifierAction,
15
- } from "./classifier.ts";
16
- import { analyzeBash, type BashAnalysis } from "./bash.ts";
17
- import {
18
- AUTO_MODE_GUIDANCE,
19
- DEFAULT_ALLOW,
20
- DEFAULT_ENVIRONMENT,
21
- DEFAULT_HARD_DENY,
22
- DEFAULT_PROTECTED_PATHS,
23
- DEFAULT_SOFT_DENY,
24
- PATH_BEARING_TOOLS,
25
- PI_GLOBAL_SETTINGS,
26
- READ_ONLY_TOOLS,
27
- } from "./constants.ts";
28
- import {
29
- type GlobalConfigPreparation,
30
- loadEffectiveConfigWithDiagnostics,
31
- prepareGlobalConfig,
32
- writeGlobalClassifierModel,
33
- } from "./config.ts";
34
- import { deterministicHardDeny } from "./hard-deny.ts";
35
- import {
36
- createLogger,
37
- newDecisionId,
38
- resolveLogPath,
39
- type Logger,
40
- } from "./log.ts";
41
- import { formatModelSpec, parseModelSpec } from "./model.ts";
42
- import { promptForClassifierModel } from "./model-selector.ts";
43
- import {
44
- matchesAllowedToolPatterns,
45
- matchesDeniedPath,
46
- matchesToolPattern,
47
- matchingBashCommandText,
48
- recursiveSearchMayReachDeniedPath,
49
- } from "./permissions.ts";
50
- import {
51
- extractInputPath,
52
- isInside,
53
- isProtectedPath,
54
- resolvePathForPolicy,
55
- resolveToolInputPath,
56
- } from "./paths.ts";
57
- import {
58
- actionSummary,
59
- formatDenials,
60
- pushDenial,
61
- restoreState,
62
- statusLine,
63
- statusText,
64
- } from "./state.ts";
65
- import { loadedContextFromSystemPromptOptions } from "./transcript.ts";
66
- import type {
67
- AutoModeState,
68
- ClassifierReasoningLog,
69
- ClassifyAction,
70
- ClassifyResult,
71
- ConfigLoadResult,
72
- DecisionKind,
73
- DenialRecord,
74
- EffectiveConfig,
75
- } from "./types.ts";
76
- import { safeJson, truncateMiddle } from "./utils.ts";
77
-
78
- const INSPECT_TOOL = "automode_inspect";
79
- const INSPECTION_ACTIONS = ["status", "config", "defaults", "denials"] as const;
80
- type InspectionAction = (typeof INSPECTION_ACTIONS)[number];
81
-
82
- function matchedCommandSummary(command: string | undefined): string | undefined {
83
- return command ? truncateMiddle(command, 500) : undefined;
84
- }
85
-
86
- function canonicalPath(path: string): string {
87
- try {
88
- return realpathSync(path);
89
- } catch {
90
- return resolve(path);
91
- }
92
- }
93
-
94
- const EXTENSION_PATH = canonicalPath(fileURLToPath(import.meta.url));
95
- const EXTENSION_ENTRY_PATH = canonicalPath(
96
- resolve(dirname(EXTENSION_PATH), "../auto-mode.ts"),
97
- );
98
-
99
- export function modelVisibleConfigDiagnostics(
100
- diagnostics: string[],
101
- ): string[] {
102
- return diagnostics.map((diagnostic) =>
103
- diagnostic.replace(
104
- /invalid JSON \([\s\S]*\)$/,
105
- "invalid JSON (parser details omitted from model-visible output)",
106
- )
107
- );
108
- }
109
-
110
- function projectIsTrusted(
111
- ctx: { isProjectTrusted?: () => boolean },
112
- ): boolean {
113
- return typeof ctx.isProjectTrusted === "function"
114
- ? ctx.isProjectTrusted()
115
- : false;
116
- }
117
-
118
- export type PiAutomodeOptions = {
119
- /** Override config loading in tests. Runtime code uses Pi-owned disk settings. */
120
- loadConfig?: (cwd: string, projectTrusted: boolean) => EffectiveConfig;
121
- /** Override classifier calls in tests so unit tests never need a real LLM/API key. */
122
- classifyAction?: ClassifyAction;
123
- /** Override classifier-model persistence in tests. Runtime code writes the active global config. */
124
- saveClassifierModel?: (classifierModel: string) => void;
125
- /** Override global config migration and path selection in tests. */
126
- prepareGlobalConfig?: () => GlobalConfigPreparation;
127
- /** Override the application-owned observability log root in tests. */
128
- logRoot?: string;
129
- /** Override the observability log clock in tests. */
130
- now?: () => Date;
131
- /** Override Bash analysis in tests. Runtime code uses unbash. */
132
- analyzeBash?: typeof analyzeBash;
133
- };
134
-
135
- type LogCtx = {
136
- logger: Logger;
137
- decisionId: string;
138
- classifierModel?: string;
139
- reasoning: ClassifierReasoningLog;
140
- };
141
-
142
- /** Append ccusage-compatible usage and optional classifier I/O entries. */
143
- function logClassifierIo(decision: ClassifyResult, log: LogCtx): void {
144
- if (decision.reasoning) log.reasoning = decision.reasoning;
145
- if (decision.io) log.reasoning = decision.io.reasoning;
146
- if (!log.logger.enabled || !decision.io) return;
147
-
148
- for (const attempt of decision.io.attempts) {
149
- const response = attempt.response;
150
- if (!response) continue;
151
- log.logger.append({
152
- type: "message",
153
- timestamp: new Date(response.timestamp).toISOString(),
154
- message: {
155
- role: "assistant",
156
- model: response.model,
157
- usage: response.usage,
158
- },
159
- });
160
- }
161
-
162
- if (!log.logger.classifierIo) return;
163
- log.logger.append({
164
- type: "classifier",
165
- ts: new Date().toISOString(),
166
- decisionId: log.decisionId,
167
- model: decision.io.model,
168
- reasoning: decision.io.reasoning,
169
- prompt: decision.io.prompt,
170
- attempts: decision.io.attempts,
171
- durationMs: decision.io.durationMs,
172
- parsed: {
173
- decision: decision.decision,
174
- tier: decision.tier,
175
- reason: decision.reason,
176
- },
177
- });
178
- }
179
-
180
- /** Create a Pi extension instance. Default export uses production dependencies. */
181
- export function createPiAutomode(options: PiAutomodeOptions = {}) {
182
- const classify = options.classifyAction ?? defaultClassifyAction;
183
- const now = options.now ?? (() => new Date());
184
-
185
- return function piAutomode(pi: ExtensionAPI) {
186
- const globalConfig = options.prepareGlobalConfig?.() ??
187
- (options.loadConfig
188
- ? { status: "current" as const, activePath: PI_GLOBAL_SETTINGS[0] }
189
- : prepareGlobalConfig());
190
- const loadConfigWithDiagnostics = (
191
- cwd: string,
192
- projectTrusted: boolean,
193
- ): ConfigLoadResult => {
194
- const result = options.loadConfig
195
- ? {
196
- config: options.loadConfig(cwd, projectTrusted),
197
- diagnostics: [],
198
- }
199
- : loadEffectiveConfigWithDiagnostics(
200
- cwd,
201
- projectTrusted,
202
- globalConfig.activePath,
203
- );
204
- return globalConfig.diagnostic
205
- ? {
206
- ...result,
207
- diagnostics: [...result.diagnostics, globalConfig.diagnostic],
208
- }
209
- : result;
210
- };
211
- const persistClassifierModel = options.saveClassifierModel ??
212
- ((classifierModel: string) =>
213
- writeGlobalClassifierModel(classifierModel, globalConfig.activePath));
214
- const saveClassifierModel = globalConfig.writeBlockedReason
215
- ? (_classifierModel: string) => {
216
- throw new Error(globalConfig.writeBlockedReason);
217
- }
218
- : persistClassifierModel;
219
- let loadResult = loadConfigWithDiagnostics(process.cwd(), false);
220
- let config: EffectiveConfig = loadResult.config;
221
- let configDiagnostics: string[] = loadResult.diagnostics;
222
- let state: AutoModeState = {
223
- checkedActions: 0,
224
- blockedActions: 0,
225
- classifierAllowed: 0,
226
- classifierDenied: 0,
227
- recentDenials: [],
228
- };
229
- let loadedContext = "";
230
- let globalConfigNoticeShown = false;
231
-
232
- function effectiveConfig(): EffectiveConfig {
233
- return {
234
- ...config,
235
- enabled: state.enabledOverride ?? config.enabled,
236
- };
237
- }
238
-
239
- function ownsInspectionTool(): boolean {
240
- const tool = pi.getAllTools().find(({ name }) => name === INSPECT_TOOL);
241
- if (!tool) return false;
242
- const sourcePath = canonicalPath(tool.sourceInfo.path);
243
- return sourcePath === EXTENSION_PATH || sourcePath === EXTENSION_ENTRY_PATH;
244
- }
245
-
246
- function persist(): void {
247
- pi.appendEntry("pi-automode-state", state);
248
- }
249
-
250
- function updateUi(ctx: ExtensionContext): void {
251
- if (!ctx.hasUI) return;
252
- const cfg = effectiveConfig();
253
- const text = statusLine(cfg, state);
254
- ctx.ui.setStatus(
255
- "pi-automode",
256
- cfg.enabled
257
- ? ctx.ui.theme.fg("accent", text)
258
- : ctx.ui.theme.fg("dim", text),
259
- );
260
- }
261
-
262
- function inspectAutomode(
263
- action: InspectionAction,
264
- ctx: ExtensionContext,
265
- ): unknown {
266
- const cfg = effectiveConfig();
267
- if (action === "status") {
268
- const status = [
269
- `enabled: ${cfg.enabled ? "yes" : "no"}`,
270
- `classifier: ${cfg.classifierModel ?? "current session model"}`,
271
- `classifier reasoning: ${cfg.classifierReasoningLevel ?? "server default"}`,
272
- `checked actions: ${state.checkedActions}`,
273
- `blocked actions: ${state.blockedActions}`,
274
- `classifier allowed: ${state.classifierAllowed}`,
275
- `classifier denied: ${state.classifierDenied}`,
276
- `permissions.deny rules: ${cfg.permissionDeny.length}`,
277
- `permissions.ask rules: ${cfg.permissionAsk.length}`,
278
- `permissions.allow rules: ${cfg.permissionAllow.length}`,
279
- `environment entries: ${cfg.environment.length}`,
280
- `allow entries: ${cfg.allow.length}`,
281
- `soft_deny entries: ${cfg.softDeny.length}`,
282
- `hard_deny entries: ${cfg.hardDeny.length}`,
283
- `last decision: ${state.lastDecision ?? "none"}`,
284
- "last reason: omitted from model-visible inspection",
285
- ].join("\n");
286
- return {
287
- status,
288
- state: {
289
- enabledOverride: state.enabledOverride,
290
- lastDecision: state.lastDecision,
291
- checkedActions: state.checkedActions,
292
- blockedActions: state.blockedActions,
293
- classifierAllowed: state.classifierAllowed,
294
- classifierDenied: state.classifierDenied,
295
- },
296
- };
297
- }
298
- if (action === "config") {
299
- return {
300
- config: cfg,
301
- logFile: resolveLogPath(
302
- ctx.sessionManager.getSessionFile?.(),
303
- ctx.sessionManager.getSessionDir?.() ?? "",
304
- ctx.sessionManager.getSessionId?.() ?? "unknown",
305
- ctx.cwd,
306
- options.logRoot,
307
- now(),
308
- ),
309
- diagnostics: modelVisibleConfigDiagnostics(configDiagnostics),
310
- };
311
- }
312
- if (action === "defaults") {
313
- return {
314
- environment: DEFAULT_ENVIRONMENT,
315
- allow: DEFAULT_ALLOW,
316
- protectedPaths: DEFAULT_PROTECTED_PATHS,
317
- soft_deny: DEFAULT_SOFT_DENY,
318
- hard_deny: DEFAULT_HARD_DENY,
319
- };
320
- }
321
- const denials = state.recentDenials.slice().reverse().map((denial) => ({
322
- timestamp: denial.timestamp,
323
- kind: denial.kind,
324
- toolName: denial.toolName,
325
- }));
326
- return {
327
- summary: denials.length === 0
328
- ? "No recent auto-mode denials."
329
- : `${denials.length} recent auto-mode denial(s). Reasons and action payloads are omitted.`,
330
- denials,
331
- };
332
- }
333
-
334
- function block(
335
- ctx: ExtensionContext,
336
- denial: DenialRecord,
337
- logCtx: LogCtx,
338
- ): { block: true; reason: string } {
339
- state.blockedActions += 1;
340
- state.lastDecision = "block";
341
- state.lastReason = denial.reason;
342
- pushDenial(state, denial);
343
- persist();
344
- updateUi(ctx);
345
- if (logCtx.logger.enabled) {
346
- logCtx.logger.append({
347
- type: "decision",
348
- ts: new Date().toISOString(),
349
- decisionId: logCtx.decisionId,
350
- sessionId: ctx.sessionManager.getSessionId?.(),
351
- cwd: ctx.cwd,
352
- tool: denial.toolName,
353
- summary: denial.action,
354
- kind: denial.kind,
355
- outcome: "block",
356
- reason: denial.reason,
357
- classifierModel: logCtx.classifierModel,
358
- reasoning: logCtx.reasoning,
359
- });
360
- }
361
- if (ctx.hasUI) {
362
- ctx.ui.notify(
363
- `Auto mode blocked ${denial.toolName}: ${denial.reason}`,
364
- "warning",
365
- );
366
- }
367
- return { block: true, reason: `[pi-automode] ${denial.reason}` };
368
- }
369
-
370
- function allow(
371
- ctx: ExtensionContext,
372
- kind: DecisionKind,
373
- reason: string,
374
- toolName: string,
375
- summary: string,
376
- logCtx: LogCtx,
377
- ): undefined {
378
- state.lastDecision = "allow";
379
- state.lastReason = reason;
380
- persist();
381
- updateUi(ctx);
382
- if (logCtx.logger.enabled) {
383
- logCtx.logger.append({
384
- type: "decision",
385
- ts: new Date().toISOString(),
386
- decisionId: logCtx.decisionId,
387
- sessionId: ctx.sessionManager.getSessionId?.(),
388
- cwd: ctx.cwd,
389
- tool: toolName,
390
- summary,
391
- kind,
392
- outcome: "allow",
393
- reason,
394
- classifierModel: logCtx.classifierModel,
395
- reasoning: logCtx.reasoning,
396
- });
397
- }
398
- return undefined;
399
- }
400
-
401
- pi.on("session_start", (_event, ctx) => {
402
- loadResult = loadConfigWithDiagnostics(
403
- ctx.cwd,
404
- projectIsTrusted(ctx),
405
- );
406
- config = loadResult.config;
407
- configDiagnostics = loadResult.diagnostics;
408
- state = restoreState(ctx);
409
- if (
410
- ctx.hasUI &&
411
- globalConfig.notification &&
412
- !globalConfigNoticeShown
413
- ) {
414
- ctx.ui.notify(
415
- globalConfig.notification,
416
- globalConfig.status === "migrated" ? "info" : "warning",
417
- );
418
- globalConfigNoticeShown = true;
419
- }
420
- updateUi(ctx);
421
- });
422
-
423
- pi.on("before_agent_start", (event) => {
424
- const cfg = effectiveConfig();
425
- if (!cfg.enabled) return undefined;
426
- loadedContext = loadedContextFromSystemPromptOptions(
427
- event.systemPromptOptions,
428
- );
429
- return { systemPrompt: `${event.systemPrompt}\n\n${AUTO_MODE_GUIDANCE}` };
430
- });
431
-
432
- pi.on("tool_call", async (event, ctx) => {
433
- // Enforcement order:
434
- // 1. permission deny/ask rules,
435
- // 2. deterministic hard-deny checks that never consult the model,
436
- // 3. extension-owned read-only inspection tool,
437
- // 4. deterministic path denials,
438
- // 5. accepted ask rules force classifier review and skip all allow tiers,
439
- // 6. inside-CWD, permissions.allow, and read-only allow tiers,
440
- // 7. classifier for every remaining action, fail-closed on setup/parse errors.
441
- const cfg = effectiveConfig();
442
- if (!cfg.enabled) return undefined;
443
- if (ctx.signal?.aborted) return { block: true, reason: "Cancelled" };
444
-
445
- const isOwnedInspection = event.toolName === INSPECT_TOOL &&
446
- ownsInspectionTool();
447
- const input = event.input as Record<string, unknown>;
448
- let bashAnalysis: BashAnalysis | undefined;
449
- if (event.toolName === "bash") {
450
- const source = typeof input.command === "string" ? input.command : "";
451
- try {
452
- bashAnalysis = (options.analyzeBash ?? analyzeBash)(source);
453
- } catch (error) {
454
- bashAnalysis = {
455
- source,
456
- commands: [],
457
- redirects: [],
458
- redirectTargets: [],
459
- structure: [],
460
- allowStructureSafe: false,
461
- errors: [{
462
- message: `Bash analysis failed: ${
463
- error instanceof Error ? error.message : String(error)
464
- }`,
465
- }],
466
- };
467
- }
468
- }
469
- const summary = actionSummary(event.toolName, input);
470
- if (!isOwnedInspection) state.checkedActions += 1;
471
- const logCtx: LogCtx = {
472
- logger: createLogger({
473
- enabled: cfg.log.enabled,
474
- classifierIo: cfg.log.classifierIo,
475
- sessionFile: ctx.sessionManager.getSessionFile?.(),
476
- sessionDir: ctx.sessionManager.getSessionDir?.() ?? "",
477
- sessionCwd: ctx.cwd,
478
- sessionId: ctx.sessionManager.getSessionId?.() ?? "unknown",
479
- logRoot: options.logRoot,
480
- now: now(),
481
- }),
482
- decisionId: newDecisionId(),
483
- classifierModel: cfg.classifierModel,
484
- reasoning: classifierReasoningForConfig(cfg.classifierReasoningLevel),
485
- };
486
-
487
- for (const pattern of cfg.permissionDeny) {
488
- if (
489
- matchesToolPattern(
490
- pattern,
491
- event.toolName,
492
- input,
493
- ctx.cwd,
494
- "match",
495
- bashAnalysis,
496
- )
497
- ) {
498
- const matchedCommand = matchedCommandSummary(
499
- matchingBashCommandText(pattern, bashAnalysis),
500
- );
501
- if (isOwnedInspection) state.checkedActions += 1;
502
- return block(ctx, {
503
- timestamp: Date.now(),
504
- toolName: event.toolName,
505
- reason: `Blocked by permissions.deny: ${pattern.raw}${
506
- matchedCommand ? `; matched command: ${matchedCommand}` : ""
507
- }`,
508
- action: summary,
509
- kind: "permissions.deny",
510
- }, logCtx);
511
- }
512
- }
513
-
514
- let askRequiresClassifier = false;
515
- for (const pattern of cfg.permissionAsk) {
516
- if (
517
- !matchesToolPattern(
518
- pattern,
519
- event.toolName,
520
- input,
521
- ctx.cwd,
522
- "match",
523
- bashAnalysis,
524
- )
525
- ) {
526
- continue;
527
- }
528
- if (!ctx.hasUI) {
529
- const matchedCommand = matchedCommandSummary(
530
- matchingBashCommandText(pattern, bashAnalysis),
531
- );
532
- if (isOwnedInspection) state.checkedActions += 1;
533
- return block(ctx, {
534
- timestamp: Date.now(),
535
- toolName: event.toolName,
536
- reason:
537
- `Matched permissions.ask (${pattern.raw})${
538
- matchedCommand ? ` for command: ${matchedCommand}` : ""
539
- } but no UI is available`,
540
- action: summary,
541
- kind: "permissions.ask",
542
- }, logCtx);
543
- }
544
- const allowed = await ctx.ui.confirm(
545
- "Auto mode permission ask",
546
- `Rule: ${pattern.raw}\n\nAction:\n${summary}\n\nAllow this action to continue to auto-mode classification?`,
547
- { signal: ctx.signal },
548
- );
549
- if (!allowed) {
550
- const matchedCommand = matchedCommandSummary(
551
- matchingBashCommandText(pattern, bashAnalysis),
552
- );
553
- if (isOwnedInspection) state.checkedActions += 1;
554
- return block(ctx, {
555
- timestamp: Date.now(),
556
- toolName: event.toolName,
557
- reason: `Declined permissions.ask: ${pattern.raw}${
558
- matchedCommand ? `; matched command: ${matchedCommand}` : ""
559
- }`,
560
- action: summary,
561
- kind: "permissions.ask",
562
- }, logCtx);
563
- }
564
- askRequiresClassifier = true;
565
- }
566
-
567
- const deterministicReason = deterministicHardDeny(
568
- event.toolName,
569
- input,
570
- ctx.cwd,
571
- bashAnalysis,
572
- );
573
- if (deterministicReason) {
574
- if (isOwnedInspection) state.checkedActions += 1;
575
- return block(ctx, {
576
- timestamp: Date.now(),
577
- toolName: event.toolName,
578
- reason: deterministicReason,
579
- action: summary,
580
- kind: "deterministic-hard-deny",
581
- }, logCtx);
582
- }
583
-
584
- if (isOwnedInspection && !askRequiresClassifier) return undefined;
585
- if (isOwnedInspection) state.checkedActions += 1;
586
-
587
- // Deterministic path gate for file tools.
588
- //
589
- // `deniedPaths` always applies: a matching path is hard-denied before any
590
- // classifier or fast path, so secrets and system dirs never reach the
591
- // model. `allowInsideWorkingDirectory` adds a deterministic silent-allow
592
- // tier for file tools whose resolved path is inside the working
593
- // directory, and routes outside-CWD file access to the classifier
594
- // (bypassing the read-only fast path so reads outside the tree are
595
- // reviewed too).
596
- //
597
- // The gate is skipped entirely when both features are off, so the
598
- // default configuration costs no extra filesystem calls.
599
- let readOnlyFastPath =
600
- !askRequiresClassifier &&
601
- !cfg.classifyReadOnlyTools &&
602
- READ_ONLY_TOOLS.has(event.toolName);
603
- if (
604
- (cfg.deniedPaths.length > 0 || cfg.allowInsideWorkingDirectory) &&
605
- PATH_BEARING_TOOLS.has(event.toolName)
606
- ) {
607
- const inputPath = extractInputPath(event.toolName, input);
608
- if (inputPath !== undefined) {
609
- const resolved =
610
- resolveToolInputPath(event.toolName, ctx.cwd, inputPath) ??
611
- inputPath;
612
- const policyPath = resolvePathForPolicy(resolved) ?? resolved;
613
- const denied =
614
- cfg.deniedPaths.length > 0 &&
615
- (matchesDeniedPath(resolved, cfg.deniedPaths) ||
616
- matchesDeniedPath(policyPath, cfg.deniedPaths));
617
- if (denied) {
618
- return block(ctx, {
619
- timestamp: Date.now(),
620
- toolName: event.toolName,
621
- reason: `Path denied by policy: ${policyPath}`,
622
- action: summary,
623
- kind: "deterministic-path-deny",
624
- }, logCtx);
625
- }
626
- let recursiveSearch =
627
- event.toolName === "grep" || event.toolName === "find";
628
- if (recursiveSearch) {
629
- try {
630
- recursiveSearch = statSync(policyPath).isDirectory();
631
- } catch {
632
- // A missing search root will fail in the tool. Treat it as a
633
- // directory here so a denied scope cannot fail open in a race.
634
- }
635
- }
636
- const deniedSearchScope =
637
- recursiveSearch &&
638
- cfg.deniedPaths.length > 0 &&
639
- (recursiveSearchMayReachDeniedPath(resolved, cfg.deniedPaths) ||
640
- recursiveSearchMayReachDeniedPath(
641
- policyPath,
642
- cfg.deniedPaths,
643
- ));
644
- if (deniedSearchScope) {
645
- return block(ctx, {
646
- timestamp: Date.now(),
647
- toolName: event.toolName,
648
- reason: `Search scope can contain a path denied by policy: ${policyPath}`,
649
- action: summary,
650
- kind: "deterministic-path-deny",
651
- }, logCtx);
652
- }
653
- if (cfg.allowInsideWorkingDirectory) {
654
- const policyCwd = resolvePathForPolicy(ctx.cwd) ?? ctx.cwd;
655
- if (isInside(policyPath, policyCwd)) {
656
- // Protected in-tree writes and accepted ask rules must still
657
- // reach the classifier. They cannot use the inside-CWD tier.
658
- const protectedWrite =
659
- (event.toolName === "write" || event.toolName === "edit") &&
660
- isProtectedPath(policyPath, policyCwd, cfg.protectedPaths);
661
- if (!askRequiresClassifier && !protectedWrite) {
662
- return allow(
663
- ctx,
664
- "inside-working-directory",
665
- `Path inside working directory: ${policyPath}`,
666
- event.toolName,
667
- summary,
668
- logCtx,
669
- );
670
- }
671
- }
672
- // Outside the working directory, protected writes, and accepted
673
- // ask rules must not use the read-only fast path.
674
- readOnlyFastPath = false;
675
- }
676
- }
677
- }
678
-
679
- // Deterministic allow tier. It runs after every deterministic denial.
680
- // Accepted ask rules skip this tier and always reach the classifier.
681
- if (!askRequiresClassifier) {
682
- if (
683
- matchesAllowedToolPatterns(
684
- cfg.permissionAllow,
685
- event.toolName,
686
- input,
687
- ctx.cwd,
688
- bashAnalysis,
689
- )
690
- ) {
691
- // A protected-path write/edit is never covered by permissions.allow;
692
- // it stays on the classifier path (same rule as the inside-CWD tier).
693
- let protectedWrite = false;
694
- if (event.toolName === "write" || event.toolName === "edit") {
695
- const inputPath = extractInputPath(event.toolName, input);
696
- const resolved = inputPath === undefined
697
- ? undefined
698
- : resolveToolInputPath(event.toolName, ctx.cwd, inputPath) ??
699
- inputPath;
700
- if (
701
- resolved !== undefined &&
702
- isProtectedPath(resolved, ctx.cwd, cfg.protectedPaths)
703
- ) {
704
- protectedWrite = true;
705
- }
706
- }
707
- if (!protectedWrite) {
708
- return allow(
709
- ctx,
710
- "permissions.allow",
711
- "Allowed by permissions.allow",
712
- event.toolName,
713
- summary,
714
- logCtx,
715
- );
716
- }
717
- }
718
- }
719
-
720
- if (readOnlyFastPath) {
721
- return allow(
722
- ctx,
723
- "read-only",
724
- `Read-only built-in tool: ${event.toolName}`,
725
- event.toolName,
726
- summary,
727
- logCtx,
728
- );
729
- }
730
-
731
- const decision = await classify(
732
- ctx,
733
- cfg,
734
- serializeClassifierAction(event.toolName, input),
735
- loadedContext,
736
- );
737
- logClassifierIo(decision, logCtx);
738
- if (decision.decision === "allow") {
739
- state.classifierAllowed += 1;
740
- return allow(
741
- ctx,
742
- "classifier",
743
- decision.reason,
744
- event.toolName,
745
- summary,
746
- logCtx,
747
- );
748
- }
749
-
750
- state.classifierDenied += 1;
751
- return block(ctx, {
752
- timestamp: Date.now(),
753
- toolName: event.toolName,
754
- reason: decision.reason,
755
- action: summary,
756
- kind: "classifier",
757
- }, logCtx);
758
- });
759
-
760
- pi.registerTool({
761
- name: INSPECT_TOOL,
762
- label: "Inspect Auto Mode",
763
- description:
764
- "Inspect the active pi-automode status, effective config, built-in defaults, or recent denial metadata. This tool is read-only and cannot enable, disable, reload, reset, or reconfigure auto mode. Its output is sent to the current model; denial reasons and action payloads are omitted.",
765
- promptSnippet:
766
- "Inspect active pi-automode state and diagnostic information without changing it",
767
- parameters: Type.Object({
768
- action: StringEnum(INSPECTION_ACTIONS, {
769
- description: "The read-only auto-mode view to return",
770
- }),
771
- }),
772
- async execute(_toolCallId, params, signal, _onUpdate, ctx) {
773
- if (signal?.aborted) throw new Error("Auto-mode inspection cancelled");
774
- const result = inspectAutomode(params.action, ctx);
775
- return {
776
- content: [{ type: "text", text: safeJson(result, 16000) }],
777
- details: result,
778
- };
779
- },
780
- });
781
-
782
- async function handleAutomodeCommand(
783
- args: string,
784
- ctx: ExtensionCommandContext,
785
- ): Promise<void> {
786
- const [command = "status", ...rest] = args
787
- .trim()
788
- .split(/\s+/)
789
- .filter(Boolean);
790
- const remainder = rest.join(" ").trim();
791
-
792
- if (command === "status") {
793
- ctx.ui.notify(statusText(effectiveConfig(), state), "info");
794
- return;
795
- }
796
- if (command === "on") {
797
- state.enabledOverride = true;
798
- persist();
799
- updateUi(ctx);
800
- ctx.ui.notify("pi-automode enabled for this session", "info");
801
- return;
802
- }
803
- if (command === "off") {
804
- state.enabledOverride = false;
805
- persist();
806
- updateUi(ctx);
807
- ctx.ui.notify("pi-automode disabled for this session", "warning");
808
- return;
809
- }
810
- if (command === "reload") {
811
- loadResult = loadConfigWithDiagnostics(
812
- ctx.cwd,
813
- projectIsTrusted(ctx),
814
- );
815
- config = loadResult.config;
816
- configDiagnostics = loadResult.diagnostics;
817
- persist();
818
- updateUi(ctx);
819
- ctx.ui.notify(
820
- "pi-automode config reloaded",
821
- configDiagnostics.length > 0 ? "warning" : "info",
822
- );
823
- return;
824
- }
825
- if (command === "reset") {
826
- state = {
827
- checkedActions: 0,
828
- blockedActions: 0,
829
- classifierAllowed: 0,
830
- classifierDenied: 0,
831
- recentDenials: [],
832
- enabledOverride: state.enabledOverride,
833
- };
834
- persist();
835
- updateUi(ctx);
836
- ctx.ui.notify("pi-automode counters reset", "info");
837
- return;
838
- }
839
- if (command === "defaults") {
840
- ctx.ui.notify(
841
- safeJson(
842
- {
843
- environment: DEFAULT_ENVIRONMENT,
844
- allow: DEFAULT_ALLOW,
845
- protectedPaths: DEFAULT_PROTECTED_PATHS,
846
- soft_deny: DEFAULT_SOFT_DENY,
847
- hard_deny: DEFAULT_HARD_DENY,
848
- },
849
- 12000,
850
- ),
851
- "info",
852
- );
853
- return;
854
- }
855
- if (command === "config") {
856
- const logFile = resolveLogPath(
857
- ctx.sessionManager.getSessionFile?.(),
858
- ctx.sessionManager.getSessionDir?.() ?? "",
859
- ctx.sessionManager.getSessionId?.() ?? "unknown",
860
- ctx.cwd,
861
- options.logRoot,
862
- now(),
863
- );
864
- ctx.ui.notify(
865
- safeJson(
866
- {
867
- config: effectiveConfig(),
868
- logFile,
869
- diagnostics: configDiagnostics,
870
- },
871
- 16000,
872
- ),
873
- configDiagnostics.length > 0 ? "warning" : "info",
874
- );
875
- return;
876
- }
877
- if (command === "denials") {
878
- ctx.ui.notify(
879
- formatDenials(state),
880
- state.recentDenials.length > 0 ? "warning" : "info",
881
- );
882
- return;
883
- }
884
- if (command === "model") {
885
- const selected = remainder || await promptForClassifierModel(
886
- ctx,
887
- effectiveConfig().classifierModel,
888
- );
889
- if (!selected) {
890
- ctx.ui.notify("Classifier model unchanged", "info");
891
- return;
892
- }
893
- const parsed = parseModelSpec(selected);
894
- const model = parsed
895
- ? ctx.modelRegistry.find(parsed.provider, parsed.id)
896
- : undefined;
897
- if (!model) {
898
- ctx.ui.notify(`Model not found: ${selected}`, "error");
899
- return;
900
- }
901
- const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
902
- if (!auth.ok) {
903
- ctx.ui.notify(auth.error, "error");
904
- return;
905
- }
906
- const modelSpec = formatModelSpec(model);
907
- try {
908
- saveClassifierModel(modelSpec);
909
- } catch (error) {
910
- ctx.ui.notify(
911
- `Failed to save classifier model: ${
912
- error instanceof Error ? error.message : String(error)
913
- }`,
914
- "error",
915
- );
916
- return;
917
- }
918
- loadResult = loadConfigWithDiagnostics(
919
- ctx.cwd,
920
- projectIsTrusted(ctx),
921
- );
922
- config = loadResult.config;
923
- configDiagnostics = loadResult.diagnostics;
924
- persist();
925
- updateUi(ctx);
926
- const active = effectiveConfig().classifierModel ??
927
- "current session model";
928
- ctx.ui.notify(
929
- active === modelSpec
930
- ? `pi-automode classifier saved globally: ${modelSpec}`
931
- : `pi-automode classifier saved globally: ${modelSpec}; current config uses ${active}`,
932
- "info",
933
- );
934
- return;
935
- }
936
-
937
- ctx.ui.notify(
938
- "Usage: /automode [status|on|off|reload|reset|defaults|config|denials|model [provider/id]]",
939
- "error",
940
- );
941
- }
942
-
943
- pi.registerCommand("automode", {
944
- description:
945
- "Control pi-automode: status, on, off, reload, reset, defaults, config, denials, model",
946
- handler: handleAutomodeCommand,
947
- });
948
-
949
- pi.registerCommand("auto-mode", {
950
- description: "Alias for /automode",
951
- handler: handleAutomodeCommand,
952
- });
953
- };
954
- }