pi-lens 4.2.1 → 4.4.0

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 (671) hide show
  1. package/CHANGELOG.md +1562 -0
  2. package/README.md +157 -24
  3. package/config/dependency-cruiser-eager-allowlist.json +6 -2
  4. package/dist/chunk-22AVIGXS.js +560 -0
  5. package/dist/chunk-27BBHSZC.js +106 -0
  6. package/dist/chunk-2BEMIWEW.js +51 -0
  7. package/dist/chunk-2EECBNC5.js +89 -0
  8. package/dist/chunk-34U7PLGE.js +23 -0
  9. package/dist/chunk-3I5QFT3N.js +181 -0
  10. package/dist/chunk-3MED75HT.js +37 -0
  11. package/dist/chunk-47D2BO4S.js +87 -0
  12. package/dist/chunk-4RERDEXR.js +348 -0
  13. package/dist/chunk-4W2PLC4J.js +27 -0
  14. package/dist/chunk-52H5B5TC.js +202 -0
  15. package/dist/chunk-54B42YRX.js +27 -0
  16. package/dist/chunk-5RXBNSW2.js +266 -0
  17. package/dist/chunk-5THXD6X5.js +201 -0
  18. package/dist/chunk-5W3R6LFT.js +24 -0
  19. package/dist/chunk-5ZEJHV35.js +341 -0
  20. package/dist/chunk-6P62QVEA.js +118 -0
  21. package/dist/chunk-6QIUX4EP.js +459 -0
  22. package/dist/chunk-6VCD4VKZ.js +78 -0
  23. package/dist/chunk-7F72PXY2.js +326 -0
  24. package/dist/chunk-7FBF7NPR.js +998 -0
  25. package/dist/chunk-7U2IDNXN.js +37 -0
  26. package/dist/chunk-ABBOA7UD.js +157 -0
  27. package/dist/chunk-AHDW3GPE.js +50 -0
  28. package/dist/chunk-AJ3QXTRQ.js +756 -0
  29. package/dist/chunk-AJVLWNFN.js +1610 -0
  30. package/dist/chunk-AJXODZY6.js +112 -0
  31. package/dist/chunk-ARFCXG7S.js +84 -0
  32. package/dist/chunk-AS44CFQ4.js +708 -0
  33. package/dist/chunk-AWE5TMPO.js +38 -0
  34. package/dist/chunk-B3I2UUT4.js +4692 -0
  35. package/dist/chunk-BRJKOBZP.js +331 -0
  36. package/dist/chunk-BW5KFIQC.js +59 -0
  37. package/dist/chunk-C2AUYXQH.js +149 -0
  38. package/dist/chunk-CGGAIP7J.js +29 -0
  39. package/dist/chunk-D6QS3U6M.js +30 -0
  40. package/dist/chunk-DFVMIKMQ.js +349 -0
  41. package/dist/chunk-DIULIWKU.js +169 -0
  42. package/dist/chunk-DQ2NZ7C4.js +2317 -0
  43. package/dist/chunk-E2WFBRHM.js +213 -0
  44. package/dist/chunk-E4MAEDPM.js +50 -0
  45. package/dist/chunk-EOKRQ3SA.js +30 -0
  46. package/dist/chunk-F3AU2TMM.js +2626 -0
  47. package/dist/chunk-F3P7VQ7H.js +170 -0
  48. package/dist/chunk-FC4RP4I2.js +12 -0
  49. package/dist/chunk-FJNKANOS.js +32 -0
  50. package/dist/chunk-GUOGVTCQ.js +146 -0
  51. package/dist/chunk-GVXA6CCE.js +18 -0
  52. package/dist/chunk-HHYAELLJ.js +2334 -0
  53. package/dist/chunk-HMFXILPV.js +83 -0
  54. package/dist/chunk-HVUZXSWK.js +421 -0
  55. package/dist/chunk-HWBZIGMJ.js +62 -0
  56. package/dist/chunk-I3R7GKDR.js +17 -0
  57. package/dist/chunk-I7SPGOWI.js +55 -0
  58. package/dist/chunk-IIXOAGHR.js +38 -0
  59. package/dist/chunk-IVP5KAB7.js +2250 -0
  60. package/dist/chunk-IXEBJSUE.js +22 -0
  61. package/dist/chunk-JOLITGCC.js +27 -0
  62. package/dist/chunk-K6VYLB2U.js +145 -0
  63. package/dist/chunk-LJDODBBZ.js +78 -0
  64. package/dist/chunk-LNAKEO63.js +78 -0
  65. package/dist/chunk-LSMJ6KBE.js +4139 -0
  66. package/dist/chunk-LW6XKDZQ.js +5565 -0
  67. package/dist/chunk-N3YQJI6O.js +899 -0
  68. package/dist/chunk-NABLC7W3.js +148 -0
  69. package/dist/chunk-NKOUFPQF.js +659 -0
  70. package/dist/chunk-NL5GLMFI.js +211 -0
  71. package/dist/chunk-NMABWBZN.js +327 -0
  72. package/dist/chunk-NTHH2J74.js +27 -0
  73. package/dist/chunk-NTUJEGDZ.js +59 -0
  74. package/dist/chunk-NXL4FFQQ.js +47 -0
  75. package/dist/chunk-NXPS2L6U.js +466 -0
  76. package/dist/chunk-O5WILGMI.js +42 -0
  77. package/dist/chunk-O6TQT6RI.js +48 -0
  78. package/dist/chunk-OAXNDZJ7.js +323 -0
  79. package/dist/chunk-ODEEOXPH.js +44 -0
  80. package/dist/chunk-OQGFCZJ4.js +4533 -0
  81. package/dist/chunk-ORTMY4Q4.js +282 -0
  82. package/dist/chunk-OS32RNR2.js +442 -0
  83. package/dist/chunk-PAK6IDME.js +189 -0
  84. package/dist/chunk-PWFHBTSF.js +406 -0
  85. package/dist/chunk-Q5U6FMDI.js +2352 -0
  86. package/dist/chunk-QOZXJ4NA.js +3364 -0
  87. package/dist/chunk-QYF5S6P6.js +45 -0
  88. package/dist/chunk-RIISMHKN.js +36 -0
  89. package/dist/chunk-RPD4ZSB7.js +25 -0
  90. package/dist/chunk-RPD5K5NC.js +79 -0
  91. package/dist/chunk-RUOG5D2C.js +262 -0
  92. package/dist/chunk-T4ERT73Y.js +599 -0
  93. package/dist/chunk-T6D4Q4AQ.js +54 -0
  94. package/dist/chunk-TGPXLY2J.js +391 -0
  95. package/dist/chunk-TLGFBM3F.js +12116 -0
  96. package/dist/chunk-TO6WL3GM.js +88 -0
  97. package/dist/chunk-TT22N74J.js +517 -0
  98. package/dist/chunk-TVN5N6GY.js +21216 -0
  99. package/dist/chunk-U3VGR4VU.js +308 -0
  100. package/dist/chunk-UK3CMEAL.js +1250 -0
  101. package/dist/chunk-UOPVJQ4N.js +103 -0
  102. package/dist/chunk-UXVNL3D7.js +23 -0
  103. package/dist/chunk-UY752QSN.js +45 -0
  104. package/dist/chunk-V5PXFSZK.js +188 -0
  105. package/dist/chunk-V7R425XG.js +398 -0
  106. package/dist/chunk-VCSRSYLF.js +81 -0
  107. package/dist/chunk-VD4JQCQC.js +162 -0
  108. package/dist/chunk-VDBPVIKD.js +62 -0
  109. package/dist/chunk-VHQLPIRJ.js +114 -0
  110. package/dist/chunk-VN2AXLEX.js +5414 -0
  111. package/dist/chunk-VNMT7C64.js +478 -0
  112. package/dist/chunk-WONERJTP.js +309 -0
  113. package/dist/chunk-XECLWQZN.js +85 -0
  114. package/dist/chunk-XIMYZ3JA.js +27 -0
  115. package/dist/chunk-XK4CIILI.js +144 -0
  116. package/dist/chunk-Y4T27COV.js +562 -0
  117. package/dist/chunk-YGVYKNSN.js +1860 -0
  118. package/dist/chunk-YLF3PPT5.js +308 -0
  119. package/dist/chunk-YSNFMJKN.js +14 -0
  120. package/dist/chunk-Z3CCVDUB.js +76 -0
  121. package/dist/chunk-Z54ZSOBZ.js +61 -0
  122. package/dist/chunk-ZAWW3AF6.js +97 -0
  123. package/dist/chunk-ZMOL6RQC.js +167 -0
  124. package/dist/index.js +19039 -8956
  125. package/dist/mcp/analyze-cli.js +186 -228
  126. package/dist/mcp/cli.js +137 -98
  127. package/dist/mcp/server.js +22592 -1564
  128. package/dist/mcp/worker.js +97 -38
  129. package/dist/probes/installer.js +135 -0
  130. package/dist/workers/project-snapshot-persist-worker.js +22 -0
  131. package/dist/workers/review-graph-persist-worker.js +15 -0
  132. package/docs/adr/0001-stale-advisory-live-arm.md +29 -0
  133. package/docs/adr/0002-workspace-hash-rendezvous.md +30 -0
  134. package/docs/adr/0003-git-guard-latch-writer.md +203 -0
  135. package/docs/adr/0004-disposition-policy-seam.md +38 -0
  136. package/docs/adr/0005-tool-availability-enforcement-seam.md +29 -0
  137. package/docs/adr/0006-derived-state-benchmark-first.md +31 -0
  138. package/docs/adr/0007-end-to-end-witness-per-seam-slice.md +38 -0
  139. package/docs/adr/0008-turn-end-lane-interface.md +165 -0
  140. package/docs/adr/0009-reported-path-attribution.md +114 -0
  141. package/docs/adr/README.md +26 -0
  142. package/docs/agent-guide.md +13 -4
  143. package/docs/agent-tools.md +60 -1
  144. package/docs/ast-grep_rules_catalog.md +6 -4
  145. package/docs/configuration.md +75 -2
  146. package/docs/engineering-principles.md +202 -0
  147. package/docs/environment-variables.md +113 -6
  148. package/docs/features.md +10 -6
  149. package/docs/globalconfig.md +7 -1
  150. package/docs/io-bridge-v2-migration.md +94 -0
  151. package/docs/language-coverage.md +17 -0
  152. package/docs/lsp-capability-matrix.md +131 -32
  153. package/docs/lsp-idle-eviction.md +124 -0
  154. package/docs/mcp.md +21 -5
  155. package/docs/merge-queue-rollout.md +77 -0
  156. package/docs/pi-lens-fixer.md +161 -39
  157. package/docs/pi-lens-investigator.md +77 -20
  158. package/docs/pi-lens-merge-policy.md +694 -0
  159. package/docs/pi-lens-monitor.md +73 -7
  160. package/docs/pi-lens-retro.md +6 -4
  161. package/docs/pi-lens-reviewer.md +190 -34
  162. package/docs/pi-lens-subagent.md +247 -37
  163. package/docs/pi-lens-warden.md +18 -7
  164. package/docs/public-api-stability.md +56 -0
  165. package/docs/release-qa-baseline.md +86 -17
  166. package/docs/rfcs/3654-unified-io-bridge-v2.md +316 -0
  167. package/docs/servercapabilities.md +66 -52
  168. package/docs/settings.md +13 -3
  169. package/docs/subagent-compat.md +1 -1
  170. package/docs/usage.md +5 -1
  171. package/grammars/tree-sitter-bash.wasm +0 -0
  172. package/grammars/tree-sitter-bash.wasm.json +3 -3
  173. package/package.json +23 -9
  174. package/rules/ast-grep-rules/rule-tests/advisory-registry-subset-test.yml +41 -0
  175. package/rules/ast-grep-rules/rule-tests/no-assert-tuple-test.yml +13 -0
  176. package/rules/ast-grep-rules/rule-tests/no-boolean-in-except-test.yml +26 -0
  177. package/rules/ast-grep-rules/rule-tests/no-http-headers-bracket-access-test.yml +10 -0
  178. package/rules/ast-grep-rules/rule-tests/unchecked-numeric-parse-python-test.yml +18 -0
  179. package/rules/ast-grep-rules/rule-tests/unchecked-throwing-call-python-test.yml +4 -3
  180. package/rules/ast-grep-rules/rules/advisory-registry-subset.yml +50 -0
  181. package/rules/ast-grep-rules/rules/go-no-fmt-println.yml +2 -2
  182. package/rules/ast-grep-rules/rules/no-assert-tuple.yml +23 -13
  183. package/rules/ast-grep-rules/rules/no-boolean-in-except.yml +7 -3
  184. package/rules/ast-grep-rules/rules/no-console-except-error-js.yml +2 -2
  185. package/rules/ast-grep-rules/rules/no-console-except-error.yml +2 -2
  186. package/rules/ast-grep-rules/rules/no-http-headers-bracket-access.yml +12 -7
  187. package/rules/ast-grep-rules/rules/unchecked-numeric-parse-python.yml +44 -0
  188. package/rules/ast-grep-rules/rules/unchecked-throwing-call-python.yml +5 -3
  189. package/rules/tree-sitter-queries/typescript/console-statement.yml +2 -2
  190. package/scripts/analyze-pi-lens-logs.mjs +1275 -62
  191. package/scripts/download-grammars.js +5 -0
  192. package/scripts/grammars.lock.json +6 -1
  193. package/scripts/install-selftest.mjs +38 -35
  194. package/scripts/lib/host-provided-deps.mjs +13 -4
  195. package/scripts/lib/web-tree-sitter-dir.mjs +141 -0
  196. package/dist/clients/actionable-warnings-logger.js +0 -35
  197. package/dist/clients/actionable-warnings.js +0 -1594
  198. package/dist/clients/advisory-provenance.js +0 -457
  199. package/dist/clients/agent-behavior-client.js +0 -147
  200. package/dist/clients/agent-nudge.js +0 -325
  201. package/dist/clients/analysed-root.js +0 -1
  202. package/dist/clients/ast-grep-client.js +0 -731
  203. package/dist/clients/ast-grep-rule-manager.js +0 -146
  204. package/dist/clients/ast-grep-tool-logger.js +0 -118
  205. package/dist/clients/ast-grep-types.js +0 -10
  206. package/dist/clients/ast-grep-yaml-synth.js +0 -154
  207. package/dist/clients/atomic-write-staging.js +0 -75
  208. package/dist/clients/atomic-write.js +0 -189
  209. package/dist/clients/availability-probe-flight.js +0 -12
  210. package/dist/clients/bash-file-access.js +0 -1056
  211. package/dist/clients/biome-client.js +0 -357
  212. package/dist/clients/blocker-freshness.js +0 -837
  213. package/dist/clients/blocker-past-eof.js +0 -129
  214. package/dist/clients/bootstrap.js +0 -569
  215. package/dist/clients/bounded-cache.js +0 -190
  216. package/dist/clients/bounded-pid-file-lock.js +0 -202
  217. package/dist/clients/bounded-telemetry.js +0 -295
  218. package/dist/clients/build-identity.js +0 -95
  219. package/dist/clients/bundled-resource-health.js +0 -113
  220. package/dist/clients/bus-events-logger.js +0 -105
  221. package/dist/clients/bus-publish.js +0 -171
  222. package/dist/clients/cache/rule-cache.js +0 -190
  223. package/dist/clients/cache-manager.js +0 -465
  224. package/dist/clients/cache-observability.js +0 -1313
  225. package/dist/clients/call-graph.js +0 -852
  226. package/dist/clients/cargo-manifest.js +0 -422
  227. package/dist/clients/cascade-budget.js +0 -196
  228. package/dist/clients/cascade-format.js +0 -315
  229. package/dist/clients/cascade-logger.js +0 -31
  230. package/dist/clients/cascade-types.js +0 -9
  231. package/dist/clients/child-unref.js +0 -115
  232. package/dist/clients/code-quality-warnings.js +0 -286
  233. package/dist/clients/codebase-model.js +0 -173
  234. package/dist/clients/collateral-test-role.js +0 -40
  235. package/dist/clients/complexity-client.js +0 -462
  236. package/dist/clients/config-core/deny.js +0 -221
  237. package/dist/clients/config-core/index.js +0 -50
  238. package/dist/clients/config-core/merge.js +0 -357
  239. package/dist/clients/config-core/normalize.js +0 -340
  240. package/dist/clients/config-core/process-spec.js +0 -248
  241. package/dist/clients/config-core/provenance.js +0 -164
  242. package/dist/clients/config-core/records.js +0 -218
  243. package/dist/clients/config-core/resolve.js +0 -125
  244. package/dist/clients/config-core/safe-object.js +0 -78
  245. package/dist/clients/config-core/schema.js +0 -165
  246. package/dist/clients/config-diagnostic-codes.js +0 -283
  247. package/dist/clients/config-locations.js +0 -160
  248. package/dist/clients/config-resolve.js +0 -792
  249. package/dist/clients/config-schema.js +0 -251
  250. package/dist/clients/config-warn.js +0 -407
  251. package/dist/clients/console-guard-install.js +0 -26
  252. package/dist/clients/cooperative-budget.js +0 -42
  253. package/dist/clients/dead-code-client.js +0 -510
  254. package/dist/clients/dead-code-logger.js +0 -30
  255. package/dist/clients/deadline-utils.js +0 -280
  256. package/dist/clients/debug-handles.js +0 -211
  257. package/dist/clients/debug-heap.js +0 -174
  258. package/dist/clients/deferred-lsp-work.js +0 -106
  259. package/dist/clients/degradation-ledger.js +0 -396
  260. package/dist/clients/demoted-finding-render.js +0 -169
  261. package/dist/clients/dependency-checker.js +0 -862
  262. package/dist/clients/deps/ast-grep-napi.js +0 -22
  263. package/dist/clients/deps/js-yaml.js +0 -9
  264. package/dist/clients/deps/minimatch.js +0 -4
  265. package/dist/clients/deps/pi-tui.js +0 -14
  266. package/dist/clients/deps/typebox.js +0 -14
  267. package/dist/clients/deps/vscode-jsonrpc.js +0 -1
  268. package/dist/clients/deps/web-tree-sitter.js +0 -24
  269. package/dist/clients/diagnostic-dispositions.js +0 -594
  270. package/dist/clients/diagnostic-line-freshness.js +0 -305
  271. package/dist/clients/diagnostic-logger.js +0 -73
  272. package/dist/clients/diagnostic-tracker.js +0 -105
  273. package/dist/clients/diagnostics-publish.js +0 -230
  274. package/dist/clients/dispatch/ast-grep-catalog.js +0 -65
  275. package/dist/clients/dispatch/auxiliary-lsp.js +0 -343
  276. package/dist/clients/dispatch/collect-later-tier.js +0 -35
  277. package/dist/clients/dispatch/diagnostic-taxonomy.js +0 -66
  278. package/dist/clients/dispatch/dispatcher.js +0 -1269
  279. package/dist/clients/dispatch/fact-provider-types.js +0 -1
  280. package/dist/clients/dispatch/fact-rule-runner.js +0 -17
  281. package/dist/clients/dispatch/fact-runner.js +0 -30
  282. package/dist/clients/dispatch/fact-scheduler.js +0 -68
  283. package/dist/clients/dispatch/fact-store.js +0 -377
  284. package/dist/clients/dispatch/facts/comment-facts.js +0 -22
  285. package/dist/clients/dispatch/facts/file-content.js +0 -19
  286. package/dist/clients/dispatch/facts/function-facts.js +0 -348
  287. package/dist/clients/dispatch/facts/import-facts.js +0 -204
  288. package/dist/clients/dispatch/facts/tree-sitter-facts.js +0 -34
  289. package/dist/clients/dispatch/facts/try-catch-facts.js +0 -119
  290. package/dist/clients/dispatch/finding-policy.js +0 -222
  291. package/dist/clients/dispatch/indent-detect.js +0 -411
  292. package/dist/clients/dispatch/inline-suppressions.js +0 -70
  293. package/dist/clients/dispatch/integration.js +0 -2377
  294. package/dist/clients/dispatch/lazy.js +0 -11
  295. package/dist/clients/dispatch/pending-runner-findings.js +0 -87
  296. package/dist/clients/dispatch/plan.js +0 -278
  297. package/dist/clients/dispatch/priorities.js +0 -20
  298. package/dist/clients/dispatch/rule-id-normalize.js +0 -50
  299. package/dist/clients/dispatch/rule-ignores.js +0 -62
  300. package/dist/clients/dispatch/rule-policy.js +0 -135
  301. package/dist/clients/dispatch/rules/async-noise.js +0 -40
  302. package/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +0 -27
  303. package/dist/clients/dispatch/rules/cors-wildcard.js +0 -54
  304. package/dist/clients/dispatch/rules/error-obscuring.js +0 -32
  305. package/dist/clients/dispatch/rules/error-swallowing.js +0 -31
  306. package/dist/clients/dispatch/rules/framework-call-noise.js +0 -67
  307. package/dist/clients/dispatch/rules/high-complexity.js +0 -72
  308. package/dist/clients/dispatch/rules/high-fan-out.js +0 -74
  309. package/dist/clients/dispatch/rules/high-import-coupling.js +0 -37
  310. package/dist/clients/dispatch/rules/missing-error-propagation.js +0 -62
  311. package/dist/clients/dispatch/rules/no-commented-credentials.js +0 -52
  312. package/dist/clients/dispatch/rules/pass-through-wrappers.js +0 -42
  313. package/dist/clients/dispatch/rules/placeholder-comments.js +0 -40
  314. package/dist/clients/dispatch/rules/unsafe-boundary.js +0 -126
  315. package/dist/clients/dispatch/runner-context.js +0 -52
  316. package/dist/clients/dispatch/runners/actionlint.js +0 -113
  317. package/dist/clients/dispatch/runners/ast-grep-napi.js +0 -1196
  318. package/dist/clients/dispatch/runners/biome-check.js +0 -283
  319. package/dist/clients/dispatch/runners/cpp-check.js +0 -236
  320. package/dist/clients/dispatch/runners/credo.js +0 -80
  321. package/dist/clients/dispatch/runners/cue-vet.js +0 -398
  322. package/dist/clients/dispatch/runners/dart-analyze.js +0 -202
  323. package/dist/clients/dispatch/runners/detekt.js +0 -169
  324. package/dist/clients/dispatch/runners/dotnet-build.js +0 -159
  325. package/dist/clients/dispatch/runners/elixir-check.js +0 -210
  326. package/dist/clients/dispatch/runners/eslint.js +0 -154
  327. package/dist/clients/dispatch/runners/fact-rules.js +0 -29
  328. package/dist/clients/dispatch/runners/fish-indent.js +0 -69
  329. package/dist/clients/dispatch/runners/gleam-check.js +0 -87
  330. package/dist/clients/dispatch/runners/go-vet.js +0 -90
  331. package/dist/clients/dispatch/runners/golangci-lint.js +0 -120
  332. package/dist/clients/dispatch/runners/hadolint.js +0 -79
  333. package/dist/clients/dispatch/runners/helm-lint.js +0 -171
  334. package/dist/clients/dispatch/runners/helm-render.js +0 -940
  335. package/dist/clients/dispatch/runners/htmlhint.js +0 -108
  336. package/dist/clients/dispatch/runners/index.js +0 -108
  337. package/dist/clients/dispatch/runners/javac.js +0 -89
  338. package/dist/clients/dispatch/runners/ktlint.js +0 -136
  339. package/dist/clients/dispatch/runners/lsp.js +0 -378
  340. package/dist/clients/dispatch/runners/markdownlint.js +0 -160
  341. package/dist/clients/dispatch/runners/mypy.js +0 -101
  342. package/dist/clients/dispatch/runners/oxlint.js +0 -398
  343. package/dist/clients/dispatch/runners/php-lint.js +0 -62
  344. package/dist/clients/dispatch/runners/phpstan.js +0 -120
  345. package/dist/clients/dispatch/runners/prisma-validate.js +0 -62
  346. package/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -445
  347. package/dist/clients/dispatch/runners/pyright.js +0 -169
  348. package/dist/clients/dispatch/runners/rubocop.js +0 -90
  349. package/dist/clients/dispatch/runners/ruff.js +0 -89
  350. package/dist/clients/dispatch/runners/rust-clippy.js +0 -251
  351. package/dist/clients/dispatch/runners/shellcheck.js +0 -169
  352. package/dist/clients/dispatch/runners/shfmt.js +0 -102
  353. package/dist/clients/dispatch/runners/spellcheck.js +0 -121
  354. package/dist/clients/dispatch/runners/spotbugs.js +0 -259
  355. package/dist/clients/dispatch/runners/sqlfluff.js +0 -174
  356. package/dist/clients/dispatch/runners/stylelint.js +0 -158
  357. package/dist/clients/dispatch/runners/swiftlint.js +0 -154
  358. package/dist/clients/dispatch/runners/taplo.js +0 -168
  359. package/dist/clients/dispatch/runners/terragrunt.js +0 -168
  360. package/dist/clients/dispatch/runners/tflint.js +0 -110
  361. package/dist/clients/dispatch/runners/tree-sitter.js +0 -620
  362. package/dist/clients/dispatch/runners/trivy-config.js +0 -247
  363. package/dist/clients/dispatch/runners/utils/availability-policy.js +0 -496
  364. package/dist/clients/dispatch/runners/utils/candidate-probe.js +0 -90
  365. package/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -85
  366. package/dist/clients/dispatch/runners/utils/lazy-installer.js +0 -274
  367. package/dist/clients/dispatch/runners/utils/runner-helpers.js +0 -1701
  368. package/dist/clients/dispatch/runners/utils/spawn-outcome.js +0 -101
  369. package/dist/clients/dispatch/runners/utils/tool-failure.js +0 -235
  370. package/dist/clients/dispatch/runners/utils/toolchain-availability.js +0 -169
  371. package/dist/clients/dispatch/runners/utils.js +0 -15
  372. package/dist/clients/dispatch/runners/vale.js +0 -116
  373. package/dist/clients/dispatch/runners/yaml-rule-parser.js +0 -242
  374. package/dist/clients/dispatch/runners/yamllint.js +0 -85
  375. package/dist/clients/dispatch/runners/zig-check.js +0 -91
  376. package/dist/clients/dispatch/suppress-writer.js +0 -69
  377. package/dist/clients/dispatch/tool-profile.js +0 -30
  378. package/dist/clients/dispatch/types.js +0 -21
  379. package/dist/clients/dispatch/utils/format-utils.js +0 -45
  380. package/dist/clients/dispatch/utils/lsp-diagnostics.js +0 -35
  381. package/dist/clients/disposition-logger.js +0 -32
  382. package/dist/clients/disposition-publish.js +0 -124
  383. package/dist/clients/durable-store.js +0 -97
  384. package/dist/clients/effective-config.js +0 -413
  385. package/dist/clients/env-utils.js +0 -83
  386. package/dist/clients/error-class.js +0 -23
  387. package/dist/clients/eval-timestamp.js +0 -17
  388. package/dist/clients/event-loop-hold.js +0 -274
  389. package/dist/clients/event-loop-monitor.js +0 -250
  390. package/dist/clients/extension-log.js +0 -504
  391. package/dist/clients/extension-mode.js +0 -73
  392. package/dist/clients/feature-hints.js +0 -103
  393. package/dist/clients/file-kinds.js +0 -412
  394. package/dist/clients/file-role.js +0 -134
  395. package/dist/clients/file-time.js +0 -160
  396. package/dist/clients/file-utils.js +0 -1032
  397. package/dist/clients/finding-delivery-gate.js +0 -463
  398. package/dist/clients/finding-identity.js +0 -88
  399. package/dist/clients/fix-worklog.js +0 -103
  400. package/dist/clients/format-events-publish.js +0 -343
  401. package/dist/clients/format-service.js +0 -222
  402. package/dist/clients/formatters-lazy.js +0 -9
  403. package/dist/clients/formatters.js +0 -1994
  404. package/dist/clients/freshness-cadence.js +0 -17
  405. package/dist/clients/freshness.js +0 -38
  406. package/dist/clients/generated-artifacts.js +0 -394
  407. package/dist/clients/generation-guard.js +0 -215
  408. package/dist/clients/git-guard.js +0 -923
  409. package/dist/clients/git-tracked-ignore.js +0 -243
  410. package/dist/clients/gitleaks-client.js +0 -509
  411. package/dist/clients/go-client.js +0 -104
  412. package/dist/clients/govulncheck-client.js +0 -605
  413. package/dist/clients/gradle-ktfmt-style.js +0 -252
  414. package/dist/clients/grammar-source.js +0 -465
  415. package/dist/clients/gzip-stage-write.js +0 -135
  416. package/dist/clients/hashline-anchor.js +0 -424
  417. package/dist/clients/hook-budgets.js +0 -76
  418. package/dist/clients/host-edit-normalize.js +0 -140
  419. package/dist/clients/host-ports.js +0 -39
  420. package/dist/clients/indent-retarget.js +0 -154
  421. package/dist/clients/install-diagnostics.js +0 -143
  422. package/dist/clients/installer/index.js +0 -5167
  423. package/dist/clients/installer/managed-tool-refresh-session.js +0 -58
  424. package/dist/clients/installer/managed-tool-refresh.js +0 -783
  425. package/dist/clients/instance-reaper-state.js +0 -7
  426. package/dist/clients/instance-reaper.js +0 -1137
  427. package/dist/clients/instance-registry-lock.js +0 -145
  428. package/dist/clients/instance-registry.js +0 -773
  429. package/dist/clients/jscpd-client.js +0 -370
  430. package/dist/clients/json-cache-read.js +0 -65
  431. package/dist/clients/knip-client.js +0 -788
  432. package/dist/clients/language-policy.js +0 -252
  433. package/dist/clients/language-profile.js +0 -361
  434. package/dist/clients/language-registry.js +0 -603
  435. package/dist/clients/latency-logger.js +0 -719
  436. package/dist/clients/lazy-import.js +0 -21
  437. package/dist/clients/ledger-bounds.js +0 -54
  438. package/dist/clients/lens-config.js +0 -346
  439. package/dist/clients/lens-engine.js +0 -358
  440. package/dist/clients/lens-events.js +0 -125
  441. package/dist/clients/lens-flag-registry.js +0 -467
  442. package/dist/clients/lens-map.js +0 -1156
  443. package/dist/clients/live-bus-emitter.js +0 -74
  444. package/dist/clients/log-cleanup.js +0 -222
  445. package/dist/clients/lsp/aggregation.js +0 -178
  446. package/dist/clients/lsp/cascade-tier.js +0 -421
  447. package/dist/clients/lsp/client.js +0 -4465
  448. package/dist/clients/lsp/config.js +0 -689
  449. package/dist/clients/lsp/diagnostic-binding.js +0 -225
  450. package/dist/clients/lsp/document-drift.js +0 -467
  451. package/dist/clients/lsp/edits.js +0 -1219
  452. package/dist/clients/lsp/index.js +0 -8305
  453. package/dist/clients/lsp/inferred-project.js +0 -216
  454. package/dist/clients/lsp/jvm-runtime.js +0 -298
  455. package/dist/clients/lsp/language.js +0 -63
  456. package/dist/clients/lsp/launch.js +0 -725
  457. package/dist/clients/lsp/lombok.js +0 -158
  458. package/dist/clients/lsp/path-utils.js +0 -5
  459. package/dist/clients/lsp/pending-aux-coverage.js +0 -251
  460. package/dist/clients/lsp/position-encoding.js +0 -61
  461. package/dist/clients/lsp/ruby-drive-dirs.js +0 -83
  462. package/dist/clients/lsp/server.js +0 -3209
  463. package/dist/clients/lsp/session-roots.js +0 -192
  464. package/dist/clients/lsp/spawn-history.js +0 -17
  465. package/dist/clients/lsp/sync-kind.js +0 -42
  466. package/dist/clients/lsp/tsserver-sync.js +0 -332
  467. package/dist/clients/lsp/wait-policy/capability-snapshot.js +0 -1
  468. package/dist/clients/lsp/wait-policy/classification.js +0 -85
  469. package/dist/clients/lsp/wait-policy/index.js +0 -10
  470. package/dist/clients/lsp/wait-policy/strategies.js +0 -303
  471. package/dist/clients/lsp/watch-queue.js +0 -61
  472. package/dist/clients/lsp/workspace-diagnostics-cache.js +0 -639
  473. package/dist/clients/lsp/workspace-diagnostics-session.js +0 -30
  474. package/dist/clients/lsp/workspace-sweep-hold.js +0 -184
  475. package/dist/clients/lsp-budget.js +0 -186
  476. package/dist/clients/lsp-document-symbols.js +0 -110
  477. package/dist/clients/lsp-lazy.js +0 -9
  478. package/dist/clients/lsp-mutation.js +0 -396
  479. package/dist/clients/lsp-status.js +0 -59
  480. package/dist/clients/map-with-concurrency.js +0 -40
  481. package/dist/clients/mcp/analyze.js +0 -420
  482. package/dist/clients/mcp/host-shim.js +0 -31
  483. package/dist/clients/mcp/ipc.js +0 -335
  484. package/dist/clients/mcp/review.js +0 -151
  485. package/dist/clients/mcp/session.js +0 -412
  486. package/dist/clients/memory-sampler.js +0 -208
  487. package/dist/clients/message-end-attribution.js +0 -30
  488. package/dist/clients/metrics-client.js +0 -107
  489. package/dist/clients/metrics-history.js +0 -327
  490. package/dist/clients/middle-man-analysis.js +0 -263
  491. package/dist/clients/model-provider.js +0 -53
  492. package/dist/clients/module-report-lsp.js +0 -189
  493. package/dist/clients/module-report.js +0 -2150
  494. package/dist/clients/mutating-tool.js +0 -651
  495. package/dist/clients/mutation-attribution.js +0 -368
  496. package/dist/clients/mutation-bridge.js +0 -240
  497. package/dist/clients/ndjson-logger.js +0 -734
  498. package/dist/clients/observed-mutation-sources.js +0 -101
  499. package/dist/clients/observed-mutation.js +0 -1253
  500. package/dist/clients/oldtext-autopatch.js +0 -128
  501. package/dist/clients/opaque-mutation-scan.js +0 -496
  502. package/dist/clients/opengrep-client.js +0 -328
  503. package/dist/clients/opengrep-config.js +0 -76
  504. package/dist/clients/package-manager.js +0 -607
  505. package/dist/clients/package-root.js +0 -38
  506. package/dist/clients/partial-edit-apply.js +0 -439
  507. package/dist/clients/path-attribution-telemetry.js +0 -43
  508. package/dist/clients/path-keyed-map.js +0 -108
  509. package/dist/clients/path-utils.js +0 -1244
  510. package/dist/clients/performance-report.js +0 -331
  511. package/dist/clients/persist-debounce.js +0 -70
  512. package/dist/clients/persistent-reverify.js +0 -271
  513. package/dist/clients/php-cs-fixer-config.js +0 -119
  514. package/dist/clients/pipeline.js +0 -1425
  515. package/dist/clients/post-autofix-notice.js +0 -37
  516. package/dist/clients/print-mode.js +0 -21
  517. package/dist/clients/probe-home-state.js +0 -228
  518. package/dist/clients/process-bridge.js +0 -66
  519. package/dist/clients/process-singletons.js +0 -210
  520. package/dist/clients/process-snapshot.js +0 -68
  521. package/dist/clients/project-changes.js +0 -181
  522. package/dist/clients/project-conventions.js +0 -171
  523. package/dist/clients/project-diagnostics/cache.js +0 -137
  524. package/dist/clients/project-diagnostics/extractors.js +0 -109
  525. package/dist/clients/project-diagnostics/fresh-fetch.js +0 -650
  526. package/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +0 -118
  527. package/dist/clients/project-diagnostics/runner-adapters/dead-code.js +0 -47
  528. package/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +0 -40
  529. package/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +0 -34
  530. package/dist/clients/project-diagnostics/runner-adapters/jscpd.js +0 -38
  531. package/dist/clients/project-diagnostics/runner-adapters/knip.js +0 -47
  532. package/dist/clients/project-diagnostics/runner-adapters/madge.js +0 -48
  533. package/dist/clients/project-diagnostics/runner-adapters/opengrep.js +0 -40
  534. package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +0 -127
  535. package/dist/clients/project-diagnostics/runner-adapters/trivy.js +0 -81
  536. package/dist/clients/project-diagnostics/scanner.js +0 -520
  537. package/dist/clients/project-diagnostics/types.js +0 -1
  538. package/dist/clients/project-lens-config.js +0 -799
  539. package/dist/clients/project-report.js +0 -701
  540. package/dist/clients/project-scale.js +0 -237
  541. package/dist/clients/project-scan-policy.js +0 -11
  542. package/dist/clients/project-snapshot-fingerprint.js +0 -48
  543. package/dist/clients/project-snapshot-persist-worker.js +0 -20
  544. package/dist/clients/project-snapshot.js +0 -1653
  545. package/dist/clients/project-trust.js +0 -181
  546. package/dist/clients/python-environment.js +0 -217
  547. package/dist/clients/python-provenance.js +0 -639
  548. package/dist/clients/quiet-window-config.js +0 -47
  549. package/dist/clients/quiet-window.js +0 -236
  550. package/dist/clients/read-bridge.js +0 -139
  551. package/dist/clients/read-expansion.js +0 -266
  552. package/dist/clients/read-guard-logger.js +0 -251
  553. package/dist/clients/read-guard-tool-lines.js +0 -1306
  554. package/dist/clients/read-guard.js +0 -1561
  555. package/dist/clients/recent-touches.js +0 -240
  556. package/dist/clients/redact/secrets.js +0 -449
  557. package/dist/clients/resource-sampler.js +0 -628
  558. package/dist/clients/reverse-deps.js +0 -270
  559. package/dist/clients/review-graph/builder.js +0 -5118
  560. package/dist/clients/review-graph/format.js +0 -33
  561. package/dist/clients/review-graph/git-identity.js +0 -157
  562. package/dist/clients/review-graph/import-resolvers.js +0 -481
  563. package/dist/clients/review-graph/persist-worker.js +0 -10
  564. package/dist/clients/review-graph/query.js +0 -244
  565. package/dist/clients/review-graph/revision-drift.js +0 -21
  566. package/dist/clients/review-graph/service.js +0 -96
  567. package/dist/clients/review-graph/shared-extraction-ir.js +0 -82
  568. package/dist/clients/review-graph/symbol-id.js +0 -103
  569. package/dist/clients/review-graph/tsconfig-paths.js +0 -299
  570. package/dist/clients/review-graph/types.js +0 -1
  571. package/dist/clients/review-graph/workspace-modules.js +0 -449
  572. package/dist/clients/review-graph-logger.js +0 -81
  573. package/dist/clients/ruff-client.js +0 -152
  574. package/dist/clients/rules-scanner.js +0 -131
  575. package/dist/clients/run-duration.js +0 -55
  576. package/dist/clients/runtime-agent-end.js +0 -746
  577. package/dist/clients/runtime-config.js +0 -77
  578. package/dist/clients/runtime-context.js +0 -211
  579. package/dist/clients/runtime-coordinator.js +0 -1290
  580. package/dist/clients/runtime-session.js +0 -2485
  581. package/dist/clients/runtime-tool-call.js +0 -1284
  582. package/dist/clients/runtime-tool-result.js +0 -2034
  583. package/dist/clients/runtime-turn.js +0 -3600
  584. package/dist/clients/rust-client.js +0 -97
  585. package/dist/clients/safe-spawn.js +0 -2166
  586. package/dist/clients/sanitize.js +0 -286
  587. package/dist/clients/scan-utils.js +0 -25
  588. package/dist/clients/scratch-tree-policy.js +0 -165
  589. package/dist/clients/search-read-registration.js +0 -94
  590. package/dist/clients/secret-findings.js +0 -114
  591. package/dist/clients/security-scan-client.js +0 -352
  592. package/dist/clients/session-event-guard.js +0 -297
  593. package/dist/clients/session-lifecycle.js +0 -484
  594. package/dist/clients/session-start-observability.js +0 -94
  595. package/dist/clients/session-state-store.js +0 -101
  596. package/dist/clients/session-summary.js +0 -15
  597. package/dist/clients/sessionstart-logger.js +0 -27
  598. package/dist/clients/sg-runner.js +0 -876
  599. package/dist/clients/sgconfig.js +0 -347
  600. package/dist/clients/shared-checkout-guard.js +0 -338
  601. package/dist/clients/single-flight.js +0 -158
  602. package/dist/clients/situational-tool-telemetry.js +0 -108
  603. package/dist/clients/skills-resolver.js +0 -141
  604. package/dist/clients/slow-fs.js +0 -138
  605. package/dist/clients/smells-rollup.js +0 -231
  606. package/dist/clients/source-filter.js +0 -596
  607. package/dist/clients/source-walker.js +0 -211
  608. package/dist/clients/spawn-output-cap.js +0 -37
  609. package/dist/clients/spawn-timeout-cooldown.js +0 -110
  610. package/dist/clients/stale-marker.js +0 -17
  611. package/dist/clients/startup-scan.js +0 -400
  612. package/dist/clients/startup-timing.js +0 -130
  613. package/dist/clients/string-utils.js +0 -33
  614. package/dist/clients/subagent-mode.js +0 -111
  615. package/dist/clients/symbol-containment.js +0 -24
  616. package/dist/clients/symbol-types.js +0 -5
  617. package/dist/clients/test-runner-client.js +0 -2507
  618. package/dist/clients/test-runner-delivery.js +0 -287
  619. package/dist/clients/todo-scanner.js +0 -229
  620. package/dist/clients/tool-agreement.js +0 -423
  621. package/dist/clients/tool-config.js +0 -249
  622. package/dist/clients/tool-cwd.js +0 -270
  623. package/dist/clients/tool-definition.js +0 -47
  624. package/dist/clients/tool-event.js +0 -105
  625. package/dist/clients/tool-policy.js +0 -2755
  626. package/dist/clients/tool-probe.js +0 -53
  627. package/dist/clients/tool-render.js +0 -156
  628. package/dist/clients/tool-set-policy.js +0 -118
  629. package/dist/clients/tree-sitter-cache.js +0 -364
  630. package/dist/clients/tree-sitter-client.js +0 -4134
  631. package/dist/clients/tree-sitter-logger.js +0 -100
  632. package/dist/clients/tree-sitter-navigator.js +0 -269
  633. package/dist/clients/tree-sitter-query-loader.js +0 -569
  634. package/dist/clients/tree-sitter-shared.js +0 -158
  635. package/dist/clients/tree-sitter-symbol-extractor.js +0 -1265
  636. package/dist/clients/trivy-client.js +0 -467
  637. package/dist/clients/tui-fit.js +0 -50
  638. package/dist/clients/turn-context.js +0 -52
  639. package/dist/clients/turn-summary-render.js +0 -72
  640. package/dist/clients/turn-summary.js +0 -132
  641. package/dist/clients/typos-config.js +0 -29
  642. package/dist/clients/user-notify.js +0 -51
  643. package/dist/clients/vanished-instance-marker.js +0 -57
  644. package/dist/clients/warm-attach.js +0 -246
  645. package/dist/clients/widget-state.js +0 -1677
  646. package/dist/clients/word-index-logger.js +0 -49
  647. package/dist/clients/word-index-store.js +0 -459
  648. package/dist/clients/word-index.js +0 -2080
  649. package/dist/clients/workspace-topology.js +0 -389
  650. package/dist/clients/write-ordering-guard.js +0 -65
  651. package/dist/clients/zizmor-config.js +0 -327
  652. package/dist/i18n.js +0 -69
  653. package/dist/mcp/build-staleness.js +0 -124
  654. package/dist/scripts/lib/process-scan.mjs +0 -583
  655. package/dist/scripts/lib/skills-predicate.mjs +0 -129
  656. package/dist/tools/activate-tools.js +0 -87
  657. package/dist/tools/ast-grep-outline.js +0 -137
  658. package/dist/tools/ast-grep-replace.js +0 -244
  659. package/dist/tools/ast-grep-search.js +0 -762
  660. package/dist/tools/effective-config.js +0 -90
  661. package/dist/tools/lens-diagnostic-mark.js +0 -375
  662. package/dist/tools/lens-diagnostics.js +0 -2544
  663. package/dist/tools/lsp-diagnostics.js +0 -1634
  664. package/dist/tools/lsp-navigation.js +0 -1367
  665. package/dist/tools/lsp-structured-output.js +0 -294
  666. package/dist/tools/module-report.js +0 -382
  667. package/dist/tools/project-report.js +0 -86
  668. package/dist/tools/render-compact.js +0 -374
  669. package/dist/tools/scan-progress.js +0 -53
  670. package/dist/tools/shared.js +0 -29
  671. package/dist/tools/symbol-search.js +0 -155
