pi-lens 4.4.3 → 4.4.4

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 (143) hide show
  1. package/CHANGELOG.md +281 -0
  2. package/README.md +13 -1
  3. package/dist/{chunk-SS36ZX4J.js → chunk-24AKHFGP.js} +6 -6
  4. package/dist/{chunk-JSQFTSC6.js → chunk-2FN63LPV.js} +11 -11
  5. package/dist/{chunk-5UCAH366.js → chunk-2KXTHF3M.js} +20 -20
  6. package/dist/{chunk-EU63SDCG.js → chunk-3DA2JDOL.js} +367 -281
  7. package/dist/{chunk-RWWYJ623.js → chunk-3KAZU3VR.js} +1 -1
  8. package/dist/chunk-3PTHXJXR.js +28 -0
  9. package/dist/chunk-3QED2P4V.js +31 -0
  10. package/dist/{chunk-N2H3ZPZY.js → chunk-3RKP3FMS.js} +1 -0
  11. package/dist/{chunk-XVF2VVXP.js → chunk-5HSRT26B.js} +2 -2
  12. package/dist/{chunk-MXGY6KPT.js → chunk-5IUSNGOC.js} +15 -15
  13. package/dist/{chunk-EJIUXPBX.js → chunk-5OFV52AM.js} +1 -1
  14. package/dist/{chunk-AW7R2GWG.js → chunk-5VSF4UUQ.js} +12 -7
  15. package/dist/{chunk-6STA6Z7I.js → chunk-5WRPDP2A.js} +18 -18
  16. package/dist/{chunk-YIUAVF7E.js → chunk-6DLWKFEX.js} +35 -34
  17. package/dist/{chunk-WOA4LN2R.js → chunk-7D6L4MTX.js} +2 -2
  18. package/dist/{chunk-GAEGEPOC.js → chunk-7HXE67BN.js} +2 -2
  19. package/dist/chunk-AGSS7277.js +84 -0
  20. package/dist/{chunk-R3PCY3YS.js → chunk-AMERCH6V.js} +13 -13
  21. package/dist/{chunk-WU567RSC.js → chunk-ATKJFDQN.js} +14 -10
  22. package/dist/{chunk-VSSBEUFT.js → chunk-AWSCPCN2.js} +16 -16
  23. package/dist/{chunk-VCHXCQFG.js → chunk-BWD5EPKO.js} +3 -3
  24. package/dist/{chunk-H6MF6EVN.js → chunk-CT4MN5IU.js} +1200 -230
  25. package/dist/{chunk-FCSOVF55.js → chunk-CUHSHSB6.js} +6 -6
  26. package/dist/{chunk-CHOW74WE.js → chunk-CYRKQRPM.js} +7 -10
  27. package/dist/{chunk-Q7DUK4LX.js → chunk-DEL2U6CM.js} +5 -5
  28. package/dist/{chunk-6C7JWPCP.js → chunk-DGUKIYSI.js} +26 -23
  29. package/dist/{chunk-PHDMFAKM.js → chunk-DUO2TU77.js} +2 -2
  30. package/dist/{chunk-MRXKYPCS.js → chunk-EBC36LNN.js} +2 -2
  31. package/dist/{chunk-CSHFH5IN.js → chunk-ED36RF6Y.js} +1 -1
  32. package/dist/{chunk-76MWC2K6.js → chunk-EUXZEFTV.js} +90 -97
  33. package/dist/{chunk-QOZXJ4NA.js → chunk-FM4RDMKT.js} +11 -8
  34. package/dist/{chunk-ACXEQZOU.js → chunk-GAQ7OQG6.js} +31 -28
  35. package/dist/{chunk-XAGLWMJ4.js → chunk-GL5LUMZE.js} +23 -23
  36. package/dist/{chunk-BKE7E7TC.js → chunk-GMX5OS7B.js} +9 -9
  37. package/dist/{chunk-ZHFZWTZV.js → chunk-GSLNCYGG.js} +11 -11
  38. package/dist/{chunk-3W7IEVVL.js → chunk-GSM5UU7A.js} +26 -34
  39. package/dist/{chunk-724UL2TJ.js → chunk-GXFV6MYU.js} +3 -3
  40. package/dist/chunk-HAD7QRQI.js +30 -0
  41. package/dist/{chunk-QOBUHWHX.js → chunk-HRNHZA3N.js} +6 -6
  42. package/dist/{chunk-VWCXW2E7.js → chunk-HWCCCQNX.js} +1 -1
  43. package/dist/{chunk-7DYBLXYA.js → chunk-HYGZEMX6.js} +3 -3
  44. package/dist/chunk-IJYGIDF4.js +186 -0
  45. package/dist/{chunk-4W4VLAPJ.js → chunk-IQWN5JCP.js} +11 -11
  46. package/dist/{chunk-SQ4TVJBR.js → chunk-IUE7SSKV.js} +7 -7
  47. package/dist/{chunk-IBGIOBEE.js → chunk-IWDLU6K3.js} +68 -5
  48. package/dist/{chunk-XCJN5IJU.js → chunk-IYNJ6YPZ.js} +4 -4
  49. package/dist/{chunk-JAU72XNY.js → chunk-J64T55EI.js} +3 -3
  50. package/dist/{chunk-2BBLFX75.js → chunk-J6MPGQTS.js} +5 -5
  51. package/dist/chunk-JORED7EX.js +44 -0
  52. package/dist/{chunk-6SDEP3AJ.js → chunk-JUKMK5Y4.js} +8 -8
  53. package/dist/{chunk-FMSO6RDZ.js → chunk-JW3YWBVB.js} +15 -15
  54. package/dist/{chunk-L3BLXZG7.js → chunk-KAPJINKN.js} +30 -69
  55. package/dist/{chunk-2B73ZPOK.js → chunk-KN35K52N.js} +3 -3
  56. package/dist/{chunk-C34ZXZ7O.js → chunk-KUA4OJ5Y.js} +24 -11
  57. package/dist/chunk-KYUB6QDC.js +72 -0
  58. package/dist/{chunk-67WMNWEE.js → chunk-LLJNQKQW.js} +12 -10
  59. package/dist/chunk-LZSEQW6A.js +57 -0
  60. package/dist/{chunk-ZANLILYD.js → chunk-M2IFVFWU.js} +2 -2
  61. package/dist/chunk-M2UD3LPU.js +88 -0
  62. package/dist/{chunk-XIJZVPD5.js → chunk-MBKQHGA7.js} +15 -15
  63. package/dist/{chunk-P7266HLM.js → chunk-MPNJLASA.js} +2 -2
  64. package/dist/{chunk-6GFRNVYR.js → chunk-MZGQNL3Y.js} +10 -10
  65. package/dist/{chunk-ZWFS3OEF.js → chunk-N5FLEO7W.js} +33 -32
  66. package/dist/{chunk-3JNKMP3B.js → chunk-O4NWQT6I.js} +4 -4
  67. package/dist/{chunk-T6CPIZNT.js → chunk-OHKJ7PQF.js} +33 -32
  68. package/dist/{chunk-VRZP76YM.js → chunk-P63GXC37.js} +4 -1
  69. package/dist/{chunk-XTMJ7XDT.js → chunk-POMLXPMY.js} +1 -1
  70. package/dist/{chunk-QQNQ7SJW.js → chunk-QBFBRV2B.js} +10 -4
  71. package/dist/{chunk-CMMHNUZ2.js → chunk-R3T7CLQ2.js} +60 -38
  72. package/dist/{chunk-23WOO7TX.js → chunk-R7YOCSNV.js} +10 -10
  73. package/dist/{chunk-2CD5RS6M.js → chunk-RKBXZN3A.js} +23 -2
  74. package/dist/{chunk-OCAGUNBC.js → chunk-RKKOYPRA.js} +12 -12
  75. package/dist/{chunk-52FUZI6K.js → chunk-RMLC67LF.js} +1 -1
  76. package/dist/{chunk-XMSXXAHW.js → chunk-ROYSX2S5.js} +2 -2
  77. package/dist/{chunk-M6HSLBWY.js → chunk-RVK72CZX.js} +6507 -7514
  78. package/dist/{chunk-FLKWCIZH.js → chunk-S2HEXIWI.js} +5 -5
  79. package/dist/{chunk-UZ3TUTRV.js → chunk-S67KYG37.js} +1 -1
  80. package/dist/{chunk-VCO24T4L.js → chunk-SD4I7ZO4.js} +14 -14
  81. package/dist/{chunk-Z527N6YQ.js → chunk-SKAMFC7A.js} +12 -12
  82. package/dist/{chunk-MQLUAEOC.js → chunk-SRYEJNES.js} +2 -2
  83. package/dist/{chunk-BRGDSBJE.js → chunk-SYE6T2EB.js} +12 -12
  84. package/dist/{chunk-GXW6K4YJ.js → chunk-T57LYLZT.js} +1 -1
  85. package/dist/{chunk-ZXYDQ3TZ.js → chunk-T7FEBKXN.js} +48 -15
  86. package/dist/{chunk-3EFKIGZN.js → chunk-TIQ2PKDW.js} +3 -3
  87. package/dist/{chunk-QILIYSJF.js → chunk-TLX72JTG.js} +1 -1
  88. package/dist/{chunk-TYM6OUIT.js → chunk-TOY6QKOH.js} +12 -12
  89. package/dist/{chunk-IZSHAIJ7.js → chunk-TRETHTQO.js} +12 -12
  90. package/dist/{chunk-3VPVG5SU.js → chunk-U44DJXQP.js} +10 -6
  91. package/dist/{chunk-PNQDPDRC.js → chunk-UA7ZXYDR.js} +1 -1
  92. package/dist/{chunk-OXXKQCSV.js → chunk-UBOQLVT2.js} +4 -4
  93. package/dist/{chunk-QGQ5CCCG.js → chunk-UMS2MFPZ.js} +2 -2
  94. package/dist/{chunk-QHJ6DQ2R.js → chunk-US7ECLD7.js} +55 -13
  95. package/dist/{chunk-AB3YYG6N.js → chunk-UZ2XCLYS.js} +8 -8
  96. package/dist/chunk-V35JG4JB.js +2667 -0
  97. package/dist/{chunk-N3X6Q4YT.js → chunk-VOOAPROD.js} +3 -3
  98. package/dist/{chunk-SAEFPSB6.js → chunk-W66O5E25.js} +21 -11
  99. package/dist/{chunk-KBG47GHP.js → chunk-WMYE35PX.js} +15 -11
  100. package/dist/{chunk-5NGR67U5.js → chunk-XBJVAEEO.js} +32 -5
  101. package/dist/{chunk-YYOIQFRS.js → chunk-XHJBS436.js} +122 -44
  102. package/dist/{chunk-GBPP5P7X.js → chunk-XTBDCR27.js} +4 -4
  103. package/dist/{chunk-4UNXBNE2.js → chunk-XTFDJI3T.js} +11 -11
  104. package/dist/{chunk-UXGETRP4.js → chunk-XYURS6EN.js} +13 -12
  105. package/dist/{chunk-RPLINGVB.js → chunk-Y6GZHWSJ.js} +6 -6
  106. package/dist/{chunk-2VUQMUMT.js → chunk-YCWYO6TQ.js} +41 -38
  107. package/dist/{chunk-77M37OKR.js → chunk-YH5MVY2U.js} +7 -7
  108. package/dist/{chunk-AOHZU2BM.js → chunk-YNE3RP52.js} +1 -1
  109. package/dist/{chunk-YSZNHOBY.js → chunk-ZHH37KZL.js} +12 -12
  110. package/dist/{chunk-ZGDGW2V3.js → chunk-ZIVJBOO3.js} +2 -2
  111. package/dist/{chunk-RXCOMYPY.js → chunk-ZVUL7QYY.js} +29 -28
  112. package/dist/index.js +11052 -5852
  113. package/dist/mcp/analyze-cli.js +10 -9
  114. package/dist/mcp/cli.js +41 -38
  115. package/dist/mcp/server.js +1120 -474
  116. package/dist/mcp/worker.js +61 -58
  117. package/dist/probes/installer.js +19 -19
  118. package/dist/workers/project-snapshot-persist-worker.js +28 -9
  119. package/dist/workers/review-graph-persist-worker.js +1 -1
  120. package/docs/configuration.md +11 -0
  121. package/docs/engineering-principles.md +32 -0
  122. package/docs/lsp-capability-matrix.md +1 -1
  123. package/docs/lsp-idle-eviction.md +15 -26
  124. package/docs/pi-lens-fixer.md +117 -13
  125. package/docs/pi-lens-merge-policy.md +97 -7
  126. package/docs/pi-lens-monitor.md +8 -2
  127. package/docs/pi-lens-retro.md +4 -1
  128. package/docs/pi-lens-reviewer.md +112 -4
  129. package/docs/pi-lens-subagent.md +22 -14
  130. package/docs/release-qa-baseline.md +3 -1
  131. package/docs/servercapabilities.md +9 -19
  132. package/package.json +7 -5
  133. package/scripts/download-grammars.js +40 -7
  134. package/scripts/lib/latency-log-phases.mjs +95 -0
  135. package/scripts/rpc-load-check.mjs +570 -81
  136. package/dist/chunk-3YN6V5TV.js +0 -85
  137. package/dist/chunk-466Z26MG.js +0 -30
  138. package/dist/chunk-EC47DWFA.js +0 -56
  139. package/dist/chunk-KDERNTCQ.js +0 -28
  140. package/dist/chunk-KSX3TXSZ.js +0 -69
  141. package/dist/chunk-LOP6GRPH.js +0 -31
  142. package/dist/chunk-UOPVJQ4N.js +0 -103
  143. /package/dist/{chunk-FC4RP4I2.js → chunk-6HWVI6MI.js} +0 -0
@@ -2,7 +2,7 @@ import { createRequire as __pilensCreateRequire } from "node:module"; const requ
2
2
  import {
3
3
  getLazyInstallAttempt,
4
4
  tryLazyInstall
5
- } from "./chunk-OXXKQCSV.js";
5
+ } from "./chunk-UBOQLVT2.js";
6
6
  import {
7
7
  findingsResult,
8
8
  finishParsedRun,
@@ -11,24 +11,29 @@ import {
11
11
  isRunnerClaimSource,
12
12
  isRunnerSkipReason,
13
13
  parseToolRun,
14
- skipUnlessToolRan
15
- } from "./chunk-2CD5RS6M.js";
14
+ reachedNoVerdict,
15
+ skipUnlessToolRan,
16
+ spanOverlapsModifiedRanges
17
+ } from "./chunk-RKBXZN3A.js";
16
18
  import {
17
19
  stripAnsi
18
- } from "./chunk-FC4RP4I2.js";
20
+ } from "./chunk-6HWVI6MI.js";
19
21
  import {
20
22
  spawnFailedWithNoOutput
21
- } from "./chunk-UZ3TUTRV.js";
23
+ } from "./chunk-S67KYG37.js";
24
+ import {
25
+ computeImpactCascade
26
+ } from "./chunk-KN35K52N.js";
22
27
  import {
23
28
  goClient
24
- } from "./chunk-XMSXXAHW.js";
29
+ } from "./chunk-ROYSX2S5.js";
25
30
  import {
26
31
  isTrivyEnabled,
27
32
  resolveSeverityFloor
28
- } from "./chunk-77M37OKR.js";
33
+ } from "./chunk-YH5MVY2U.js";
29
34
  import {
30
35
  rustClient
31
- } from "./chunk-MRXKYPCS.js";
36
+ } from "./chunk-EBC36LNN.js";
32
37
  import {
33
38
  coveringLaneAvailable,
34
39
  createAvailabilityChecker,
@@ -44,7 +49,7 @@ import {
44
49
  resolveToolCommand,
45
50
  resolveToolCommandWithInstallFallback,
46
51
  resolveVendorToolCommand
47
- } from "./chunk-YSZNHOBY.js";
52
+ } from "./chunk-ZHH37KZL.js";
48
53
  import {
49
54
  biomeConfigArgs,
50
55
  findCompiledClassesDir,
@@ -63,27 +68,24 @@ import {
63
68
  hasYamllintConfig,
64
69
  markdownlintConfigArgs,
65
70
  ruffConfigArgs
66
- } from "./chunk-RPLINGVB.js";
71
+ } from "./chunk-Y6GZHWSJ.js";
67
72
  import {
68
73
  importFactProvider
69
- } from "./chunk-XCJN5IJU.js";
74
+ } from "./chunk-IYNJ6YPZ.js";
70
75
  import {
71
76
  functionFactProvider
72
- } from "./chunk-76MWC2K6.js";
77
+ } from "./chunk-EUXZEFTV.js";
73
78
  import {
74
79
  extractFactsFromTree
75
- } from "./chunk-GXW6K4YJ.js";
76
- import {
77
- computeImpactCascade
78
- } from "./chunk-2B73ZPOK.js";
80
+ } from "./chunk-T57LYLZT.js";
79
81
  import {
80
82
  buildOrUpdateGraph,
81
83
  clearReviewGraphWorkspaceCache,
82
84
  fileContentProvider
83
- } from "./chunk-YYOIQFRS.js";
85
+ } from "./chunk-XHJBS436.js";
84
86
  import {
85
87
  MAX_BLOCKING_RULE_COMPLEXITY,
86
- RUNTIME_CONFIG,
88
+ PRIMARY_UNAVAILABLE_CAUSE,
87
89
  applyDispositions,
88
90
  applyDispositionsMultiFile,
89
91
  buildEffectiveAstGrepCatalog,
@@ -92,17 +94,18 @@ import {
92
94
  classifyDiagnostic,
93
95
  classifyDiagnosticTier,
94
96
  clearPendingAuxiliaryCoverage,
97
+ combinePrimaryUnavailableCauses,
95
98
  enabledAuxiliaryLspServerIds,
96
- exceedsLspSyncLimits,
97
99
  fetchTsserverProjectIdentity,
98
100
  getLSPService,
99
- getRunnerTimeoutFloorMs,
100
101
  hasAnyDispositionMarks,
101
102
  hasAuxiliaryLspPublishedForRoot,
102
103
  hasPendingAuxiliaryCoverage,
103
104
  isOverlyBroadPattern,
104
105
  isRuleIgnoredForPath,
105
106
  isStructuredRule,
107
+ lineContentHash,
108
+ primaryUnavailableCause,
106
109
  recordDiagnostics,
107
110
  recordNapiFallbackCoverage,
108
111
  recordRunner,
@@ -110,25 +113,40 @@ import {
110
113
  touchCoverageGap,
111
114
  tryWarmAttachedCodeActions,
112
115
  tryWarmAttachedDiagnostics
113
- } from "./chunk-M6HSLBWY.js";
116
+ } from "./chunk-RVK72CZX.js";
117
+ import {
118
+ ANY_POSITION,
119
+ retainRecordedFindings
120
+ } from "./chunk-IWDLU6K3.js";
114
121
  import {
115
122
  mapWithConcurrency
116
123
  } from "./chunk-XIMYZ3JA.js";
117
124
  import {
118
125
  LSP_SERVERS,
126
+ disabledPrimaries,
119
127
  getAstGrepRuleSources,
120
128
  registerRunnerId,
121
129
  resolveAnalysisRoot
122
- } from "./chunk-EU63SDCG.js";
130
+ } from "./chunk-3DA2JDOL.js";
123
131
  import {
124
132
  augmentPythonEnvironment,
125
133
  detectPythonEnvironment
126
- } from "./chunk-FCSOVF55.js";
134
+ } from "./chunk-CUHSHSB6.js";
127
135
  import {
128
136
  loadProjectSnapshot,
129
137
  readJsonCache,
130
138
  readJsonCacheAsync
131
- } from "./chunk-ZXYDQ3TZ.js";
139
+ } from "./chunk-T7FEBKXN.js";
140
+ import {
141
+ RUNTIME_CONFIG,
142
+ exceedsLspSyncLimits,
143
+ getRunnerTimeoutFloorMs,
144
+ loadAstGrepNapi
145
+ } from "./chunk-V35JG4JB.js";
146
+ import {
147
+ buildModuleGraph,
148
+ clearModuleGraphCache
149
+ } from "./chunk-XTBDCR27.js";
132
150
  import {
133
151
  BUNDLED_QUERIES_ROOT,
134
152
  classifyTreeSitterWasmError,
@@ -148,44 +166,44 @@ import {
148
166
  ruleFilesForLanguage,
149
167
  ruleSourceLanguages,
150
168
  walk
151
- } from "./chunk-QHJ6DQ2R.js";
152
- import {
153
- buildModuleGraph,
154
- clearModuleGraphCache
155
- } from "./chunk-GBPP5P7X.js";
169
+ } from "./chunk-US7ECLD7.js";
156
170
  import {
157
171
  WORD_INDEX_MAX_BYTES,
158
172
  deserializeWordIndex,
159
173
  logWordIndex,
160
174
  removeWordIndexDocumentAsync,
161
175
  scheduleWordIndexPersist,
176
+ setWordIndexPublishedListener,
162
177
  updateWordIndexDocumentForEdit
163
- } from "./chunk-SAEFPSB6.js";
178
+ } from "./chunk-W66O5E25.js";
164
179
  import {
165
180
  PathKeyedMap
166
181
  } from "./chunk-Z3CCVDUB.js";
