@herbertgao/pi-extensions 2026.9.6 → 2026.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/README.md +3 -8
  2. package/THIRD_PARTY_NOTICES.md +0 -4
  3. package/node_modules/@narumitw/pi-btw/README.md +2 -0
  4. package/node_modules/@narumitw/pi-btw/dist/index.ts +284 -365
  5. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
  6. package/node_modules/@narumitw/pi-btw/package.json +52 -53
  7. package/node_modules/@narumitw/pi-btw/src/bring-to-main.ts +494 -547
  8. package/node_modules/@narumitw/pi-btw/src/btw.ts +814 -856
  9. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +673 -674
  10. package/node_modules/@narumitw/pi-btw/src/keybindings.ts +228 -270
  11. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +309 -318
  12. package/node_modules/@narumitw/pi-btw/src/menu.ts +416 -454
  13. package/node_modules/@narumitw/pi-btw/src/settings.ts +203 -219
  14. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +173 -201
  15. package/node_modules/@narumitw/pi-btw/src/text.ts +21 -23
  16. package/node_modules/@narumitw/pi-btw/src/transcript-markdown.ts +65 -0
  17. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +611 -662
  18. package/node_modules/pi-lens/CHANGELOG.md +239 -0
  19. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -0
  20. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +30 -4
  21. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +2 -0
  22. package/node_modules/pi-lens/dist/clients/bash-file-access.js +26 -0
  23. package/node_modules/pi-lens/dist/clients/biome-client.js +1 -1
  24. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +403 -35
  25. package/node_modules/pi-lens/dist/clients/bootstrap.js +6 -1
  26. package/node_modules/pi-lens/dist/clients/cascade-format.js +200 -4
  27. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +10 -2
  28. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +16 -1
  29. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +12 -1
  30. package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +65 -0
  31. package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +28 -0
  32. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +9 -2
  33. package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +222 -0
  34. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +389 -13
  35. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +175 -25
  36. package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +62 -0
  37. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +21 -41
  38. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +6 -1
  39. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +7 -7
  40. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -12
  41. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +2 -21
  42. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +1 -1
  43. package/node_modules/pi-lens/dist/clients/dispatch/types.js +4 -1
  44. package/node_modules/pi-lens/dist/clients/disposition-publish.js +1 -0
  45. package/node_modules/pi-lens/dist/clients/effective-config.js +1 -1
  46. package/node_modules/pi-lens/dist/clients/file-kinds.js +26 -1
  47. package/node_modules/pi-lens/dist/clients/file-role.js +21 -21
  48. package/node_modules/pi-lens/dist/clients/file-utils.js +156 -10
  49. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +56 -9
  50. package/node_modules/pi-lens/dist/clients/format-service.js +39 -21
  51. package/node_modules/pi-lens/dist/clients/formatters.js +60 -13
  52. package/node_modules/pi-lens/dist/clients/generation-guard.js +2 -2
  53. package/node_modules/pi-lens/dist/clients/git-guard.js +11 -3
  54. package/node_modules/pi-lens/dist/clients/indent-retarget.js +78 -8
  55. package/node_modules/pi-lens/dist/clients/installer/index.js +333 -114
  56. package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +7 -0
  57. package/node_modules/pi-lens/dist/clients/instance-reaper.js +20 -10
  58. package/node_modules/pi-lens/dist/clients/language-profile.js +23 -1
  59. package/node_modules/pi-lens/dist/clients/language-registry.js +5 -1
  60. package/node_modules/pi-lens/dist/clients/latency-logger.js +4 -1
  61. package/node_modules/pi-lens/dist/clients/lens-engine.js +12 -3
  62. package/node_modules/pi-lens/dist/clients/lens-events.js +2 -1
  63. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +79 -0
  64. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +2 -1
  65. package/node_modules/pi-lens/dist/clients/lsp/client.js +25 -21
  66. package/node_modules/pi-lens/dist/clients/lsp/config.js +2 -2
  67. package/node_modules/pi-lens/dist/clients/lsp/index.js +7 -0
  68. package/node_modules/pi-lens/dist/clients/lsp/launch.js +16 -4
  69. package/node_modules/pi-lens/dist/clients/lsp/server.js +19 -2
  70. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +2 -1
  71. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +5 -3
  72. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +12 -8
  73. package/node_modules/pi-lens/dist/clients/mcp/session.js +36 -4
  74. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +24 -7
  75. package/node_modules/pi-lens/dist/clients/observed-mutation.js +40 -2
  76. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +23 -3
  77. package/node_modules/pi-lens/dist/clients/opengrep-client.js +110 -15
  78. package/node_modules/pi-lens/dist/clients/path-utils.js +123 -3
  79. package/node_modules/pi-lens/dist/clients/performance-report.js +5 -1
  80. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +1 -1
  81. package/node_modules/pi-lens/dist/clients/pipeline.js +74 -5
  82. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +37 -5
  83. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +145 -18
  84. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +4 -1
  85. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +59 -3
  86. package/node_modules/pi-lens/dist/clients/project-lens-config.js +45 -2
  87. package/node_modules/pi-lens/dist/clients/read-guard.js +70 -11
  88. package/node_modules/pi-lens/dist/clients/resource-sampler.js +147 -9
  89. package/node_modules/pi-lens/dist/clients/ruff-client.js +1 -1
  90. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +33 -5
  91. package/node_modules/pi-lens/dist/clients/runtime-context.js +6 -2
  92. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +77 -7
  93. package/node_modules/pi-lens/dist/clients/runtime-session.js +21 -2
  94. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +43 -12
  95. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +330 -85
  96. package/node_modules/pi-lens/dist/clients/runtime-turn.js +147 -17
  97. package/node_modules/pi-lens/dist/clients/safe-spawn.js +258 -5
  98. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +6 -0
  99. package/node_modules/pi-lens/dist/clients/session-event-guard.js +50 -5
  100. package/node_modules/pi-lens/dist/clients/sg-runner.js +23 -11
  101. package/node_modules/pi-lens/dist/clients/sgconfig.js +79 -2
  102. package/node_modules/pi-lens/dist/clients/test-runner-client.js +6 -41
  103. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +43 -0
  104. package/node_modules/pi-lens/dist/clients/tool-agreement.js +423 -0
  105. package/node_modules/pi-lens/dist/clients/tool-config.js +50 -0
  106. package/node_modules/pi-lens/dist/clients/tool-cwd.js +47 -76
  107. package/node_modules/pi-lens/dist/clients/tool-policy.js +163 -7
  108. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +120 -0
  109. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +127 -189
  110. package/node_modules/pi-lens/dist/clients/widget-state.js +98 -0
  111. package/node_modules/pi-lens/dist/clients/word-index.js +5 -2
  112. package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +7 -0
  113. package/node_modules/pi-lens/dist/index.js +27416 -24153
  114. package/node_modules/pi-lens/dist/mcp/server.js +5 -2
  115. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +7 -1
  116. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +168 -53
  117. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +244 -72
  118. package/node_modules/pi-lens/docs/audit1.md +5 -5
  119. package/node_modules/pi-lens/docs/dependencies.md +11 -0
  120. package/node_modules/pi-lens/docs/dispositions.md +52 -0
  121. package/node_modules/pi-lens/docs/environment-variables.md +2 -2
  122. package/node_modules/pi-lens/docs/features.md +4 -4
  123. package/node_modules/pi-lens/docs/globalconfig.md +3 -1
  124. package/node_modules/pi-lens/docs/language-coverage.md +1 -0
  125. package/node_modules/pi-lens/docs/pi-lens-fixer.md +53 -94
  126. package/node_modules/pi-lens/docs/pi-lens-investigator.md +43 -38
  127. package/node_modules/pi-lens/docs/pi-lens-retro.md +97 -0
  128. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +53 -66
  129. package/node_modules/pi-lens/docs/release-qa-baseline.md +22 -0
  130. package/node_modules/pi-lens/docs/settings.md +7 -4
  131. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
  132. package/node_modules/pi-lens/docs/usage.md +2 -0
  133. package/node_modules/pi-lens/package.json +1 -1
  134. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +24 -12
  135. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +40 -0
  136. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +15 -0
  137. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +7 -0
  138. package/package.json +5 -11
  139. package/node_modules/@luxusai/pi-hindsight/CHANGELOG.md +0 -376
  140. package/node_modules/@luxusai/pi-hindsight/README.md +0 -92
  141. package/node_modules/@luxusai/pi-hindsight/docs/adr/001-memory-lifecycle-and-scope.md +0 -56
  142. package/node_modules/@luxusai/pi-hindsight/docs/adr/002-explicit-routing-strategy-seam.md +0 -169
  143. package/node_modules/@luxusai/pi-hindsight/docs/adr/003-tui-memory-mode-vocabulary.md +0 -107
  144. package/node_modules/@luxusai/pi-hindsight/docs/adr/004-lifeos-dual-bank-design.md +0 -89
  145. package/node_modules/@luxusai/pi-hindsight/docs/adr/005-domain-banks-and-agent-first-surface.md +0 -189
  146. package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark.webp +0 -0
  147. package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark@2x.webp +0 -0
  148. package/node_modules/@luxusai/pi-hindsight/docs/coding-memory-evaluation.md +0 -54
  149. package/node_modules/@luxusai/pi-hindsight/docs/compatibility.md +0 -75
  150. package/node_modules/@luxusai/pi-hindsight/docs/hindsight-core-functions.md +0 -300
  151. package/node_modules/@luxusai/pi-hindsight/docs/mission-and-mental-model-quality.md +0 -158
  152. package/node_modules/@luxusai/pi-hindsight/docs/next-opt-out-design.md +0 -164
  153. package/node_modules/@luxusai/pi-hindsight/docs/risky-memory-modes.md +0 -139
  154. package/node_modules/@luxusai/pi-hindsight/docs/starter-mental-model-suggestions.md +0 -74
  155. package/node_modules/@luxusai/pi-hindsight/docs/surface-reference.md +0 -261
  156. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-operations.ts +0 -151
  157. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-selection.ts +0 -18
  158. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-settings-presenter.ts +0 -46
  159. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-templates.ts +0 -383
  160. package/node_modules/@luxusai/pi-hindsight/extensions/banks/banking.ts +0 -240
  161. package/node_modules/@luxusai/pi-hindsight/extensions/banks/knowledge-page-seed.ts +0 -176
  162. package/node_modules/@luxusai/pi-hindsight/extensions/banks/retain-strategies.ts +0 -178
  163. package/node_modules/@luxusai/pi-hindsight/extensions/client/client-retry.ts +0 -41
  164. package/node_modules/@luxusai/pi-hindsight/extensions/client/client.ts +0 -386
  165. package/node_modules/@luxusai/pi-hindsight/extensions/client/fetch-compat.ts +0 -39
  166. package/node_modules/@luxusai/pi-hindsight/extensions/client/timeout.ts +0 -34
  167. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-defaults.ts +0 -140
  168. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-model.ts +0 -61
  169. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-registry.ts +0 -925
  170. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-field-paths.ts +0 -247
  171. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-normalize.ts +0 -547
  172. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-writer.ts +0 -480
  173. package/node_modules/@luxusai/pi-hindsight/extensions/config/config.ts +0 -182
  174. package/node_modules/@luxusai/pi-hindsight/extensions/config/setup-gate.ts +0 -86
  175. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-execute.ts +0 -1010
  176. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-parse.ts +0 -175
  177. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-plan.ts +0 -425
  178. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-presentation.ts +0 -210
  179. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-sessions.ts +0 -817
  180. package/node_modules/@luxusai/pi-hindsight/extensions/index.ts +0 -25
  181. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/git-seed.ts +0 -319
  182. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-recall.ts +0 -196
  183. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-retain.ts +0 -189
  184. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-runtime.ts +0 -65
  185. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle.ts +0 -260
  186. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/mental-models.ts +0 -244
  187. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/observation-scopes.ts +0 -59
  188. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-cleanup.ts +0 -82
  189. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-visibility.ts +0 -45
  190. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall.ts +0 -331
  191. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-cursor.ts +0 -354
  192. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-job-builder.ts +0 -66
  193. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-receipts.ts +0 -132
  194. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain.ts +0 -228
  195. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-bank-template-operations.ts +0 -74
  196. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-config-operations.ts +0 -16
  197. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-control-operations.ts +0 -648
  198. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-diagnostics-operations.ts +0 -105
  199. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-identity.ts +0 -68
  200. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-service.ts +0 -71
  201. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-types.ts +0 -13
  202. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-recall-operations.ts +0 -162
  203. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-retain-operations.ts +0 -131
  204. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-scope.ts +0 -104
  205. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-session-operations.ts +0 -37
  206. package/node_modules/@luxusai/pi-hindsight/extensions/operations/operation-catalog.ts +0 -893
  207. package/node_modules/@luxusai/pi-hindsight/extensions/operations/reflect-presenter.ts +0 -35
  208. package/node_modules/@luxusai/pi-hindsight/extensions/operations/scope-migrate.ts +0 -177
  209. package/node_modules/@luxusai/pi-hindsight/extensions/operations/tools.ts +0 -7
  210. package/node_modules/@luxusai/pi-hindsight/extensions/queue/flush-presenter.ts +0 -21
  211. package/node_modules/@luxusai/pi-hindsight/extensions/queue/jsonl-queue-store.ts +0 -112
  212. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-delivery.ts +0 -111
  213. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-lock.ts +0 -214
  214. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-operations.ts +0 -70
  215. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue.ts +0 -392
  216. package/node_modules/@luxusai/pi-hindsight/extensions/tui/bank-template-presentation.ts +0 -45
  217. package/node_modules/@luxusai/pi-hindsight/extensions/tui/commands.ts +0 -9
  218. package/node_modules/@luxusai/pi-hindsight/extensions/tui/guided-setup.ts +0 -860
  219. package/node_modules/@luxusai/pi-hindsight/extensions/tui/prefill-input.ts +0 -105
  220. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-flow.ts +0 -189
  221. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-server-probe.ts +0 -299
  222. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-actions.ts +0 -201
  223. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-facts.ts +0 -47
  224. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-render.ts +0 -239
  225. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-types.ts +0 -50
  226. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui.ts +0 -259
  227. package/node_modules/@luxusai/pi-hindsight/extensions/tui/tool-presenters.ts +0 -77
  228. package/node_modules/@luxusai/pi-hindsight/extensions/types.ts +0 -534
  229. package/node_modules/@luxusai/pi-hindsight/extensions/utils/diagnostics.ts +0 -319
  230. package/node_modules/@luxusai/pi-hindsight/extensions/utils/messages.ts +0 -325
  231. package/node_modules/@luxusai/pi-hindsight/extensions/utils/sanitize.ts +0 -42
  232. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-memory-meta.ts +0 -244
  233. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-operations.ts +0 -56
  234. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session.ts +0 -50
  235. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-fields.ts +0 -167
  236. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-health.ts +0 -207
  237. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status.ts +0 -120
  238. package/node_modules/@luxusai/pi-hindsight/extensions/version.ts +0 -8
  239. package/node_modules/@luxusai/pi-hindsight/package.json +0 -118
  240. package/node_modules/@luxusai/pi-hindsight/skills/hindsight-memory-doctor/SKILL.md +0 -75