@@ -1,799 +0,0 @@
1
- /**
2
- * Project-level `.pi-lens.json` config loader.
3
- *
4
- * Reads an optional `.pi-lens.json` (or `pi-lens.json`) at the project root and
5
- * surfaces the fields the rest of pi-lens honors:
6
- *
7
- * - `ignore` — gitignore-style glob patterns added to every scan (LSP walk,
8
- * fact-rules, tree-sitter, jscpd, knip, review graph, source-filter). Wired
9
- * into `getProjectIgnoreMatcher` in `file-utils.ts` via the existing
10
- * `createProjectIgnoreMatcher(rootDir, extraPatterns)` extension point.
11
- *
12
- * - `rules` — per-rule threshold overrides. Currently honored:
13
- * rules["high-complexity"].threshold — cyclomatic complexity (default 15)
14
- * rules["high-fan-out"].threshold — distinct-function calls (default 20)
15
- *
16
- * - `maxProjectFiles` — the base project-size scale knob (#776). Read by
17
- * `clients/project-scale.ts`'s `getProjectScaleBase`, which derives the
18
- * five subsystem size budgets (project-diagnostics scanner, review graph,
19
- * startup scan, jscpd, word index) as documented ratios of this value.
20
- *
21
- * - `reviewGraph.maxFiles` — explicit review-graph file-budget override
22
- * (#775 R2), for monorepos that want a bigger graph than
23
- * `project-scale.ts`'s adaptive taper would derive from
24
- * `maxProjectFiles` alone. Tolerantly parsed (numeric strings coerce via
25
- * `toPositiveFinite`, same as `maxProjectFiles`) and clamped to
26
- * `[100, 20_000]` — a value outside that range is silently clamped
27
- * rather than rejected (still an explicit, deliberate opt-in; only a
28
- * non-numeric/non-positive value warns and is dropped). Read by
29
- * `getReviewGraphMaxFilesDerived`, where it takes precedence over the
30
- * taper (but the subsystem's own PRE-EXISTING
31
- * `PI_LENS_REVIEW_GRAPH_MAX_FILES` env override still wins outright over
32
- * both, unchanged).
33
- *
34
- * - `format.enabled`, `autofix.enabled`, and
35
- * `actionableWarnings.autoFix.enabled` — project-owned mutation controls.
36
- * These can disable pi-lens writes while leaving diagnostics enabled.
37
- *
38
- * The file is loaded once per `(path, mtimeMs)` and cached — editing the file
39
- * invalidates the cache so the next access sees the new values without
40
- * restarting pi. Discovery is cached by starting directory and validated by the
41
- * cached directory mtimes plus the config-file mtime, so hot paths do not repeat
42
- * candidate-file probes on every dispatch.
43
- *
44
- * The loader walks up from the starting directory until it finds a config file
45
- * (mirroring `lsp/config.ts`'s `loadLSPConfig` so project-monorepos with a
46
- * `.pi-lens.json` at the repo root work without per-subdir configs).
47
- *
48
- * A malformed file is treated as "no config" and logged once — we never want a
49
- * stray syntax error in user-edited JSON to break diagnostics.
50
- *
51
- * `findPiLensConfigInDir` / `loadPiLensConfigInDir` are the per-directory
52
- * (no upward walk) counterparts used by `file-utils.ts`'s
53
- * `getProjectIgnoreMatcher` to layer NESTED `.pi-lens.json` `ignore` fields
54
- * the same way nested `.gitignore`s are already layered (#783): every
55
- * ancestor directory between the git root and a scanned file is checked for
56
- * its own config file, so a package-local `.pi-lens.json`'s `ignore`
57
- * patterns apply to files inside that package, in addition to (and with
58
- * higher precedence than) the root config's `ignore` patterns.
59
- */
60
- import { resetIgnoredConfigWarnCache, warnIgnoredConfigOnce, } from "./config-warn.js";
61
- import * as fs from "node:fs";
62
- import * as os from "node:os";
63
- import * as path from "node:path";
64
- import { toPositiveFinite } from "./env-utils.js";
65
- import { FRESHNESS_CADENCE_MS } from "./freshness-cadence.js";
66
- import { assignFlagConfigSection, flagConfigSectionKeys, GLOBAL_NON_FLAG_CONFIG_SECTIONS, GLOBAL_ONLY_NON_FLAG_KEYS, hasFlagConfigPath, LENS_FLAGS, PROJECT_FOREIGN_CONFIG_NAMESPACES, PROJECT_FOREIGN_NAMESPACE_HONORED_KEYS, PROJECT_NON_FLAG_CONFIG_SECTIONS, PROJECT_SCOPED_LENS_FLAGS, readFlagConfigValue, } from "./lens-flag-registry.js";
67
- import { configSearchDirs, getPiLensGlobalConfigPath, PROJECT_CONFIG_BASENAMES, PROJECT_CONFIG_LOCATIONS, } from "./config-locations.js";
68
- import { deprecationRecords, ignoredRecordCollector, projectLocationFor, readConfigDocument, reportConfigReadFailure, reportPiLensConfigRecords, resolveOnePiLensConfigDocument, } from "./config-resolve.js";
69
- import { finalizeRecords, } from "./config-core/records.js";
70
- import { homeRelativePath, isAtOrAboveHomeDir, walkUpDirs, } from "./path-utils.js";
71
- import { findPiLensConfigMarkerInDir } from "./workspace-topology.js";
72
- import { readToolConfig } from "./tool-config.js";
73
- /**
74
- * Project config basenames, in DESCENDING precedence (first match wins), for
75
- * this loader's own discovery.
76
- *
77
- * Re-exported from `config-locations.ts`, which owns the derivation, so this
78
- * loader's long-standing export keeps working while there is exactly ONE list
79
- * (#2426 review round 2: `workspace-topology.ts` was still carrying a third
80
- * copy).
81
- */
82
- export { PROJECT_CONFIG_BASENAMES } from "./config-locations.js";
83
- /** Clamp bounds for `reviewGraph.maxFiles` — see the field's doc comment above. */
84
- const REVIEW_GRAPH_MAX_FILES_MIN = 100;
85
- const REVIEW_GRAPH_MAX_FILES_MAX = 20_000;
86
- export const EMPTY_PROJECT_CONFIG = {
87
- ignore: [],
88
- rules: {},
89
- maxProjectFiles: undefined,
90
- reviewGraph: undefined,
91
- raw: undefined,
92
- configPath: undefined,
93
- };
94
- /** Cache by absolute config path; we read each candidate's mtime before reuse. */
95
- const configCache = new Map();
96
- const discoveryCache = new Map();
97
- /**
98
- * The shared body of `loadPiLensProjectConfig` and `loadPiLensConfigInDir`:
99
- * reuse a fresh cache entry, or parse and cache one, and — EITHER WAY (#2426
100
- * review round 3, F2) — report the resolution's migration records. Content
101
- * caching (skip re-reading unchanged bytes off disk) and per-session
102
- * reporting (tell the user once a session about a deprecated setting) are two
103
- * different lifetimes; folding "already cached" into "already reported"
104
- * silently dropped the report the moment the file stopped changing.
105
- */
106
- function loadCachedConfigFile(info) {
107
- const cached = configCache.get(info.path);
108
- if (cached && cached.mtimeMs === info.mtimeMs && cached.size === info.size) {
109
- replayConfigNotices(cached);
110
- return cached.config;
111
- }
112
- const parsed = parseConfigFile(info.path);
113
- const entry = {
114
- mtimeMs: info.mtimeMs,
115
- size: info.size,
116
- config: parsed.config,
117
- records: parsed.records,
118
- ignored: parsed.ignored,
119
- };
120
- configCache.set(info.path, entry);
121
- replayConfigNotices(entry);
122
- return parsed.config;
123
- }
124
- /**
125
- * Report BOTH classes of notice a parse produced (#2426 review round 4, F3).
126
- *
127
- * The resolution's records go through the shared seam, which derives each
128
- * one's subsystem from the record. This loader's own records do not: they are
129
- * `project-lens-config`'s by construction — it composed their prose — so
130
- * routing them through that derivation would relabel `"lsp.enabled" is a
131
- * global-only pi-lens setting` as an LSP-config problem, which is the opposite
132
- * of what it says.
133
- */
134
- function replayConfigNotices(entry) {
135
- reportPiLensConfigRecords(entry.records);
136
- for (const record of entry.ignored) {
137
- warnIgnoredConfigOnce({
138
- subsystem: "project-lens-config",
139
- file: record.file,
140
- reason: record.reason,
141
- code: record.code,
142
- });
143
- }
144
- }
145
- /**
146
- * Walk up from `startDir` looking for a `.pi-lens.json` or `pi-lens.json`.
147
- * Returns the parsed config, or an empty config if none was found.
148
- */
149
- export function loadPiLensProjectConfig(startDir, preloadedInfo) {
150
- // ONE discovery per load (#2426 review round 5, F-C). The legacy-document
151
- // enumeration below and the config-file lookup are two READS of the same
152
- // cache entry, and resolving that entry twice — the default argument once,
153
- // the record replay again — doubled the freshness stats every warm load
154
- // pays, on a path called per dispatch. The entry is resolved here and both
155
- // halves read it.
156
- const entry = discoveryEntryFor(startDir);
157
- // #2426 review round 4, F4. The half-migrated notices — a legacy
158
- // `pi-lens.json` sitting BESIDE or ABOVE the canonical `.pi-lens.json` this
159
- // walk stops at — used to be produced only by `loadLSPConfig`, whose
160
- // discovery collects from every bearing directory rather than stopping at
161
- // the first. Under `--no-lsp`, `lsp.enabled: false`, or a subagent session
162
- // (#449, routine) that loader never runs, and the pi-lens-OWNED records for
163
- // a pi-lens-owned file were then produced by nobody at all. The pi-lens
164
- // loader now enumerates those documents itself, for records only: nothing
165
- // here is merged, and which file supplies a VALUE is unchanged.
166
- const configInfo = preloadedInfo ?? freshInfoFor(entry);
167
- // The selected legacy document is resolved below and its bounded migration
168
- // records are cached with that projection. Reporting the discovery copy as
169
- // well would produce two suppression records with different totals, because
170
- // the discovery walk and the single-document resolution see different
171
- // record populations. Keep discovery-only documents here; the selected file
172
- // is reported by `loadCachedConfigFile`.
173
- const selectedLegacyPath = configInfo?.path;
174
- reportPiLensConfigRecords(entry.legacyRecords.filter((record) => record.file !== selectedLegacyPath));
175
- if (!configInfo)
176
- return EMPTY_PROJECT_CONFIG;
177
- return loadCachedConfigFile(configInfo);
178
- }
179
- /** For tests + callers that need to force a re-read (e.g. config-watcher hooks). */
180
- export function resetProjectLensConfigCache() {
181
- configCache.clear();
182
- discoveryCache.clear();
183
- resetIgnoredConfigWarnCache("project-lens-config");
184
- }
185
- /**
186
- * Look for a `.pi-lens.json`/`pi-lens.json` directly IN `dir` — no upward
187
- * walk. Used to layer nested per-package configs (#783) the same way
188
- * `file-utils.ts` layers nested `.gitignore`s: each ancestor directory
189
- * between the git root and a target file is checked for its OWN config
190
- * file, independent of whatever config `loadPiLensProjectConfig`'s upward
191
- * walk would find starting from `dir`.
192
- *
193
- * Sourced from the shared workspace-topology marker index (#806) — one
194
- * `readdir` pass per directory visit collects this marker alongside
195
- * `tsconfig.json`/workspace-manifest markers other consumers need for the
196
- * SAME directory, instead of each subsystem re-probing it independently.
197
- */
198
- export function findPiLensConfigInDir(dir) {
199
- const marker = findPiLensConfigMarkerInDir(dir);
200
- if (!marker)
201
- return undefined;
202
- return {
203
- path: marker.path,
204
- dir: marker.dir,
205
- mtimeMs: marker.mtimeMs,
206
- size: marker.size,
207
- };
208
- }
209
- /**
210
- * Find the closest config, between an edited file and the project root, that
211
- * explicitly defines one mutation flag. The walk uses the shared primitive
212
- * and refuses to inspect HOME or any ancestor of HOME.
213
- */
214
- export function findNestedProjectMutationValue(spec, editedFilePath, projectRoot, homeDir = os.homedir()) {
215
- const root = path.resolve(projectRoot);
216
- const start = path.dirname(path.resolve(editedFilePath));
217
- for (const dir of walkUpDirs(start)) {
218
- if (isAtOrAboveHomeDir(dir, homeDir))
219
- break;
220
- const rel = path.relative(root, dir);
221
- if (rel.startsWith("..") || path.isAbsolute(rel))
222
- break;
223
- const config = loadPiLensConfigInDir(dir);
224
- const enabled = readFlagConfigValue(config, spec.configKey);
225
- if (enabled !== undefined)
226
- return { value: enabled, dir };
227
- if (dir === root)
228
- break;
229
- }
230
- return undefined;
231
- }
232
- /**
233
- * Load the `.pi-lens.json`/`pi-lens.json` directly IN `dir` (no upward
234
- * walk) — the per-directory counterpart to `loadPiLensProjectConfig`'s
235
- * upward-walking discovery. Shares `configCache` (keyed by absolute config
236
- * path + mtime), so a directory whose config was already loaded via the
237
- * upward-walk path (e.g. the git root itself) is not re-read here.
238
- */
239
- export function loadPiLensConfigInDir(dir) {
240
- const info = findPiLensConfigInDir(dir);
241
- if (!info)
242
- return EMPTY_PROJECT_CONFIG;
243
- return loadCachedConfigFile(info);
244
- }
245
- /** The cached discovery for `startDir`, recomputed when it is no longer fresh. */
246
- function discoveryEntryFor(startDir) {
247
- const cacheKey = path.resolve(startDir);
248
- const cached = discoveryCache.get(cacheKey);
249
- if (cached && discoveryCacheStillFresh(cached))
250
- return cached;
251
- const discovered = discoverPiLensProjectConfig(cacheKey);
252
- discoveryCache.set(cacheKey, discovered);
253
- return discovered;
254
- }
255
- export function findPiLensProjectConfig(startDir) {
256
- return freshInfoFor(discoveryEntryFor(startDir));
257
- }
258
- /**
259
- * The winning config file's info, re-statted.
260
- *
261
- * Split out of `findPiLensProjectConfig` so a caller that already holds the
262
- * discovery entry does not resolve it a second time to get here (#2426 review
263
- * round 5, F-C). The re-stat itself stays: the entry can be fresh by directory
264
- * mtime while the config file was rewritten in place, and the caller keys its
265
- * content cache on the mtime/size this returns.
266
- */
267
- function freshInfoFor(entry) {
268
- if (!entry.info)
269
- return undefined;
270
- const stat = safeFileStat(entry.info.path);
271
- if (!stat?.isFile())
272
- return entry.info;
273
- return { ...entry.info, mtimeMs: stat.mtimeMs, size: stat.size };
274
- }
275
- function safeFileStat(filePath) {
276
- try {
277
- return fs.statSync(filePath);
278
- }
279
- catch {
280
- return undefined;
281
- }
282
- }
283
- function safeDirMtimeMs(dir) {
284
- try {
285
- return fs.statSync(dir).mtimeMs;
286
- }
287
- catch {
288
- return -1;
289
- }
290
- }
291
- function discoveryCacheStillFresh(entry) {
292
- if (!entry.dirMtimes.every((cached) => safeDirMtimeMs(cached.dir) === cached.mtimeMs)) {
293
- return false;
294
- }
295
- // #2483 round 2. When no config was found anywhere, `bearingDirMtimes`'s
296
- // no-`info` branch scopes `dirMtimes` to `startDir` alone, so the `.every`
297
- // above can only ever see churn AT `startDir` — a config file created at
298
- // any ancestor above it moves nothing this entry tracks, and the entry
299
- // would then read as fresh for the rest of the process (no production
300
- // caller resets it, and a same-`startDir` load never rediscovers). The
301
- // issue's acceptance line rules that out ("a new config file created in
302
- // the chain IS noticed (bounded staleness stated)"), so a no-`info` entry
303
- // is force-expired at most once per `FRESHNESS_CADENCE_MS` regardless of
304
- // what `dirMtimes` says — the same negative-cache cadence bound
305
- // `file-utils.ts`'s `patternsForDir` already applies to its own no-match
306
- // case, reused rather than a second cadence mechanism invented here. The
307
- // forced re-walk that follows re-stats the FULL ancestor chain (see
308
- // `discoverPiLensProjectConfig`), so a config created anywhere in it is
309
- // found — bounded to at most one cadence window of staleness, not
310
- // unbounded.
311
- if (entry.info === undefined &&
312
- Date.now() - entry.checkedAtMs >= FRESHNESS_CADENCE_MS) {
313
- return false;
314
- }
315
- // A legacy document can be EDITED in place without its directory's mtime
316
- // moving, and its top-level key set is what `legacyRecords` describes — so
317
- // those files carry their own freshness axis, the same (mtime, size) pair
318
- // `CacheEntry` uses.
319
- return entry.legacySources.every((source) => {
320
- const stat = safeFileStat(source.path);
321
- return (stat?.isFile() === true &&
322
- stat.mtimeMs === source.mtimeMs &&
323
- stat.size === source.size);
324
- });
325
- }
326
- const LEGACY_PROJECT_LOCATIONS = PROJECT_CONFIG_LOCATIONS.filter((location) => location.legacy);
327
- function discoverPiLensProjectConfig(startDir) {
328
- const dirMtimes = [];
329
- const legacyDocuments = [];
330
- const legacySources = [];
331
- let info;
332
- // #2426: ceiling-bounded, same rule and same shared primitive as the LSP
333
- // loader and as `findNestedProjectMutationValue` below. This walk ran to the
334
- // filesystem root before, so a `.pi-lens.json` in `$HOME` (or at `C:\`) was
335
- // adopted by every project on the machine — the #622/#625 class.
336
- //
337
- // It no longer STOPS at the first bearing directory (#2426 review round 4,
338
- // F4): the first hit still decides `info`, and therefore still decides every
339
- // value this loader returns, but the walk continues to the ceiling so the
340
- // legacy documents a user is being asked to migrate are enumerated wherever
341
- // they sit — beside the winning file or above it.
342
- for (const dir of configSearchDirs(startDir)) {
343
- dirMtimes.push({ dir, mtimeMs: safeDirMtimeMs(dir) });
344
- if (!info) {
345
- for (const name of PROJECT_CONFIG_BASENAMES) {
346
- const candidate = path.join(dir, name);
347
- const stat = safeFileStat(candidate);
348
- if (stat?.isFile()) {
349
- info = {
350
- path: candidate,
351
- dir,
352
- mtimeMs: stat.mtimeMs,
353
- size: stat.size,
354
- };
355
- break;
356
- }
357
- }
358
- }
359
- for (const location of LEGACY_PROJECT_LOCATIONS) {
360
- const file = path.join(dir, location.relativePath);
361
- const stat = safeFileStat(file);
362
- if (!stat?.isFile())
363
- continue;
364
- legacySources.push({
365
- path: file,
366
- mtimeMs: stat.mtimeMs,
367
- size: stat.size,
368
- });
369
- const outcome = readConfigDocument(file);
370
- if (outcome.status === "error") {
371
- reportConfigReadFailure({
372
- file,
373
- location,
374
- tier: "project",
375
- error: outcome.error,
376
- sourceText: outcome.sourceText,
377
- });
378
- continue;
379
- }
380
- if (outcome.status !== "ok")
381
- continue;
382
- legacyDocuments.push({
383
- tier: "project",
384
- file,
385
- location,
386
- value: outcome.value,
387
- });
388
- }
389
- }
390
- return {
391
- info,
392
- dirMtimes: bearingDirMtimes(dirMtimes, info),
393
- legacyRecords: legacyDeprecationRecords(legacyDocuments),
394
- legacySources,
395
- checkedAtMs: Date.now(),
396
- };
397
- }
398
- /**
399
- * The legacy documents' deprecation records, BOUNDED per document (#2426
400
- * review round 5, F-A/F-B).
401
- *
402
- * This path shipped its records raw. A legacy file's recognized top-level key
403
- * count is user input, so one 28-key `pi-lens.json` produced 28 notifications
404
- * through this loader while the LSP loader — which finalizes the same records —
405
- * produced the bound plus a count for the identical file, and five nested
406
- * legacy files produced 145 with no suppression record anywhere. Every producer
407
- * now goes through the one \`finalizeRecords\`.
408
- *
409
- * Per DOCUMENT rather than per walk, because these are not one resolution: each
410
- * file is separately actionable ("move these keys out of THIS file"), and a
411
- * per-walk bound would silently drop whole files' advice while naming only the
412
- * last one. Per document also makes the two loaders agree on the count for a
413
- * given file, which is the parity `config-notice-ownership` asserts.
414
- */
415
- function legacyDeprecationRecords(documents) {
416
- const home = os.homedir();
417
- return documents.flatMap((document) => finalizeRecords(deprecationRecords([document], home), {
418
- file: document.file,
419
- tier: document.tier,
420
- }));
421
- }
422
- /**
423
- * The directory mtimes this entry's freshness is keyed on: the BEARING chain —
424
- * `startDir` up to and including the directory that supplied `info` (#2426
425
- * review round 5, F-C).
426
- *
427
- * The walk itself no longer stops at the first bearing directory, because the
428
- * legacy documents a user is asked to migrate can sit above it. Keying
429
- * freshness on every directory it VISITS is a different decision, and a bad
430
- * one: the walk runs to just below `$HOME`, so the entry was invalidated by any
431
- * churn in an ancestor like `~/Desktop` — a full re-walk, re-read and re-parse
432
- * of every legacy document, on a hot path, triggered by an unrelated directory.
433
- *
434
- * Scoping it back to the bearing chain restores the pre-#2426 invalidation
435
- * surface exactly. Nothing that decides a VALUE is outside that chain, and an
436
- * already-discovered legacy document above it carries its own (mtime, size)
437
- * stamp in `legacySources`, so an EDIT to one is still noticed. What is given
438
- * up is noticing a legacy file NEWLY CREATED above the bearing directory
439
- * mid-process — records-only, and strictly more than the pre-#2426 loader saw,
440
- * since it never looked above that directory at all.
441
- *
442
- * When NO config file is found anywhere (#2483), `info` is undefined and there
443
- * is no bearing directory to stop at — but the majority of projects have no
444
- * config at all, so this is the COMMON case, not an edge one. Falling back to
445
- * the full chain here (the pre-fix behavior) keyed freshness on every
446
- * directory up to just below `$HOME`: unrelated churn in `~/Desktop` or
447
- * `~/projects` invalidated the entry and forced a full re-walk on the very
448
- * next load, every load, forever.
449
- *
450
- * The nearest directory a config COULD have appeared in is `startDir` itself
451
- * — `dirMtimes[0]`, first in the innermost-first walk order (`configSearchDirs`)
452
- * — so that is what per-call freshness is scoped to, the same bearing-directory
453
- * principle as the presence case (a directory always includes itself as the
454
- * degenerate bearing chain). A config file created directly in `startDir`
455
- * bumps `startDir`'s own mtime and is found on the next load.
456
- *
457
- * A config file created further up the chain does not move `startDir`'s own
458
- * mtime, so it is invisible to THIS check alone (round 1 of #2483 stopped
459
- * here, leaving staleness unbounded — no production caller ever resets the
460
- * cache and a same-`startDir` load never rediscovers). `discoveryCacheStillFresh`
461
- * closes that gap: a no-`info` entry is force-expired at most once per
462
- * `FRESHNESS_CADENCE_MS`, independent of what `dirMtimes` says, which sends
463
- * the NEXT load through a full re-walk of the ancestor chain regardless — so
464
- * a config created anywhere in it is noticed within one cadence window.
465
- * Bounded staleness, and strictly narrower than the pre-fix "any ancestor up
466
- * to $HOME invalidates on every load".
467
- */
468
- function bearingDirMtimes(dirMtimes, info) {
469
- if (info) {
470
- const bearing = dirMtimes.findIndex((entry) => entry.dir === info.dir);
471
- return bearing < 0 ? [...dirMtimes] : dirMtimes.slice(0, bearing + 1);
472
- }
473
- return dirMtimes.slice(0, 1);
474
- }
475
- function parseRulePolicyList(note, ruleId, key, value) {
476
- if (!Array.isArray(value)) {
477
- note(`rules.${ruleId}.${key} must be an array of strings`);
478
- return { list: [], invalid: true };
479
- }
480
- const list = [];
481
- for (const entry of value) {
482
- if (typeof entry !== "string")
483
- continue;
484
- const trimmed = entry.trim();
485
- if (trimmed.length > 0)
486
- list.push(trimmed);
487
- }
488
- if (list.length === 0) {
489
- // #1087: an explicitly empty array (`"disable": []`) is a well-formed
490
- // no-op, not an error — don't warn. Only warn when the array HAD entries
491
- // but none were usable strings (all blank / non-string), which is a real
492
- // authoring mistake that must not fail silently.
493
- if (value.length > 0) {
494
- note(`rules.${ruleId}.${key} must contain at least one non-empty string`);
495
- return { list: [], invalid: true };
496
- }
497
- return { list: [], invalid: false };
498
- }
499
- return { list, invalid: false };
500
- }
501
- const NO_RECORDS = [];
502
- function parseConfigFile(configPath) {
503
- const { note, records: ignoredRecords } = ignoredRecordCollector(configPath, "project");
504
- const outcome = readConfigDocument(configPath);
505
- if (outcome.status === "error") {
506
- // `{ parseError }`, not a pre-stringified message (#2431): the raw error
507
- // reaches one seam, which decides how much of a `JSON.parse`
508
- // `SyntaxError#message` (which embeds a snippet of the file being parsed)
509
- // survives into the sinks.
510
- note({ parseError: outcome.error, sourceText: outcome.sourceText });
511
- return {
512
- config: EMPTY_PROJECT_CONFIG,
513
- records: NO_RECORDS,
514
- ignored: ignoredRecords(),
515
- };
516
- }
517
- // The file was stat'd as present by the discovery above, so `missing` here is
518
- // a delete that raced the read. Nothing to warn about — the next call
519
- // re-discovers and finds no config at all.
520
- if (outcome.status === "missing") {
521
- return {
522
- config: EMPTY_PROJECT_CONFIG,
523
- records: NO_RECORDS,
524
- ignored: NO_RECORDS,
525
- };
526
- }
527
- if (!outcome.value ||
528
- typeof outcome.value !== "object" ||
529
- Array.isArray(outcome.value)) {
530
- note("top-level value must be an object");
531
- return {
532
- config: EMPTY_PROJECT_CONFIG,
533
- records: NO_RECORDS,
534
- ignored: ignoredRecords(),
535
- };
536
- }
537
- // Through the #2425 core (#2426): the value is validated against the
538
- // canonical schema before this loader's own field parsing sees it, so the
539
- // depth bound, the prototype-key policy and the declared `lsp.*` types apply
540
- // here exactly as they do in the LSP loader. `raw` is the VALIDATED value
541
- // rather than the parse output, which is the one place the field's
542
- // documented "the parsed JSON as-is" narrows: a `__proto__` key or a
543
- // 4000-deep blob no longer rides into `.raw`'s consumers (`trivy-client.ts`,
544
- // the helm-render runner).
545
- const resolved = resolveOnePiLensConfigDocument({
546
- tier: "project",
547
- file: configPath,
548
- location: projectLocationFor(configPath),
549
- value: outcome.value,
550
- });
551
- // The records are RETURNED, not reported here (#2426 review round 3, F2):
552
- // this function's result is what the caller caches, and a cache HIT must
553
- // still report them every session — a call this function never makes on a
554
- // hit. Reporting here would fire only on a cache MISS (session 1), and
555
- // `resetProjectLensConfigCache` has no production caller, so every session
556
- // after the first silently lost the `config-deprecated` ledger row despite
557
- // the file still being the exact deprecated one on disk.
558
- const raw = resolved.value;
559
- const obj = resolved.value;
560
- const ignore = Array.isArray(obj.ignore)
561
- ? obj.ignore.filter((p) => typeof p === "string")
562
- : [];
563
- const mutations = {};
564
- for (const spec of PROJECT_SCOPED_LENS_FLAGS) {
565
- assignFlagConfigSection(obj, mutations, spec.configKey, note);
566
- }
567
- const toolConfig = readToolConfig(obj, note);
568
- const rules = {};
569
- if (obj.rules && typeof obj.rules === "object" && !Array.isArray(obj.rules)) {
570
- const rawRules = obj.rules;
571
- for (const [ruleId, ruleCfg] of Object.entries(rawRules)) {
572
- // #444's own example writes the lists directly under `rules` (`rules.
573
- // disable`), which lands here as an array and would otherwise be
574
- // dropped without a word — the one shape a user is most likely to try.
575
- if (!ruleCfg || typeof ruleCfg !== "object" || Array.isArray(ruleCfg)) {
576
- note(`rules.${ruleId} must be an object with threshold, disable, or select; ignored`);
577
- continue;
578
- }
579
- const r = ruleCfg;
580
- const entry = {};
581
- if (typeof r.threshold === "number" &&
582
- Number.isFinite(r.threshold) &&
583
- r.threshold > 0) {
584
- entry.threshold = r.threshold;
585
- }
586
- else if ("threshold" in r) {
587
- note(`rules.${ruleId}.threshold must be a positive finite number`);
588
- }
589
- if ("disable" in r) {
590
- const parsed = parseRulePolicyList(note, ruleId, "disable", r.disable);
591
- // #1087: an explicitly empty list is valid-but-empty (no warning);
592
- // don't store a pointless no-op entry for it.
593
- if (!parsed.invalid && parsed.list.length > 0)
594
- entry.disable = parsed.list;
595
- }
596
- if ("select" in r) {
597
- const parsed = parseRulePolicyList(note, ruleId, "select", r.select);
598
- if (!parsed.invalid && parsed.list.length > 0)
599
- entry.select = parsed.list;
600
- }
601
- // Honor both threshold-only and policy-only entries; only drop if
602
- // the entry had no recognized fields at all (e.g. { unrelated: true }).
603
- // A recognized-but-malformed field already warned above, so only warn
604
- // here when nothing recognized was spelled at all — #444 proposed
605
- // `only` rather than `select`, and that typo must not fail silent.
606
- if (entry.threshold !== undefined || entry.disable || entry.select) {
607
- rules[ruleId] = entry;
608
- }
609
- else if (!("threshold" in r) && !("disable" in r) && !("select" in r)) {
610
- note(`rules.${ruleId} has no recognized setting (threshold, disable, select); ignored`);
611
- }
612
- }
613
- }
614
- let maxProjectFiles;
615
- if ("maxProjectFiles" in obj) {
616
- if (typeof obj.maxProjectFiles === "number" &&
617
- Number.isFinite(obj.maxProjectFiles) &&
618
- obj.maxProjectFiles > 0) {
619
- maxProjectFiles = obj.maxProjectFiles;
620
- }
621
- else {
622
- note("maxProjectFiles must be a positive finite number");
623
- }
624
- }
625
- let reviewGraph;
626
- if (obj.reviewGraph !== undefined) {
627
- if (!obj.reviewGraph ||
628
- typeof obj.reviewGraph !== "object" ||
629
- Array.isArray(obj.reviewGraph)) {
630
- note("reviewGraph must be an object");
631
- }
632
- else {
633
- const rg = obj.reviewGraph;
634
- if ("maxFiles" in rg) {
635
- const parsed = toPositiveFinite(rg.maxFiles);
636
- if (parsed > 0) {
637
- const clamped = Math.min(REVIEW_GRAPH_MAX_FILES_MAX, Math.max(REVIEW_GRAPH_MAX_FILES_MIN, Math.floor(parsed)));
638
- reviewGraph = { maxFiles: clamped };
639
- }
640
- else {
641
- note("reviewGraph.maxFiles must be a positive finite number");
642
- }
643
- }
644
- }
645
- }
646
- let startup;
647
- if (obj.startup !== undefined) {
648
- if (!obj.startup ||
649
- typeof obj.startup !== "object" ||
650
- Array.isArray(obj.startup)) {
651
- note("startup must be an object");
652
- }
653
- else {
654
- const section = obj.startup;
655
- if (section.mode === "quick" ||
656
- section.mode === "full" ||
657
- section.mode === "minimal") {
658
- startup = { mode: section.mode };
659
- }
660
- else if ("mode" in section) {
661
- note('startup.mode must be "quick", "full", or "minimal"');
662
- }
663
- if (section.scans !== undefined) {
664
- if (!section.scans ||
665
- typeof section.scans !== "object" ||
666
- Array.isArray(section.scans)) {
667
- note("startup.scans must be an object");
668
- }
669
- else if (typeof section.scans.enabled ===
670
- "boolean") {
671
- startup ??= {};
672
- startup.scans = {
673
- enabled: section.scans
674
- .enabled,
675
- };
676
- }
677
- else if ("enabled" in section.scans) {
678
- note("startup.scans.enabled must be a boolean");
679
- }
680
- }
681
- }
682
- }
683
- // #533 hygiene: mirror the global loader's unknown-key warn so a typo in a
684
- // shared `.pi-lens.json` (e.g. `maxProjectFile`, `lps`) produces a signal
685
- // instead of silently doing nothing. The recognized set is single-sourced
686
- // (#883): the project loader's own keys + the project-scoped flag sections
687
- // (registry-derived) + the foreign namespaces the LSP loader reads from this
688
- // same file. A key recognized ONLY at global scope (e.g. `lsp`, `tests`,
689
- // `delta`) gets a distinct, honest signal that it does nothing here rather
690
- // than being lumped in with typos — docs previously called this "silently
691
- // ignored".
692
- const knownProjectKeys = new Set([
693
- ...PROJECT_NON_FLAG_CONFIG_SECTIONS,
694
- ...flagConfigSectionKeys(PROJECT_SCOPED_LENS_FLAGS),
695
- ...PROJECT_FOREIGN_CONFIG_NAMESPACES,
696
- ]);
697
- const globalScopeOnlyKeys = new Set([
698
- ...flagConfigSectionKeys(LENS_FLAGS),
699
- ...GLOBAL_NON_FLAG_CONFIG_SECTIONS,
700
- ].filter((key) => !knownProjectKeys.has(key)));
701
- // The full DOTTED config keys of the flags a project file cannot set, for the
702
- // sub-key scan below. Section-level keys are too coarse for a namespace that
703
- // mixes scopes: `lsp` holds four project-scoped settings AND the global-only
704
- // `lsp.enabled`.
705
- const projectScoped = new Set(PROJECT_SCOPED_LENS_FLAGS.map((spec) => spec.configKey));
706
- const globalScopeOnlyFlagKeys = LENS_FLAGS.map((spec) => spec.configKey).filter((configKey) => !projectScoped.has(configKey));
707
- // Non-flag global-only keys sitting inside an otherwise project-recognized
708
- // section (#3131) — `actionableWarnings.autoFix.maxFixes` is not a
709
- // `LENS_FLAGS` entry (no CLI flag, not boolean), so it is invisible to the
710
- // derivation above. `GLOBAL_ONLY_NON_FLAG_KEYS` is the single place that
711
- // population is enumerated instead of a second hand-typed list here.
712
- const globalScopeOnlyDottedKeys = [
713
- ...globalScopeOnlyFlagKeys,
714
- ...GLOBAL_ONLY_NON_FLAG_KEYS,
715
- ];
716
- // The literal path is derived, not hand-typed (#2426 review round 3, S1):
717
- // under `PI_LENS_CONFIG_PATH` the canonical global config is NOT
718
- // `~/.pi-lens/config.json`, and a hardcoded string in this notice would name
719
- // a file the resolver never reads.
720
- const globalConfigLabel = homeRelativePath(getPiLensGlobalConfigPath(), os.homedir());
721
- const warnUnhonoredKey = (label, globalOnly) => {
722
- note(globalOnly
723
- ? `"${label}" is a global-only pi-lens setting and is not honored in a project .pi-lens.json (set it in ${globalConfigLabel} or pass the matching CLI flag); ignored`
724
- : `unknown key "${label}" is not a recognized pi-lens setting (check for a typo); ignored`);
725
- };
726
- for (const key of Object.keys(obj)) {
727
- if (knownProjectKeys.has(key))
728
- continue;
729
- warnUnhonoredKey(key, globalScopeOnlyKeys.has(key));
730
- }
731
- // A tolerated foreign namespace is tolerated KEY BY KEY (#2426 review round
732
- // 2, F3). `lsp` is read out of this file by the LSP loader, but only four of
733
- // its keys are project-scoped; `lsp.enabled` is the `--no-lsp` flag, which is
734
- // `scope: "global"`. Blanket namespace tolerance swallowed it silently, which
735
- // `docs/configuration.md`'s "nothing is ignored silently" forbids — so every
736
- // other sub-key gets the same global-only-vs-typo signal a top-level key does,
737
- // named with its DOTTED path so the notice says which setting is meant.
738
- for (const [namespace, honored] of PROJECT_FOREIGN_NAMESPACE_HONORED_KEYS) {
739
- const section = obj[namespace];
740
- if (!section || typeof section !== "object" || Array.isArray(section)) {
741
- continue;
742
- }
743
- for (const key of Object.keys(section)) {
744
- if (honored.includes(key))
745
- continue;
746
- const dotted = `${namespace}.${key}`;
747
- warnUnhonoredKey(dotted, globalScopeOnlyDottedKeys.some((configKey) => configKey === dotted || configKey.startsWith(`${dotted}.`)));
748
- }
749
- }
750
- // MIXED-SCOPE SECTIONS (#3112, #3131). A section can be project-scoped as a
751
- // whole and still hold a global-only setting: `tools` carries the
752
- // project-owned per-tool `enabled` leaves AND the global `tools.lazy`
753
- // (`--no-lazy-tools`), the same split `lsp` has (#2426 review round 2, F3);
754
- // `actionableWarnings` carries the project-owned `autoFix.enabled` AND the
755
- // global-only `autoFix.maxFixes`, which is not even a flag (#3131). The
756
- // top-level scan above sees only the SECTION name, so a global-only setting
757
- // written inside a recognized project section was dropped with no signal at
758
- // all — the thing `docs/configuration.md` promises never happens.
759
- //
760
- // WHICH sub-keys those are is DERIVED from the registry — `LENS_FLAGS` for
761
- // flag keys, `GLOBAL_ONLY_NON_FLAG_KEYS` for the rest — never listed here,
762
- // so there is no second scope table to drift from the first. A key whose
763
- // SECTION is not recognized at project scope is skipped: the top-level
764
- // scan already reported that whole section, and a second notice naming the
765
- // same setting twice under two spellings is the duplicate-notice noise #2426
766
- // review round 6 removed. Everything else under a recognized section is left
767
- // to that section's own parser — `readToolConfig` reports an unknown tool
768
- // name under its own code (`PILENS_CFG_0009`).
769
- //
770
- // An enumerated-honored-keys namespace (`lsp`) needs no skip here: the loop
771
- // above reports the very same dotted key with the very same reason, and
772
- // `warnIgnoredConfigOnce`'s latch collapses the two into one notice. A skip
773
- // for it was written and then deleted — mutating it out changed no output.
774
- for (const configKey of globalScopeOnlyDottedKeys) {
775
- const dotIndex = configKey.indexOf(".");
776
- if (dotIndex < 0)
777
- continue;
778
- if (!knownProjectKeys.has(configKey.slice(0, dotIndex)))
779
- continue;
780
- if (!hasFlagConfigPath(obj, configKey))
781
- continue;
782
- warnUnhonoredKey(configKey, true);
783
- }
784
- return {
785
- config: {
786
- ...(toolConfig === undefined ? {} : { tools: toolConfig }),
787
- ...(startup === undefined ? {} : { startup }),
788
- ignore,
789
- rules,
790
- ...mutations,
791
- maxProjectFiles,
792
- reviewGraph,
793
- raw,
794
- configPath,
795
- },
796
- records: resolved.records,
797
- ignored: ignoredRecords(),
798
- };
799
- }