167
182
  import {
168
183
  WARM_CODE_ACTION_LOOKUP_LIMIT,
169
- contentHash,
170
- resolveRunnerPath
171
- } from "./chunk-L3BLXZG7.js";
184
+ contentHash
185
+ } from "./chunk-KAPJINKN.js";
186
+ import {
187
+ resolveRunnerPath,
188
+ toRunnerDisplayPath
189
+ } from "./chunk-AGSS7277.js";
172
190
  import {
173
191
  isSpawnableCommand
174
- } from "./chunk-ACXEQZOU.js";
192
+ } from "./chunk-GAQ7OQG6.js";
175
193
  import {
176
194
  logRunnerAdvisoryOnce,
177
195
  resolveRunnerCwd,
178
196
  resolveRunnerCwdWithReason
179
- } from "./chunk-Q7DUK4LX.js";
197
+ } from "./chunk-DEL2U6CM.js";
180
198
  import {
181
199
  findLocalBinUpwards,
182
200
  localBinPath
183
- } from "./chunk-AW7R2GWG.js";
201
+ } from "./chunk-5VSF4UUQ.js";
184
202
  import {
185
203
  createAvailabilityProbeFlight,
186
204
  getIsolatedNpxSpawnOptions,
187
205
  probeToolAsync
188
- } from "./chunk-RWWYJ623.js";
206
+ } from "./chunk-3KAZU3VR.js";
189
207
  import {
190
208
  classifyProbeFailure,
191
209
  createAvailabilityLatch,
@@ -195,12 +213,12 @@ import {
195
213
  logAvailabilityDecision,
196
214
  startHostStallSampler,
197
215
  transientRetryDelayMs
198
- } from "./chunk-VRZP76YM.js";
216
+ } from "./chunk-P63GXC37.js";
199
217
  import {
200
218
  getLspCapableKinds,
201
219
  getPrimaryDispatchGroup,
202
220
  resolveLanguageRootForFile
203
- } from "./chunk-QOBUHWHX.js";
221
+ } from "./chunk-HRNHZA3N.js";
204
222
  import {
205
223
  getGlobalPiLensDir,
206
224
  getProjectDataDir,
@@ -210,25 +228,25 @@ import {
210
228
  noteSpawnTimeout,
211
229
  resolvePiLensFlag,
212
230
  safeSpawnAsync
213
- } from "./chunk-C34ZXZ7O.js";
231
+ } from "./chunk-KUA4OJ5Y.js";
214
232
  import {
215
233
  detectFileKind
216
- } from "./chunk-PHDMFAKM.js";
234
+ } from "./chunk-DUO2TU77.js";
217
235
  import {
218
236
  classifyGeneratedOrArtifactDetailed,
219
237
  detectFileRole,
220
238
  loadPiLensProjectConfig,
221
239
  resolvePiLensConfig
222
- } from "./chunk-2BBLFX75.js";
240
+ } from "./chunk-J6MPGQTS.js";
223
241
  import {
224
242
  findNearestDirWithAnyBasename,
225
243
  findNearestDirWithMarker,
226
244
  getPiLensGlobalConfigPath
227
- } from "./chunk-XVF2VVXP.js";
245
+ } from "./chunk-5HSRT26B.js";
228
246
  import {
229
247
  getProjectTrustState,
230
248
  projectTrustDenialReason
231
- } from "./chunk-ZANLILYD.js";
249
+ } from "./chunk-M2IFVFWU.js";
232
250
  import {
233
251
  killedForOutputCap,
234
252
  truncatedByOutputCap,
@@ -237,27 +255,28 @@ import {
237
255
  import {
238
256
  incrementDegradationCount,
239
257
  recordDegradationOnce
240
- } from "./chunk-JAU72XNY.js";
258
+ } from "./chunk-J64T55EI.js";
241
259
  import {
242
260
  createSubsystemLogger,
243
261
  logExtension
244
- } from "./chunk-XTMJ7XDT.js";
262
+ } from "./chunk-POMLXPMY.js";
245
263
  import {
246
264
  logLatency,
247
265
  phaseFinished,
248
266
  phaseStarted
249
- } from "./chunk-QQNQ7SJW.js";
267
+ } from "./chunk-QBFBRV2B.js";
250
268
  import {
251
269
  createNdjsonLogger,
252
270
  getGlobalPiLensLogDir,
253
271
  getMaxLogSizeMB,
254
272
  isTestMode
255
- } from "./chunk-AOHZU2BM.js";
273
+ } from "./chunk-YNE3RP52.js";
256
274
  import {
257
275
  FactStore,
258
276
  setFactStoreEvictionReporter
259
- } from "./chunk-CHOW74WE.js";
277
+ } from "./chunk-CYRKQRPM.js";
260
278
  import {
279
+ BoundedFifoMap,
261
280
  BoundedLruCache
262
281
  } from "./chunk-ABBOA7UD.js";
263
282
  import {
@@ -270,7 +289,7 @@ import {
270
289
  normalizeMapKey,
271
290
  pathsEqual,
272
291
  toProjectRelativePath
273
- } from "./chunk-N2H3ZPZY.js";
292
+ } from "./chunk-3RKP3FMS.js";
274
293
 
275
294
  // dist/clients/mcp/analyze.js
276
295
  import * as fs19 from "node:fs";