@@ -15,7 +15,7 @@ import * as os from "node:os";
15
15
  import * as path from "node:path";
16
16
  import { BoundedLruCache } from "./bounded-cache.js";
17
17
  import { createGenerationSource } from "./generation-guard.js";
18
- import { normalizeMapKey } from "./path-utils.js";
18
+ import { findNearestContaining, normalizeMapKey } from "./path-utils.js";
19
19
  import { resolveToolCwd } from "./tool-cwd.js";
20
20
  import { resolveCargoPackageEdition } from "./cargo-manifest.js";
21
21
  import { resolveKtfmtGradleStyle } from "./gradle-ktfmt-style.js";
@@ -28,6 +28,8 @@ import { compareOrdinal } from "./string-utils.js";
28
28
  import { classifyProbeFailure, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
29
29
  import { findGlobalBinary, findLocalBinUpwards, VENDOR_BIN_DIRS, VENV_BIN_DIRS, } from "./package-manager.js";
30
30
  import { safeSpawnAsync } from "./safe-spawn.js";
31
+ import { establishToolAgreement } from "./tool-agreement.js";
32
+ import { recordDegradationOnce } from "./degradation-ledger.js";
31
33
  import { probeToolAsync } from "./tool-probe.js";
32
34
  import { assertInstallAllowed } from "./project-trust.js";
33
35
  import { tryLazyInstallForFormatter } from "./dispatch/runners/utils/lazy-installer.js";
@@ -71,6 +73,7 @@ export const SKIP_FORMATTING = "skip-formatting";
71
73
  * producer collapsed into an ordinary failed result).
