@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
@@ -1,3 +1,8 @@
1
+ import * as fs from "node:fs";
2
+ import { retagAuxiliaryDiagnostics } from "./dispatch/auxiliary-lsp.js";
3
+ import { applyFindingPolicy, loadProjectRulePolicyMap, renderedRuleIdentities, } from "./dispatch/finding-policy.js";
4
+ import { detectFileRole } from "./file-role.js";
5
+ import { logLatency } from "./latency-logger.js";
1
6
  import { convertLspDiagnostics } from "./dispatch/utils/lsp-diagnostics.js";
2
7
  import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
3
8
  export function formatCascadeNeighborDiagnostics(cwd, neighbors, options = {}) {
@@ -60,20 +65,64 @@ export function formatCascadeNeighborDiagnostics(cwd, neighbors, options = {}) {
60
65
  * Lives here rather than inline in the quiet-window callback so the delivery
61
66
  * path (reconcile → run → turn_end) is testable end to end; index.ts only wires
62
67
  * it to `runtime.appendCascadeRun`.
68
+ *
69
+ * #3102: the survivors are what the agent READS, so they go through the same
70
+ * `clients/dispatch/finding-policy.ts` stack — inline `pi-lens-ignore` → stored
71
+ * dispositions → the project's `.pi-lens.json` rule policy — that the per-edit
72
+ * dispatcher, `mode=full` and the `source=lsp` probe lane apply. Without it a
73
+ * cold-neighbour ERROR the agent already marked `false-positive` came back on
74
+ * every quiet-window reconcile.
63
75
  */
64
76
  export function buildResolvedFoundCascadeRun(cwd, neighbor) {
65
77
  const { filePath } = neighbor;
66
- const diagnostics = convertLspDiagnostics(neighbor.diagnostics.filter((d) => d.severity === 1), filePath, { tool: "lsp" });
67
- if (diagnostics.length === 0)
78
+ const errors = neighbor.diagnostics.filter((d) => d.severity === 1);
79
+ if (errors.length === 0)
68
80
  return undefined;
81
+ const policyStart = Date.now();
82
+ // This lane's roots are one and the same: index.ts hands `runtime.projectRoot`
83
+ // in as `cwd`, so the retag's config lookup and the disposition/rule-policy
84
+ // store read the same directory. The in-lane cascade's do NOT — see
85
+ // `applyCascadeDisplayPolicy`.
86
+ const { diagnostics, suppressed, auxSuppressed, total } = applyCascadeDisplayPolicy(errors, {
87
+ cwd,
88
+ policyRoot: cwd,
89
+ filePath,
90
+ });
91
+ // One bounded record per RUN, never one per finding (AGENTS.md "bounded
92
+ // observability"). This is a PUSH surface: silence after a mark is the mark
93
+ // working, not a clean verdict, so the count is recorded here rather than
94
+ // re-announced to the agent on every reconcile. `durationMs` covers the
95
+ // content read too — the latency this fold added to the quiet-window
96
+ // callback, measurable in the same per-phase record as every other cost.
97
+ recordCascadeFindingPolicy({
98
+ filePath,
99
+ durationMs: Date.now() - policyStart,
100
+ suppressed,
101
+ total,
102
+ auxSuppressed,
103
+ });
104
+ // No zero-length early return here: `formatCascadeNeighborDiagnostics`
105
+ // renders "" for a neighbour with no diagnostics and the `!formatted` guard
106
+ // below already returns `undefined` for it — a second check was mutation-
107
+ // inert (M7: deleting it left all 7 cascade cases green).
69
108
  const neighbors = [
70
109
  { filePath, reason: "references", diagnostics, lspTouched: true },
71
110
  ];
72
- const formatted = formatCascadeNeighborDiagnostics(cwd, neighbors, {
111
+ const rendered = formatCascadeNeighborDiagnostics(cwd, neighbors, {
73
112
  noun: "cold neighbor",
74
113
  });
75
- if (!formatted)
114
+ if (!rendered)
76
115
  return undefined;
116
+ // #1616 / #3102 AC 4, round 2 F2: a delivery that still has something to say
117
+ // states what it dropped, once per delivery — the same sentence the
118
+ // late-auxiliary advisory renders (`clients/runtime-turn.ts`). Policy drops
119
+ // only: an aux drop is the file's own suppression comment, which the
120
+ // per-edit dispatch path honours silently too, and it stays in the record
121
+ // above. A delivery with NOTHING left says nothing at all — silence on a
122
+ // push surface is not a claim that the neighbour is clean.
123
+ const formatted = suppressed > 0
124
+ ? `${rendered}\nsuppressed by disposition: ${suppressed} finding(s) (marked false-positive or won't-fix).`
125
+ : rendered;
77
126
  return {
78
127
  filePath,
79
128
  result: {
@@ -93,3 +142,150 @@ export function buildResolvedFoundCascadeRun(cwd, neighbor) {
93
142
  diagnosticCount: diagnostics.length,
94
143
  };
95
144
  }
145
+ /**
146
+ * The ONE convert → retag → `applyFindingPolicy` derivation every cascade
147
+ * DISPLAY lane runs before its diagnostics reach the agent (#3102/#3157).
148
+ *
149
+ * Five lanes render `CascadeNeighborResult.diagnostics` through
150
+ * `formatCascadeNeighborDiagnostics`: the quiet-window builder above, and the
151
+ * four in-lane sites in `clients/dispatch/integration.ts` (passive cold
152
+ * snapshot, fresh touch, touch-error fallback, degraded fallback). Each one
153
+ * used to open-code `convertLspDiagnostics(...)` and render the result raw, so
154
+ * a neighbour ERROR the agent had marked `false-positive` was hidden by
155
+ * `mode=delta`/`mode=full`/the per-edit dispatcher/the probe lane and STILL
156
+ * re-reported by the cascade block on every edit that cascaded to it (#3157).
157
+ *
158
+ * TWO ROOTS, deliberately separate (the #1030 rule):
159
+ * - `cwd` is the LANGUAGE root, and only `retagAuxiliaryDiagnostics` sees it
160
+ * — `profile.allowBlocking(cwd)` looks for a local opengrep/zizmor/typos
161
+ * config, exactly as the per-edit runner (`runners/lsp.ts`) passes
162
+ * `ctx.cwd`.
163
+ * - `policyRoot` is the PROJECT root, and the disposition store and the
164
+ * `.pi-lens.json` rule map are read from it — `lens_diagnostic_mark` writes
165
+ * under `runtime.projectRoot` (`index.ts`), so reading from a nested
166
+ * language root in a monorepo opens a different
167
+ * `diagnostic-dispositions.json` and silently no-ops every mark. This is
168
+ * what `dispatcher.ts` already does with `ctx.projectRoot ?? ctx.cwd`.
169
+ *
170
+ * The auxiliary drop set (native `# nosemgrep`-style comments, `skipTestFiles`)
171
+ * is KEPT: every caller's diagnostics come straight off a client cache or a
172
+ * `touchFile` result, so nothing upstream ran `applyAuxiliarySuppressions`
173
+ * (only the `mode=full` workspace sweep does, `clients/lsp/index.ts`) and this
174
+ * is the FIRST application, not a double-apply — which is why the probe lane
175
+ * ignores it and these lanes do not.
176
+ *
177
+ * No `range.start.line` pre-partition here, unlike the late-auxiliary drain,
178
+ * whose own comment gives the alignment reason (`clients/runtime-turn.ts`,
179
+ * above its `anchored` filter): `convertLspDiagnostics` drops line-less
180
+ * entries, which would break the 1:1 index pairing `retagAuxiliaryDiagnostics`
181
+ * needs. That reason holds here too — it is simply already satisfied. Every
182
+ * caller reads the same `client.getAllDiagnostics()` / `touchFile` map, whose
183
+ * `mergeDiagnosticLists` (`clients/lsp/client.ts`) dereferences
184
+ * `diagnostic.range.start.line` unguarded, so a line-less entry throws long
185
+ * before any builder sees it and `converted` is always 1:1 with `errors`.
186
+ *
187
+ * Reads nothing when there is nothing to render, and never logs: the RECORD is
188
+ * the caller's decision, because the in-lane lane walks up to
189
+ * `CASCADE_NEIGHBOUR_BUDGET` neighbours per edit and must aggregate one row per
190
+ * run rather than one per neighbour.
191
+ */
192
+ export function applyCascadeDisplayPolicy(
193
+ /** ERROR-severity raw diagnostics, already capped by the caller's own
194
+ * display cap (`MAX_PER_FILE` in-lane; uncapped for the single-neighbour
195
+ * quiet-window run). */
196
+ errors, options) {
197
+ if (errors.length === 0) {
198
+ return { diagnostics: [], suppressed: 0, auxSuppressed: 0, total: 0 };
199
+ }
200
+ const { cwd, policyRoot, filePath } = options;
201
+ // `undefined` → the fail-open empty string: inline suppression becomes a
202
+ // no-op and a STRICT `false-positive` anchor hashes an empty line, so a
203
+ // finding is never hidden by an I/O error (AGENTS.md shape 48).
204
+ const content = options.content ?? readNeighborContent(filePath);
205
+ const converted = convertLspDiagnostics(errors, filePath);
206
+ // #692/#3046: identity comes from the ONE shared derivation every other
207
+ // surface anchors a mark against — never a hardcoded `tool: "lsp"`.
208
+ const retained = retagAuxiliaryDiagnostics(converted, errors, content ?? "", {
209
+ cwd,
210
+ fileRole: detectFileRole(filePath, content),
211
+ });
212
+ const { kept } = applyFindingPolicy(retained, {
213
+ cwd: policyRoot,
214
+ filePath,
215
+ content: content ?? "",
216
+ policyMap: loadProjectRulePolicyMap(policyRoot),
217
+ identities: renderedRuleIdentities,
218
+ });
219
+ return {
220
+ diagnostics: options.displayCap === undefined
221
+ ? kept
222
+ : kept.slice(0, options.displayCap),
223
+ // Policy drops only, DISJOINT from `auxSuppressed` — the same split the
224
+ // `late_auxiliary_findings` record uses, so one operator reading both
225
+ // records does not have to know that one nests and the other does not.
226
+ suppressed: retained.length - kept.length,
227
+ auxSuppressed: converted.length - retained.length,
228
+ total: converted.length,
229
+ };
230
+ }
231
+ /**
232
+ * The ONE `cascade_finding_policy` row both cascade display lanes write.
233
+ *
234
+ * Gated on EITHER counter (#3102 round 2, F1): gating on the policy count alone
235
+ * made an aux-only drop — an ERROR master rendered, removed by the profile's own
236
+ * `# nosemgrep` / `skipTestFiles` rule — vanish with no row at all, the
237
+ * silent-drop shape this record exists to prevent (AGENTS.md shape 10). The
238
+ * `late_auxiliary_findings` twin reports both counters every drain.
239
+ */
240
+ export function recordCascadeFindingPolicy(entry) {
241
+ const inputTruncated = entry.inputTruncated ?? 0;
242
+ if (entry.suppressed === 0 &&
243
+ entry.auxSuppressed === 0 &&
244
+ inputTruncated === 0)
245
+ return;
246
+ logLatency({
247
+ type: "phase",
248
+ toolName: "cascade",
249
+ filePath: entry.filePath,
250
+ phase: "cascade_finding_policy",
251
+ durationMs: entry.durationMs,
252
+ metadata: {
253
+ suppressed: entry.suppressed,
254
+ total: entry.total,
255
+ auxSuppressed: entry.auxSuppressed,
256
+ ...(inputTruncated > 0 && { inputTruncated }),
257
+ },
258
+ });
259
+ }
260
+ /**
261
+ * The neighbour's current bytes, for the two content-bound halves of the
262
+ * policy stack (inline `pi-lens-ignore` and the STRICT `false-positive`
263
+ * anchor). One synchronous read, paid only once a neighbour actually has ERROR
264
+ * diagnostics to render — the same read `mode=full` pays per flagged file and
265
+ * the probe lane pays per cache replay.
266
+ *
267
+ * SYNC here, while the in-lane cascade's own neighbour read at
268
+ * `clients/dispatch/integration.ts` (the active touch, under its "A6: async
269
+ * read to avoid blocking event loop on network-mounted drives" comment) is
270
+ * ASYNC. The asymmetry is deliberate and measured, not an oversight: that read
271
+ * happens for EVERY actively-touched neighbour in a parallel fan-out, while
272
+ * this one is paid at most once per DELIVERING neighbour — 0.0125 ms per
273
+ * neighbour measured on 40 real 15.6 KiB sources (#3157 AC 3), on loops that
274
+ * already do synchronous fs work (`nodeFs.existsSync` in the degraded
275
+ * fallback, and the `boundToCurrentDisk` verify whenever the producer attached
276
+ * a content hash and its (path, mtime, size) memo misses —
277
+ * `clients/lsp/diagnostic-binding.ts`). An async read here would force the
278
+ * quiet-window builder's synchronous callback to become async for no
279
+ * measurable gain.
280
+ *
281
+ * `undefined` on any failure: the caller degrades to the content-free half
282
+ * rather than hiding a finding it could not identify.
283
+ */
284
+ function readNeighborContent(filePath) {
285
+ try {
286
+ return fs.readFileSync(filePath, "utf-8");
287
+ }
288
+ catch {
289
+ return undefined;
290
+ }
291
+ }
@@ -4,6 +4,7 @@ import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
4
4
  import { displayProjectDataPath, getProjectDataDir } from "./file-utils.js";
5
5
  import { normalizeMessage, stableFindingId } from "./finding-identity.js";
6
6
  import { normalizeMapKey } from "./path-utils.js";
7
+ import { resolveLensToolName } from "./tool-config.js";
7
8
  /**
8
9
  * Report-budget precedence (#1777). A hint is an opinion; a warning is a
9
10
  * finding. When the report is capped, warnings must be spent first, or an
@@ -239,7 +240,7 @@ export function appendCodeQualityWarningsHistory(cwd, report) {
239
240
  * {@link formatActionableWarningsAdvisory} (#2521): this advisory carried the
240
241
  * identical hardcoded-legacy-path defect, one cache key over.
241
242
  */
242
- export function formatCodeQualityWarningsAdvisory(report, cwd) {
243
+ export function formatCodeQualityWarningsAdvisory(report, cwd, host = "pi") {
243
244
  if (report.summary.warnings === 0)
244
245
  return undefined;
245
246
  const topRules = report.summary.topRules
@@ -261,6 +262,9 @@ export function formatCodeQualityWarningsAdvisory(report, cwd) {
261
262
  .filter(Boolean)
262
263
  .join(", ")
263
264
  : "";
265
+ // #2535 F3: a known tool with no mapping on this host resolves to
266
+ // undefined — omit the instruction rather than naming a dead tool.
267
+ const diagnosticsTool = resolveLensToolName("lens_diagnostics", host);
264
268
  return [
265
269
  `Code-quality warnings introduced/touched this turn: ${report.summary.warnings} across ${report.summary.files} file(s).`,
266
270
  tiers
@@ -269,7 +273,11 @@ export function formatCodeQualityWarningsAdvisory(report, cwd) {
269
273
  topRules ? `Top rules: ${topRules}` : undefined,
270
274
  // #2521: tool route first (`mode=delta` covers the code-quality cache
271
275
  // as well as the actionable one), resolved path second.
272
- "Use lens_diagnostics with mode=delta to inspect these warnings.",
276
+ // #2535 F3: omit when unmapped on this host, never name a dead tool
277
+ // (pinned available on both hosts; defensive only).
278
+ diagnosticsTool
279
+ ? `Use ${diagnosticsTool} with mode=delta to inspect these warnings.`
280
+ : undefined,
273
281
  "No action required unless you are already refactoring these areas.",
274
282
  `Raw report (only if you need the JSON): ${displayProjectDataPath(cwd, "cache", "code-quality-warnings.json")}`,
275
283
  ]
@@ -2,7 +2,7 @@
2
2
  import { logExtension } from "./extension-log.js";
3
3
  import { DEGRADATION_ENTRIES_PER_KIND, LEDGER_FIELD_MAX, truncateForLedger, } from "./ledger-bounds.js";
4
4
  import { logLatency } from "./latency-logger.js";
5
- import { getSinkRotations, getSinkWriteFailures, resetSinkRotations, resetSinkWriteFailures, } from "./ndjson-logger.js";
5
+ import { getSinkRotations, getSinkOptionConflicts, getSinkWriteFailures, resetSinkRotations, resetSinkWriteFailures, } from "./ndjson-logger.js";
6
6
  // #2146: pulled at READ time, never pushed. `process-singletons.ts` is a
7
7
  // dependency leaf on purpose — it cannot import this module without closing a
8
8
  // no-client-cycles cycle through instance-registry/instance-reaper — so the
@@ -233,6 +233,18 @@ export function getDegradationSummary() {
233
233
  })),
234
234
  });
235
235
  }
236
+ const optionConflicts = getSinkOptionConflicts();
237
+ if (optionConflicts.length > 0) {
238
+ summary.push({
239
+ kind: "log-sink-option-conflict",
240
+ count: optionConflicts.length,
241
+ droppedCount: 0,
242
+ latestReasons: optionConflicts.map((sink) => ({
243
+ subject: truncateForLedger(sink.file),
244
+ reason: truncateForLedger("shared writer rotation options differed across module graphs; first writer retained ownership"),
245
+ })),
246
+ });
247
+ }
236
248
  // #2146, same read-time fold: process-singleton resets live in the leaf
237
249
  // module's own bounded log. One entry per family, so this group's count is
238
250
  // the number of families this build could not adopt, never an event tally.
@@ -310,6 +322,9 @@ const INFORMATIONAL_DEGRADATION_KINDS = new Set([
310
322
  // doc comment above) and is frequent/self-healing by design — a `⚠` would
311
323
  // cry wolf on the sampler's ordinary best-effort data loss.
312
324
  "resource-sampler-scanner-escalated",
325
+ // #2874: a successful legacy-directory migration is an upgrade tally, not
326
+ // a call to action. The hash-only subject avoids exposing the project path.
327
+ "data_dir_migrated",
313
328
  ]);
314
329
  export function renderDegradationLines(summary = getDegradationSummary()) {
315
330
  if (!isRenderableSummary(summary))
@@ -78,6 +78,11 @@ import { lineContentHash } from "./read-guard.js";
78
78
  // below goes through `deferredKey`, which folds the resolved cwd into the Set
79
79
  // key, so the two projects can no longer collide.
80
80
  const deferredThisSession = new Set();
81
+ let widgetDispositionReconciler = null;
82
+ /** Register the widget's mirror of the durable disposition state. */
83
+ export function registerWidgetDispositionReconciler(reconciler) {
84
+ widgetDispositionReconciler = reconciler;
85
+ }
81
86
  function deferredKey(cwd, anchor) {
82
87
  return `${normalizeMapKey(cwd)}::${anchor}`;
83
88
  }
@@ -97,7 +102,8 @@ export const normalizeMessage = sharedNormalizeMessage;
97
102
  // case, symlink/realpath, or slash difference between the two forms silently
98
103
  // orphans the agent's own false-positive/flagged mark (a #533 dropped-signal).
99
104
  // Canonicalize BOTH inputs through `normalizeMapKey` (the SAME normalizer the
100
- // read side already relies on — realpathSync.native on Windows) BEFORE computing
105
+ // read side already relies on — realpathSync.native on Windows, and on POSIX
106
+ // too since #3098) BEFORE computing
101
107
  // the relative path, so write and read produce identical anchors regardless of
102
108
  // the form the caller held. `normalizeMapKey` is idempotent, so the already-
103
109
  // canonicalized read side is unaffected; the realpath I/O is acceptable here
@@ -357,6 +363,7 @@ function emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing, i
357
363
  publishDisposition({
358
364
  cwd,
359
365
  filePath: target.filePath,
366
+ message: target.message,
360
367
  disposition,
361
368
  tool: target.tool,
362
369
  rule: target.rule,
@@ -390,6 +397,7 @@ export function markDisposition(cwd, target, disposition, reason, identity) {
390
397
  const existing = readState(cwd).dispositions?.[anchor];
391
398
  if (disposition === "defer") {
392
399
  deferredThisSession.add(deferredKey(cwd, anchor));
400
+ widgetDispositionReconciler?.(cwd, target, disposition);
393
401
  emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing, identity);
394
402
  return anchor;
395
403
  }
@@ -407,6 +415,9 @@ export function markDisposition(cwd, target, disposition, reason, identity) {
407
415
  lineText,
408
416
  };
409
417
  commitDisposition(cwd, anchor, entry);
418
+ // The durable commit is the guard for this mirror: a failed commit must not
419
+ // make the in-memory widget claim that a mark exists.
420
+ widgetDispositionReconciler?.(cwd, target, disposition);
410
421
  emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing, identity);
411
422
  return anchor;
412
423
  }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The effective ast-grep rule catalog for a workspace root (#3053).
3
+ *
4
+ * Before this module, `clients/dispatch/runners/ast-grep-napi.ts` (the NAPI
5
+ * runner) and `clients/dispatch/rule-ignores.ts` (the LSP-seam matcher, #3041)
6
+ * each walked `getAstGrepRuleSources(root)` and resolved rule-id precedence
7
+ * independently — two implementations of the same derivation. They had
8
+ * already drifted once by the time #3053 was filed: #3046 round 2 found the
9
+ * LSP seam claiming an id only when the winning document declared `ignores`,
10
+ * so a project rule that redefines a bundled id with NO `ignores` fired
11
+ * per-edit (the runner) but stayed carved out over LSP (the seam) — the same
12
+ * defect the whole PR existed to remove, just inverted. This module is the
13
+ * one place that walk and that precedence rule now live.
14
+ *
15
+ * Precedence: sources are walked in `getAstGrepRuleSources`'s order (project
16
+ * primary, project secondary, bundled primary, bundled secondary), each
17
+ * loaded with the same loader the runner always used (`loadYamlRulesFresh`
18
+ * for mutable project trees, `loadYamlRules`'s mtime-cached path for
19
+ * immutable bundled catalogs). The FIRST rule sighted for an id — scanning
20
+ * sources in order, and each source's own rules in file order — claims it;
21
+ * no later document for the same id is ever consulted.
22
+ *
23
+ * Deliberately does NOT special-case an id that recurs WITHIN one source's
24
+ * own rule list. That is `duplicateRuleIds` in ast-grep-napi.ts, a
25
+ * runner-only concern layered on top of this catalog's plain result: the
26
+ * runner drops such an id entirely (one Duplicate-rule-id diagnostic, no
27
+ * rule fires for it), but `materializeMergedRuleDir` (clients/sgconfig.ts) —
28
+ * what ast-grep's own LSP binary actually runs — keeps every document an
29
+ * EARLIER source hasn't already claimed, so both copies of a within-source
30
+ * duplicate still survive into what ast-grep publishes over LSP. Baking the
31
+ * runner's drop into this catalog would leave those published findings with
32
+ * no registered `ignores` at all, silently unsuppressed — reopening #3041 for
33
+ * that id (#3046 round 2 F7). So the catalog resolves a within-source
34
+ * duplicate to its first-sighted document like any other collision, and it
35
+ * is each consumer's job to decide what a within-source duplicate means for
36
+ * it — the runner still computes `duplicateRuleIds` itself and skips those
37
+ * ids before ever consulting this catalog's answer for them.
38
+ */
39
+ import { getAstGrepRuleSources } from "../sgconfig.js";
40
+ import { loadYamlRules, loadYamlRulesFresh, } from "./runners/yaml-rule-parser.js";
41
+ /**
42
+ * Build the effective ast-grep catalog for `root`. See the module docstring
43
+ * for the precedence rule and the within-source-duplicate decision.
44
+ *
45
+ * No try/catch around either loader: every I/O path inside them already
46
+ * swallows its own failure (missing dir, unreadable readdir, unreadable file
47
+ * all yield an empty list — `yaml-rule-parser.ts`'s `findYamlRuleFiles` and
48
+ * `loadYamlRuleFiles`), so a catch here would be a guard no mutation can red.
49
+ */
50
+ export function buildEffectiveAstGrepCatalog(root) {
51
+ const sources = [];
52
+ const effectiveRules = new Map();
53
+ for (const source of getAstGrepRuleSources(root)) {
54
+ const rules = source.origin === "project"
55
+ ? loadYamlRulesFresh(source.dir)
56
+ : loadYamlRules(source.dir);
57
+ sources.push({ source, rules });
58
+ for (const rule of rules) {
59
+ if (effectiveRules.has(rule.id))
60
+ continue;
61
+ effectiveRules.set(rule.id, { rule, source });
62
+ }
63
+ }
64
+ return { sources, effectiveRules };
65
+ }
@@ -22,6 +22,7 @@ import { findLocalOpengrepConfig } from "../opengrep-config.js";
22
22
  import { findLocalTyposConfig } from "../typos-config.js";
23
23
  import { findLocalZizmorConfig } from "../zizmor-config.js";
24
24
  import { classifyDefect } from "./diagnostic-taxonomy.js";
25
+ import { isRuleIgnoredForPath, loadRuleIgnorePatterns, } from "./rule-ignores.js";
25
26
  /**
26
27
  * Semgrep/opengrep `# nosemgrep` / `# nosemgrep: <rule-id>[,<rule-id>]` inline
27
28
  * suppression (#441). A bare `# nosemgrep` drops every finding on its line; the
@@ -248,8 +249,22 @@ export function isAuxiliaryDiagnosticSuppressed(d, content) {
248
249
  * files that are suppressed per-edit reappeared wholesale in every
249
250
  * `mode=full` sweep. Omitting `opts` (or `opts.fileRole`) keeps every existing
250
251
  * 2-arg call site's behavior byte-for-byte unchanged.
252
+ *
253
+ * #3041: `opts.filePath` + `opts.scanRoot` additionally apply each rule's own
254
+ * `ignores` path carve-out (#965) — the THIRD member of the same defect class
255
+ * as #586 and #692 above. `ast-grep scan` applies `ignores` during its own
256
+ * project walk, but `ast-grep lsp` publishes per-document diagnostics WITHOUT
257
+ * applying them (measured against ast-grep 0.45.3), so a rule the NAPI runner
258
+ * correctly skips on `scripts/**` came back over LSP on every `source=lsp`
259
+ * query and every `mode=full` sweep. Both routes reach this function, so the
260
+ * carve-out belongs here rather than in either route's own output merge.
251
261
  */
252
262
  export function applyAuxiliarySuppressions(diagnostics, content, opts) {
263
+ // One catalog read per call, not per diagnostic. Both rule loaders behind it
264
+ // are cached (~0.09 ms per call on pi-lens's own catalog), so a per-file call
265
+ // from a sweep costs the same order as the NAPI runner's own per-file load.
266
+ const { filePath, scanRoot } = opts ?? {};
267
+ const ruleIgnores = filePath && scanRoot ? loadRuleIgnorePatterns(scanRoot) : undefined;
253
268
  return diagnostics.filter((d) => {
254
269
  if (isAuxiliaryDiagnosticSuppressed(d, content))
255
270
  return false;
@@ -258,6 +273,19 @@ export function applyAuxiliarySuppressions(diagnostics, content, opts) {
258
273
  if (profile?.skipTestFiles)
259
274
  return false;
260
275
  }
276
+ // Keyed by the diagnostic's own rule id — ast-grep's LSP reports the bare
277
+ // catalog id as `code`. Gated on the producing tool as well: the map is
278
+ // built from the AST-GREP catalog, so without the gate any auxiliary whose
279
+ // own rule id happened to collide with a catalog id (several are generic —
280
+ // `no-raw-types`, `no-string-concat-in-loop`) would be silently dropped on
281
+ // a path an unrelated rule carved out. Losing a finding silently is the
282
+ // harm this whole change exists to stop, so the collision is gated, not
283
+ // accepted (#3041 r2 F4).
284
+ if (ruleIgnores !== undefined &&
285
+ findAuxiliaryProfileForSource(d.source)?.tool === "ast-grep" &&
286
+ isRuleIgnoredForPath(filePath, scanRoot, ruleIgnores.get(String(d.code)))) {
287
+ return false;
288
+ }
261
289
  return true;
262
290
  });
263
291
  }
@@ -26,7 +26,7 @@ import { getPrimaryDispatchGroup } from "../language-policy.js";
26
26
  import { resolveLanguageRootForFile } from "../language-profile.js";
27
27
  import { logLatency, phaseFinished, phaseStarted } from "../latency-logger.js";
28
28
  import { isSpawnableCommand } from "../installer/index.js";
29
- import { isAbsoluteFilePath, normalizeEphemeralMapKey, normalizeMapKey, } from "../path-utils.js";
29
+ import { isAbsoluteFilePath, isWindowsPath, normalizeEphemeralMapKey, normalizeMapKey, } from "../path-utils.js";
30
30
  import { loadPiLensProjectConfig } from "../project-lens-config.js";
31
31
  import { RUNTIME_CONFIG, getRunnerTimeoutFloorMs } from "../runtime-config.js";
32
32
  import { probeToolAsync } from "../tool-probe.js";
@@ -249,7 +249,13 @@ telemetryModel, telemetryProvider) {
249
249
  const normalizedProjectRoot = normalizeMapKey(path.resolve(projectRoot ?? cwd));
250
250
  const normalizedCwd = normalizeMapKey(resolveLanguageRootForFile(absoluteFilePath, cwd));
251
251
  const normalizedFilePath = normalizeMapKey(absoluteFilePath);
252
- const kind = detectFileKind(normalizedFilePath);
252
+ // `resolveRunnerPath` intentionally normalizes map keys, which folds a
253
+ // literal POSIX backslash into a separator. Preserve that one proven
254
+ // classifier distinction while still resolving relative inputs from `cwd`.
255
+ const kindFilePath = !isWindowsPath(filePath) && filePath.includes("\\")
256
+ ? path.resolve(cwd, filePath)
257
+ : absoluteFilePath;
258
+ const kind = detectFileKind(kindFilePath);
253
259
  const contentPrefix = readFilePrefix(normalizedFilePath);
254
260
  const fileRole = detectFileRole(normalizedFilePath, contentPrefix);
255
261
  // Captured once here so the generated short-circuit below can emit a
@@ -282,6 +288,7 @@ telemetryModel, telemetryProvider) {
282
288
  writeIndex,
283
289
  telemetryModel,
284
290
  telemetryProvider,
291
+ toolCwdMemo: {},
285
292
  async hasTool(command) {
286
293
  return checkToolAvailability(command, facts);
287
294
  },