@@ -889,6 +908,62 @@ function createDiagnosticLogger() {
889
908
  import * as fs3 from "node:fs";
890
909
  import * as path4 from "node:path";
891
910
 
911
+ // dist/clients/line-alignment.js
912
+ var MAX_OWED_LINE_TEXTS = 4096;
913
+ function lineHashOf(line) {
914
+ return Number.parseInt(lineContentHash(line), 16);
915
+ }
916
+ function stampLineHashes(findings, content, isOwn) {
917
+ if (content === void 0)
918
+ return [...findings];
919
+ let lines;
920
+ return findings.map((d) => {
921
+ if (d.lineHash !== void 0 || d.line === void 0 || !isOwn(d))
922
+ return d;
923
+ lines ??= content.split(/\r?\n/);
924
+ const text = lines[d.line - 1];
925
+ return text === void 0 ? d : { ...d, lineHash: lineHashOf(text) };
926
+ });
927
+ }
928
+ function linesOfRanges(ranges) {
929
+ const lines = [];
930
+ for (const { start, end } of ranges)
931
+ for (let line = start; line <= end; line += 1)
932
+ lines.push(line);
933
+ return lines;
934
+ }
935
+ function rangesOfLines(lines) {
936
+ const sorted = [...new Set(lines)].sort((a, b) => a - b);
937
+ const ranges = [];
938
+ for (const line of sorted) {
939
+ const last = ranges.at(-1);
940
+ if (last && line === last.end + 1)
941
+ last.end = line;
942
+ else
943
+ ranges.push({ start: line, end: line });
944
+ }
945
+ return ranges;
946
+ }
947
+ function owedContentOf(ranges, lineHashes) {
948
+ if (lineHashes === void 0)
949
+ return "whole-file";
950
+ const texts = /* @__PURE__ */ new Set();
951
+ for (const line of linesOfRanges(ranges)) {
952
+ const hash = lineHashes[line - 1];
953
+ if (hash !== void 0)
954
+ texts.add(hash);
955
+ if (texts.size > MAX_OWED_LINE_TEXTS)
956
+ return "whole-file";
957
+ }
958
+ return Uint32Array.from(texts).sort();
959
+ }
960
+ function owedByContentOnly(anchor) {
961
+ const owedContent = new Map(anchor?.owedContent ?? []);
962
+ for (const [runnerId, ranges] of anchor?.owedRanges ?? [])
963
+ owedContent.set(runnerId, owedContentOf(ranges, anchor?.lineHashes));
964
+ return owedContent.size > 0 ? { lineHashes: void 0, owedRanges: /* @__PURE__ */ new Map(), owedContent } : void 0;
965
+ }
966
+
892
967
  // dist/clients/dispatch/rule-id-normalize.js
893
968
  import * as fs2 from "node:fs";
894
969
  import * as path3 from "node:path";
@@ -1384,8 +1459,8 @@ async function drainPendingRunnerFindings(maxWaitMs = 2e3, currentSession, curre
1384
1459
  if (maxWaitMs === 0)
1385
1460
  await Promise.resolve();
1386
1461
  if (maxWaitMs > 0 && current.some((entry) => !entry.settled)) {
1387
- await new Promise((resolve44) => {
1388
- const timer = setTimeout(resolve44, maxWaitMs);
1462
+ await new Promise((resolve45) => {
1463
+ const timer = setTimeout(resolve45, maxWaitMs);
1389
1464
  timer.unref?.();
1390
1465
  });
1391
1466
  }
@@ -1448,6 +1523,30 @@ function filterGroupsForAdoptedRoot(groups) {
1448
1523
  return filtered;
1449
1524
  }
1450
1525
 
1526
+ // dist/clients/dispatch/lane-copies.js
1527
+ function semanticRank(semantic) {
1528
+ if (semantic === "blocking")
1529
+ return 4;
1530
+ if (semantic === "warning")
1531
+ return 3;
1532
+ if (semantic === "fixed")
1533
+ return 2;
1534
+ if (semantic === "silent")
1535
+ return 1;
1536
+ return 0;
1537
+ }
1538
+ function laneKey(d) {
1539
+ return `${d.line ?? 1}\0${normalizeRuleId(d.rule ?? "")}`;
1540
+ }
1541
+ function laneCopyStandsFor(lane) {
1542
+ const rankByKey = /* @__PURE__ */ new Map();
1543
+ for (const d of lane) {
1544
+ const key2 = laneKey(d);
1545
+ rankByKey.set(key2, Math.max(rankByKey.get(key2) ?? 0, semanticRank(d.semantic)));
1546
+ }
1547
+ return (d) => (rankByKey.get(laneKey(d)) ?? -1) >= semanticRank(d.semantic);
1548
+ }
1549
+
1451
1550
  // dist/clients/dispatch/rule-policy.js
1452
1551
  function resolvedRulePolicyMap(root) {
1453
1552
  const resolved = resolvePiLensConfig({
@@ -1579,6 +1678,58 @@ function getToolProfile(tool, defectClass) {
1579
1678
  return TOOL_PROFILE_MAP[t] ?? DEFAULT_TOOL_PROFILE;
1580
1679
  }
1581
1680
 
1681
+ // dist/clients/dispatch/pre-edit-seed.js
1682
+ var PRE_EDIT_SEED_RUNNER_IDS = /* @__PURE__ */ new Set([
1683
+ "actionlint",
1684
+ "biome-check-json",
1685
+ "credo",
1686
+ "detekt",
1687
+ "eslint",
1688
+ "golangci-lint",
1689
+ "hadolint",
1690
+ "htmlhint",
1691
+ "ktlint",
1692
+ "markdownlint",
1693
+ "oxlint",
1694
+ "psscriptanalyzer",
1695
+ "rubocop",
1696
+ "ruff-lint",
1697
+ "rust-clippy",
1698
+ "shellcheck",
1699
+ "spellcheck",
1700
+ "sqlfluff",
1701
+ "stylelint",
1702
+ "swiftlint",
1703
+ "tflint",
1704
+ "vale",
1705
+ "yamllint"
1706
+ ]);
1707
+ function preEditSeedFactKey(filePath) {
1708
+ return `session.preEdit.${filePath}`;
1709
+ }
1710
+ function classifyPreExisting(seed, current, content, answered) {
1711
+ let lines;
1712
+ const hashAt = (line) => {
1713
+ if (line === void 0)
1714
+ return void 0;
1715
+ lines ??= content.split(/\r?\n/);
1716
+ const text = lines[line - 1];
1717
+ return text === void 0 ? void 0 : lineHashOf(text);
1718
+ };
1719
+ const candidates = current.filter((d) => d.runnerId !== void 0 && PRE_EDIT_SEED_RUNNER_IDS.has(d.runnerId));
1720
+ const { kept } = retainRecordedFindings(seed, candidates, {
1721
+ positionOf: () => ANY_POSITION,
1722
+ keepUnobserved: (d) => d.runnerId === void 0 || !answered.has(d.runnerId),
1723
+ matchesAnywhere: (prior, observed2) => prior.lineHash !== void 0 && prior.lineHash === hashAt(observed2.line)
1724
+ });
1725
+ const observed = new Set(candidates);
1726
+ const preExisting = kept.filter((d) => observed.has(d));
1727
+ return {
1728
+ preExisting,
1729
+ nextSeed: stampLineHashes(kept, content, (d) => observed.has(d))
1730
+ };
1731
+ }
1732
+
1582
1733
  // dist/clients/dispatch/utils/format-utils.js
1583
1734
  var EMOJI = {
1584
1735
  blocking: "\u{1F534}",
@@ -1833,6 +1984,9 @@ function createDispatchContext(filePath, cwd, pi, facts, blockingOnly, modifiedR
1833
1984
  }
1834
1985
  };
1835
1986
  }
1987
+ function deltaBaselineFactKey(filePath) {
1988
+ return `session.baseline.${filePath}`;
1989
+ }
1836
1990
  function filterDelta(after, before, keyFn) {
1837
1991
  const beforeSet = new Set((before ?? []).map(keyFn));
1838
1992
  const afterSet = new Set(after.map(keyFn));
@@ -1840,17 +1994,6 @@ function filterDelta(after, before, keyFn) {
1840
1994
  const newItems = after.filter((d) => !beforeSet.has(keyFn(d)));
1841
1995
  return { new: newItems, fixed };
1842
1996
  }
1843
- function semanticRank(semantic) {
1844
- if (semantic === "blocking")
1845
- return 4;
1846
- if (semantic === "warning")
1847
- return 3;
1848
- if (semantic === "fixed")
1849
- return 2;
1850
- if (semantic === "silent")
1851
- return 1;
1852
- return 0;
1853
- }
1854
1997
  function toolPriority(tool, defectClass) {
1855
1998
  return getToolProfile(tool, defectClass).dedupPriority;
1856
1999
  }
@@ -1949,14 +2092,14 @@ function promoteDeltaUnusedToBlockers(diagnostics) {
1949
2092
  }
1950
2093
  function buildCoverageNotice(ctx, runnerLatencies, dedupe) {
1951
2094
  if (!ctx.kind)
1952
- return void 0;
2095
+ return {};
1953
2096
  const lspEnabled = !ctx.pi.getFlag("no-lsp");
1954
2097
  const primary2 = getPrimaryDispatchGroup(ctx.kind, lspEnabled);
1955
2098
  if (!primary2 || primary2.runnerIds.length === 0)
1956
- return void 0;
2099
+ return {};
1957
2100
  const relevant = runnerLatencies.filter((r) => primary2.runnerIds.includes(r.runnerId));
1958
2101
  if (relevant.length === 0)
1959
- return void 0;
2102
+ return {};
1960
2103
  const unconfirmedServerIds = [
1961
2104
  ...new Set(relevant.flatMap((r) => r.unconfirmedServerIds ?? []))
1962
2105
  ];
@@ -1973,7 +2116,7 @@ function buildCoverageNotice(ctx, runnerLatencies, dedupe) {
1973
2116
  const onceKey2 = `${ctx.kind}:${ctx.filePath}:${dedupeSet(silentServerIds)}|${dedupeSet(deferredServerIds)}`;
1974
2117
  if (dedupe) {
1975
2118
  if (coverageNoticeSeen.has(onceKey2))
1976
- return void 0;
2119
+ return {};
1977
2120
  coverageNoticeSeen.add(onceKey2);
1978
2121
  }
1979
2122
  const coverageParts = [];
@@ -1984,20 +2127,22 @@ function buildCoverageNotice(ctx, runnerLatencies, dedupe) {
1984
2127
  coverageParts.push(`coverage: ${markerFor(silentServerIds)} silent \u2014 diagnostics are incomplete (not a clean result).`);
1985
2128
  }
1986
2129
  return {
1987
- id: `coverage-partial:${ctx.kind}:${path4.basename(ctx.filePath)}`,
1988
- message: coverageParts.join("\n"),
1989
- filePath: ctx.filePath,
1990
- severity: "warning",
1991
- semantic: "warning",
1992
- tool: "pi-lens"
2130
+ notice: {
2131
+ id: `coverage-partial:${ctx.kind}:${path4.basename(ctx.filePath)}`,
2132
+ message: coverageParts.join("\n"),
2133
+ filePath: ctx.filePath,
2134
+ severity: "warning",
2135
+ semantic: "warning",
2136
+ tool: "pi-lens"
2137
+ }
1993
2138
  };
1994
2139
  }
1995
2140
  const primaryHasCoverage = relevant.some(hasUsableResult);
1996
2141
  if (primaryHasCoverage)
1997
- return void 0;
2142
+ return {};
1998
2143
  const primaryStillInFlight = relevant.some((r) => r.status === "pending" || r.status === "deferred");
1999
2144
  if (primaryStillInFlight)
2000
- return void 0;
2145
+ return {};
2001
2146
  const plan = getToolPlan(ctx.kind);
2002
2147
  const fallbackRunnerIds = new Set((plan?.groups ?? []).filter((group) => !group.runnerIds.every((runnerId) => primary2.runnerIds.includes(runnerId))).flatMap((group) => group.runnerIds).filter((runnerId) => !primary2.runnerIds.includes(runnerId)));
2003
2148
  const STRUCTURAL_RUNNERS = /* @__PURE__ */ new Set([
@@ -2010,20 +2155,48 @@ function buildCoverageNotice(ctx, runnerLatencies, dedupe) {
2010
2155
  const TYPE_CAPABLE_FALLBACK_RUNNERS = /* @__PURE__ */ new Set(["mypy"]);
2011
2156
  const anyFallbackHasCoverage = runnerLatencies.some((r) => fallbackRunnerIds.has(r.runnerId) && !STRUCTURAL_RUNNERS.has(r.runnerId) && hasUsableResult(r) && (!plan?.capabilities.includes("types") || TYPE_CAPABLE_FALLBACK_RUNNERS.has(r.runnerId)));
2012
2157
  if (anyFallbackHasCoverage)
2013
- return void 0;
2158
+ return {};
2159
+ const cause = combinePrimaryUnavailableCauses(relevant.flatMap((r) => r.primaryUnavailableCause ?? []));
2160
+ const unconfirmedStatus = cause ? `no language server answered (${PRIMARY_UNAVAILABLE_CAUSE[cause]})` : "not fully checked (a language tool is missing, timed out, or failed to run)";
2161
+ if (cause === "disabled" || cause === "disabledFallback") {
2162
+ const disabledIds = [
2163
+ ...new Set(relevant.flatMap((r) => r.disabledPrimaryServerIds ?? []))
2164
+ ].sort((a, b) => Number(a > b) - Number(a < b));
2165
+ const onceKeys = disabledIds.map((id) => `disabled-primary:${id}`);
2166
+ if (dedupe) {
2167
+ if (onceKeys.every((key2) => coverageNoticeSeen.has(key2)))
2168
+ return { unconfirmedStatus };
2169
+ for (const key2 of onceKeys)
2170
+ coverageNoticeSeen.add(key2);
2171
+ }
2172
+ return {
2173
+ notice: {
2174
+ id: `coverage-disabled:${ctx.kind}:${disabledIds.join(",")}`,
2175
+ message: cause === "disabled" ? `Pi-lens ${ctx.kind} analysis unconfirmed \u2014 Primary LSP (${disabledIds.join(", ")}) is disabled in config by lsp.disabledServers, so no language server checks these files this session (not a clean result).` : `Pi-lens ${ctx.kind} analysis unconfirmed \u2014 Primary LSP (${disabledIds.join(", ")}) is disabled in config by lsp.disabledServers and its fallback did not answer, so no language server checked these files (not a clean result).`,
2176
+ filePath: ctx.filePath,
2177
+ severity: "warning",
2178
+ semantic: "warning",
2179
+ tool: "pi-lens"
2180
+ },
2181
+ unconfirmedStatus
2182
+ };
2183
+ }
2014
2184
  const onceKey = `${ctx.kind}:${ctx.filePath}`;
2015
2185
  if (dedupe) {
2016
2186
  if (coverageNoticeSeen.has(onceKey))
2017
- return void 0;
2187
+ return { unconfirmedStatus };
2018
2188
  coverageNoticeSeen.add(onceKey);
2019
2189
  }
2020
2190
  return {
2021
- id: `coverage-unavailable:${ctx.kind}:${path4.basename(ctx.filePath)}`,
2022
- message: `Pi-lens ${ctx.kind} analysis unavailable \u2014 a language tool is missing, timed out, or failed to run, or the LSP server isn't ready yet, so this file was not fully checked (not a clean result).`,
2023
- filePath: ctx.filePath,
2024
- severity: "warning",
2025
- semantic: "warning",
2026
- tool: "pi-lens"
2191
+ notice: {
2192
+ id: `coverage-unavailable:${ctx.kind}:${path4.basename(ctx.filePath)}`,
2193
+ message: `Pi-lens ${ctx.kind} analysis unavailable \u2014 a language tool is missing, timed out, or failed to run, or the LSP server isn't ready yet, so this file was not fully checked (not a clean result).`,
2194
+ filePath: ctx.filePath,
2195
+ severity: "warning",
2196
+ semantic: "warning",
2197
+ tool: "pi-lens"
2198
+ },
2199
+ unconfirmedStatus
2027
2200
  };
2028
2201
  }
2029
2202
  var latencyReports = [];
@@ -2036,10 +2209,29 @@ function clearCoverageNoticeState() {
2036
2209
  coverageNoticeSeen.clear();
2037
2210
  generatedSkipRecorded.clear();
2038
2211
  }
2212
+ function runnerDispatchContext(ctx, runnerId) {
2213
+ const owed = ctx.owedRanges?.get(runnerId);
2214
+ if (!owed?.length || !ctx.modifiedRanges)
2215
+ return ctx;
2216
+ return {
2217
+ ...ctx,
2218
+ modifiedRanges: rangesOfLines([
2219
+ ...linesOfRanges(ctx.modifiedRanges),
2220
+ ...linesOfRanges(owed)
2221
+ ])
2222
+ };
2223
+ }
2039
2224
  async function runGroup(ctx, group, registry, onRunnerResult) {
2040
2225
  const diagnostics = [];
2041
2226
  const latencies = [];
2042
2227
  let hadBlocker = false;
2228
+ const outOfEditScope = [];
2229
+ const inconclusiveRunnerIds = [];
2230
+ const inconclusiveServerIds = [];
2231
+ const collectLaterRunnerIds = [];
2232
+ const editScopedRunnerIds = [];
2233
+ const answeredRunnerIds = [];
2234
+ const coveredRunners = [];
2043
2235
  const runnerIds = group.filterKinds ? group.runnerIds.filter((id) => {
2044
2236
  const runner = registry.get(id);
2045
2237
  return runner && ctx.kind && group.filterKinds?.includes(ctx.kind);
@@ -2121,6 +2313,7 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
2121
2313
  } catch (error) {
2122
2314
  ctx.log(`Runner ${runner.id} precondition failed: ${error}`);
2123
2315
  shouldRun = false;
2316
+ inconclusiveRunnerIds.push(runner.id);
2124
2317
  }
2125
2318
  }
2126
2319
  if (!shouldRun) {
@@ -2145,10 +2338,11 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
2145
2338
  continue;
2146
2339
  }
2147
2340
  const projectRoot = ctx.projectRoot ?? ctx.cwd;
2341
+ const runnerCtx = runnerDispatchContext(ctx, runner.id);
2148
2342
  const observedTier = ctx.writeIndex === void 0 ? "inline" : classifyObservedRunner(projectRoot, runner.id);
2149
2343
  if (observedTier === "collect-later") {
2150
2344
  const markedAtMs = Date.now();
2151
- const deferred = runRunner(ctx, runner, semantic).then((result2) => {
2345
+ const deferred = runRunner(runnerCtx, runner, semantic).then((result2) => {
2152
2346
  const durationMs = Date.now() - markedAtMs;
2153
2347
  const tier2 = observeRunnerLatency({
2154
2348
  projectRoot,
@@ -2215,6 +2409,8 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
2215
2409
  semantic
2216
2410
  });
2217
2411
  recordRunner(ctx.filePath, runner.id, "pending", 0, 0, ctx.writeIndex);
2412
+ inconclusiveRunnerIds.push(runner.id);
2413
+ collectLaterRunnerIds.push(runner.id);
2218
2414
  logLatency({
2219
2415
  type: "runner",
2220
2416
  filePath: ctx.filePath,
@@ -2227,7 +2423,7 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
2227
2423
  });
2228
2424
  continue;
2229
2425
  }
2230
- const result = await runRunner(ctx, runner, semantic);
2426
+ const result = await runRunner(runnerCtx, runner, semantic);
2231
2427
  onRunnerResult?.(runnerId, result);
2232
2428
  const runnerEnd = Date.now();
2233
2429
  const duration = runnerEnd - runnerStart;
@@ -2282,6 +2478,12 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
2282
2478
  },
2283
2479
  ...result.deferredServerIds !== void 0 && {
2284
2480
  deferredServerIds: result.deferredServerIds
2481
+ },
2482
+ ...result.primaryUnavailableCause !== void 0 && {
2483
+ primaryUnavailableCause: result.primaryUnavailableCause
2484
+ },
2485
+ ...result.disabledPrimaryServerIds !== void 0 && {
2486
+ disabledPrimaryServerIds: result.disabledPrimaryServerIds
2285
2487
  }
2286
2488
  });
2287
2489
  logLatency({
@@ -2301,7 +2503,28 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
2301
2503
  })) : void 0,
2302
2504
  metadata: failureRowMetadata(result) ?? (skipReason ? { skipReason, ...claimSource !== void 0 && { claimSource } } : void 0)
2303
2505
  });
2304
- recordRunner(ctx.filePath, runnerId, result.status, result.diagnostics.length, duration, ctx.writeIndex);
2506
+ if (!ctx.preEditCapture)
2507
+ recordRunner(ctx.filePath, runnerId, result.status, result.diagnostics.length, duration, ctx.writeIndex);
2508
+ for (const d of result.diagnostics)
2509
+ d.runnerId ??= runnerId;
2510
+ for (const d of result.outOfEditScope ?? []) {
2511
+ d.runnerId ??= runnerId;
2512
+ outOfEditScope.push(d);
2513
+ }
2514
+ if (reachedNoVerdict(result))
2515
+ inconclusiveRunnerIds.push(runnerId);
2516
+ else {
2517
+ answeredRunnerIds.push(runnerId);
2518
+ if (result.coveredBy)
2519
+ coveredRunners.push({
2520
+ runnerId,
2521
+ lane: result.coveredBy,
2522
+ ran: result.skipReason !== "covered-by-primary"
2523
+ });
2524
+ if (result.editScopedVerdict === true)
2525
+ editScopedRunnerIds.push(runnerId);
2526
+ }
2527
+ inconclusiveServerIds.push(...result.unconfirmedServerIds ?? [], ...result.deferredServerIds ?? []);
2305
2528
  diagnostics.push(...result.diagnostics);
2306
2529
  const resultSemantic = result.semantic ?? semantic;
2307
2530
  if (resultSemantic === "blocking" && result.diagnostics.length > 0 || result.diagnostics.some((d) => d.semantic === "blocking")) {
@@ -2311,7 +2534,18 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
2311
2534
  break;
2312
2535
  }
2313
2536
  }
2314
- return { diagnostics, latencies, hadBlocker };
2537
+ return {
2538
+ diagnostics,
2539
+ latencies,
2540
+ hadBlocker,
2541
+ outOfEditScope,
2542
+ inconclusiveRunnerIds,
2543
+ inconclusiveServerIds,
2544
+ collectLaterRunnerIds,
2545
+ editScopedRunnerIds,
2546
+ answeredRunnerIds,
2547
+ coveredRunners
2548
+ };
2315
2549
  }
2316
2550
  async function dispatchForFile(ctx, groups, registry, onRunnerResult, options) {
2317
2551
  const _overallStart = Date.now();
@@ -2385,7 +2619,8 @@ async function dispatchForFile(ctx, groups, registry, onRunnerResult, options) {
2385
2619
  metadata: {
2386
2620
  groupCount: groups.length,
2387
2621
  kind: ctx.kind,
2388
- runners: allRunnerIds.join(",")
2622
+ runners: allRunnerIds.join(","),
2623
+ ...ctx.preEditCapture && { preEditCapture: true }
2389
2624
  }
2390
2625
  });
2391
2626
  const groupResults = await Promise.all(groups.map((group) => runGroup(ctx, group, registry, onRunnerResult)));
@@ -2397,17 +2632,51 @@ async function dispatchForFile(ctx, groups, registry, onRunnerResult, options) {
2397
2632
  reason: "dispatchForFile requires an absolute ctx.filePath to key the delta baseline (refs #2489)"
2398
2633
  });
2399
2634
  }
2400
- const baselineAbsKey = `session.baseline.${ctx.filePath}`;
2635
+ const baselineAbsKey = deltaBaselineFactKey(ctx.filePath);
2401
2636
  const previousBaseline = ctx.deltaMode && baselinePathSafe ? ctx.facts.getBoundedSessionFact(baselineAbsKey) : void 0;
2402
2637
  const baselineWarnings = previousBaseline?.filter((d) => d.semantic === "warning" || d.semantic === "none");
2403
2638
  const baselineWarningCount = baselineWarnings?.length ?? 0;
2404
- for (const { diagnostics: groupDiags, latencies, hadBlocker } of groupResults) {
2639
+ const outOfEditScope = [];
2640
+ const inconclusiveRunnerIds = /* @__PURE__ */ new Set();
2641
+ const inconclusiveServerIds = /* @__PURE__ */ new Set();
2642
+ const collectLaterRunnerIds = /* @__PURE__ */ new Set();
2643
+ const editScopedRunnerIds = [];
2644
+ const answeredRunnerIds = /* @__PURE__ */ new Set();
2645
+ const coveredRunners = [];
2646
+ for (const { diagnostics: groupDiags, latencies, hadBlocker, outOfEditScope: groupOutOfScope, inconclusiveRunnerIds: groupRunnerIds, inconclusiveServerIds: groupServerIds, collectLaterRunnerIds: groupCollectLater, editScopedRunnerIds: groupEditScoped, answeredRunnerIds: groupAnswered, coveredRunners: groupCovered } of groupResults) {
2405
2647
  runnerLatencies.push(...latencies);
2648
+ outOfEditScope.push(...groupOutOfScope);
2649
+ for (const id of groupRunnerIds)
2650
+ inconclusiveRunnerIds.add(id);
2651
+ for (const id of groupServerIds)
2652
+ inconclusiveServerIds.add(id);
2653
+ for (const id of groupCollectLater)
2654
+ collectLaterRunnerIds.add(id);
2655
+ editScopedRunnerIds.push(...groupEditScoped);
2656
+ for (const id of groupAnswered)
2657
+ answeredRunnerIds.add(id);
2658
+ coveredRunners.push(...groupCovered);
2406
2659
  allDiagnostics.push(...groupDiags);
2407
2660
  if (hadBlocker)
2408
2661
  stopped = true;
2409
2662
  }
2410
- const dedupedDiagnostics = dedupeOverlappingDiagnostics(allDiagnostics);
2663
+ const voidedCovers = /* @__PURE__ */ new Set();
2664
+ const laneCopyByRunner = /* @__PURE__ */ new Map();
2665
+ for (const { runnerId, lane, ran } of coveredRunners) {
2666
+ const silent = !answeredRunnerIds.has(lane.runnerId) || lane.serverId !== void 0 && inconclusiveServerIds.has(lane.serverId);
2667
+ if (ran) {
2668
+ if (silent)
2669
+ continue;
2670
+ laneCopyByRunner.set(runnerId, laneCopyStandsFor(allDiagnostics.flatMap((d) => d.runnerId === lane.runnerId && (lane.serverId === void 0 || d.serverId === lane.serverId) ? [d] : [])));
2671
+ continue;
2672
+ }
2673
+ if (!silent)
2674
+ continue;
2675
+ voidedCovers.add(runnerId);
2676
+ inconclusiveRunnerIds.add(runnerId);
2677
+ }
2678
+ const deliveredDiagnostics = laneCopyByRunner.size === 0 ? allDiagnostics : allDiagnostics.flatMap((d) => laneCopyByRunner.get(d.runnerId ?? "")?.(d) ? [] : [d]);
2679
+ const dedupedDiagnostics = dedupeOverlappingDiagnostics(deliveredDiagnostics);
2411
2680
  const fileContent = ctx.facts.getFileFact(ctx.filePath, "file.content") ?? "";
2412
2681
  const rulePolicy = ctx.rulePolicy ?? rulePolicyMapFromConfig(loadPiLensProjectConfig(ctx.projectRoot ?? ctx.cwd).rules);
2413
2682
  const applyOutputFilters = (diags) => {
@@ -2422,11 +2691,33 @@ async function dispatchForFile(ctx, groups, registry, onRunnerResult, options) {
2422
2691
  };
2423
2692
  let visibleDiagnostics = applyOutputFilters(dedupedDiagnostics);
2424
2693
  let resolvedCount = 0;
2694
+ const stillObserved = outOfEditScope.length > 0 ? applyOutputFilters(outOfEditScope).flatMap((d) => d.semantic === "blocking" ? [d] : []) : [];
2695
+ let heldBackBlockers = [];
2425
2696
  if (ctx.deltaMode && previousBaseline) {
2426
2697
  const filtered = filterDelta(visibleDiagnostics, applyOutputFilters(previousBaseline), (d) => d.id);
2698
+ const isNew = new Set(filtered.new);
2699
+ heldBackBlockers = visibleDiagnostics.filter((d) => d.semantic === "blocking" && !isNew.has(d));
2700
+ stillObserved.push(...promoteDeltaUnusedToBlockers(visibleDiagnostics.flatMap((d) => !isNew.has(d) && d.semantic !== "blocking" && isUnusedValueDiagnostic(d) ? [d] : [])));
2427
2701
  visibleDiagnostics = promoteDeltaUnusedToBlockers(filtered.new);
2428
2702
  resolvedCount = filtered.fixed.length;
2429
2703
  }
2704
+ const seedKey = preEditSeedFactKey(ctx.filePath);
2705
+ const preEditSeed = ctx.deltaMode && baselinePathSafe ? ctx.facts.getBoundedSessionFact(seedKey) : void 0;
2706
+ const seedContent = ctx.facts.getFileFact(ctx.filePath, "file.content");
2707
+ let preExisting = [];
2708
+ if (preEditSeed && seedContent !== void 0) {
2709
+ const classified = classifyPreExisting(preEditSeed, [
2710
+ ...visibleDiagnostics.filter((d) => d.semantic === "blocking"),
2711
+ ...heldBackBlockers
2712
+ ], seedContent, new Set([...answeredRunnerIds].filter((id) => !inconclusiveRunnerIds.has(id))));
2713
+ if (classified.preExisting.length > 0) {
2714
+ const held = new Set(classified.preExisting);
2715
+ visibleDiagnostics = visibleDiagnostics.filter((d) => !held.has(d));
2716
+ heldBackBlockers = heldBackBlockers.filter((d) => !held.has(d));
2717
+ preExisting = classified.preExisting;
2718
+ }
2719
+ ctx.facts.setBoundedSessionFact(seedKey, classified.nextSeed);
2720
+ }
2430
2721
  if (ctx.deltaMode && baselinePathSafe) {
2431
2722
  ctx.facts.setBoundedSessionFact(baselineAbsKey, [...dedupedDiagnostics]);
2432
2723
  }
@@ -2438,21 +2729,21 @@ async function dispatchForFile(ctx, groups, registry, onRunnerResult, options) {
2438
2729
  provider: ctx.telemetryProvider
2439
2730
  };
2440
2731
  if (fixedItems.length > 0) {
2441
- import("./chunk-6SDEP3AJ.js").then(({ appendToWorklog }) => {
2732
+ import("./chunk-JUKMK5Y4.js").then(({ appendToWorklog }) => {
2442
2733
  appendToWorklog(ctx.cwd, fixedItems, true, worklogIdentity);
2443
2734
  }).catch(() => {
2444
2735
  });
2445
2736
  }
2446
2737
  const fixableWarnings = warnings.filter((d) => d.fixable);
2447
2738
  if (fixableWarnings.length > 0) {
2448
- import("./chunk-6SDEP3AJ.js").then(({ appendToWorklog }) => {
2739
+ import("./chunk-JUKMK5Y4.js").then(({ appendToWorklog }) => {
2449
2740
  appendToWorklog(ctx.cwd, fixableWarnings, false, worklogIdentity);
2450
2741
  }).catch(() => {
2451
2742
  });
2452
2743
  }
2453
2744
  const inlineBlockers = blockers;
2454
2745
  const inlineFixed = fixedItems;
2455
- const coverageNotice = buildCoverageNotice(ctx, runnerLatencies, options?.dedupeCoverageNotice ?? true);
2746
+ const { notice: coverageNotice, unconfirmedStatus } = buildCoverageNotice(ctx, runnerLatencies, options?.dedupeCoverageNotice ?? true);
2456
2747
  const blockerOutput = formatDiagnostics(inlineBlockers, "blocking");
2457
2748
  let output = blockerOutput;
2458
2749
  output += formatDiagnostics(inlineFixed, "fixed");
@@ -2508,7 +2799,19 @@ async function dispatchForFile(ctx, groups, registry, onRunnerResult, options) {
2508
2799
  // (above, `metadata: { groupCount, kind, runners }`) fires before the
2509
2800
  // baseline read ever runs and cannot carry either value.
2510
2801
  baselineHit: previousBaseline !== void 0,
2511
- baselineWarningCount
2802
+ baselineWarningCount,
2803
+ // #4484: blockers still observed that the delta did not call new.
2804
+ heldBackBlockers: heldBackBlockers.length,
2805
+ // #4483: whether a pre-edit seed was read, and the blocking linter
2806
+ // findings it reported as pre-existing.
2807
+ preEditSeed: preEditSeed !== void 0,
2808
+ preExisting: preExisting.length,
2809
+ ...ctx.preEditCapture && { preEditCapture: true },
2810
+ // #4484 r2: findings that only keep a recorded blocker pending, and
2811
+ // the sources whose silence is no evidence.
2812
+ stillObserved: stillObserved.length,
2813
+ inconclusiveRunners: inconclusiveRunnerIds.size,
2814
+ inconclusiveServers: inconclusiveServerIds.size
2512
2815
  }
2513
2816
  });
2514
2817
  return {
@@ -2521,7 +2824,21 @@ async function dispatchForFile(ctx, groups, registry, onRunnerResult, options) {
2521
2824
  resolvedCount,
2522
2825
  output,
2523
2826
  blockerOutput,
2524
- hasBlockers: blockers.length > 0
2827
+ hasBlockers: blockers.length > 0,
2828
+ ...heldBackBlockers.length > 0 && { heldBackBlockers },
2829
+ ...preExisting.length > 0 && { preExisting },
2830
+ ...stillObserved.length > 0 && { stillObserved },
2831
+ ...editScopedRunnerIds.some((id) => !voidedCovers.has(id)) && {
2832
+ editScopedRunnerIds: editScopedRunnerIds.flatMap((id) => voidedCovers.has(id) ? [] : [id])
2833
+ },
2834
+ ...(inconclusiveRunnerIds.size > 0 || inconclusiveServerIds.size > 0) && {
2835
+ inconclusiveSources: {
2836
+ runnerIds: [...inconclusiveRunnerIds],
2837
+ serverIds: [...inconclusiveServerIds],
2838
+ collectLaterRunnerIds: [...collectLaterRunnerIds]
2839
+ }
2840
+ },
2841
+ ...unconfirmedStatus !== void 0 && { unconfirmedStatus }
2525
2842
  };
2526
2843
  }
2527
2844
  var RUNNER_TIMEOUT_MS = RUNTIME_CONFIG.dispatch.runnerTimeoutMs;
@@ -2602,6 +2919,16 @@ var DEFAULT_NEIGHBOUR_BUDGET = 40;
2602
2919
  var CASCADE_NEIGHBOUR_BUDGET = Math.max(MIN_NEIGHBOUR_BUDGET, Number.parseInt(process.env.PI_LENS_CASCADE_NEIGHBOUR_BUDGET ?? "40", 10) || DEFAULT_NEIGHBOUR_BUDGET);
2603
2920
  var NEIGHBOUR_BUDGET_OVERRIDDEN = process.env.PI_LENS_CASCADE_NEIGHBOUR_BUDGET !== void 0 && CASCADE_NEIGHBOUR_BUDGET !== DEFAULT_NEIGHBOUR_BUDGET;
2604
2921
 
2922
+ // dist/clients/dispatch/runners/utils.js
2923
+ import * as fs4 from "node:fs";
2924
+ function readFileContent(filePath) {
2925
+ try {
2926
+ return fs4.readFileSync(filePath, "utf-8");
2927
+ } catch {
2928
+ return void 0;
2929
+ }
2930
+ }
2931
+
2605
2932
  // dist/clients/dispatch/utils/lsp-diagnostics.js
2606
2933
  function convertLspDiagnostics(diags, filePath, options = {}) {
2607
2934
  const tool = options.tool ?? "lsp";
@@ -2630,7 +2957,10 @@ function convertLspDiagnostics(diags, filePath, options = {}) {
2630
2957
  autoFixAvailable: false,
2631
2958
  fixKind: hasSuggestion ? "suggestion" : void 0,
2632
2959
  fixSuggestion: options.fixSuggestionByIndex?.get(idx),
2633
- scanOrigin: options.scanOrigin
2960
+ scanOrigin: options.scanOrigin,
2961
+ // #4484 r2: the publishing server, so a run that leaves it
2962
+ // unconfirmed keeps its recorded blockers. Never identity.
2963
+ ...d.serverId !== void 0 && { serverId: d.serverId }
2634
2964
  };
2635
2965
  });
2636
2966
  }
@@ -2644,7 +2974,8 @@ function applyFindingPolicy(diagnostics, options) {
2644
2974
  root: options.cwd,
2645
2975
  filePath: options.filePath
2646
2976
  }),
2647
- inlineKept
2977
+ inlineKept,
2978
+ disposed
2648
2979
  };
2649
2980
  }
2650
2981
  function loadProjectRulePolicyMap(cwd) {
@@ -2709,6 +3040,28 @@ function applyPushedFindingPolicy(findings, options) {
2709
3040
  });
2710
3041
  return { kept, suppressed: findings.length - kept.length };
2711
3042
  }
3043
+ function applyPushedFindingPolicyByFile(findings, cwd) {
3044
+ const byFile = /* @__PURE__ */ new Map();
3045
+ for (const finding of findings) {
3046
+ const group = byFile.get(finding.filePath);
3047
+ if (group)
3048
+ group.push(finding);
3049
+ else
3050
+ byFile.set(finding.filePath, [finding]);
3051
+ }
3052
+ const kept = [];
3053
+ let suppressed = 0;
3054
+ for (const [filePath, group] of byFile) {
3055
+ const policy = applyPushedFindingPolicy(group, {
3056
+ cwd,
3057
+ filePath,
3058
+ content: readFileContent(filePath)
3059
+ });
3060
+ kept.push(...policy.kept);
3061
+ suppressed += policy.suppressed;
3062
+ }
3063
+ return { kept, suppressed };
3064
+ }
2712
3065
  function expandRenderedIdentities(rendered) {
2713
3066
  const out = [];
2714
3067
  const seen = /* @__PURE__ */ new Set();
@@ -2819,6 +3172,12 @@ function formatCacheAgeLabel(scannedAt, nowMs = Date.now()) {
2819
3172
  const remMinutes = ageMinutes % 60;
2820
3173
  return remMinutes === 0 ? `scanned ${ageHours}h ago` : `scanned ${ageHours}h ${remMinutes}m ago`;
2821
3174
  }
3175
+ function dispositionSuppressedLine(suppressed, breakdown) {
3176
+ if (suppressed <= 0)
3177
+ return void 0;
3178
+ const lanes = breakdown ? ` (${breakdown})` : "";
3179
+ return `suppressed by disposition: ${suppressed} finding(s)${lanes} dropped from this result because they're marked false-positive/won't-fix, disabled by \`.pi-lens.json\` rule policy, or suppressed by an inline \`pi-lens-ignore\` comment. Not a clean verdict for those locations \u2014 they were found, then intentionally hidden.`;
3180
+ }
2822
3181
  function admitsProjectWideFindings(sessionRole) {
2823
3182
  return sessionRole === "primary";
2824
3183
  }
@@ -2932,6 +3291,17 @@ var DELIVERY_SURFACES = {
2932
3291
  { evidenceMin: 2 }
2933
3292
  ),
2934
3293
  "runtime-turn:cascade-blocker": labeled(RUNTIME_TURN_FILE, "Turn-end \u{1F9EA} cascade neighbor blocker.", "Cascade results settle synchronously this turn where possible (`settleCascadeRuns`), but an unsettled compute can carry over to a later turn (bounded by a carry cap) \u2014 a carried-over result renders with an explicit `(carried N turns \xB7 scanned Xm ago)` label (#3167, #3168 F3), so the agent can tell it from a fresh observation.", "live", ["cascadeCarrySuffix("], { evidenceMin: 1 }),
3294
+ // #4487: cascade neighbour errors a cascade showed in an EARLIER turn and
3295
+ // no later cascade or edit has shown gone. A cached claim, not a fresh
3296
+ // observation, so each entry passes the freshness gate against its own
3297
+ // read bound (`checkedAt`) before it renders (#4495 review F1, round 3):
3298
+ // a deleted neighbour is forgotten; one written after the check by
3299
+ // something pi never saw is named once as changed and not re-checked.
3300
+ // Survivors carry the `(scanned Xm ago)` age of that read. The evidence is
3301
+ // the two RENDERED lists, so the binding chain walks each back to the gate
3302
+ // call's own arms (a render of the ungated candidates reds, #4495 r2
3303
+ // verify census note).
3304
+ "runtime-turn:cascade-pending-errors": gated(RUNTIME_TURN_FILE, "Turn-end \u{1F4D0} list of cascade neighbour errors shown in an earlier turn and not re-checked since.", ["gateFindingsByPathFreshness"], ["unresolvedNeighbors", "changedShown"]),
2935
3305
  "runtime-turn:cascade-coverage-advisory": labeled(RUNTIME_TURN_FILE, "Turn-end cascade-coverage-gap advisories (graph/binding/budget).", "Explains what the cascade check could NOT confirm this turn \u2014 not a finding with a cited path, an absence-of-coverage disclosure computed from this turn's own indeterminate-run list. An advisory computed from a carried indeterminate run is labeled `(carried N turns \xB7 <age>)` (#3167, #3168 F4 \u2014 dropped on mixed carried/fresh buckets). The age half reads `scan age unknown` on this surface today: no indeterminate-run producer stamps `observedAt` (only the resolved-found plumb does, `clients/cascade-format.ts`), and one unstamped carried run collapses the whole bucket's age rather than claiming the stamped runs' (#3168 F13). The carry COUNT is always real.", "live", ["withCarryLabel("], { evidenceMin: 3 }),
2936
3306
  // #3102: the cold-neighbour cascade run is BUILT here, in the quiet-window
2937
3307
  // reconcile (`onResolvedFound` in index.ts), a turn earlier than the
@@ -3020,16 +3390,185 @@ var DELIVERY_SURFACES = {
3020
3390
  "read-guard-tool-lines:preflight-errors": labeled("clients/read-guard-tool-lines.ts", "Read-guard hashline BLOCKED / RE-READ REQUIRED / PARTIAL APPLY / ALREADY APPLIED preflight errors (#2402).", "Computed fresh per edit attempt and returned as that attempt's rejection \u2014 no cached findings are replayed, so there is no staleness window. The PARTIAL APPLY and ALREADY APPLIED variants describe the same attempt's commit outcome; applied-edit recognition reads RuntimeCoordinator's session-scoped record, which resets with the session.", "live"),
3021
3391
  "mutating-tool:adapter-preflight-errors": labeled("clients/mutating-tool.ts", "Shape-adapter BLOCKED preflight errors for hashline edit inputs the adapter recognized but could not resolve to a range (#2423).", "Computed fresh from the tool input on each attempt and returned as that attempt's rejection, so there is no staleness window. The adapters read only the call's own arguments; nothing cached is replayed.", "live"),
3022
3392
  "tool-call:duplicate-export-blocker": labeled("clients/runtime-tool-call.ts", "Duplicate-export STOP rejection returned inline with the failed edit.", "Synchronous preflight rejection computed per edit attempt; no stored state.", "live"),
3023
- "agent-behavior:thrashing-notice": labeled("clients/agent-behavior-client.ts", "In-result thrashing/blind-write detection notice.", "Ephemeral notice computed at detection time inside the same tool result \u2014 no persistence and no cache, so nothing can be stale.", "live")
3393
+ "agent-behavior:thrashing-notice": labeled("clients/agent-behavior-client.ts", "In-result thrashing/blind-write detection notice.", "Ephemeral notice computed at detection time inside the same tool result \u2014 no persistence and no cache, so nothing can be stale.", "live"),
3394
+ // ── #1634 enumeration round: the rest of the agent-facing sinks ─────────
3395
+ // Found by the sink census in `tests/clients/finding-delivery-gate.test.ts`,
3396
+ // which derives every production file holding an agent-facing sink (a tool
3397
+ // result's text block, a context or hook message, a tool_call block, a
3398
+ // custom entry, a status/widget line) and fails on one that is neither a
3399
+ // surface `file` here nor classified there.
3400
+ "lsp-navigation:workspace-diagnostics-path": gated(
3401
+ "tools/lsp-navigation.ts",
3402
+ "`lsp_navigation workspaceDiagnostics` with a path: the server's answer for that file, read after a touch that waits for diagnostics.",
3403
+ ["applyProbeFindingPolicy"],
3404
+ // The path read and the tracked snapshot below are the two calls.
3405
+ ["applyProbeFindingPolicy("],
3406
+ { evidenceMin: 2 }
3407
+ ),
3408
+ "lsp-navigation:tracked-snapshot": labeled("tools/lsp-navigation.ts", "`lsp_navigation workspaceDiagnostics` with no path: each tracked file's last client publish.", "A replay of what each language client last published, with no re-check of the file, so every row states its age. The rows also pass the same finding policy as the path read (`readFileContent` supplies the current content), so the label and the disposition filter compose.", "LSP client publish ts", ["lag: formatCacheAgeLabel(ts)", "readFileContent(trackedFile)"]),
3409
+ "runtime-context:persisted-findings": gated(
3410
+ "clients/runtime-context.ts",
3411
+ "Persisted turn-end and test-runner findings replayed into the next context message, the MCP turn_end tool and the Stop hook.",
3412
+ ["validateAdvisoryProvenance"],
3413
+ // peekTurnEndFindings, consumeTurnEndFindings, peekTestFindings.
3414
+ ["validateAdvisoryProvenance(findings.data, cwd, runtime)"],
3415
+ { evidenceMin: 3 }
3416
+ ),
3417
+ "index:turn-summary": labeled("index.ts", "One-line turn summary custom entry (`--lens-turn-summary`).", "Counts drained from this turn's own accumulator (`runtime.turnSummary.consume`) at the quiet window; nothing is replayed from a cache.", "live"),
3418
+ "lens-engine:project-scan": gated("clients/lens-engine.ts", "`pilens_project_scan` (MCP): a fresh cheap project scan.", ["applyPushedFindingPolicyByFile"], ["applyPushedFindingPolicyByFile(scan.diagnostics, cwd)"]),
3419
+ "mcp-server:analyze": labeled("mcp/server.ts", "`pilens_analyze` (MCP): one file's dispatch result.", "A dispatch run for this call, warm or in a fresh worker; the dispatcher applies the finding policy before it returns.", "live"),
3420
+ "mcp-analyze-cli:post-edit-report": labeled("mcp/analyze-cli.ts", "Claude Code PostToolUse hook report (`analyze-cli --hook`).", "Same per-call dispatch as `pilens_analyze`; its Stop-hook output is the `runtime-context:persisted-findings` replay or a fresh turn-end pass.", "live")
3024
3421
  };
3025
3422
 
3026
3423
  // dist/clients/cascade-format.js
3424
+ function formatCascadeNeighborDiagnostics(cwd, neighbors, options = {}) {
3425
+ const withErrors = neighbors.filter((n) => n.diagnostics.length > 0);
3426
+ const inconclusive = neighbors.filter((n) => n.inconclusive === true && n.diagnostics.length === 0);
3427
+ const uncovered = neighbors.filter((n) => n.diagnostics.length === 0 && n.inconclusive !== true && (n.unconfirmedServerIds?.length ?? 0) > 0);
3428
+ if (withErrors.length === 0 && inconclusive.length === 0 && uncovered.length === 0) {
3429
+ return "";
3430
+ }
3431
+ const noun = options.noun ?? "neighbor";
3432
+ let out = withErrors.length > 0 ? `\u{1F4D0} Cascade errors in ${withErrors.length} ${noun} file(s) \u2014 fix before finishing turn:` : "";
3433
+ for (const neighbor of withErrors) {
3434
+ const display = toRunnerDisplayPath(cwd, neighbor.filePath);
3435
+ const reason = options.includeReason ? ` reason="${neighbor.reason}"` : "";
3436
+ out += `
3437
+ <diagnostics file="${display}"${reason}>`;
3438
+ for (const d of neighbor.diagnostics) {
3439
+ const line = d.line ?? 1;
3440
+ const col = d.column ?? 1;
3441
+ const rule = d.rule ? ` rule=${d.rule}` : "";
3442
+ const since = options.stillPresentSince?.(neighbor, d);
3443
+ const label = since === void 0 ? "" : ` (still present since turn ${since})`;
3444
+ out += `
3445
+ line ${line}, col ${col}${rule}: ${d.message.split("\n")[0].slice(0, 100)}${label}`;
3446
+ }
3447
+ out += "\n</diagnostics>";
3448
+ }
3449
+ if (inconclusive.length > 0) {
3450
+ if (out)
3451
+ out += "\n";
3452
+ out += `\u26A0\uFE0F Cascade diagnostics inconclusive for ${inconclusive.length} ${noun} file(s) \u2014 no clean result was confirmed:`;
3453
+ for (const neighbor of inconclusive) {
3454
+ out += `
3455
+ ${toRunnerDisplayPath(cwd, neighbor.filePath)}`;
3456
+ }
3457
+ }
3458
+ if (uncovered.length > 0) {
3459
+ if (out)
3460
+ out += "\n";
3461
+ out += `\u26A0\uFE0F Cascade scanners did not cover ${uncovered.length} ${noun} file(s) \u2014 no findings does NOT mean clean here:`;
3462
+ for (const neighbor of uncovered) {
3463
+ const servers = (neighbor.unconfirmedServerIds ?? []).join(", ");
3464
+ out += `
3465
+ ${toRunnerDisplayPath(cwd, neighbor.filePath)} (not scanned by ${servers})`;
3466
+ }
3467
+ }
3468
+ return out;
3469
+ }
3470
+ function composeCascadeSection(block, layout, unansweredPrimaryNote = "") {
3471
+ const diagnosticsBlock = block && unansweredPrimaryNote ? `${block}
3472
+ ${unansweredPrimaryNote}` : block || unansweredPrimaryNote;
3473
+ if (!diagnosticsBlock && !layout.coverageLine)
3474
+ return "";
3475
+ let out = !diagnosticsBlock ? "" : layout.header ? `${layout.header}
3476
+ ${diagnosticsBlock}` : diagnosticsBlock;
3477
+ if (layout.truncationLine)
3478
+ out += `
3479
+ ${layout.truncationLine}`;
3480
+ if (layout.coverageLine) {
3481
+ if (out)
3482
+ out += "\n";
3483
+ out += layout.coverageLine;
3484
+ }
3485
+ if (layout.policySuppressed > 0) {
3486
+ out += `
3487
+ suppressed by disposition: ${layout.policySuppressed} finding(s) (marked false-positive or won't-fix).`;
3488
+ }
3489
+ return out;
3490
+ }
3027
3491
  function cascadeCarrySuffix(carriedTurns, observedAt) {
3028
3492
  if (carriedTurns === void 0 || carriedTurns < 1)
3029
3493
  return void 0;
3030
3494
  const noun = carriedTurns === 1 ? "turn" : "turns";
3031
3495
  return `(carried ${carriedTurns} ${noun} \xB7 ${formatCacheAgeLabel(observedAt)})`;
3032
3496
  }
3497
+ var cascadeUnansweredPrimariesDelivered = /* @__PURE__ */ new Set();
3498
+ function resetCascadeUnansweredPrimariesDelivered() {
3499
+ cascadeUnansweredPrimariesDelivered.clear();
3500
+ }
3501
+ function markCascadeUnansweredPrimariesDelivered(serverIds) {
3502
+ for (const id of serverIds)
3503
+ cascadeUnansweredPrimariesDelivered.add(id);
3504
+ }
3505
+ function renderCascadeResult(result, shownServerIds) {
3506
+ const unanswered = result.unansweredPrimaries;
3507
+ if (!unanswered)
3508
+ return { text: result.formatted, lines: [], serverIds: [] };
3509
+ const kept = unanswered.notes.filter((note) => note.serverIds.some((id) => !cascadeUnansweredPrimariesDelivered.has(id) && !shownServerIds.has(id)));
3510
+ const serverIds = kept.flatMap((note) => [...note.serverIds]);
3511
+ for (const id of serverIds)
3512
+ shownServerIds.add(id);
3513
+ return {
3514
+ text: unanswered.formattedWithoutNotes,
3515
+ lines: kept.map((note) => note.line),
3516
+ serverIds
3517
+ };
3518
+ }
3519
+ var MAX_PENDING_NEIGHBORS = 256;
3520
+ var pendingNeighbors = new BoundedFifoMap(MAX_PENDING_NEIGHBORS);
3521
+ function cascadeErrorKey(diagnostic) {
3522
+ return [
3523
+ diagnostic.id,
3524
+ diagnostic.rule ?? "",
3525
+ diagnostic.line ?? 0,
3526
+ diagnostic.column ?? 0,
3527
+ diagnostic.message
3528
+ ].join(":");
3529
+ }
3530
+ function cascadeStillPresentSince(filePath, diagnostic, turn) {
3531
+ const first = pendingNeighbors.get(normalizeMapKey(filePath))?.errors.get(cascadeErrorKey(diagnostic));
3532
+ return first !== void 0 && first < turn ? first : void 0;
3533
+ }
3534
+ function recordDeliveredCascadeNeighbor(filePath, diagnostics, turn, checkedAt) {
3535
+ const key2 = normalizeMapKey(filePath);
3536
+ const previous = pendingNeighbors.get(key2);
3537
+ const errors = /* @__PURE__ */ new Map();
3538
+ for (const d of diagnostics) {
3539
+ const k = cascadeErrorKey(d);
3540
+ errors.set(k, previous?.errors.get(k) ?? turn);
3541
+ }
3542
+ pendingNeighbors.delete(key2);
3543
+ if (errors.size === 0)
3544
+ return;
3545
+ if (pendingNeighbors.set(key2, { filePath, errors, checkedAt }).length > 0) {
3546
+ recordDegradationOnce({
3547
+ kind: "cascade-pending-neighbors-evicted",
3548
+ subject: `cap=${MAX_PENDING_NEIGHBORS}`,
3549
+ reason: "the pending cascade neighbour-error record hit its cap; the oldest neighbour's errors are no longer re-served"
3550
+ });
3551
+ }
3552
+ }
3553
+ function forgetCascadePendingNeighbor(filePath, observedAt) {
3554
+ const key2 = normalizeMapKey(filePath);
3555
+ const entry = pendingNeighbors.get(key2);
3556
+ if (!entry)
3557
+ return;
3558
+ if (observedAt !== void 0 && observedAt <= entry.checkedAt)
3559
+ return;
3560
+ pendingNeighbors.delete(key2);
3561
+ }
3562
+ function listCascadePendingNeighbors() {
3563
+ return [...pendingNeighbors.values()].map((entry) => ({
3564
+ filePath: entry.filePath,
3565
+ errorCount: entry.errors.size,
3566
+ checkedAt: entry.checkedAt
3567
+ }));
3568
+ }
3569
+ function resetCascadePendingNeighbors() {
3570
+ pendingNeighbors.clear();
3571
+ }
3033
3572
 
3034
3573
  // dist/clients/cascade-logger.js
3035
3574
  import * as path5 from "node:path";
@@ -3142,33 +3681,152 @@ function createDiagnosticTracker() {
3142
3681
  }
3143
3682
 
3144
3683
  // dist/clients/lsp/cascade-tier.js
3684
+ var MAX_OUTSTANDING_TOUCHES = 256;
3145
3685
  var OUTSTANDING_TOUCH_MAX_AGE_MS = 15 * 6e4;
3146
- var _outstandingTouches = /* @__PURE__ */ new Map();
3686
+ var _outstandingTouches = new BoundedFifoMap(MAX_OUTSTANDING_TOUCHES);
3147
3687
  var _expiredSinceLastSweep = 0;
3148
3688
  var _evictedSinceLastSweep = 0;
3689
+ var MAX_LOGGED_PATHS = 20;
3690
+ function pruneExpiredOutstandingTouches(now, guard, reads = /* @__PURE__ */ new Map()) {
3691
+ const expired = [];
3692
+ for (const [key2, touch] of _outstandingTouches) {
3693
+ if (now - touch.touchedAt <= OUTSTANDING_TOUCH_MAX_AGE_MS)
3694
+ break;
3695
+ _outstandingTouches.delete(key2);
3696
+ _expiredSinceLastSweep++;
3697
+ expired.push(touch);
3698
+ }
3699
+ if (expired.length === 0)
3700
+ return;
3701
+ let found = 0;
3702
+ let clean = 0;
3703
+ for (const touch of expired) {
3704
+ const outcome = readTouchedClient(touch, now, reads);
3705
+ if (!outcome)
3706
+ continue;
3707
+ if (outcome.outcome === "resolved-found")
3708
+ found++;
3709
+ else
3710
+ clean++;
3711
+ deliverResolvedOutcome(outcome, guard);
3712
+ }
3713
+ logCascade({
3714
+ phase: "cascade_tier3_expired",
3715
+ filePath: "<cascade-registry>",
3716
+ metadata: {
3717
+ count: expired.length,
3718
+ found,
3719
+ clean,
3720
+ unanswered: expired.length - found - clean,
3721
+ maxAgeCapMs: OUTSTANDING_TOUCH_MAX_AGE_MS,
3722
+ paths: expired.slice(0, MAX_LOGGED_PATHS).map((t) => t.filePath),
3723
+ ...expired.length > MAX_LOGGED_PATHS && {
3724
+ pathsTruncated: expired.length - MAX_LOGGED_PATHS
3725
+ }
3726
+ }
3727
+ });
3728
+ }
3729
+ function readTouchedClient(touch, now, reads) {
3730
+ const client = touch.client?.deref();
3731
+ if (!client || client.serverId !== touch.serverId)
3732
+ return void 0;
3733
+ if (client.isAlive?.() === false)
3734
+ return void 0;
3735
+ let all = reads.get(client);
3736
+ if (all === void 0) {
3737
+ all = client.getAllDiagnostics();
3738
+ reads.set(client, all);
3739
+ }
3740
+ const entry = all.get(normalizeMapKey(touch.filePath));
3741
+ if (!entry || entry.ts <= touch.touchedAt)
3742
+ return void 0;
3743
+ const found = entry.diags.length > 0;
3744
+ return {
3745
+ filePath: touch.filePath,
3746
+ serverId: touch.serverId,
3747
+ outcome: found ? "resolved-found" : "resolved-clean",
3748
+ ageMs: now - touch.touchedAt,
3749
+ diagnosticCount: entry.diags.length,
3750
+ publishedAt: entry.ts,
3751
+ touchedAt: touch.touchedAt,
3752
+ ...found && { diagnostics: entry.diags }
3753
+ };
3754
+ }
3755
+ function deliverResolvedOutcome(o, guard) {
3756
+ try {
3757
+ if (o.outcome === "resolved-found" && o.diagnostics?.length) {
3758
+ const neighbor = {
3759
+ filePath: o.filePath,
3760
+ serverId: o.serverId,
3761
+ diagnostics: o.diagnostics,
3762
+ ...o.publishedAt !== void 0 ? { publishedAt: o.publishedAt } : {},
3763
+ ...o.touchedAt !== void 0 ? { touchedAt: o.touchedAt } : {}
3764
+ };
3765
+ const deliver = () => _reconcileOptions.onResolvedFound?.(neighbor);
3766
+ if (guard)
3767
+ guard.guardedWrite(o.filePath, deliver);
3768
+ else
3769
+ deliver();
3770
+ } else if (o.outcome === "resolved-clean" && o.publishedAt != null) {
3771
+ _reconcileOptions.onResolvedClean?.({
3772
+ filePath: o.filePath,
3773
+ serverId: o.serverId,
3774
+ publishedAt: o.publishedAt
3775
+ });
3776
+ }
3777
+ } catch {
3778
+ }
3779
+ }
3149
3780
  function resetCascadeTierSessionState() {
3781
+ const dropped = _outstandingTouches.size;
3150
3782
  _outstandingTouches.clear();
3151
3783
  _expiredSinceLastSweep = 0;
3152
3784
  _evictedSinceLastSweep = 0;
3785
+ if (dropped > 0) {
3786
+ logCascade({
3787
+ phase: "cascade_tier3_dropped",
3788
+ filePath: "<session-reset>",
3789
+ metadata: { count: dropped }
3790
+ });
3791
+ }
3153
3792
  }
3793
+ function peekOutstandingCascadeTouches(guard) {
3794
+ const now = Date.now();
3795
+ const reads = /* @__PURE__ */ new Map();
3796
+ pruneExpiredOutstandingTouches(now, guard, reads);
3797
+ const count = _outstandingTouches.size;
3798
+ if (count === 0)
3799
+ return;
3800
+ let found = 0;
3801
+ let clean = 0;
3802
+ for (const [key2, touch] of _outstandingTouches) {
3803
+ const outcome = readTouchedClient(touch, now, reads);
3804
+ if (!outcome)
3805
+ continue;
3806
+ _outstandingTouches.delete(key2);
3807
+ if (outcome.outcome === "resolved-found")
3808
+ found++;
3809
+ else
3810
+ clean++;
3811
+ deliverResolvedOutcome(outcome, guard);
3812
+ }
3813
+ logCascade({
3814
+ phase: "cascade_tier3_peek",
3815
+ filePath: "<turn_end>",
3816
+ metadata: {
3817
+ count,
3818
+ found,
3819
+ clean,
3820
+ outstanding: _outstandingTouches.size
3821
+ }
3822
+ });
3823
+ }
3824
+ var _reconcileOptions = {};
3154
3825
 
3155
3826
  // dist/clients/dispatch/runners/ast-grep-napi.js
3156
- import * as fs4 from "node:fs";
3827
+ import * as fs5 from "node:fs";
3157
3828
  import * as path6 from "node:path";
3158
3829
 
3159
- // dist/clients/deps/ast-grep-napi.js
3160
- import { createRequire } from "node:module";
3161
- import { pathToFileURL } from "node:url";
3162
- var _require = createRequire(import.meta.url);
3163
- function loadAstGrepNapi() {
3164
- try {
3165
- const entry = _require.resolve("@ast-grep/napi");
3166
- return import(pathToFileURL(entry).href);
3167
- } catch {
3168
- return import("@ast-grep/napi");
3169
- }
3170
- }
3171
-
3172
3830
  // dist/clients/dispatch/priorities.js
3173
3831
  var PRIORITY = {
3174
3832
  LSP_PRIMARY: 4,
@@ -3185,6 +3843,66 @@ var PRIORITY = {
3185
3843
  DEEP_LANGUAGE_ANALYSIS: 50
3186
3844
  };
3187
3845
 
3846
+ // dist/clients/dispatch/runners/ast-grep-literal-anchors.js
3847
+ var PATTERN_WORD = /\$?[A-Za-z_]\w*/g;
3848
+ var LITERAL_REGEX = /^\^?((?:[A-Za-z0-9_"'`:\- =!<>/@#%&,;~]|\\\.)+)\$?$/;
3849
+ var RELATIONAL_KEYS = ["has", "inside", "follows", "precedes"];
3850
+ function ownLiterals(node) {
3851
+ const literals = [];
3852
+ if (typeof node.pattern === "string") {
3853
+ for (const word of node.pattern.match(PATTERN_WORD) ?? []) {
3854
+ if (!word.startsWith("$"))
3855
+ literals.push(word);
3856
+ }
3857
+ }
3858
+ const regex = typeof node.regex === "string" ? node.regex : "";
3859
+ const literal = LITERAL_REGEX.exec(regex)?.[1];
3860
+ if (literal)
3861
+ literals.push(literal.replaceAll(String.raw`\.`, "."));
3862
+ return literals;
3863
+ }
3864
+ function anyOf(branches) {
3865
+ if (branches.length === 0 || branches.includes(true))
3866
+ return true;
3867
+ return { or: branches };
3868
+ }
3869
+ function requirementOf(node, utils, resolving) {
3870
+ if (!node || typeof node !== "object")
3871
+ return true;
3872
+ const of = (child) => requirementOf(child, utils, resolving);
3873
+ const parts = [
3874
+ ...ownLiterals(node),
3875
+ ...RELATIONAL_KEYS.map((key2) => of(node[key2])),
3876
+ ...Array.isArray(node.all) ? node.all.map(of) : [],
3877
+ Array.isArray(node.any) ? anyOf(node.any.map(of)) : true
3878
+ ];
3879
+ const name = node.matches;
3880
+ if (typeof name === "string" && utils != null && Object.hasOwn(utils, name) && !resolving.has(name)) {
3881
+ parts.push(requirementOf(utils[name], utils, /* @__PURE__ */ new Set([...resolving, name])));
3882
+ }
3883
+ const required = parts.filter((part) => part !== true);
3884
+ return required.length === 0 ? true : { and: required };
3885
+ }
3886
+ function satisfied(requirement, text) {
3887
+ if (requirement === true)
3888
+ return true;
3889
+ if (typeof requirement === "string")
3890
+ return text.includes(requirement);
3891
+ if ("and" in requirement) {
3892
+ return requirement.and.every((part) => satisfied(part, text));
3893
+ }
3894
+ return requirement.or.some((part) => satisfied(part, text));
3895
+ }
3896
+ var requirements = /* @__PURE__ */ new WeakMap();
3897
+ function ruleCanMatchText(rule, text) {
3898
+ let requirement = requirements.get(rule);
3899
+ if (requirement === void 0) {
3900
+ requirement = requirementOf(rule.rule, rule.utils, /* @__PURE__ */ new Set());
3901
+ requirements.set(rule, requirement);
3902
+ }
3903
+ return satisfied(requirement, text);
3904
+ }
3905
+
3188
3906
  // dist/clients/dispatch/runners/ast-grep-napi.js
3189
3907
  var defaultUnsupportedLanguageLog = /* @__PURE__ */ new Set();
3190
3908
  var UNSUPPORTED_RULE_ID_SAMPLE_SIZE = 5;
@@ -3702,12 +4420,19 @@ function evaluateAstGrepRules(filePath, rootNode, cwd, kind, options = {}) {
3702
4420
  }
3703
4421
  if (!rule.rule)
3704
4422
  continue;
4423
+ if (options.content !== void 0 && !ruleCanMatchText(rule, options.content)) {
4424
+ continue;
4425
+ }
3705
4426
  try {
3706
4427
  const scopes = runsInsideHtmlScript ? htmlInjections.map((injection) => ({
3707
4428
  root: injection.root,
3708
4429
  position(match) {
3709
4430
  const start = match.range().start;
3710
4431
  return filePositionForIndex(fileLineStarts, injection.startIndex + start.index);
4432
+ },
4433
+ endLine(match) {
4434
+ const end = match.range().end;
4435
+ return filePositionForIndex(fileLineStarts, injection.startIndex + end.index).line;
3711
4436
  }
3712
4437
  })) : [
3713
4438
  {
@@ -3715,6 +4440,9 @@ function evaluateAstGrepRules(filePath, rootNode, cwd, kind, options = {}) {
3715
4440
  position(match) {
3716
4441
  const start = match.range().start;
3717
4442
  return { line: start.line, column: start.column };
4443
+ },
4444
+ endLine(match) {
4445
+ return match.range().end.line;
3718
4446
  }
3719
4447
  }
3720
4448
  ];
@@ -3724,7 +4452,9 @@ function evaluateAstGrepRules(filePath, rootNode, cwd, kind, options = {}) {
3724
4452
  if (rule.utils)
3725
4453
  nativeConfig.utils = rule.utils;
3726
4454
  const collected = [];
4455
+ const withheld = [];
3727
4456
  let rejectLogged = false;
4457
+ const editScoped = options.modifiedRanges !== void 0 && normalizeRuleSeverity(rule.severity) === "error";
3728
4458
  for (const scope of scopes) {
3729
4459
  let found;
3730
4460
  try {
@@ -3739,19 +4469,23 @@ function evaluateAstGrepRules(filePath, rootNode, cwd, kind, options = {}) {
3739
4469
  for (const match of found) {
3740
4470
  if (collected.length >= maxMatchesPerRule)
3741
4471
  break;
3742
- collected.push({ match, position: scope.position(match) });
4472
+ const position = scope.position(match);
4473
+ if (editScoped && !spanOverlapsModifiedRanges(position.line + 1, scope.endLine(match) + 1, options.modifiedRanges)) {
4474
+ if (withheld.length < maxMatchesPerRule)
4475
+ withheld.push({ position });
4476
+ continue;
4477
+ }
4478
+ collected.push({ match, position });
3743
4479
  }
3744
4480
  if (collected.length >= maxMatchesPerRule)
3745
4481
  break;
3746
4482
  }
3747
- for (const { position } of collected) {
3748
- if (diagnostics.length >= maxTotalDiagnostics)
3749
- break;
4483
+ const toDiagnostic3 = (position) => {
3750
4484
  const severity = normalizeRuleSeverity(rule.severity);
3751
4485
  const semantic = severity === "error" ? "blocking" : "warning";
3752
4486
  const defectClass = classifyDefect(rule.id, "ast-grep-napi", rule.message || rule.id);
3753
4487
  const ruleFix = explicitRuleFixSuggestion(rule);
3754
- diagnostics.push({
4488
+ return {
3755
4489
  id: `ast-grep-napi-${position.line}-${rule.id}`,
3756
4490
  message: `[${rule.metadata?.category || "slop"}] ${rule.message || rule.id}`,
3757
4491
  filePath,
@@ -3766,7 +4500,15 @@ function evaluateAstGrepRules(filePath, rootNode, cwd, kind, options = {}) {
3766
4500
  autoFixAvailable: false,
3767
4501
  fixKind: ruleFix ? "suggestion" : void 0,
3768
4502
  fixSuggestion: semantic === "blocking" ? ruleFix ?? defaultFixSuggestion(defectClass, rule.id) : ruleFix
3769
- });
4503
+ };
4504
+ };
4505
+ for (const { position } of collected) {
4506
+ if (diagnostics.length >= maxTotalDiagnostics)
4507
+ break;
4508
+ diagnostics.push(toDiagnostic3(position));
4509
+ }
4510
+ for (const { position } of withheld) {
4511
+ options.outOfEditScope?.push(toDiagnostic3(position));
3770
4512
  }
3771
4513
  if (diagnostics.length >= maxTotalDiagnostics)
3772
4514
  break;
@@ -3788,8 +4530,10 @@ var astGrepNapiRunner = {
3788
4530
  }
3789
4531
  const astGrepLspEnabled = enabledAuxiliaryLspServerIds((f) => ctx.pi?.getFlag?.(f)).includes("ast-grep");
3790
4532
  const astGrepLspPublished = astGrepLspEnabled ? await hasAuxiliaryLspPublishedForRoot("ast-grep", ctx.filePath) : false;
3791
- if (astGrepLspEnabled && astGrepLspPublished) {
3792
- if (hasPendingAuxiliaryCoverage(ctx.filePath, "ast-grep")) {
4533
+ const coveredByServer = astGrepLspEnabled && astGrepLspPublished;
4534
+ if (coveredByServer && !ctx.recordedRunnerIds?.has("ast-grep-napi")) {
4535
+ const coveragePending = hasPendingAuxiliaryCoverage(ctx.filePath, "ast-grep");
4536
+ if (coveragePending) {
3793
4537
  incrementDegradationCount({
3794
4538
  kind: "aux-runner-findings-lost",
3795
4539
  subject: "ast-grep",
@@ -3798,13 +4542,23 @@ var astGrepNapiRunner = {
3798
4542
  reason: `Gate B skipped napi: pending late-auxiliary pair from an EARLIER touch remains undelivered while prior publication satisfies per-file gate (file: ${ctx.filePath})`
3799
4543
  });
3800
4544
  }
3801
- return { status: "skipped", diagnostics: [], semantic: "none" };
4545
+ return {
4546
+ status: "skipped",
4547
+ diagnostics: [],
4548
+ semantic: "none",
4549
+ // Round 7: the verdict holds only if the server's host answered.
4550
+ ...coveragePending ? {} : {
4551
+ skipReason: "covered-by-primary",
4552
+ coveredBy: { runnerId: "lsp", serverId: "ast-grep" }
4553
+ },
4554
+ ...ctx.blockingOnly && ctx.modifiedRanges?.length ? { editScopedVerdict: true } : {}
4555
+ };
3802
4556
  }
3803
4557
  const sgModule = await loadSg();
3804
4558
  if (!sgModule) {
3805
4559
  return { status: "skipped", diagnostics: [], semantic: "none" };
3806
4560
  }
3807
- if (!fs4.existsSync(ctx.filePath)) {
4561
+ if (!fs5.existsSync(ctx.filePath)) {
3808
4562
  return { status: "skipped", diagnostics: [], semantic: "none" };
3809
4563
  }
3810
4564
  const lang = getLang(ctx.filePath, sgModule);
@@ -3813,7 +4567,7 @@ var astGrepNapiRunner = {
3813
4567
  }
3814
4568
  let stats;
3815
4569
  try {
3816
- stats = fs4.statSync(ctx.filePath);
4570
+ stats = fs5.statSync(ctx.filePath);
3817
4571
  } catch {
3818
4572
  return { status: "skipped", diagnostics: [], semantic: "none" };
3819
4573
  }
@@ -3826,7 +4580,7 @@ var astGrepNapiRunner = {
3826
4580
  content = contentFromFacts;
3827
4581
  } else {
3828
4582
  try {
3829
- content = fs4.readFileSync(ctx.filePath, "utf-8");
4583
+ content = fs5.readFileSync(ctx.filePath, "utf-8");
3830
4584
  } catch {
3831
4585
  return { status: "skipped", diagnostics: [], semantic: "none" };
3832
4586
  }
@@ -3848,10 +4602,16 @@ var astGrepNapiRunner = {
3848
4602
  recordNapiFallbackCoverage(ctx.filePath);
3849
4603
  clearPendingAuxiliaryCoverage(ctx.filePath, "ast-grep");
3850
4604
  }
4605
+ const outOfEditScope = [];
3851
4606
  const diagnostics = evaluateAstGrepRules(ctx.filePath, rootNode, ctx.cwd, ctx.kind, {
3852
4607
  blockingOnly: runningAsLspSubstitute ? false : ctx.blockingOnly,
3853
4608
  projectRoot: ctx.projectRoot,
3854
4609
  rulePolicy: ctx.rulePolicy,
4610
+ // #4481: keyed off the per-edit pass, not the substitute floor
4611
+ // above, so a pre-existing blocker on an unchanged line never
4612
+ // STOPs the write in either role.
4613
+ modifiedRanges: ctx.blockingOnly ? ctx.modifiedRanges : void 0,
4614
+ outOfEditScope,
3855
4615
  log: (message) => ctx.log(message),
3856
4616
  content,
3857
4617
  sgModule
@@ -3881,10 +4641,16 @@ var astGrepNapiRunner = {
3881
4641
  } else if (diagnostics.length > 0) {
3882
4642
  semantic = "warning";
3883
4643
  }
3884
- return findingsResult(diagnostics, {
3885
- status: hasBlocking ? "failed" : "succeeded",
3886
- semantic
3887
- });
4644
+ return {
4645
+ ...findingsResult(diagnostics, {
4646
+ status: hasBlocking ? "failed" : "succeeded",
4647
+ semantic
4648
+ }),
4649
+ ...outOfEditScope.length > 0 && { outOfEditScope },
4650
+ ...coveredByServer && {
4651
+ coveredBy: { runnerId: "lsp", serverId: "ast-grep" }
4652
+ }
4653
+ };
3888
4654
  }
3889
4655
  };
3890
4656
  var ast_grep_napi_default = astGrepNapiRunner;
@@ -4425,7 +5191,7 @@ var biomeCheckJsonRunner = {
4425
5191
  var biome_check_default = biomeCheckJsonRunner;
4426
5192
 
4427
5193
  // dist/clients/dispatch/runners/cpp-check.js
4428
- import * as fs5 from "node:fs";
5194
+ import * as fs6 from "node:fs";
4429
5195
  import * as path9 from "node:path";
4430
5196
  var compilerCheckers = {
4431
5197
  clang: createAvailabilityChecker("clang", ".exe"),
@@ -4464,7 +5230,7 @@ var CPP_HEADER_EXTENSIONS = /* @__PURE__ */ new Set([
4464
5230
  ]);
4465
5231
  function headerLooksLikeCpp(absPath) {
4466
5232
  try {
4467
- const content = fs5.readFileSync(absPath, "utf-8");
5233
+ const content = fs6.readFileSync(absPath, "utf-8");
4468
5234
  return /\b(namespace|template|class|constexpr|concept|using)\b|std::|\b(public|private|protected)\s*:/.test(content);
4469
5235
  } catch {
4470
5236
  return false;
@@ -4615,7 +5381,7 @@ var cpp_check_default = cppCheckRunner;
4615
5381
 
4616
5382
  // dist/clients/dispatch/runners/credo.js
4617
5383
  import * as path10 from "node:path";
4618
- import * as fs6 from "node:fs";
5384
+ import * as fs7 from "node:fs";
4619
5385
  var CREDO_PROBE_BUDGET_MS = 1e4;
4620
5386
  var probeCredo = createCwdCachedProbe((cwd) => safeSpawnAsync("mix", ["credo", "--version"], {
4621
5387
  timeout: CREDO_PROBE_BUDGET_MS,
@@ -4641,7 +5407,7 @@ function parseCredoJson(raw, fallbackPath, cwd) {
4641
5407
  }
4642
5408
  }
4643
5409
  function hasMixExs(cwd) {
4644
- return fs6.existsSync(path10.join(cwd, "mix.exs"));
5410
+ return fs7.existsSync(path10.join(cwd, "mix.exs"));
4645
5411
  }
4646
5412
  var credoRunner = {
4647
5413
  id: "credo",
@@ -4678,7 +5444,7 @@ var credoRunner = {
4678
5444
  var credo_default = credoRunner;
4679
5445
 
4680
5446
  // dist/clients/dispatch/runners/cue-vet.js
4681
- import * as fs7 from "node:fs";
5447
+ import * as fs8 from "node:fs";
4682
5448
  import * as path11 from "node:path";
4683
5449
  var cue = createAvailabilityChecker("cue", ".exe", ["version"]);
4684
5450
  function parseCueVetOutput(raw) {
@@ -4783,7 +5549,7 @@ var cueVetRunner = {
4783
5549
  const singleFileArgs = ["vet", "-c=false", `./${fileName}`];
4784
5550
  let touchedContent = null;
4785
5551
  try {
4786
- touchedContent = fs7.readFileSync(absPath, "utf8");
5552
+ touchedContent = fs8.readFileSync(absPath, "utf8");
4787
5553
  } catch {
4788
5554
  touchedContent = null;
4789
5555
  }
@@ -4988,8 +5754,11 @@ var dartAnalyzeRunner = {
4988
5754
  const parsed = parseToolRun("dart-analyze", {
4989
5755
  result,
4990
5756
  output: raw,
4991
- // EXIT TABLE (Dart 3.5 measured fixture): 0 clean; 1 findings; 2 error; other nonzero rejected.
4992
- exitCodes: { ran: [1, 2] }
5757
+ // EXIT TABLE (Dart 3.13 measured fixture, tests/fixtures/runner-output/
5758
+ // dart-analyze): 0 clean; 1 infos; 2 warnings; 3 errors; other nonzero
5759
+ // rejected. The 3.5 table stopped at 2, so a file with a compile error
5760
+ // read as a rejected invocation and its findings were dropped.
5761
+ exitCodes: { ran: [1, 2, 3] }
4993
5762
  }, (out) => parseDartMachineOutput(out, ctx.filePath, cwd));
4994
5763
  if (parsed.skipped)
4995
5764
  return parsed.skipped;
@@ -5004,7 +5773,7 @@ var dartAnalyzeRunner = {
5004
5773
  var dart_analyze_default = dartAnalyzeRunner;
5005
5774
 
5006
5775
  // dist/clients/dispatch/runners/detekt.js
5007
- import * as fs8 from "node:fs";
5776
+ import * as fs9 from "node:fs";
5008
5777
  import * as path13 from "node:path";
5009
5778
  var detekt = createAvailabilityChecker("detekt", ".bat");
5010
5779
  var DETEKT_CONFIG_CANDIDATES = [
@@ -5078,7 +5847,7 @@ var DETEKT_FIXABLE_RULES = /* @__PURE__ */ new Set([
5078
5847
  function findDetektConfig(cwd) {
5079
5848
  for (const candidate of DETEKT_CONFIG_CANDIDATES) {
5080
5849
  const full = path13.join(cwd, candidate);
5081
- if (fs8.existsSync(full))
5850
+ if (fs9.existsSync(full))
5082
5851
  return full;
5083
5852
  }
5084
5853
  return void 0;
@@ -5157,11 +5926,11 @@ var detektRunner = {
5157
5926
  var detekt_default = detektRunner;
5158
5927
 
5159
5928
  // dist/clients/dispatch/runners/dotnet-build.js
5160
- import * as fs9 from "node:fs";
5929
+ import * as fs10 from "node:fs";
5161
5930
  import * as path14 from "node:path";
5162
5931
  var dotnet = createAvailabilityChecker("dotnet", ".exe");
5163
5932
  function findProjectTarget(cwd) {
5164
- const entries = fs9.readdirSync(cwd);
5933
+ const entries = fs10.readdirSync(cwd);
5165
5934
  const solution = entries.find((entry) => /\.(sln|slnx)$/i.test(entry));
5166
5935
  if (solution)
5167
5936
  return solution;
@@ -5311,12 +6080,12 @@ ${result.stderr ?? ""}`.trim();
5311
6080
  var dotnet_build_default = dotnetBuildRunner;
5312
6081
 
5313
6082
  // dist/clients/dispatch/runners/elixir-check.js
5314
- import * as fs10 from "node:fs";
6083
+ import * as fs11 from "node:fs";
5315
6084
  import * as path15 from "node:path";
5316
6085
  var mix = createAvailabilityChecker("mix", ".bat");
5317
6086
  var elixirc = createAvailabilityChecker("elixirc", ".bat");
5318
6087
  function hasMixExs2(cwd) {
5319
- return fs10.existsSync(path15.join(cwd, "mix.exs"));
6088
+ return fs11.existsSync(path15.join(cwd, "mix.exs"));
5320
6089
  }
5321
6090
  var ELIXIR_SNIPPET_LOCATION = /└─\s+(.+?):(\d+)(?::(\d+))?(?::|$)/;
5322
6091
  function parseElixirOutput(raw, filePath, cwd = process.cwd()) {
@@ -5416,7 +6185,7 @@ var elixirCheckRunner = {
5416
6185
  args = ["compile", "--warnings-as-errors"];
5417
6186
  } else if (await elixirc.isAvailableAsync(cwd)) {
5418
6187
  const outDir = path15.join(getProjectDataDir(cwd), "elixir-check");
5419
- fs10.mkdirSync(outDir, { recursive: true });
6188
+ fs11.mkdirSync(outDir, { recursive: true });
5420
6189
  command = "elixirc";
5421
6190
  args = ["-o", outDir, absPath];
5422
6191
  }
@@ -5751,9 +6520,10 @@ var parseRuffOutput = createLineParser({
5751
6520
  autoFixAvailable: ruffAutofix?.safePipelineAutofix ?? false,
5752
6521
  fixKind: ruffAutofix?.fixKind === "none" ? void 0 : ruffAutofix?.fixKind
5753
6522
  });
6523
+ var GO_VET_LINE = /^(?:vet:\s+)?(.+?):(\d+):(\d+):\s*(.+)/;
5754
6524
  var parseGoVetOutput = createLineParser({
5755
6525
  tool: "go-vet",
5756
- regex: /^(.+?):(\d+):(\d+):\s*(.+)/,
6526
+ regex: GO_VET_LINE,
5757
6527
  extractMessage: (m) => m[4],
5758
6528
  generateId: (m) => `go-vet-${m[2]}`,
5759
6529
  defectClass: "correctness"
@@ -5795,7 +6565,7 @@ var goVetRunner = {
5795
6565
  }
5796
6566
  const absTarget = resolve14(ctx.filePath);
5797
6567
  const relevant = raw.split("\n").filter((line) => {
5798
- const m = line.match(/^(.+?):(\d+):(\d+):\s*(.+)/);
6568
+ const m = line.match(GO_VET_LINE);
5799
6569
  return m != null && pathsEqual(resolve14(cwd, m[1].trim()), absTarget);
5800
6570
  }).join("\n");
5801
6571
  const diagnostics = parseGoVetOutput(relevant, ctx.filePath, cwd);
@@ -5807,6 +6577,16 @@ var go_vet_default = goVetRunner;
5807
6577
  // dist/clients/dispatch/runners/golangci-lint.js
5808
6578
  import * as path19 from "node:path";
5809
6579
  var golangci = createAvailabilityChecker("golangci-lint", ".exe");
6580
+ var OTHER_OUTPUT_FORMATS = [
6581
+ "text",
6582
+ "tab",
6583
+ "html",
6584
+ "checkstyle",
6585
+ "code-climate",
6586
+ "junit-xml",
6587
+ "teamcity",
6588
+ "sarif"
6589
+ ];
5810
6590
  function describeReplacement(replacement) {
5811
6591
  if (!replacement)
5812
6592
  return void 0;
@@ -5867,12 +6647,18 @@ var golangciRunner = {
5867
6647
  const cmd = await resolveAvailableOrInstall(golangci, "golangci-lint", cwd);
5868
6648
  if (!cmd)
5869
6649
  return { status: "skipped", diagnostics: [], semantic: "none" };
5870
- const result = await safeSpawnAsync(cmd, ["run", "--out-format=json", ctx.filePath], { timeout: 6e4, cwd });
6650
+ const result = await safeSpawnAsync(cmd, [
6651
+ "run",
6652
+ "--output.json.path=stdout",
6653
+ ...OTHER_OUTPUT_FORMATS.map((format) => `--output.${format}.path=`),
6654
+ "--show-stats=false",
6655
+ ctx.filePath
6656
+ ], { timeout: 6e4, cwd });
5871
6657
  const raw = (result.stdout ?? "").length > 0 ? result.stdout ?? "" : result.status !== 0 ? result.stderr || "" : "";
5872
6658
  const parsed = parseToolRun("golangci-lint", {
5873
6659
  result,
5874
6660
  output: raw,
5875
- // EXIT TABLE (golangci-lint 1.60 docs https://golangci-lint.run/docs/welcome/quick-start/): 0 clean; 1 findings; 2 error; 3 error; 4 error; 5 error; other nonzero rejected.
6661
+ // EXIT TABLE (golangci-lint v2 docs https://golangci-lint.run/docs/welcome/quick-start/; 2.14.0 exits 1 on findings and 3 on an unknown flag): 0 clean; 1 findings; 2 error; 3 error; 4 error; 5 error; other nonzero rejected.
5876
6662
  exitCodes: { ran: [1, 2, 3, 4, 5] }
5877
6663
  }, (raw2) => parseGolangciJson(raw2, ctx.filePath, cwd));
5878
6664
  if (parsed.skipped)
@@ -5967,7 +6753,7 @@ var hadolint_default = hadolintRunner;
5967
6753
 
5968
6754
  // dist/clients/dispatch/runners/helm-lint.js
5969
6755
  import * as path21 from "node:path";
5970
- var helm = createAvailabilityChecker("helm", ".exe");
6756
+ var helm = createAvailabilityChecker("helm", ".exe", ["version", "--short"]);
5971
6757
  var inFlightByChartRoot = new PathKeyedMap(normalizeMapKey);
5972
6758
  var HELM_LINT_TIMEOUT_MS = 3e4;
5973
6759
  var MAX_HELM_LINT_OUTPUT_BYTES = 8 * 1024 * 1024;
@@ -5981,14 +6767,17 @@ function isWithin(root, candidate) {
5981
6767
  return relative5 === "" || !relative5.startsWith(`..${path21.sep}`) && relative5 !== ".." && !path21.isAbsolute(relative5);
5982
6768
  }
5983
6769
  function diagnosticLocation(message, chartRoot) {
5984
- const location = message.match(/(?:^|\s)([^:\r\n]+\.(?:ya?ml|tpl|json))(?::(\d+))?(?::\d+)?(?::|\s)/i);
5985
- if (!location) {
6770
+ const location = message.match(/(?:^|[\s(])((?:[^\s:()\r\n]*\/[^:()\r\n"]*?|[^\s:()\r\n]+)\.(?:ya?ml|tpl|json))(?::(\d+))?(?=[:\s)]|$)/i);
6771
+ const [, found, rawLine] = location ?? [];
6772
+ if (!found) {
5986
6773
  return { filePath: path21.join(chartRoot, "Chart.yaml"), message };
5987
6774
  }
5988
- const candidate = path21.resolve(chartRoot, location[1]);
6775
+ const embedded = !message.startsWith(found);
6776
+ const relative5 = embedded ? found.slice(found.indexOf("/") + 1) : found;
6777
+ const candidate = path21.resolve(chartRoot, relative5);
5989
6778
  return {
5990
6779
  filePath: isWithin(chartRoot, candidate) ? candidate : path21.join(chartRoot, "Chart.yaml"),
5991
- line: location[2] ? Number(location[2]) : void 0,
6780
+ line: rawLine ? Number(rawLine) : void 0,
5992
6781
  message
5993
6782
  };
5994
6783
  }
@@ -6103,10 +6892,10 @@ var helmLintRunner = {
6103
6892
  var helm_lint_default = helmLintRunner;
6104
6893
 
6105
6894
  // dist/clients/dispatch/runners/helm-render.js
6106
- import * as fs11 from "node:fs";
6895
+ import * as fs12 from "node:fs";
6107
6896
  import * as os from "node:os";
6108
6897
  import * as path22 from "node:path";
6109
- var helm2 = createAvailabilityChecker("helm", ".exe");
6898
+ var helm2 = createAvailabilityChecker("helm", ".exe", ["version", "--short"]);
6110
6899
  var trivy = createAvailabilityChecker("trivy", ".exe");
6111
6900
  var inFlightByChartRoot2 = new PathKeyedMap(normalizeMapKey);
6112
6901
  var RENDER_TIMEOUT_MS = 3e4;
@@ -6140,10 +6929,10 @@ function resolveTemplateSource(sourceRef, chartRoot, realChartRoot) {
6140
6929
  if (!isWithin2(chartRoot, candidate))
6141
6930
  return null;
6142
6931
  try {
6143
- if (!fs11.lstatSync(candidate).isFile())
6932
+ if (!fs12.lstatSync(candidate).isFile())
6144
6933
  return null;
6145
- const root = realChartRoot ?? fs11.realpathSync(chartRoot);
6146
- if (!isWithin2(root, fs11.realpathSync(candidate)))
6934
+ const root = realChartRoot ?? fs12.realpathSync(chartRoot);
6935
+ if (!isWithin2(root, fs12.realpathSync(candidate)))
6147
6936
  return null;
6148
6937
  } catch {
6149
6938
  return null;
@@ -6152,7 +6941,7 @@ function resolveTemplateSource(sourceRef, chartRoot, realChartRoot) {
6152
6941
  }
6153
6942
  function canonicalChartRoot(chartRoot) {
6154
6943
  try {
6155
- return fs11.realpathSync(chartRoot);
6944
+ return fs12.realpathSync(chartRoot);
6156
6945
  } catch {
6157
6946
  return chartRoot;
6158
6947
  }
@@ -6387,7 +7176,7 @@ function collectRenderedFiles(root) {
6387
7176
  const dir = stack.pop();
6388
7177
  let entries;
6389
7178
  try {
6390
- entries = fs11.readdirSync(dir, { withFileTypes: true });
7179
+ entries = fs12.readdirSync(dir, { withFileTypes: true });
6391
7180
  } catch {
6392
7181
  continue;
6393
7182
  }
@@ -6426,7 +7215,7 @@ function readRenderedTree(outputDir, chartRoot) {
6426
7215
  for (const renderedPath of walk2.files) {
6427
7216
  let content;
6428
7217
  try {
6429
- content = fs11.readFileSync(renderedPath, "utf-8");
7218
+ content = fs12.readFileSync(renderedPath, "utf-8");
6430
7219
  } catch {
6431
7220
  unreadable += 1;
6432
7221
  continue;
@@ -6545,7 +7334,7 @@ function logRenderPass(filePath, chartRoot, startedAt, metadata) {
6545
7334
  metadata: { chartRoot, ...metadata }
6546
7335
  });
6547
7336
  }
6548
- function discardScratchDir(outputDir, filePath, remove = (target) => fs11.rmSync(target, { recursive: true, force: true })) {
7337
+ function discardScratchDir(outputDir, filePath, remove = (target) => fs12.rmSync(target, { recursive: true, force: true })) {
6549
7338
  try {
6550
7339
  remove(outputDir);
6551
7340
  } catch (error) {
@@ -6566,7 +7355,7 @@ async function renderAndValidate(chartRoot, cwd, filePath) {
6566
7355
  const helmCmd = await resolveAvailableOrInstall(helm2, "helm", cwd);
6567
7356
  if (!helmCmd)
6568
7357
  return helmUnavailableResult(cwd);
6569
- const outputDir = fs11.mkdtempSync(path22.join(os.tmpdir(), "pi-lens-helm-render-"));
7358
+ const outputDir = fs12.mkdtempSync(path22.join(os.tmpdir(), "pi-lens-helm-render-"));
6570
7359
  try {
6571
7360
  const render = await safeSpawnAsync(helmCmd, ["template", "pi-lens-render", chartRoot, "--output-dir", outputDir], {
6572
7361
  cwd,
@@ -6887,9 +7676,30 @@ function normalizeKtlintResults(parsed) {
6887
7676
  }
6888
7677
  return null;
6889
7678
  }
7679
+ function parseResults(text) {
7680
+ try {
7681
+ return normalizeKtlintResults(JSON.parse(text));
7682
+ } catch {
7683
+ return null;
7684
+ }
7685
+ }
7686
+ function parseReporterDocument(raw) {
7687
+ const lines = raw.split(/\r?\n/);
7688
+ const start = lines.findIndex((line) => /^\s*[[{]/.test(line));
7689
+ if (start < 0)
7690
+ return parseResults(raw);
7691
+ for (let end = lines.length - 1; end >= start; end--) {
7692
+ if (!/[\]}]\s*$/.test(lines[end] ?? ""))
7693
+ continue;
7694
+ const results = parseResults(lines.slice(start, end + 1).join("\n"));
7695
+ if (results)
7696
+ return results;
7697
+ }
7698
+ return null;
7699
+ }
6890
7700
  function parseKtlintOutput(raw, filePath, cwd) {
6891
7701
  try {
6892
- const parsed = normalizeKtlintResults(JSON.parse(raw));
7702
+ const parsed = parseReporterDocument(raw);
6893
7703
  if (!parsed)
6894
7704
  return null;
6895
7705
  const autofix = getAutofixCapability("ktlint");
@@ -6900,15 +7710,15 @@ function parseKtlintOutput(raw, filePath, cwd) {
6900
7710
  continue;
6901
7711
  for (const err of result.errors ?? []) {
6902
7712
  diagnostics.push({
6903
- id: `ktlint-${err.ruleId}-${err.line}-${err.col}`,
6904
- message: `[${err.ruleId}] ${err.detail}`,
7713
+ id: `ktlint-${err.rule}-${err.line}-${err.column}`,
7714
+ message: `[${err.rule}] ${err.message}`,
6905
7715
  filePath,
6906
7716
  line: err.line,
6907
- column: err.col,
7717
+ column: err.column,
6908
7718
  severity: "warning",
6909
7719
  semantic: "warning",
6910
7720
  tool: "ktlint",
6911
- rule: err.ruleId,
7721
+ rule: err.rule,
6912
7722
  fixable: true,
6913
7723
  autoFixAvailable: autofix?.safePipelineAutofix ?? false,
6914
7724
  fixKind: autofix?.fixKind === "none" ? void 0 : autofix?.fixKind
@@ -7082,16 +7892,6 @@ async function demoteInferredProjectSweepResults(results, cwd, service, signal)
7082
7892
  return out;
7083
7893
  }
7084
7894
 
7085
- // dist/clients/dispatch/runners/utils.js
7086
- import * as fs12 from "node:fs";
7087
- function readFileContent(filePath) {
7088
- try {
7089
- return fs12.readFileSync(filePath, "utf-8");
7090
- } catch {
7091
- return void 0;
7092
- }
7093
- }
7094
-
7095
7895
  // dist/clients/dispatch/runners/lsp.js
7096
7896
  var LSP_SPAWN_BUDGET_MS = RUNTIME_CONFIG.pipeline.lspSpawnBudgetMs;
7097
7897
  var LSP_DIAGNOSTICS_WAIT_MS = 2500;
@@ -7128,13 +7928,23 @@ var lspRunner = {
7128
7928
  }
7129
7929
  const lspService = getLSPService();
7130
7930
  if (!lspService.supportsLSP(ctx.filePath)) {
7131
- return { status: "skipped", diagnostics: [], semantic: "none" };
7931
+ const { disabledServerIds: disabledPrimaryServerIds } = disabledPrimaries(ctx.filePath);
7932
+ return {
7933
+ status: "skipped",
7934
+ diagnostics: [],
7935
+ semantic: "none",
7936
+ ...disabledPrimaryServerIds.length > 0 && {
7937
+ primaryUnavailableCause: "disabled",
7938
+ disabledPrimaryServerIds
7939
+ }
7940
+ };
7132
7941
  }
7133
7942
  let lspDiags = [];
7134
7943
  let serverFailed = false;
7135
7944
  let lspClientReady = true;
7136
7945
  let diagnosticsInconclusive = false;
7137
7946
  let inconclusiveServerIds = [];
7947
+ let primaryUnavailable;
7138
7948
  let unconfirmedServerIds = [];
7139
7949
  let deferredServerIds = [];
7140
7950
  let usedWarmAttach = false;
@@ -7172,6 +7982,15 @@ var lspRunner = {
7172
7982
  lspDiags = touched.diags;
7173
7983
  diagnosticsInconclusive = touched.inconclusive === true;
7174
7984
  inconclusiveServerIds = touched.inconclusiveServerIds ?? [];
7985
+ if (touched.inconclusiveReason === "primary-unavailable") {
7986
+ const disabledPrimaryServerIds = touched.disabledPrimaryServerIds ?? [];
7987
+ primaryUnavailable = {
7988
+ primaryUnavailableCause: touched.primaryUnavailableCause ?? primaryUnavailableCause(inconclusiveServerIds, disabledPrimaryServerIds),
7989
+ ...disabledPrimaryServerIds.length > 0 && {
7990
+ disabledPrimaryServerIds
7991
+ }
7992
+ };
7993
+ }
7175
7994
  unconfirmedServerIds = [
7176
7995
  .../* @__PURE__ */ new Set([
7177
7996
  ...touchCoverageGap(touched),
@@ -7223,10 +8042,20 @@ var lspRunner = {
7223
8042
  };
7224
8043
  }
7225
8044
  if (!lspClientReady) {
7226
- return { status: "skipped", diagnostics: [], semantic: "none" };
8045
+ return {
8046
+ status: "skipped",
8047
+ diagnostics: [],
8048
+ semantic: "none",
8049
+ primaryUnavailableCause: "unstarted"
8050
+ };
7227
8051
  }
7228
8052
  if (diagnosticsInconclusive && lspDiags.length === 0) {
7229
- return { status: "skipped", diagnostics: [], semantic: "none" };
8053
+ return {
8054
+ status: "skipped",
8055
+ diagnostics: [],
8056
+ semantic: "none",
8057
+ ...primaryUnavailable
8058
+ };
7230
8059
  }
7231
8060
  if (lspDiags.length === 0) {
7232
8061
  if (deferredServerIds.length > 0) {
@@ -7291,7 +8120,15 @@ var lspRunner = {
7291
8120
  }));
7292
8121
  }
7293
8122
  const diagnostics = convertLspDiagnostics(validLspDiags, diagnosticPath, { fixSuggestionByIndex });
7294
- const keptDiagnostics = retagAuxiliaryDiagnostics(diagnostics, validLspDiags, content, { cwd: ctx.cwd, fileRole: ctx.fileRole });
8123
+ const rawRangeByDiagnostic = new Map(diagnostics.map((d, i) => [d, validLspDiags[i]?.range]));
8124
+ const retaggedDiagnostics = retagAuxiliaryDiagnostics(diagnostics, validLspDiags, content, { cwd: ctx.cwd, fileRole: ctx.fileRole });
8125
+ const keptDiagnostics = [];
8126
+ const outOfEditScope = [];
8127
+ for (const d of retaggedDiagnostics) {
8128
+ const range = ctx.blockingOnly && d.tool === "ast-grep" && d.semantic === "blocking" ? rawRangeByDiagnostic.get(d) : void 0;
8129
+ const inScope = range === void 0 || spanOverlapsModifiedRanges(range.start.line + 1, (range.end?.line ?? range.start.line) + 1, ctx.modifiedRanges);
8130
+ (inScope ? keptDiagnostics : outOfEditScope).push(d);
8131
+ }
7295
8132
  const hasErrors = keptDiagnostics.some((d) => d.semantic === "blocking");
7296
8133
  const resultSemantic = hasErrors ? "blocking" : keptDiagnostics.length > 0 ? "warning" : "none";
7297
8134
  return {
@@ -7306,7 +8143,8 @@ var lspRunner = {
7306
8143
  },
7307
8144
  ...deferredServerIds.length > 0 && {
7308
8145
  deferredServerIds: [...deferredServerIds]
7309
- }
8146
+ },
8147
+ ...outOfEditScope.length > 0 && { outOfEditScope }
7310
8148
  };
7311
8149
  }
7312
8150
  };
@@ -8342,7 +9180,7 @@ var pyrightRunner = {
8342
9180
  cmd = installedPath;
8343
9181
  }
8344
9182
  if (!cmd) {
8345
- const { findCommandAsync } = await import("./chunk-67WMNWEE.js");
9183
+ const { findCommandAsync } = await import("./chunk-LLJNQKQW.js");
8346
9184
  const foundCmd = await findCommandAsync("pyright");
8347
9185
  if (foundCmd)
8348
9186
  cmd = foundCmd;
@@ -8945,6 +9783,7 @@ var spotbugs_default = spotbugsRunner;
8945
9783
  import * as fs15 from "node:fs";
8946
9784
  import * as path36 from "node:path";
8947
9785
  var shellcheck = createAvailabilityChecker("shellcheck", ".exe");
9786
+ var SHELLCHECK_EXIT_CODES = { ran: [1] };
8948
9787
  var SHELLCHECK_DIALECTS = [
8949
9788
  "sh",
8950
9789
  "bash",
@@ -9068,7 +9907,10 @@ var shellcheckRunner = {
9068
9907
  skipReason: "covered-by-primary",
9069
9908
  // WHO claimed (#3968 F2): declared (the row's own covers, gated
9070
9909
  // on the custom row's own command) or builtin-fact.
9071
- claimSource: cover.claimSource
9910
+ claimSource: cover.claimSource,
9911
+ // #4484 round 8: the skip is a verdict only when that server's
9912
+ // lane answers; the dispatcher voids it otherwise.
9913
+ coveredBy: { runnerId: "lsp", serverId: cover.serverId }
9072
9914
  };
9073
9915
  }
9074
9916
  const dialectInfo = resolveShellFileDialect(ctx.filePath);
@@ -9103,20 +9945,52 @@ var shellcheckRunner = {
9103
9945
  if (result.status === 0 && !result.stdout?.trim()) {
9104
9946
  return { status: "succeeded", diagnostics: [], semantic: "none" };
9105
9947
  }
9106
- const raw = result.stdout + result.stderr;
9107
- const diagnostics = parseShellcheckOutput(raw, ctx.filePath, cwd);
9948
+ const raw = `${result.stdout ?? ""}${result.stderr ?? ""}`;
9949
+ const parsed = parseToolRun("shellcheck", { result, output: raw, exitCodes: SHELLCHECK_EXIT_CODES }, (output) => parseShellcheckOutput(output, ctx.filePath, cwd));
9950
+ if (parsed.skipped)
9951
+ return parsed.skipped;
9108
9952
  return finishParsedRun({
9109
9953
  tool: "shellcheck",
9110
9954
  ctx,
9111
9955
  result,
9112
- diagnostics
9956
+ diagnostics: parsed.diagnostics
9113
9957
  });
9114
9958
  }
9115
9959
  };
9116
9960
  var shellcheck_default = shellcheckRunner;
9117
9961
 
9118
9962
  // dist/clients/dispatch/runners/fish-indent.js
9963
+ import { resolve as resolve32 } from "node:path";
9119
9964
  var fishIndent = createAvailabilityChecker("fish_indent");
9965
+ var fish = createAvailabilityChecker("fish");
9966
+ var SYNTAX_STEP = "fish-syntax-check";
9967
+ function parseSyntaxErrors(output, filePath, cwd) {
9968
+ const diagnostics = [];
9969
+ for (const line of output.split("\n")) {
9970
+ const [, reported, lineText, message] = line.match(/^(.+) \(line (\d+)\): (.+)$/) ?? [];
9971
+ if (!reported || !lineText || !message)
9972
+ continue;
9973
+ const wanted = resolve32(cwd, filePath);
9974
+ if (!pathsEqual(resolve32(cwd, reported), wanted) && !(reported.startsWith("~/") && wanted.endsWith(reported.slice(1))))
9975
+ continue;
9976
+ const lineNumber = Number(lineText);
9977
+ diagnostics.push({
9978
+ id: `fish-syntax-${filePath}:${lineNumber}`,
9979
+ message: `fish: ${message.trim()}`,
9980
+ filePath,
9981
+ line: lineNumber,
9982
+ column: 1,
9983
+ severity: "error",
9984
+ semantic: "blocking",
9985
+ tool: "fish-indent",
9986
+ rule: "fish-syntax-error"
9987
+ });
9988
+ break;
9989
+ }
9990
+ return diagnostics;
9991
+ }
9992
+ var FISH_SYNTAX_EXIT_CODES = { ran: [127] };
9993
+ var BLOCKING_VERDICT = { status: "failed", semantic: "blocking" };
9120
9994
  var fishIndentRunner = {
9121
9995
  id: "fish-indent",
9122
9996
  appliesTo: ["fish"],
@@ -9127,6 +10001,24 @@ var fishIndentRunner = {
9127
10001
  const cmd = ctx.analysisRootMode === "adopted" ? resolveAdoptedRootCommand("fish_indent", ctx, cwd) : await fishIndent.isAvailableAsync(cwd) ? fishIndent.getCommand(cwd) : null;
9128
10002
  if (!cmd)
9129
10003
  return { status: "skipped", diagnostics: [], semantic: "none" };
10004
+ const fishCmd = ctx.analysisRootMode === "adopted" ? resolveAdoptedRootCommand("fish", ctx, cwd) : await fish.isAvailableAsync(cwd) ? fish.getCommand(cwd) : null;
10005
+ if (fishCmd) {
10006
+ const syntax = await safeSpawnAsync(fishCmd, ["--no-config", "--no-execute", ctx.filePath], { timeout: 1e4, cwd });
10007
+ const parsed = parseToolRun(SYNTAX_STEP, {
10008
+ result: syntax,
10009
+ output: syntax.stderr ?? "",
10010
+ exitCodes: FISH_SYNTAX_EXIT_CODES
10011
+ }, (output) => parseSyntaxErrors(output, ctx.filePath, cwd));
10012
+ if (parsed.diagnostics.length > 0) {
10013
+ return findingsResult(parsed.diagnostics, BLOCKING_VERDICT);
10014
+ }
10015
+ } else {
10016
+ recordDegradationOnce({
10017
+ kind: "fish-syntax-check-unavailable",
10018
+ subject: "fish",
10019
+ reason: "fish not found next to fish_indent: syntax errors are not checked"
10020
+ });
10021
+ }
9130
10022
  const result = await safeSpawnAsync(cmd, ["--check", ctx.filePath], {
9131
10023
  timeout: 1e4,
9132
10024
  cwd
@@ -9134,7 +10026,12 @@ var fishIndentRunner = {
9134
10026
  if (result.status === 0) {
9135
10027
  return { status: "succeeded", diagnostics: [], semantic: "none" };
9136
10028
  }
9137
- const stderrLine = (result.stderr ?? "").split("\n").find((l) => l.trim());
10029
+ if (result.signal || result.status === null) {
10030
+ const didNotRun = skipUnlessToolRan("fish_indent", { result });
10031
+ if (didNotRun)
10032
+ return didNotRun;
10033
+ }
10034
+ const stderrLine = (result.stderr ?? "").split("\n").find((l) => l.trim() && !pathsEqual(resolve32(cwd, l.trim()), resolve32(cwd, ctx.filePath)));
9138
10035
  if (stderrLine) {
9139
10036
  const lineMatch = stderrLine.match(/\(line\s+(\d+)\)/);
9140
10037
  const line = lineMatch ? Number(lineMatch[1]) : 1;
@@ -9151,10 +10048,7 @@ var fishIndentRunner = {
9151
10048
  rule: "fish-indent-parse-error"
9152
10049
  }
9153
10050
  ];
9154
- return findingsResult(diagnostics2, {
9155
- status: "failed",
9156
- semantic: "blocking"
9157
- });
10051
+ return findingsResult(diagnostics2, BLOCKING_VERDICT);
9158
10052
  }
9159
10053
  const diagnostics = [
9160
10054
  {
@@ -9205,6 +10099,12 @@ var shfmtRunner = {
9205
10099
  timeout: 1e4,
9206
10100
  cwd
9207
10101
  });
10102
+ const didNotRun = skipUnlessToolRan("shfmt", {
10103
+ result,
10104
+ output: `${result.stdout ?? ""}${result.stderr ?? ""}`
10105
+ });
10106
+ if (didNotRun)
10107
+ return didNotRun;
9208
10108
  if (result.status === 0) {
9209
10109
  return { status: "succeeded", diagnostics: [], semantic: "none" };
9210
10110
  }
@@ -9786,7 +10686,10 @@ var taploRunner = {
9786
10686
  // an unexplained empty output on the delivery surfaces
9787
10687
  // (defect shape 10). F2 adds the claim's provenance beside it.
9788
10688
  skipReason: "covered-by-primary",
9789
- claimSource: cover.claimSource
10689
+ claimSource: cover.claimSource,
10690
+ // #4484 round 8: the skip is a verdict only when that server's
10691
+ // lane answers; the dispatcher voids it otherwise.
10692
+ coveredBy: { runnerId: "lsp", serverId: cover.serverId }
9790
10693
  };
9791
10694
  }
9792
10695
  let cmd = localBinPath(findLocalBinUpwards("taplo", cwd)) ?? null;
@@ -10634,11 +11537,6 @@ function defaultFixSuggestion2(defectClass, ruleId) {
10634
11537
  }
10635
11538
  return "Refactor this pattern to a safer, explicit form matching project conventions.";
10636
11539
  }
10637
- function isLineInModifiedRanges(line, ranges) {
10638
- if (!ranges || ranges.length === 0)
10639
- return true;
10640
- return ranges.some((r) => line >= r.start && line <= r.end);
10641
- }
10642
11540
  function getTotalLinesChanged(ranges) {
10643
11541
  if (!ranges || ranges.length === 0)
10644
11542
  return 0;
@@ -10763,19 +11661,20 @@ var treeSitterRunner = {
10763
11661
  const contentFromFacts = ctx.facts.getFileFact(filePath, "file.content");
10764
11662
  const contentOverride = contentFromFacts !== void 0 && contentFromFacts !== null ? contentFromFacts : void 0;
10765
11663
  const diagnostics = [];
11664
+ const withheld = [];
11665
+ let queryRunFailed = false;
10766
11666
  try {
10767
11667
  const found = await client.runQueriesOnFile(effectiveQueries, filePath, languageId, { maxResults: 10 }, contentOverride);
10768
11668
  for (const { queryDef: query, match } of found) {
10769
11669
  const { line, column } = match;
10770
11670
  const isSeverityBlocking = query.severity === "error" || query.inline_tier === "blocking" || SILENT_ERROR_QUERY_IDS.has(query.id);
10771
- if (ctx.blockingOnly && isSeverityBlocking && !isLineInModifiedRanges(line, ctx.modifiedRanges)) {
10772
- continue;
10773
- }
11671
+ const endLine = line + (match.matchedText.match(/\n/g)?.length ?? 0);
11672
+ const outOfScope = ctx.blockingOnly && isSeverityBlocking && !spanOverlapsModifiedRanges(line, endLine, ctx.modifiedRanges);
10774
11673
  const semantic = query.severity === "error" ? "blocking" : query.severity === "warning" ? "warning" : "none";
10775
11674
  const defectClass = query.defect_class ?? classifyDefect(query.id, "tree-sitter", query.message);
10776
11675
  const suggestion = query.has_fix && query.fix_action ? `${query.fix_action} this statement` : semantic === "blocking" ? defaultFixSuggestion2(defectClass, query.id) : void 0;
10777
11676
  const hasSuggestedFix = !!query.has_fix;
10778
- diagnostics.push({
11677
+ (outOfScope ? withheld : diagnostics).push({
10779
11678
  id: `tree-sitter:${query.id}:${line}`,
10780
11679
  message: query.message.replace(/\{\{(\w+)\}\}/g, (_, name) => match.captures[name]?.trim() ?? `{{${name}}}`),
10781
11680
  filePath,
@@ -10797,6 +11696,7 @@ var treeSitterRunner = {
10797
11696
  });
10798
11697
  }
10799
11698
  } catch (err) {
11699
+ queryRunFailed = true;
10800
11700
  const msg = err instanceof Error ? err.message : String(err);
10801
11701
  if (classifyTreeSitterWasmError(err) === "abort") {
10802
11702
  markTreeSitterWasmAborted();
@@ -10856,6 +11756,10 @@ var treeSitterRunner = {
10856
11756
  } catch {
10857
11757
  }
10858
11758
  }
11759
+ const observationExtras = {
11760
+ ...withheld.length > 0 && { outOfEditScope: withheld },
11761
+ ...queryRunFailed && { inconclusive: true }
11762
+ };
10859
11763
  if (diagnostics.length === 0) {
10860
11764
  if (!isTreeSitterWasmAborted() && !client.getLanguage(languageId)) {
10861
11765
  logTreeSitter({
@@ -10865,7 +11769,11 @@ var treeSitterRunner = {
10865
11769
  reason: "grammar_unavailable",
10866
11770
  status: "skipped"
10867
11771
  });
10868
- return { status: "skipped", diagnostics: [], semantic: "none" };
11772
+ return {
11773
+ status: "skipped",
11774
+ diagnostics: [],
11775
+ semantic: "none"
11776
+ };
10869
11777
  }
10870
11778
  logTreeSitter({
10871
11779
  phase: "runner_complete",
@@ -10877,7 +11785,12 @@ var treeSitterRunner = {
10877
11785
  queryCount: languageQueries.length,
10878
11786
  effectiveQueryCount: effectiveQueries.length
10879
11787
  });
10880
- return { status: "succeeded", diagnostics: [], semantic: "none" };
11788
+ return {
11789
+ status: "succeeded",
11790
+ diagnostics: [],
11791
+ semantic: "none",
11792
+ ...observationExtras
11793
+ };
10881
11794
  }
10882
11795
  const hasBlocking = diagnostics.some((d) => d.semantic === "blocking");
10883
11796
  const blockingCount = diagnostics.filter((d) => d.semantic === "blocking").length;
@@ -10891,10 +11804,13 @@ var treeSitterRunner = {
10891
11804
  queryCount: languageQueries.length,
10892
11805
  effectiveQueryCount: effectiveQueries.length
10893
11806
  });
10894
- return findingsResult(diagnostics, {
10895
- status: hasBlocking ? "failed" : "succeeded",
10896
- semantic: hasBlocking ? "blocking" : "warning"
10897
- });
11807
+ return {
11808
+ ...findingsResult(diagnostics, {
11809
+ status: hasBlocking ? "failed" : "succeeded",
11810
+ semantic: hasBlocking ? "blocking" : "warning"
11811
+ }),
11812
+ ...observationExtras
11813
+ };
10898
11814
  }
10899
11815
  };
10900
11816
  var tree_sitter_default = treeSitterRunner;
@@ -11305,8 +12221,6 @@ function isTestSuiteOrganizer(calls) {
11305
12221
  // dist/clients/dispatch/rules/high-complexity.js
11306
12222
  var DEFAULT_HIGH_COMPLEXITY_THRESHOLD = 15;
11307
12223
  var DEFAULT_HIGH_COMPLEXITY_DEPTH_THRESHOLD = 6;
11308
- var ccThreshold = DEFAULT_HIGH_COMPLEXITY_THRESHOLD;
11309
- var depthThreshold = DEFAULT_HIGH_COMPLEXITY_DEPTH_THRESHOLD;
11310
12224
  var highComplexityRule = {
11311
12225
  id: "high-complexity",
11312
12226
  requires: ["file.functionSummaries"],
@@ -11317,12 +12231,12 @@ var highComplexityRule = {
11317
12231
  const fns = store.getFileFact(ctx.filePath, "file.functionSummaries") ?? [];
11318
12232
  const diagnostics = [];
11319
12233
  const configuredCcThreshold = ctx.projectConfig?.rules["high-complexity"]?.threshold;
11320
- const activeCcThreshold = configuredCcThreshold ?? ccThreshold;
12234
+ const activeCcThreshold = configuredCcThreshold ?? DEFAULT_HIGH_COMPLEXITY_THRESHOLD;
11321
12235
  for (const f of fns) {
11322
12236
  if (isTestSuiteOrganizer(f.outgoingCalls))
11323
12237
  continue;
11324
12238
  const ccBreached = f.cyclomaticComplexity >= activeCcThreshold;
11325
- const depthBreached = f.maxNestingDepth >= depthThreshold;
12239
+ const depthBreached = f.maxNestingDepth >= DEFAULT_HIGH_COMPLEXITY_DEPTH_THRESHOLD;
11326
12240
  if (!ccBreached && !depthBreached)
11327
12241
  continue;
11328
12242
  const parts = [];
@@ -11348,7 +12262,6 @@ var highComplexityRule = {
11348
12262
 
11349
12263
  // dist/clients/dispatch/rules/high-fan-out.js
11350
12264
  var DEFAULT_HIGH_FAN_OUT_THRESHOLD = 20;
11351
- var fanOutThreshold = DEFAULT_HIGH_FAN_OUT_THRESHOLD;
11352
12265
  var highFanOutRule = {
11353
12266
  id: "high-fan-out",
11354
12267
  requires: ["file.functionSummaries"],
@@ -11359,7 +12272,7 @@ var highFanOutRule = {
11359
12272
  const fns = store.getFileFact(ctx.filePath, "file.functionSummaries") ?? [];
11360
12273
  const diagnostics = [];
11361
12274
  const configuredFanOutThreshold = ctx.projectConfig?.rules["high-fan-out"]?.threshold;
11362
- const activeFanOutThreshold = configuredFanOutThreshold ?? fanOutThreshold;
12275
+ const activeFanOutThreshold = configuredFanOutThreshold ?? DEFAULT_HIGH_FAN_OUT_THRESHOLD;
11363
12276
  for (const f of fns) {
11364
12277
  if (isTestSuiteOrganizer(f.outgoingCalls))
11365
12278
  continue;
@@ -11718,6 +12631,8 @@ function applyProjectLensConfig(cwd) {
11718
12631
  return loadPiLensProjectConfig(cwd);
11719
12632
  }
11720
12633
  var sessionFacts = new FactStore("dispatch");
12634
+ var preEditCaptureFacts = new FactStore("pre-edit-capture");
12635
+ var preEditCapturesInFlight = /* @__PURE__ */ new Map();
11721
12636
  setFactStoreEvictionReporter((subject, axis, reason) => {
11722
12637
  if (axis === "pinned-over-budget") {
11723
12638
  recordDegradationOnce({
@@ -11899,12 +12814,16 @@ function resetDispatchBaselines(cwd) {
11899
12814
  resetAstGrepNapiLoadState();
11900
12815
  resetTreeSitterClientLoadState();
11901
12816
  sessionFacts.clearAll();
12817
+ for (const controller of preEditCapturesInFlight.values())
12818
+ controller.abort();
12819
+ preEditCaptureFacts.clearAll();
11902
12820
  resetSessionSlopScore();
11903
12821
  clearCoverageNoticeState();
11904
12822
  clearReviewGraphWorkspaceCache();
11905
12823
  clearReverseDepsIndexCache();
11906
12824
  clearModuleGraphCache();
11907
- recentlyCleanNeighborCache.clear();
12825
+ resetCascadeUnansweredPrimariesDelivered();
12826
+ resetCascadePendingNeighbors();
11908
12827
  primaryFilesThisTurn.clear();
11909
12828
  cascadeSessionStats = {
11910
12829
  runs: 0,
@@ -11920,7 +12839,6 @@ var cascadeSessionStats = {
11920
12839
  diagnosticsSurfaced: 0,
11921
12840
  coldSnapshotTouches: 0
11922
12841
  };
11923
- var recentlyCleanNeighborCache = /* @__PURE__ */ new Map();
11924
12842
  var primaryFilesThisTurn = /* @__PURE__ */ new Set();
11925
12843
  var MAX_PER_FILE = RUNTIME_CONFIG.pipeline.cascadeMaxDiagnosticsPerFile;
11926
12844
  var MAX_FILES = RUNTIME_CONFIG.pipeline.cascadeMaxFiles;
@@ -11948,8 +12866,16 @@ var CASCADE_GRAPH_KINDS = /* @__PURE__ */ new Set([
11948
12866
  "ruby",
11949
12867
  "cxx"
11950
12868
  ]);
12869
+ var IMPLICIT_SCOPE_NEIGHBOR_CAP = MAX_FILES * 2;
11951
12870
  async function dispatchLintWithResult(filePath, cwd, pi, modifiedRanges, logContext, options) {
11952
- const ctx = createDispatchContext(filePath, cwd, pi, sessionFacts, options?.blockingOnly ?? true, modifiedRanges, options?.projectRoot, options?.writeIndex, options?.telemetryModel, options?.telemetryProvider, options?.sessionGeneration, options?.sessionId, options?.analysisRootMode);
12871
+ const created = createDispatchContext(filePath, cwd, pi, sessionFacts, options?.blockingOnly ?? true, modifiedRanges, options?.projectRoot, options?.writeIndex, options?.telemetryModel, options?.telemetryProvider, options?.sessionGeneration, options?.sessionId, options?.analysisRootMode);
12872
+ const ctx = options?.owedRanges?.size || options?.recordedRunnerIds?.size ? {
12873
+ ...created,
12874
+ ...options.owedRanges?.size && { owedRanges: options.owedRanges },
12875
+ ...options.recordedRunnerIds?.size && {
12876
+ recordedRunnerIds: options.recordedRunnerIds
12877
+ }
12878
+ } : created;
11953
12879
  sessionFacts.clearFileFactsFor(ctx.filePath);
11954
12880
  try {
11955
12881
  const kind = ctx.kind;
@@ -12080,14 +13006,17 @@ function enforceWarmWordIndexLruCap() {
12080
13006
  return;
12081
13007
  }
12082
13008
  }
13009
+ function usableWarmWordIndex(index) {
13010
+ return index?.forward ? index : void 0;
13011
+ }
12083
13012
  function acquireWarmWordIndex(cwd) {
12084
13013
  const key2 = path50.resolve(cwd);
12085
13014
  let entry = warmWordIndexes.get(key2);
12086
13015
  if (!entry) {
12087
13016
  const snapshot = loadProjectSnapshot(key2);
12088
- const index = deserializeWordIndex(snapshot?.wordIndex) ?? void 0;
13017
+ const index = usableWarmWordIndex(deserializeWordIndex(snapshot?.wordIndex) ?? void 0);
12089
13018
  entry = {
12090
- index: index?.forward ? index : void 0,
13019
+ index,
12091
13020
  timer: void 0,
12092
13021
  leases: 0,
12093
13022
  lastUsedAt: Date.now(),
@@ -12115,6 +13044,26 @@ function acquireWarmWordIndex(cwd) {
12115
13044
  }
12116
13045
  };
12117
13046
  }
13047
+ function adoptPublishedWarmWordIndex(cwd, published, trigger) {
13048
+ const index = usableWarmWordIndex(published);
13049
+ if (!index)
13050
+ return;
13051
+ const key2 = path50.resolve(cwd);
13052
+ const entry = warmWordIndexes.get(key2);
13053
+ if (!entry || entry.index)
13054
+ return;
13055
+ entry.index = index;
13056
+ entry.lastUsedAt = Date.now();
13057
+ scheduleWarmWordIndexIdleEviction(key2, entry);
13058
+ logWordIndex({
13059
+ phase: "warm_cache_adopted",
13060
+ cwd: key2,
13061
+ trigger,
13062
+ indexedFileCount: index.docCount,
13063
+ tokens: index.postings.size
13064
+ });
13065
+ }
13066
+ setWordIndexPublishedListener(adoptPublishedWarmWordIndex);
12118
13067
  function _resetWarmWordIndexCacheForTests() {
12119
13068
  for (const entry of warmWordIndexes.values())
12120
13069
  clearWarmWordIndexTimer(entry);
@@ -12177,7 +13126,13 @@ async function warmLspForFile(absPath, host) {
12177
13126
  clientScope: "primary",
12178
13127
  maxClientWaitMs: WARMUP_CLIENT_WAIT_MS,
12179
13128
  maxDiagnosticsWaitMs: WARMUP_DIAGNOSTICS_WAIT_MS,
12180
- source: "mcp-warmup"
13129
+ source: "mcp-warmup",
13130
+ // #3405: every caller of `analyzeFile` (the MCP hosts' post-write hooks,
13131
+ // warm and cold, and `pilens_analyze`) asks about the bytes on disk now,
13132
+ // and this is the analysis's first push of them, so it is the save. A
13133
+ // save-triggered server (Expert compiles on didSave and on nothing else)
13134
+ // answers an MCP host's edit only because of it.
13135
+ saved: true
12181
13136
  });
12182
13137
  } catch {
12183
13138
  }
@@ -12298,6 +13253,18 @@ export {
12298
13253
  CacheManager,
12299
13254
  getDiagnosticTracker,
12300
13255
  normalizeRuleId,
13256
+ readFileContent,
13257
+ applyRulePolicy,
13258
+ convertLspDiagnostics,
13259
+ applyFindingPolicy,
13260
+ loadProjectRulePolicyMap,
13261
+ inlineBlockerIdentities,
13262
+ applyPushedFindingPolicy,
13263
+ applyPushedFindingPolicyByFile,
13264
+ applyLspFindingPolicy,
13265
+ filterFindingsByDisposition,
13266
+ stampLineHashes,
13267
+ owedByContentOnly,
12301
13268
  getToolPlan,
12302
13269
  resetObservedRunnerLatency,
12303
13270
  requeueRunnerFindings,
@@ -12305,25 +13272,28 @@ export {
12305
13272
  dropStaleRunnerFindings,
12306
13273
  resetPendingRunnerFindings,
12307
13274
  pendingRunnerFindingsSize,
12308
- applyRulePolicy,
13275
+ laneCopyStandsFor,
12309
13276
  DELTA_UNUSED_PROMOTION_NOTE,
12310
13277
  formatDiagnostics,
12311
13278
  createDispatchContext,
12312
13279
  getLatencyReports,
12313
13280
  cascadeSettleWaitMs,
12314
- convertLspDiagnostics,
12315
- applyFindingPolicy,
12316
- loadProjectRulePolicyMap,
12317
- inlineBlockerIdentities,
12318
- applyPushedFindingPolicy,
12319
- applyLspFindingPolicy,
12320
- filterFindingsByDisposition,
12321
13281
  markUnreconciledFindings,
12322
13282
  formatCacheAgeLabel,
13283
+ dispositionSuppressedLine,
12323
13284
  admitsProjectWideFindings,
13285
+ formatCascadeNeighborDiagnostics,
13286
+ composeCascadeSection,
12324
13287
  cascadeCarrySuffix,
13288
+ markCascadeUnansweredPrimariesDelivered,
13289
+ renderCascadeResult,
13290
+ cascadeStillPresentSince,
13291
+ recordDeliveredCascadeNeighbor,
13292
+ forgetCascadePendingNeighbor,
13293
+ listCascadePendingNeighbors,
12325
13294
  logCascade,
12326
13295
  resetCascadeTierSessionState,
13296
+ peekOutstandingCascadeTouches,
12327
13297
  loadSg,
12328
13298
  canHandle,
12329
13299
  getLang,