72
74
  */
73
75
  export const FORMATTER_UNAVAILABLE = "formatter-unavailable";
76
+ export const FORMATTER_AGREEMENT_UNAVAILABLE = "formatter-agreement-unavailable";
74
77
  // --- Utility Functions ---
75
78
  async function fileExists(filePath) {
76
79
  try {
@@ -464,11 +467,17 @@ async function canUseBundleExec(cwd) {
464
467
  async function resolveManagedSmartDefaultCommand(formatterName, filePath, args) {
465
468
  const toolId = getAutoInstallToolIdForFormatter(formatterName);
466
469
  if (!toolId)
467
- return null;
470
+ return FORMATTER_UNAVAILABLE;
468
471
  const { ensureTool } = await import("./installer/index.js");
469
472
  const installed = await ensureTool(toolId);
470
- if (!installed)
471
- return null;
473
+ if (!installed) {
474
+ recordDegradationOnce({
475
+ kind: "formatter-unavailable",
476
+ subject: formatterName,
477
+ reason: "managed formatter lookup and installation returned no executable",
478
+ });
479
+ return FORMATTER_UNAVAILABLE;
480
+ }
472
481
  return [installed, ...args, filePath];
473
482
  }
474
483
  /** Resolve an explicitly selected formatter from the shared managed-tool seam. */
@@ -568,13 +577,7 @@ function hasExplicitFormatterConfig(formatterName, cwd, ext) {
568
577
  export const FORMATTERS_WITH_EXPLICIT_CONFIG_CHECK = new Set(EXPLICIT_FORMATTER_CONFIG_CHECKS.keys());
569
578
  // --- Formatter Definitions ---
570
579
  async function hasEditorConfig(cwd) {
571
- try {
572
- await fs.access(path.join(cwd, ".editorconfig"));
573
- return true;
574
- }
575
- catch {
576
- return false;
577
- }
580
+ return findNearestContaining(cwd, [".editorconfig"]) !== undefined;
578
581
  }
579
582
  async function indentationArgs(filePath, tool, cwd) {
580
583
  if ((await hasEditorConfig(cwd)) ||
@@ -592,6 +595,20 @@ async function indentationArgs(filePath, tool, cwd) {
592
595
  if (!hasDetectableIndentation(content))
593
596
  return null;
594
597
  const indentation = detectIndentation(content);
598
+ if (!indentation) {
599
+ // #3038: the file HAS indentation, but only nested runs, so no unit can be
600
+ // proven. The caller turns this into SKIP_FORMATTING — a file the user
601
+ // expected to be formatted silently is not — so the refusal gets one
602
+ // bounded row. Subject is the tool (four possible values, recorded once
603
+ // per session each) rather than the path: a session that edits many such
604
+ // files must not write one ledger key per file.
605
+ recordDegradationOnce({
606
+ kind: "formatter-skip",
607
+ subject: tool,
608
+ reason: "indentation evidence is ambiguous; formatter style not pinned",
609
+ });
610
+ return null;
611
+ }
595
612
  if (tool === "shfmt")
596
613
  return indentation.style === "tab"
597
614
  ? ["-i", "0"]
@@ -634,7 +651,7 @@ export function resolveFormatterCwd(absolutePath, formatterName, homeDir = os.ho
634
651
  // ceiling prevents it from escaping the user's workspace.
635
652
  cwd: path.parse(path.resolve(absolutePath)).root,
636
653
  homeDir,
637
- });
654
+ }).cwd;
638
655
  }
639
656
  /**
640
657
  * Every biome invocation must carry this. Biome exits 1 with "No files were
@@ -1123,6 +1140,18 @@ export const gleamFormatter = {
1123
1140
  return false;
1124
1141
  },
1125
1142
  };
1143
+ export const typstyleFormatter = {
1144
+ name: "typstyle",
1145
+ command: ["typstyle", "-i", "$FILE"],
1146
+ extensions: [".typ", ".typc"],
1147
+ async resolveCommand(filePath) {
1148
+ const inPath = await which("typstyle");
1149
+ if (inPath)
1150
+ return [inPath, "-i", filePath];
1151
+ return resolveManagedSmartDefaultCommand("typstyle", filePath, ["-i"]);
1152
+ },
1153
+ detect: managedToolDetect("typstyle", undefined, async () => (await which("typstyle")) !== null),
1154
+ };
1126
1155
  export const terraformFormatter = {
1127
1156
  name: "terraform",
1128
1157
  command: ["terraform", "fmt", "$FILE"],
@@ -1395,6 +1424,7 @@ export const ALL_FORMATTERS = [
1395
1424
  rubocopFormatter,
1396
1425
  standardrbFormatter,
1397
1426
  gleamFormatter,
1427
+ typstyleFormatter,
1398
1428
  taploFormatter,
1399
1429
  googleJavaFormatFormatter,
1400
1430
  cljfmtFormatter,
@@ -1829,6 +1859,15 @@ export function firstDiagnosticLine(text) {
1829
1859
  * materialized.
1830
1860
  */
1831
1861
  async function resolveFormatterCommand(formatter, absolutePath, cwd) {
1862
+ const agreement = establishToolAgreement(formatter.name, cwd);
1863
+ if (agreement.decision === "decline") {
1864
+ recordDegradationOnce({
1865
+ kind: "formatter-agreement-unavailable",
1866
+ subject: agreement.subject,
1867
+ reason: agreement.reason,
1868
+ });
1869
+ return FORMATTER_AGREEMENT_UNAVAILABLE;
1870
+ }
1832
1871
  const resolved = formatter.resolveCommand
1833
1872
  ? await formatter.resolveCommand(absolutePath, cwd)
1834
1873
  : null;
@@ -1859,7 +1898,7 @@ export async function formatFile(filePath, formatter) {
1859
1898
  try {
1860
1899
  const absolutePath = path.resolve(filePath);
1861
1900
  const cwd = path.dirname(absolutePath);
1862
- const formatterCwd = resolveFormatterCwd(absolutePath, formatter.name);
1901
+ const formatterCwd = resolveToolCwd("formatter", formatter.name, absolutePath, { cwd: path.parse(absolutePath).root }).cwd;
1863
1902
  const contentBefore = await fs.readFile(absolutePath, "utf-8");
1864
1903
  // Resolve command: prefer local (venv/vendor/node_modules) over global.
1865
1904
  // The shared seam must honor SKIP_FORMATTING before selecting the static
@@ -1882,6 +1921,14 @@ export async function formatFile(filePath, formatter) {
1882
1921
  error: `${formatter.name}: formatter executable not found`,
1883
1922
  };
1884
1923
  }
1924
+ if (cmd === FORMATTER_AGREEMENT_UNAVAILABLE) {
1925
+ return {
1926
+ success: true,
1927
+ changed: false,
1928
+ outcome: "unavailable",
1929
+ error: `${formatter.name}: project tool agreement could not be established`,
1930
+ };
1931
+ }
1885
1932
  // Run formatter without blocking the event loop.
1886
1933
  const result = await safeSpawnAsync(cmd[0], cmd.slice(1), {
1887
1934
  timeout: 15000,
@@ -45,8 +45,8 @@
45
45
  * keyed map it is a compare while nothing has been invalidated, and a
46
46
  * normalize plus one `Map.get` once something has. The hand-rolled form it
47
47
  * replaces normalized on EVERY call, and this repo's normalizer runs
48
- * `realpathSync.native` on Windows, so an uninterrupted sweep pays that
49
- * syscall once rather than once per file. See
48
+ * `realpathSync.native` on Windows and, since #3098, on POSIX as well, so an
49
+ * uninterrupted sweep pays that syscall once rather than once per file. See
50
50
  * `GenerationMapOptions.normalizeKey` for the correctness reason the slow
51
51
  * path exists at all.
52
52
  */
@@ -3,6 +3,7 @@ import * as path from "node:path";
3
3
  import { isPathIgnoredByProject } from "./file-utils.js";
4
4
  import { tokenizeShellCommand } from "./bash-file-access.js";
5
5
  import { logLatency } from "./latency-logger.js";
6
+ import { resolveLensToolName } from "./tool-config.js";
6
7
  import { advisoryFileHash, advisoryPathKey, MAX_ADVISORY_AFFECTED_FILES, snapshotAdvisoryProvenance, } from "./advisory-provenance.js";
7
8
  function resolveGuardPath(filePath, cwd) {
8
9
  return path.resolve(cwd, filePath);
@@ -819,7 +820,14 @@ export function clearGitGuardTestFailure(cacheManager, cwd, runtime, passedFiles
819
820
  fileContentHashes: {},
820
821
  });
821
822
  }
822
- export function evaluateGitGuard(runtime, cacheManager, cwd) {
823
+ export function evaluateGitGuard(runtime, cacheManager, cwd, host = "pi") {
824
+ // #2535 F3: a known tool with no mapping on this host resolves to
825
+ // undefined — rephrase rather than naming a dead tool. `lens_diagnostics`
826
+ // is pinned available on both hosts, so the fallback is defensive only.
827
+ const diagnosticsTool = resolveLensToolName("lens_diagnostics", host);
828
+ const inspectLine = diagnosticsTool
829
+ ? `Run ${diagnosticsTool} mode=all for full details, then commit again.`
830
+ : "Inspect the full diagnostics, then commit again.";
823
831
  if (runtime.gitGuardHasBlockers) {
824
832
  logDecision(cwd, "blocked", "runtime_blockers", {
825
833
  projectSeq: runtime.projectSeq,
@@ -829,7 +837,7 @@ export function evaluateGitGuard(runtime, cacheManager, cwd) {
829
837
  : "";
830
838
  return {
831
839
  block: true,
832
- reason: `🔴 COMMIT BLOCKED (--lens-guard): unresolved blockers must be fixed before commit/push.${detail}\nRun lens_diagnostics mode=all for full details, then commit again.`,
840
+ reason: `🔴 COMMIT BLOCKED (--lens-guard): unresolved blockers must be fixed before commit/push.${detail}\n${inspectLine}`,
833
841
  };
834
842
  }
835
843
  if (runtime.gitGuardCacheUnknownReason) {
@@ -910,6 +918,6 @@ export function evaluateGitGuard(runtime, cacheManager, cwd) {
910
918
  });
911
919
  return {
912
920
  block: true,
913
- reason: "🔴 COMMIT BLOCKED (--lens-guard): unresolved blockers must be fixed before commit/push.\nRun lens_diagnostics mode=all for full details, then commit again.",
921
+ reason: `🔴 COMMIT BLOCKED (--lens-guard): unresolved blockers must be fixed before commit/push.\n${inspectLine}`,
914
922
  };
915
923
  }
@@ -1,3 +1,33 @@
1
+ import { blockCommentInteriorMask, templateLiteralInteriorMask, } from "./dispatch/indent-detect.js";
2
+ /**
3
+ * Computes both interior masks for `oldLines`, anchored to the real file
4
+ * when `fileContext` resolves cleanly, falling back to the fragment alone
5
+ * otherwise. `blockCommentInteriorMask` has no fragment-ambiguity to correct
6
+ * (a lone `*\/` cannot be mistaken for an opener the way a lone backtick
7
+ * can), so it always runs file-anchored when a file is available — this is
8
+ * about `templateLiteralInteriorMask` specifically, but both take the same
9
+ * path for one lexing pass over consistent line numbers.
10
+ */
11
+ function computeInteriorMasks(oldLines, fileContext) {
12
+ if (fileContext) {
13
+ const fileLines = fileContext.content.replace(/\r\n/g, "\n").split("\n");
14
+ const start = fileContext.startLine - 1;
15
+ const end = start + oldLines.length;
16
+ if (start >= 0 && end <= fileLines.length) {
17
+ return {
18
+ commentInterior: blockCommentInteriorMask(fileLines).slice(start, end),
19
+ templateInterior: templateLiteralInteriorMask(fileLines).slice(start, end),
20
+ };
21
+ }
22
+ // startLine/length don't fit inside content — the caller resolved a
23
+ // stale or mismatched range; fall through to the fragment-only path
24
+ // rather than slicing out of bounds.
25
+ }
26
+ return {
27
+ commentInterior: blockCommentInteriorMask(oldLines),
28
+ templateInterior: templateLiteralInteriorMask(oldLines),
29
+ };
30
+ }
1
31
  /**
2
32
  * Retargets the leading-whitespace style of newText to match the indentation
3
33
  * correction that was applied to oldText.
@@ -7,39 +37,79 @@
7
37
  * Returns undefined — leaving newText unchanged — when any non-blank line in
8
38
  * newText has indentation that cannot be resolved, to avoid producing
9
39
  * mixed-indentation output.
40
+ *
41
+ * A line inside a terminated `/* … *\/` block comment, or inside a
42
+ * multi-line template literal, still gets an exact entry in the map — a
43
+ * replacement that reintroduces that SAME indent (adding another JSDoc
44
+ * continuation, or another template line, say) still resolves by direct
45
+ * lookup — but that indent is never eligible to be picked as the shortest
46
+ * ("base") unit and extrapolated to a deeper level `newText` adds that
47
+ * oldText never showed: its leading space is alignment (on the comment
48
+ * opener's `*` column, or on the template string's own content), not a
49
+ * nesting unit, and that alignment's ratio can differ from the code's own
50
+ * indentation ratio, silently mis-scaling every such deeper line (#3052,
51
+ * #3116). Uses the same lexer as `clients/dispatch/indent-detect.ts`'s
52
+ * `detectIndentation` (#3039, #3059) rather than a second one.
53
+ *
54
+ * `fileContext`, when the caller has it, anchors that lexer to the real file
55
+ * (see {@link IndentRetargetFileContext}) so a template boundary the
56
+ * oldText fragment crosses is read correctly instead of ambiguously. Without
57
+ * it (match position unknown — the caller never found a unique file span,
58
+ * or is calling with a synthetic fragment that has no backing file), the
59
+ * masks fall back to running over the fragment alone, which is exact for a
60
+ * block comment (`*\/` cannot be mistaken for an opener) but can misread a
61
+ * template literal whose opener or closer lies outside the fragment.
10
62
  */
11
- export function retargetReplacementIndentation(newText, oldText, correctedOldText) {
63
+ export function retargetReplacementIndentation(newText, oldText, correctedOldText, fileContext) {
12
64
  const newline = newText.includes("\r\n") ? "\r\n" : "\n";
13
65
  const oldLines = oldText.replace(/\r\n/g, "\n").split("\n");
14
66
  const correctedLines = correctedOldText.replace(/\r\n/g, "\n").split("\n");
15
67
  if (oldLines.length !== correctedLines.length)
16
68
  return undefined;
69
+ const { commentInterior, templateInterior } = computeInteriorMasks(oldLines, fileContext);
17
70
  const indentMap = new Map();
71
+ // oldIndent keys backed by at least one line OUTSIDE a comment or
72
+ // template-literal interior — the only keys eligible to be picked as the
73
+ // base unit below. An interior line still lands in indentMap (exact-width
74
+ // lookups must still resolve, #3052 F1), it just cannot anchor the
75
+ // extrapolation.
76
+ const structuralIndents = new Set();
18
77
  const ambiguousIndents = new Set();
19
- for (let i = 0; i < oldLines.length; i += 1) {
20
- const oldIndent = oldLines[i].match(/^[\t ]*/)?.[0] ?? "";
21
- const correctedIndent = correctedLines[i].match(/^[\t ]*/)?.[0] ?? "";
78
+ for (const [i, oldLine] of oldLines.entries()) {
79
+ // oldLines.length === correctedLines.length is checked above; the "" is
80
+ // unreachable, only satisfying noUncheckedIndexedAccess.
81
+ const correctedLine = correctedLines[i] ?? "";
82
+ const oldIndent = oldLine.match(/^[\t ]*/)?.[0] ?? "";
83
+ const correctedIndent = correctedLine.match(/^[\t ]*/)?.[0] ?? "";
22
84
  if (oldIndent === correctedIndent)
23
85
  continue;
24
86
  const previous = indentMap.get(oldIndent);
25
87
  if (previous !== undefined && previous !== correctedIndent) {
26
88
  indentMap.delete(oldIndent);
89
+ structuralIndents.delete(oldIndent);
27
90
  ambiguousIndents.add(oldIndent);
28
91
  continue;
29
92
  }
30
93
  if (!ambiguousIndents.has(oldIndent)) {
31
94
  indentMap.set(oldIndent, correctedIndent);
95
+ if (!commentInterior[i] && !templateInterior[i]) {
96
+ structuralIndents.add(oldIndent);
97
+ }
32
98
  }
33
99
  }
34
100
  if (indentMap.size === 0)
35
101
  return undefined;
36
- // Find the shortest non-empty mapped key as the base unit so that nesting
37
- // levels in newText that are deeper than anything in oldText can be remapped
38
- // as n × baseFrom → n × baseTo.
102
+ // Find the shortest structurally-backed mapped key as the base unit so
103
+ // that nesting levels in newText that are deeper than anything in
104
+ // oldText can be remapped as n × baseFrom → n × baseTo. A key backed
105
+ // only by comment- or template-interior lines is skipped here (but stays
106
+ // in indentMap for direct lookups above).
39
107
  let baseFrom = "";
40
108
  let baseTo = "";
41
109
  for (const [from, to] of indentMap) {
42
- if (from.length > 0 && (baseFrom === "" || from.length < baseFrom.length)) {
110
+ if (from.length > 0 &&
111
+ structuralIndents.has(from) &&
112
+ (baseFrom === "" || from.length < baseFrom.length)) {
43
113
  baseFrom = from;
44
114
  baseTo = to;
45
115
  }