@herbertgao/pi-extensions 2026.9.0 → 2026.9.1

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 (293) hide show
  1. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +13 -1
  2. package/node_modules/@czottmann/pi-automode/README.md +20 -0
  3. package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +1 -1
  4. package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +2 -1
  5. package/node_modules/@czottmann/pi-automode/docs/defaults.md +3 -2
  6. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +38 -2
  7. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +5 -3
  8. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +11 -3
  9. package/node_modules/@czottmann/pi-automode/package.json +1 -1
  10. package/node_modules/@pi-plugins/fast-mode/README.md +5 -3
  11. package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -1
  12. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +20 -55
  13. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  14. package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
  15. package/node_modules/pi-lens/CHANGELOG.md +988 -0
  16. package/node_modules/pi-lens/README.md +3 -0
  17. package/node_modules/pi-lens/config/biome/core.jsonc +11 -2
  18. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +13 -1
  19. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +2 -2
  20. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +927 -80
  21. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +13 -4
  22. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +123 -3
  23. package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +60 -5
  24. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +2 -2
  25. package/node_modules/pi-lens/dist/clients/bash-file-access.js +1 -3
  26. package/node_modules/pi-lens/dist/clients/biome-client.js +9 -2
  27. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +14 -0
  28. package/node_modules/pi-lens/dist/clients/bootstrap.js +509 -73
  29. package/node_modules/pi-lens/dist/clients/bounded-cache.js +152 -12
  30. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +59 -4
  31. package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +113 -0
  32. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +2 -2
  33. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +31 -4
  34. package/node_modules/pi-lens/dist/clients/cache-manager.js +105 -6
  35. package/node_modules/pi-lens/dist/clients/cache-observability.js +44 -18
  36. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +422 -0
  37. package/node_modules/pi-lens/dist/clients/cascade-logger.js +2 -2
  38. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +13 -3
  39. package/node_modules/pi-lens/dist/clients/complexity-client.js +16 -5
  40. package/node_modules/pi-lens/dist/clients/config-core/deny.js +221 -0
  41. package/node_modules/pi-lens/dist/clients/config-core/index.js +47 -0
  42. package/node_modules/pi-lens/dist/clients/config-core/merge.js +357 -0
  43. package/node_modules/pi-lens/dist/clients/config-core/normalize.js +340 -0
  44. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +248 -0
  45. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +171 -0
  46. package/node_modules/pi-lens/dist/clients/config-core/records.js +218 -0
  47. package/node_modules/pi-lens/dist/clients/config-core/resolve.js +125 -0
  48. package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +78 -0
  49. package/node_modules/pi-lens/dist/clients/config-core/schema.js +167 -0
  50. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +281 -0
  51. package/node_modules/pi-lens/dist/clients/config-locations.js +160 -0
  52. package/node_modules/pi-lens/dist/clients/config-resolve.js +789 -0
  53. package/node_modules/pi-lens/dist/clients/config-schema.js +197 -0
  54. package/node_modules/pi-lens/dist/clients/config-warn.js +407 -0
  55. package/node_modules/pi-lens/dist/clients/dead-code-client.js +19 -14
  56. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +2 -2
  57. package/node_modules/pi-lens/dist/clients/deadline-utils.js +178 -0
  58. package/node_modules/pi-lens/dist/clients/debug-handles.js +2 -2
  59. package/node_modules/pi-lens/dist/clients/debug-heap.js +3 -3
  60. package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +106 -0
  61. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +113 -7
  62. package/node_modules/pi-lens/dist/clients/dependency-checker.js +8 -19
  63. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +12 -9
  64. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +14 -4
  65. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +162 -18
  66. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +97 -0
  67. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +9 -7
  68. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -1
  69. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +380 -26
  70. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -1
  71. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +0 -1
  72. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -1
  73. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -1
  74. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -1
  75. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -1
  76. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -1
  77. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -1
  78. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -1
  79. package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -1
  80. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -1
  81. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -1
  82. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +1 -3
  83. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -1
  84. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -1
  85. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -1
  86. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +0 -1
  87. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -1
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -1
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -1
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -1
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -1
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +13 -18
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -1
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -1
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -1
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +7 -4
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -1
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +0 -1
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +1 -3
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +0 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -1
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +0 -1
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -1
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -1
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -1
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -1
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -1
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -1
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -1
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -1
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -1
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -4
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +194 -80
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +84 -15
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -1
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +0 -1
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -1
  121. package/node_modules/pi-lens/dist/clients/disposition-logger.js +3 -3
  122. package/node_modules/pi-lens/dist/clients/effective-config.js +403 -0
  123. package/node_modules/pi-lens/dist/clients/error-class.js +23 -0
  124. package/node_modules/pi-lens/dist/clients/event-loop-hold.js +274 -0
  125. package/node_modules/pi-lens/dist/clients/extension-log.js +2 -2
  126. package/node_modules/pi-lens/dist/clients/feature-hints.js +2 -1
  127. package/node_modules/pi-lens/dist/clients/file-time.js +6 -1
  128. package/node_modules/pi-lens/dist/clients/file-utils.js +112 -9
  129. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +50 -13
  130. package/node_modules/pi-lens/dist/clients/format-service.js +6 -1
  131. package/node_modules/pi-lens/dist/clients/formatters.js +276 -102
  132. package/node_modules/pi-lens/dist/clients/freshness-cadence.js +17 -0
  133. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +114 -19
  134. package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -9
  135. package/node_modules/pi-lens/dist/clients/git-guard.js +1 -1
  136. package/node_modules/pi-lens/dist/clients/go-client.js +39 -0
  137. package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +252 -0
  138. package/node_modules/pi-lens/dist/clients/hashline-anchor.js +424 -0
  139. package/node_modules/pi-lens/dist/clients/hook-budgets.js +76 -0
  140. package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +5 -2
  141. package/node_modules/pi-lens/dist/clients/host-ports.js +1 -1
  142. package/node_modules/pi-lens/dist/clients/installer/index.js +71 -52
  143. package/node_modules/pi-lens/dist/clients/instance-reaper.js +104 -230
  144. package/node_modules/pi-lens/dist/clients/instance-registry.js +6 -3
  145. package/node_modules/pi-lens/dist/clients/language-registry.js +595 -0
  146. package/node_modules/pi-lens/dist/clients/latency-logger.js +160 -2
  147. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +13 -0
  148. package/node_modules/pi-lens/dist/clients/lens-config.js +141 -30
  149. package/node_modules/pi-lens/dist/clients/lens-engine.js +30 -37
  150. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +59 -6
  151. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -2
  152. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +3 -1
  153. package/node_modules/pi-lens/dist/clients/lsp/client.js +168 -30
  154. package/node_modules/pi-lens/dist/clients/lsp/config.js +483 -158
  155. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +1 -1
  156. package/node_modules/pi-lens/dist/clients/lsp/edits.js +15 -2
  157. package/node_modules/pi-lens/dist/clients/lsp/index.js +676 -71
  158. package/node_modules/pi-lens/dist/clients/lsp/language.js +18 -169
  159. package/node_modules/pi-lens/dist/clients/lsp/launch.js +2 -1
  160. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +5 -16
  161. package/node_modules/pi-lens/dist/clients/lsp/server.js +284 -147
  162. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +116 -23
  163. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +2 -7
  164. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +9 -2
  165. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +1 -1
  166. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +163 -21
  167. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +36 -0
  168. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +33 -4
  169. package/node_modules/pi-lens/dist/clients/mcp/session.js +5 -16
  170. package/node_modules/pi-lens/dist/clients/memory-sampler.js +8 -3
  171. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +2 -4
  172. package/node_modules/pi-lens/dist/clients/module-report.js +20 -45
  173. package/node_modules/pi-lens/dist/clients/mutating-tool.js +651 -0
  174. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +368 -0
  175. package/node_modules/pi-lens/dist/clients/mutation-bridge.js +240 -0
  176. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +247 -20
  177. package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +101 -0
  178. package/node_modules/pi-lens/dist/clients/observed-mutation.js +1215 -0
  179. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +68 -22
  180. package/node_modules/pi-lens/dist/clients/opengrep-config.js +4 -0
  181. package/node_modules/pi-lens/dist/clients/package-manager.js +195 -26
  182. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +359 -80
  183. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +23 -7
  184. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +21 -2
  185. package/node_modules/pi-lens/dist/clients/path-utils.js +428 -13
  186. package/node_modules/pi-lens/dist/clients/persist-debounce.js +8 -1
  187. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +114 -0
  188. package/node_modules/pi-lens/dist/clients/pipeline.js +77 -16
  189. package/node_modules/pi-lens/dist/clients/probe-home-state.js +228 -0
  190. package/node_modules/pi-lens/dist/clients/process-bridge.js +66 -0
  191. package/node_modules/pi-lens/dist/clients/process-snapshot.js +68 -0
  192. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
  193. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +27 -3
  194. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +51 -20
  195. package/node_modules/pi-lens/dist/clients/project-lens-config.js +393 -122
  196. package/node_modules/pi-lens/dist/clients/project-snapshot.js +52 -24
  197. package/node_modules/pi-lens/dist/clients/python-environment.js +217 -0
  198. package/node_modules/pi-lens/dist/clients/python-provenance.js +639 -0
  199. package/node_modules/pi-lens/dist/clients/quiet-window.js +6 -1
  200. package/node_modules/pi-lens/dist/clients/read-bridge.js +13 -21
  201. package/node_modules/pi-lens/dist/clients/read-expansion.js +9 -46
  202. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +15 -4
  203. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +364 -212
  204. package/node_modules/pi-lens/dist/clients/read-guard.js +21 -0
  205. package/node_modules/pi-lens/dist/clients/resource-sampler.js +195 -96
  206. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +152 -109
  207. package/node_modules/pi-lens/dist/clients/review-graph/service.js +55 -3
  208. package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +7 -15
  209. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +10 -74
  210. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +9 -3
  211. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +18 -2
  212. package/node_modules/pi-lens/dist/clients/runtime-context.js +49 -16
  213. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +25 -6
  214. package/node_modules/pi-lens/dist/clients/runtime-session.js +139 -22
  215. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +168 -35
  216. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +797 -243
  217. package/node_modules/pi-lens/dist/clients/runtime-turn.js +1028 -53
  218. package/node_modules/pi-lens/dist/clients/rust-client.js +31 -0
  219. package/node_modules/pi-lens/dist/clients/safe-spawn.js +33 -10
  220. package/node_modules/pi-lens/dist/clients/sanitize.js +7 -1
  221. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +1 -3
  222. package/node_modules/pi-lens/dist/clients/security-scan-client.js +3 -0
  223. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +1 -1
  224. package/node_modules/pi-lens/dist/clients/session-start-observability.js +79 -0
  225. package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +12 -3
  226. package/node_modules/pi-lens/dist/clients/sgconfig.js +6 -1
  227. package/node_modules/pi-lens/dist/clients/skills-resolver.js +105 -0
  228. package/node_modules/pi-lens/dist/clients/smells-rollup.js +2 -2
  229. package/node_modules/pi-lens/dist/clients/string-utils.js +13 -0
  230. package/node_modules/pi-lens/dist/clients/subagent-mode.js +17 -4
  231. package/node_modules/pi-lens/dist/clients/test-runner-client.js +176 -39
  232. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +239 -0
  233. package/node_modules/pi-lens/dist/clients/tool-definition.js +41 -1
  234. package/node_modules/pi-lens/dist/clients/tool-policy.js +91 -21
  235. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +40 -40
  236. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +86 -48
  237. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +2 -2
  238. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +81 -1
  239. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +51 -46
  240. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +28 -0
  241. package/node_modules/pi-lens/dist/clients/typos-config.js +5 -0
  242. package/node_modules/pi-lens/dist/clients/user-notify.js +6 -2
  243. package/node_modules/pi-lens/dist/clients/widget-state.js +191 -26
  244. package/node_modules/pi-lens/dist/clients/word-index-logger.js +2 -2
  245. package/node_modules/pi-lens/dist/clients/word-index-store.js +20 -5
  246. package/node_modules/pi-lens/dist/clients/word-index.js +243 -31
  247. package/node_modules/pi-lens/dist/clients/workspace-topology.js +8 -1
  248. package/node_modules/pi-lens/dist/clients/zizmor-config.js +3 -0
  249. package/node_modules/pi-lens/dist/index.js +64335 -54300
  250. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -1
  251. package/node_modules/pi-lens/dist/mcp/server.js +127 -5
  252. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +583 -0
  253. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +129 -0
  254. package/node_modules/pi-lens/dist/tools/effective-config.js +89 -0
  255. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +95 -24
  256. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +67 -80
  257. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +37 -16
  258. package/node_modules/pi-lens/dist/tools/shared.js +0 -1
  259. package/node_modules/pi-lens/docs/agent-guide.md +64 -1
  260. package/node_modules/pi-lens/docs/configuration.md +222 -0
  261. package/node_modules/pi-lens/docs/dependencies.md +3 -3
  262. package/node_modules/pi-lens/docs/features.md +44 -5
  263. package/node_modules/pi-lens/docs/language-coverage.md +2 -2
  264. package/node_modules/pi-lens/docs/pi-lens-fixer.md +25 -0
  265. package/node_modules/pi-lens/docs/pi-lens-investigator.md +25 -0
  266. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +27 -0
  267. package/node_modules/pi-lens/docs/pi-lens-subagent.md +38 -0
  268. package/node_modules/pi-lens/docs/pi-lens-warden.md +55 -0
  269. package/node_modules/pi-lens/docs/public-api-stability.md +359 -0
  270. package/node_modules/pi-lens/docs/release-qa-baseline.md +182 -0
  271. package/node_modules/pi-lens/docs/subagent-compat.md +110 -30
  272. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +2 -2
  273. package/node_modules/pi-lens/package.json +15 -23
  274. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
  275. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
  276. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
  277. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +192 -1
  278. package/node_modules/pi-lens/scripts/install-selftest.mjs +58 -2
  279. package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +129 -0
  280. package/node_modules/pi-lens/scripts/rpc-load-check.mjs +3 -1
  281. package/node_modules/pi-web-access/CHANGELOG.md +28 -0
  282. package/node_modules/pi-web-access/README.md +62 -15
  283. package/node_modules/pi-web-access/curator-page.ts +3 -1
  284. package/node_modules/pi-web-access/curator-server.ts +5 -1
  285. package/node_modules/pi-web-access/gemini-search.ts +8 -4
  286. package/node_modules/pi-web-access/github-extract.ts +242 -1
  287. package/node_modules/pi-web-access/index.ts +118 -64
  288. package/node_modules/pi-web-access/mistral-search.ts +281 -0
  289. package/node_modules/pi-web-access/package.json +2 -2
  290. package/node_modules/pi-web-access/perplexity.ts +14 -1
  291. package/node_modules/pi-web-access/utils.ts +23 -6
  292. package/node_modules/pi-web-access/xai-search.ts +96 -33
  293. package/package.json +5 -5
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Monotonic deny precedence (#2425, scope item 4; #2415 AC 3).
3
+ *
4
+ * Ordinary config values are last-tier-wins. Denials are not. A denial is a
5
+ * SECURITY decision, and the tier that made it is usually the tier the user
6
+ * actually controls, so the ordinary rule is exactly backwards for it: today a
7
+ * repository can hand pi-lens a `.pi-lens.json` that re-enables a server the
8
+ * user disabled globally, and nothing notices.
9
+ *
10
+ * The rule, in one sentence: a denial from an OPERATOR tier is never lifted, and
11
+ * a denial from any other tier is lifted only by an explicit operator allow of
12
+ * higher precedence. `provenance.ts` owns the three-way tier classification;
13
+ * this module owns what it means.
14
+ *
15
+ * The third class is why the sentence says "any other tier" rather than "a repo
16
+ * tier" (#2440 review finding F3). `builtin` is `default`, not `operator`: a
17
+ * denial pi-lens itself ships is liftable by the person running pi-lens — their
18
+ * global config, their environment, their command line — and remains unliftable
19
+ * by anything that arrived with a checkout. Treating a shipped default as an
20
+ * operator decision made it permanent for everyone, which is a hard-coded
21
+ * decision rather than a default.
22
+ *
23
+ * The two shapes a denial takes (`schema.ts`'s `x-deny`):
24
+ *
25
+ * - `boolean-false` — `enabled: false`. Resolution below.
26
+ * - `array-union` — `disabledServers: ["x"]`. The union of every tier's
27
+ * members, always. There is no vocabulary for UN-denying a member, so a
28
+ * nearer tier that omits one is expressing nothing, not an allow. This is the
29
+ * one place `x-merge-strategy` does not decide the outcome: a `replace`
30
+ * strategy still governs how the array is SPELLED across tiers, and the deny
31
+ * union still governs which members survive. A deny that a merge strategy
32
+ * could erase would not be monotonic.
33
+ *
34
+ * Pure functions over their arguments. No state, no ledger writes: a denial
35
+ * being honored is the normal case, not a degradation.
36
+ */
37
+ import { isOperatorTier, tierPrecedence, } from "./provenance.js";
38
+ function provenanceOfContribution(source, key) {
39
+ if (!source)
40
+ return undefined;
41
+ return {
42
+ tier: source.tier,
43
+ key,
44
+ ...(source.file === undefined ? {} : { file: source.file }),
45
+ ...(source.trust === undefined ? {} : { trust: source.trust }),
46
+ };
47
+ }
48
+ /** Build a provenance entry for a resolved deny leaf. */
49
+ export function denyProvenance(contributions, resolution, key) {
50
+ return provenanceOfContribution(contributions[resolution.winner], key);
51
+ }
52
+ /**
53
+ * One provenance entry per SURVIVING member of an `array-union`, keyed by the
54
+ * member's own JSON pointer.
55
+ *
56
+ * The array's own entry still exists and still names the first denying tier;
57
+ * these are strictly additional, so a consumer that asks about the array is
58
+ * unaffected and a consumer that asks about a member gets the tier that
59
+ * actually denied it (#2427 review round 2, F2).
60
+ */
61
+ export function denyMemberProvenance(contributions, resolution, key) {
62
+ const entries = [];
63
+ resolution.memberWinners.forEach((index, position) => {
64
+ const pointer = `${key}/${position}`;
65
+ const entry = provenanceOfContribution(contributions[index], pointer);
66
+ if (entry)
67
+ entries.push(entry);
68
+ });
69
+ return entries;
70
+ }
71
+ /** The `memberWinners` of a leaf with no members. One shared frozen empty. */
72
+ const EMPTY_MEMBER_WINNERS = Object.freeze([]);
73
+ /** Contributions sorted by tier precedence, lowest first, ties in caller order. */
74
+ function byPrecedence(contributions) {
75
+ return contributions
76
+ .map((contribution, index) => ({ contribution, index }))
77
+ .sort((left, right) => tierPrecedence(left.contribution.tier) -
78
+ tierPrecedence(right.contribution.tier));
79
+ }
80
+ /**
81
+ * Resolve a `boolean-false` deny.
82
+ *
83
+ * - No `false` anywhere: ordinary last-tier-wins.
84
+ * - An OPERATOR tier said `false`: DENIED, pinned, unliftable by anything. The
85
+ * winner is the LOWEST-precedence operator denial, because that is the answer
86
+ * to "who denied this" — the first authority to say no, not the last tier to
87
+ * repeat it. This holds even for a nearer operator tier: `cli: true` does not
88
+ * lift `global: false`. The escape hatch for an operator who changes their
89
+ * mind is to change the operator-tier config that denied, not to out-rank it
90
+ * from another operator tier.
91
+ * - Otherwise every denial came from `default` or `repo` tiers, and an OPERATOR
92
+ * tier ranked above the FIRST such denial lifts it by saying `true` outright.
93
+ * That is what makes a built-in default a default (`builtin: false` +
94
+ * `global: true` -> `true`) while keeping repository content out of the
95
+ * decision (`builtin: false` + `project: true` -> `false`). A repo tier never
96
+ * lifts anything, its own class included.
97
+ *
98
+ * The lift test asks `isOperatorTier`, NOT `!isRepoTier`. The negative spelling
99
+ * is what silently swept `builtin` into the operator class and made every
100
+ * shipped default permanent; the affirmative one cannot.
101
+ *
102
+ * Non-boolean contributions are ignored here: `normalize.ts` has already
103
+ * dropped values that failed the schema, so anything left is a boolean.
104
+ */
105
+ export function resolveBooleanDeny(contributions) {
106
+ const ordered = byPrecedence(contributions);
107
+ if (ordered.length === 0)
108
+ return {
109
+ value: undefined,
110
+ winner: -1,
111
+ denied: false,
112
+ memberWinners: EMPTY_MEMBER_WINNERS,
113
+ };
114
+ const denials = ordered.filter((entry) => entry.contribution.value === false);
115
+ if (denials.length === 0) {
116
+ const last = ordered[ordered.length - 1];
117
+ return {
118
+ value: last.contribution.value,
119
+ winner: last.index,
120
+ denied: false,
121
+ memberWinners: EMPTY_MEMBER_WINNERS,
122
+ };
123
+ }
124
+ const operatorDenial = denials.find((entry) => isOperatorTier(entry.contribution.tier));
125
+ if (operatorDenial) {
126
+ return {
127
+ value: false,
128
+ winner: operatorDenial.index,
129
+ denied: true,
130
+ memberWinners: EMPTY_MEMBER_WINNERS,
131
+ };
132
+ }
133
+ // Every denial came from a shipped default or from repo content. An operator
134
+ // tier ranked above the FIRST such denial may lift it by saying `true`.
135
+ const firstDenial = denials[0];
136
+ const lift = ordered.find((entry) => entry.contribution.value === true &&
137
+ isOperatorTier(entry.contribution.tier) &&
138
+ tierPrecedence(entry.contribution.tier) >
139
+ tierPrecedence(firstDenial.contribution.tier));
140
+ if (lift) {
141
+ return {
142
+ value: true,
143
+ winner: lift.index,
144
+ denied: false,
145
+ memberWinners: EMPTY_MEMBER_WINNERS,
146
+ };
147
+ }
148
+ return {
149
+ value: false,
150
+ winner: firstDenial.index,
151
+ denied: true,
152
+ memberWinners: EMPTY_MEMBER_WINNERS,
153
+ };
154
+ }
155
+ /**
156
+ * Resolve an `array-union` deny: the union of every tier's members.
157
+ *
158
+ * Members keep first-contribution order, lowest precedence first, so the
159
+ * resolved list reads oldest-authority-first and is stable across runs.
160
+ * Duplicates are folded by their JSON encoding, which is exact for the
161
+ * primitives a deny list holds and conservative (keeps both) for anything else.
162
+ *
163
+ * The winner is the FIRST tier that contributed a surviving member: the tier
164
+ * that answers "why can I not turn this back on".
165
+ */
166
+ export function resolveArrayDeny(contributions) {
167
+ const ordered = byPrecedence(contributions);
168
+ const seen = new Set();
169
+ const members = [];
170
+ // Positionally parallel to `members`: the contribution that introduced each
171
+ // surviving member, which is the per-member answer to "who denied this"
172
+ // (#2427 review round 2, F2).
173
+ const memberWinners = [];
174
+ let winner = -1;
175
+ for (const entry of ordered) {
176
+ const value = entry.contribution.value;
177
+ if (!Array.isArray(value))
178
+ continue;
179
+ for (const member of value) {
180
+ const identity = primitiveIdentity(member);
181
+ if (identity !== undefined) {
182
+ if (seen.has(identity))
183
+ continue;
184
+ seen.add(identity);
185
+ }
186
+ members.push(member);
187
+ memberWinners.push(entry.index);
188
+ if (winner === -1)
189
+ winner = entry.index;
190
+ }
191
+ }
192
+ if (winner === -1) {
193
+ // Nothing denied. Still report the array so a consumer sees an empty list
194
+ // rather than an absent field, and attribute it to the last contributor.
195
+ const last = ordered[ordered.length - 1];
196
+ return {
197
+ value: members,
198
+ winner: last ? last.index : -1,
199
+ denied: false,
200
+ memberWinners,
201
+ };
202
+ }
203
+ return { value: members, winner, denied: members.length > 0, memberWinners };
204
+ }
205
+ /**
206
+ * A fold key for a primitive member, or `undefined` for anything else.
207
+ *
208
+ * Only primitives are de-duplicated. Two structurally equal objects can carry
209
+ * different meaning, and keeping both is the conservative half of a deny list —
210
+ * a union that dropped one would be un-denying something.
211
+ */
212
+ function primitiveIdentity(member) {
213
+ if (typeof member === "string")
214
+ return `s:${member}`;
215
+ if (typeof member === "number" || typeof member === "boolean") {
216
+ return `p:${String(member)}`;
217
+ }
218
+ if (member === null)
219
+ return "null";
220
+ return undefined;
221
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * THE shared config core (#2425). Every future config loader, catalog, and
3
+ * selector resolves through this module rather than growing a fourth merge
4
+ * semantics of its own.
5
+ *
6
+ * The pipeline, in one line:
7
+ *
8
+ * RawConfig -> validate(schema) -> NormalizedConfig -> merge(sources) -> Resolved<T>
9
+ *
10
+ * `resolveConfig` runs both halves and is the front door. It is a pure function
11
+ * of its arguments: no file reads, no ledger writes, no logging. Reporting is a
12
+ * separate, explicit step — `reportPiLensConfigRecords` in
13
+ * `clients/config-resolve.ts` — so a caller decides when a user gets warned and
14
+ * under which subsystem; a library that warns on its own would fragment the
15
+ * warn-once latch across every consumer.
16
+ *
17
+ * That step used to live HERE, in `records.ts`, which meant this "pure" library
18
+ * imported `config-warn.js` and the degradation ledger (#2426). Purity was the
19
+ * claim and the import graph disagreed: it broke every suite that `vi.mock`s the
20
+ * ledger wholesale, and it closed seven import cycles the moment the first
21
+ * loader — all of which sit downstream of `file-utils.ts` — resolved through the
22
+ * core. The reporting function moved to the module that owns the loaders'
23
+ * subsystem vocabulary, and the one constant records needs from the ledger
24
+ * (`DEGRADATION_ENTRIES_PER_KIND`) moved to the `ledger-bounds.js` leaf that
25
+ * exists for exactly this. Nothing under `config-core/` imports a sink now.
26
+ *
27
+ * ALL THREE LOADERS ARE WIRED as of #2426, through `clients/config-resolve.ts`
28
+ * and the canonical schema in `clients/config-schema.ts`:
29
+ * `clients/lsp/config.ts` (`loadLSPConfig`), `clients/lens-config.ts`
30
+ * (`loadPiLensGlobalConfig`), and `clients/project-lens-config.ts`
31
+ * (`loadPiLensProjectConfig`) are now projections of one resolution. #2416
32
+ * brings the `lsp.servers.<id>` shape the schema currently only reserves.
33
+ */
34
+ export { additionalPropertyPolicy, DEFAULT_MERGE_STRATEGY, DENY_KEY, DENY_POLICIES, denyPolicyOf, isDenyPolicy, isConfigObject, isKnownSchemaType, isMergeStrategy, isPlainObject, isSchemaNode, itemsSchema, keyedField, MERGE_STRATEGY_KEY, mergeStrategyOf, propertySchema, SCHEMA_TYPES, schemaType, STABILITY_TIER_KEY, } from "./schema.js";
35
+ export { compareKeys, isOperatorTier, isRepoTier, provenanceFor, provenanceView, SOURCE_TIERS, TIER_CLASS, TIER_PRECEDENCE, tierPrecedence, } from "./provenance.js";
36
+ export { isUnsafeConfigKey, MAX_CONFIG_DEPTH, safeAssign, UNSAFE_CONFIG_KEYS, UNSAFE_KEY_REASON, } from "./safe-object.js";
37
+ export { denyProvenance, resolveArrayDeny, resolveBooleanDeny, } from "./deny.js";
38
+ export { validate, } from "./normalize.js";
39
+ export { boundedKeyLabel, jsonTypeName, MAX_MIGRATION_RECORDS, MAX_RECORD_KEY_LENGTH, MigrationRecordCollector, migrationSubject, } from "./records.js";
40
+ export { buildProcessSpec, MAX_ARGV_BYTES, MAX_ARGV_ENTRIES, MAX_ENV_BYTES, MAX_ENV_ENTRIES, MAX_TIMEOUT_MS, redactProcessSpec, toSpawnArgs, } from "./process-spec.js";
41
+ // `resolveConfig` and its types live in `./resolve.js` (#2426) so a caller can
42
+ // import the front door without also importing this barrel's width — in
43
+ // particular `process-spec.js` -> `project-trust.js`, which closed three
44
+ // import cycles once the config loaders (all downstream of `file-utils.ts`)
45
+ // started resolving through the core. The public surface is unchanged: this is
46
+ // still where the supported entry point is exported from.
47
+ export { resolveConfig, } from "./resolve.js";
@@ -0,0 +1,357 @@
1
+ /**
2
+ * Field-wise merge with per-leaf provenance (#2425, scope items 2 and 3).
3
+ *
4
+ * The three loaders this replaces each merge differently: `lsp/config.ts`
5
+ * spreads two objects and hand-patches four keys, `lens-config.ts` projects
6
+ * field by field, and `project-lens-config.ts` never merges with the global
7
+ * config at all. One merger with a per-node strategy expresses all three, and
8
+ * — unlike any of them — it can say afterwards where each value came from.
9
+ *
10
+ * Rules:
11
+ *
12
+ * - OBJECTS ARE ALWAYS FIELD-WISE. A nearer tier that sets one key of an object
13
+ * does not erase the rest. This is #2415's "never whole-object replacement".
14
+ * - ARRAYS follow the node's `x-merge-strategy`: `replace` by default, or
15
+ * `append`, or `keyed:<field>`.
16
+ * - DENY-ANNOTATED NODES go to `deny.ts` instead, whatever the merge strategy
17
+ * says. Monotonic deny outranks precedence by construction.
18
+ * - EVERY LEAF GETS PROVENANCE, keyed by its JSON-pointer path in the RESOLVED
19
+ * value, so a consumer navigates from an entry to the value it describes.
20
+ *
21
+ * Sources are sorted by tier precedence rather than trusted in caller order, so
22
+ * assembling them in a different order cannot silently invert precedence. Ties
23
+ * keep caller order, which is how several `nested-project` files stay ordered
24
+ * from outermost to innermost.
25
+ *
26
+ * `merge()`'S CONTRACT IS NARROWER THAN "IT ENFORCES THE TWO BOUNDS TOO", which
27
+ * an earlier version of this comment claimed (#2440 review round 3, finding
28
+ * F8). It required
29
+ * every source's `value` to already be `validate()`'s output — a sentence on
30
+ * `ConfigSource`, not a compiler check — and does NOT independently re-walk a
31
+ * value to confirm that. Five arms hand a contributor's value back BY
32
+ * REFERENCE, never walked: the leaf fall-through in `mergeNode`, the
33
+ * no-object arm in `mergeObject`, the no-array arm in `mergeArray`, `replace`,
34
+ * and `appendArrays`. Any of those can be reached with an opaque schema node
35
+ * whose tiers disagree on shape (one contributes an array, another a deeply
36
+ * nested object at the same key) — `isObjectNode`/`isArrayNode` both answer
37
+ * "no" for a mixed-shape node, the recursion falls through to the leaf arm,
38
+ * and whatever depth or hostile keys live inside that value ride along
39
+ * unexamined and unrecorded. Closing those five arms would mean re-deriving
40
+ * `normalize.ts`'s walk a second time here — the "half-enforcement" this
41
+ * function used to attempt and get half right.
42
+ *
43
+ * The fix is the CONTRACT, not a second walk: `merge()` is exported from this
44
+ * file (`@internal`) for `resolveConfig` and for tests that probe it
45
+ * directly, but it is not part of `index.ts`'s public surface. `resolveConfig`
46
+ * is the one supported way in, and it validates every source before merging —
47
+ * a mutation test in `hostile-input.test.ts` proves that by skipping
48
+ * `validate()` on one source and watching the suite go red.
49
+ *
50
+ * The depth counter (`mergeNode`) and the prototype-key check (`mergeObject`,
51
+ * over the KEYS an already-validated object's field-wise merge is about to
52
+ * combine) still run. They are a backstop for the common, well-typed
53
+ * recursion — a value `validate()` already bounded and sanitized, or a
54
+ * same-shape hand-built value in a direct `merge()` test — not an
55
+ * independent enforcement boundary; the paragraph above is what they do NOT
56
+ * cover. The key check also still matters for one real case even after
57
+ * `validate()`: a hostile key one tier introduces at a node the OTHER tier
58
+ * also contributes to is a key `mergeObject` discovers only when it unions
59
+ * that node's field names across contributions, so it is the one place that
60
+ * can still name and record it, even though `safeAssign` alone would already
61
+ * have refused the write.
62
+ *
63
+ * Pure: no state, no I/O, no ledger writes. Records, when the caller supplies a
64
+ * collector, describe what the two backstops refused.
65
+ */
66
+ import { denyMemberProvenance, denyProvenance, resolveArrayDeny, resolveBooleanDeny, } from "./deny.js";
67
+ import { isUnsafeConfigKey, MAX_CONFIG_DEPTH, safeAssign, UNSAFE_KEY_REASON, } from "./safe-object.js";
68
+ import { boundedKeyLabel, MigrationRecordCollector, migrationSubject, } from "./records.js";
69
+ import { tierPrecedence, } from "./provenance.js";
70
+ import { denyPolicyOf, isConfigObject, isKnownSchemaType, isPlainObject, isSchemaNode, itemsSchema, keyedField, mergeStrategyOf, propertySchema, schemaType, } from "./schema.js";
71
+ /**
72
+ * Merge normalized sources into one resolved config with per-leaf provenance.
73
+ *
74
+ * Sources whose value is `undefined` contribute nothing — that is how a config
75
+ * file that failed validation drops out without a special case.
76
+ *
77
+ * @internal Requires every source's `value` to already be `validate()`'s
78
+ * output. Not part of `clients/config-core/index.ts`'s public surface —
79
+ * `resolveConfig` is. See this module's doc comment for what merging a
80
+ * hand-built value can still get past.
81
+ */
82
+ export function merge(sources, schema, options = {}) {
83
+ const ordered = [...sources]
84
+ .map((source, order) => ({ source, order }))
85
+ .sort((left, right) => {
86
+ const byTier = tierPrecedence(left.source.tier) - tierPrecedence(right.source.tier);
87
+ return byTier !== 0 ? byTier : left.order - right.order;
88
+ })
89
+ .map((entry, rank) => ({ source: entry.source, rank }));
90
+ const contributions = [];
91
+ for (const entry of ordered) {
92
+ // A source whose value is `undefined` contributes nothing. Filtering with
93
+ // a narrowing loop rather than `.filter()` keeps the element type exact:
94
+ // `Array.filter` cannot drop `undefined` from the value's type on its own.
95
+ const value = entry.source.value;
96
+ if (value === undefined)
97
+ continue;
98
+ contributions.push({ source: entry.source, value, rank: entry.rank });
99
+ }
100
+ const provenance = new Map();
101
+ const context = {
102
+ provenance,
103
+ collector: options.collector ?? new MigrationRecordCollector(),
104
+ file: options.file ?? "",
105
+ };
106
+ const value = mergeNode(contributions, schema, "", context, 0);
107
+ return { value: value, provenance };
108
+ }
109
+ /** Record one node the merger's own bounds refused. Never carries a value. */
110
+ function record(context, entry) {
111
+ const key = boundedKeyLabel(entry.key);
112
+ context.collector.add({
113
+ code: entry.code,
114
+ file: context.file,
115
+ key,
116
+ subject: migrationSubject(context.file, key),
117
+ reason: entry.reason,
118
+ });
119
+ }
120
+ function provenanceOf(contribution, key) {
121
+ const { source } = contribution;
122
+ return {
123
+ tier: source.tier,
124
+ key,
125
+ ...(source.file === undefined ? {} : { file: source.file }),
126
+ ...(source.trust === undefined ? {} : { trust: source.trust }),
127
+ };
128
+ }
129
+ function stamp(context, contribution, key) {
130
+ context.provenance.set(key, provenanceOf(contribution, key));
131
+ }
132
+ function mergeNode(contributions, schema, key, context, depth) {
133
+ if (contributions.length === 0)
134
+ return undefined;
135
+ // The merger's own depth bound. `validate()` already truncated anything it
136
+ // walked, so this fires only for a value that reached `merge()` without it —
137
+ // which the exported signature permits and a review probe demonstrated.
138
+ if (depth > MAX_CONFIG_DEPTH) {
139
+ record(context, {
140
+ code: "PILENS_CFG_0005",
141
+ key,
142
+ reason: `config nesting exceeds ${MAX_CONFIG_DEPTH} levels; ignored`,
143
+ });
144
+ return undefined;
145
+ }
146
+ const denyPolicy = denyPolicyOf(schema);
147
+ if (denyPolicy)
148
+ return mergeDeny(contributions, denyPolicy, key, context);
149
+ if (isObjectNode(schema, contributions)) {
150
+ return mergeObject(contributions, schema, key, context, depth);
151
+ }
152
+ if (isArrayNode(schema, contributions)) {
153
+ return mergeArray(contributions, schema, key, context, depth);
154
+ }
155
+ const winner = contributions[contributions.length - 1];
156
+ stamp(context, winner, key);
157
+ return winner.value;
158
+ }
159
+ /**
160
+ * An object node: either the schema says so, or every contribution IS a plain
161
+ * object. The second arm matters because a schema may be opaque about a
162
+ * sub-object, and merging two objects whole would drop the field-wise rule the
163
+ * moment a schema is incomplete.
164
+ */
165
+ function isObjectNode(schema, contributions) {
166
+ const declared = schemaType(schema);
167
+ if (declared === "object")
168
+ return true;
169
+ // Only a RECOGNIZED non-object type rules object-ness out. An unrecognized
170
+ // keyword leaves the node opaque, and an opaque node is decided by the
171
+ // value's shape — the same rule `normalize.ts` walks by, so one schema typo
172
+ // cannot give the two halves different merge semantics.
173
+ if (isKnownSchemaType(declared))
174
+ return false;
175
+ if (schema && isSchemaNode(schema.properties))
176
+ return true;
177
+ return contributions.every((entry) => isPlainObject(entry.value));
178
+ }
179
+ function isArrayNode(schema, contributions) {
180
+ const declared = schemaType(schema);
181
+ if (declared === "array")
182
+ return true;
183
+ if (isKnownSchemaType(declared))
184
+ return false;
185
+ return contributions.every((entry) => Array.isArray(entry.value));
186
+ }
187
+ function mergeDeny(contributions, policy, key, context) {
188
+ const denyContributions = contributions.map((entry) => ({
189
+ tier: entry.source.tier,
190
+ value: entry.value,
191
+ ...(entry.source.file === undefined ? {} : { file: entry.source.file }),
192
+ ...(entry.source.trust === undefined ? {} : { trust: entry.source.trust }),
193
+ }));
194
+ const resolution = policy === "boolean-false"
195
+ ? resolveBooleanDeny(denyContributions)
196
+ : resolveArrayDeny(denyContributions);
197
+ const entry = denyProvenance(denyContributions, resolution, key);
198
+ if (entry)
199
+ context.provenance.set(key, entry);
200
+ // Plus one entry per surviving MEMBER of a union (#2427 review round 2,
201
+ // F2). The array's own entry answers "which tier denied first"; a caller
202
+ // asking about one member — "why is THIS server off" — needs the tier that
203
+ // contributed that member, and reading the array entry for it reported the
204
+ // wrong tier for every member after the first.
205
+ for (const member of denyMemberProvenance(denyContributions, resolution, key)) {
206
+ context.provenance.set(member.key, member);
207
+ }
208
+ return resolution.value;
209
+ }
210
+ function mergeObject(contributions, schema, key, context, depth) {
211
+ const objects = contributions.filter((entry) => isConfigObject(entry.value));
212
+ if (objects.length === 0) {
213
+ const winner = contributions[contributions.length - 1];
214
+ stamp(context, winner, key);
215
+ return winner.value;
216
+ }
217
+ // Field order: lowest tier's keys first, each nearer tier appending only the
218
+ // keys it introduces. A resolved config therefore reads in the order the
219
+ // user's own files introduced things.
220
+ //
221
+ // A prototype-modifying name is refused HERE, before it can become a child
222
+ // key: dropping it at the assignment alone would still have walked it and
223
+ // stamped provenance at a pointer no resolved value can be read from.
224
+ const names = [];
225
+ for (const entry of objects) {
226
+ for (const name of Object.keys(entry.value)) {
227
+ if (names.includes(name))
228
+ continue;
229
+ if (isUnsafeConfigKey(name)) {
230
+ record(context, {
231
+ code: "PILENS_CFG_0006",
232
+ key: `${key}/${name}`,
233
+ reason: UNSAFE_KEY_REASON,
234
+ });
235
+ continue;
236
+ }
237
+ names.push(name);
238
+ }
239
+ }
240
+ const out = {};
241
+ for (const name of names) {
242
+ const childKey = `${key}/${name}`;
243
+ const childContributions = [];
244
+ for (const entry of objects) {
245
+ const parent = entry.value;
246
+ if (!Object.hasOwn(parent, name))
247
+ continue;
248
+ childContributions.push({
249
+ source: entry.source,
250
+ value: parent[name],
251
+ rank: entry.rank,
252
+ });
253
+ }
254
+ const merged = mergeNode(childContributions, propertySchema(schema, name), childKey, context, depth + 1);
255
+ if (merged !== undefined)
256
+ safeAssign(out, name, merged);
257
+ }
258
+ return out;
259
+ }
260
+ function mergeArray(contributions, schema, key, context, depth) {
261
+ const arrays = contributions.filter((entry) => Array.isArray(entry.value));
262
+ if (arrays.length === 0) {
263
+ const winner = contributions[contributions.length - 1];
264
+ stamp(context, winner, key);
265
+ return winner.value;
266
+ }
267
+ const strategy = mergeStrategyOf(schema);
268
+ const keyed = keyedField(strategy);
269
+ if (keyed !== undefined) {
270
+ return mergeKeyedArray(arrays, schema, keyed, key, context, depth);
271
+ }
272
+ if (strategy === "append")
273
+ return appendArrays(arrays, key, context);
274
+ // `replace`: the highest-precedence contributor supplies the whole array,
275
+ // and one provenance entry at the array's own pointer says who. Per-element
276
+ // entries would be noise: every element has the same answer.
277
+ const winner = arrays[arrays.length - 1];
278
+ stamp(context, winner, key);
279
+ return [...winner.value];
280
+ }
281
+ function appendArrays(arrays, key, context) {
282
+ const out = [];
283
+ for (const entry of arrays) {
284
+ for (const member of entry.value) {
285
+ stamp(context, entry, `${key}/${out.length}`);
286
+ out.push(member);
287
+ }
288
+ }
289
+ // The array itself is attributed to its lowest-precedence contributor: with
290
+ // `append`, that is the tier the list STARTED at.
291
+ stamp(context, arrays[0], key);
292
+ return out;
293
+ }
294
+ /**
295
+ * `keyed:<field>` — match entries across tiers by `<field>` and merge them
296
+ * field-wise, so a project file can override one server's `command` without
297
+ * restating the whole catalog entry, and without erasing the sibling entries.
298
+ *
299
+ * Entries missing the key field cannot be matched, so they are appended in
300
+ * contribution order and attributed to their own source. Dropping them would be
301
+ * silent data loss; matching them together would merge unrelated entries.
302
+ */
303
+ function mergeKeyedArray(arrays, schema, field, key, context, depth) {
304
+ const items = itemsSchema(schema);
305
+ const order = [];
306
+ const groups = new Map();
307
+ const unkeyed = [];
308
+ for (const entry of arrays) {
309
+ for (const member of entry.value) {
310
+ const identity = keyIdentity(member, field);
311
+ const memberContribution = {
312
+ source: entry.source,
313
+ value: member,
314
+ rank: entry.rank,
315
+ };
316
+ if (identity === undefined) {
317
+ unkeyed.push(memberContribution);
318
+ continue;
319
+ }
320
+ const group = groups.get(identity);
321
+ if (group) {
322
+ group.push(memberContribution);
323
+ continue;
324
+ }
325
+ order.push(identity);
326
+ groups.set(identity, [memberContribution]);
327
+ }
328
+ }
329
+ const out = [];
330
+ const pushMerged = (group) => {
331
+ const merged = mergeNode(group, items, `${key}/${out.length}`, context, depth + 1);
332
+ // An entry can only merge to `undefined` when its group is empty, which
333
+ // the construction above cannot produce; dropping it keeps the resolved
334
+ // list free of holes either way.
335
+ if (merged !== undefined)
336
+ out.push(merged);
337
+ };
338
+ for (const identity of order)
339
+ pushMerged(groups.get(identity) ?? []);
340
+ for (const entry of unkeyed)
341
+ pushMerged([entry]);
342
+ // Attribute the list itself to its lowest-precedence contributor, matching
343
+ // `append`: a keyed list is a list every tier extends, not one a tier owns.
344
+ stamp(context, arrays[0], key);
345
+ return out;
346
+ }
347
+ function keyIdentity(member, field) {
348
+ if (!isConfigObject(member))
349
+ return undefined;
350
+ const value = member[field];
351
+ if (typeof value === "string")
352
+ return `s:${value}`;
353
+ if (typeof value === "number" && Number.isFinite(value)) {
354
+ return `n:${String(value)}`;
355
+ }
356
+ return undefined;
357
+ }