pi-lens 4.1.2 → 4.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (359) hide show
  1. package/CHANGELOG.md +1308 -0
  2. package/README.md +3 -0
  3. package/config/biome/core.jsonc +11 -2
  4. package/config/dependency-cruiser-eager-allowlist.json +91 -0
  5. package/dist/clients/actionable-warnings-logger.js +18 -3
  6. package/dist/clients/actionable-warnings.js +927 -80
  7. package/dist/clients/agent-behavior-client.js +13 -4
  8. package/dist/clients/ast-grep-client.js +136 -9
  9. package/dist/clients/ast-grep-rule-manager.js +60 -5
  10. package/dist/clients/ast-grep-tool-logger.js +2 -2
  11. package/dist/clients/availability-probe-flight.js +12 -0
  12. package/dist/clients/bash-file-access.js +1 -3
  13. package/dist/clients/biome-client.js +16 -2
  14. package/dist/clients/blocker-freshness.js +34 -0
  15. package/dist/clients/bootstrap.js +509 -73
  16. package/dist/clients/bounded-cache.js +152 -12
  17. package/dist/clients/bounded-telemetry.js +115 -23
  18. package/dist/clients/build-identity.js +95 -0
  19. package/dist/clients/bundled-resource-health.js +113 -0
  20. package/dist/clients/bus-events-logger.js +2 -2
  21. package/dist/clients/bus-publish.js +3 -0
  22. package/dist/clients/cache/rule-cache.js +33 -5
  23. package/dist/clients/cache-manager.js +105 -6
  24. package/dist/clients/cache-observability.js +46 -19
  25. package/dist/clients/cargo-manifest.js +422 -0
  26. package/dist/clients/cascade-logger.js +17 -3
  27. package/dist/clients/code-quality-warnings.js +13 -3
  28. package/dist/clients/complexity-client.js +16 -5
  29. package/dist/clients/config-core/deny.js +221 -0
  30. package/dist/clients/config-core/index.js +47 -0
  31. package/dist/clients/config-core/merge.js +357 -0
  32. package/dist/clients/config-core/normalize.js +340 -0
  33. package/dist/clients/config-core/process-spec.js +248 -0
  34. package/dist/clients/config-core/provenance.js +171 -0
  35. package/dist/clients/config-core/records.js +218 -0
  36. package/dist/clients/config-core/resolve.js +125 -0
  37. package/dist/clients/config-core/safe-object.js +78 -0
  38. package/dist/clients/config-core/schema.js +167 -0
  39. package/dist/clients/config-diagnostic-codes.js +281 -0
  40. package/dist/clients/config-locations.js +160 -0
  41. package/dist/clients/config-resolve.js +789 -0
  42. package/dist/clients/config-schema.js +197 -0
  43. package/dist/clients/config-warn.js +407 -0
  44. package/dist/clients/dead-code-client.js +33 -25
  45. package/dist/clients/dead-code-logger.js +2 -2
  46. package/dist/clients/deadline-utils.js +178 -0
  47. package/dist/clients/debug-handles.js +2 -2
  48. package/dist/clients/debug-heap.js +3 -3
  49. package/dist/clients/deferred-lsp-work.js +106 -0
  50. package/dist/clients/degradation-ledger.js +166 -8
  51. package/dist/clients/demoted-finding-render.js +14 -0
  52. package/dist/clients/dependency-checker.js +28 -22
  53. package/dist/clients/deps/minimatch.js +1 -1
  54. package/dist/clients/diagnostic-line-freshness.js +12 -9
  55. package/dist/clients/diagnostic-logger.js +14 -4
  56. package/dist/clients/dispatch/collect-later-tier.js +39 -0
  57. package/dist/clients/dispatch/dispatcher.js +304 -19
  58. package/dist/clients/dispatch/fact-store.js +328 -9
  59. package/dist/clients/dispatch/integration.js +214 -107
  60. package/dist/clients/dispatch/pending-runner-findings.js +87 -0
  61. package/dist/clients/dispatch/plan.js +21 -5
  62. package/dist/clients/dispatch/runners/actionlint.js +0 -1
  63. package/dist/clients/dispatch/runners/ast-grep-napi.js +550 -77
  64. package/dist/clients/dispatch/runners/biome-check.js +0 -1
  65. package/dist/clients/dispatch/runners/cpp-check.js +0 -1
  66. package/dist/clients/dispatch/runners/credo.js +0 -1
  67. package/dist/clients/dispatch/runners/cue-vet.js +0 -1
  68. package/dist/clients/dispatch/runners/dart-analyze.js +0 -1
  69. package/dist/clients/dispatch/runners/detekt.js +0 -1
  70. package/dist/clients/dispatch/runners/dotnet-build.js +0 -1
  71. package/dist/clients/dispatch/runners/elixir-check.js +0 -1
  72. package/dist/clients/dispatch/runners/eslint.js +5 -2
  73. package/dist/clients/dispatch/runners/fact-rules.js +0 -1
  74. package/dist/clients/dispatch/runners/fish-indent.js +0 -1
  75. package/dist/clients/dispatch/runners/gleam-check.js +0 -1
  76. package/dist/clients/dispatch/runners/go-vet.js +1 -3
  77. package/dist/clients/dispatch/runners/golangci-lint.js +0 -1
  78. package/dist/clients/dispatch/runners/hadolint.js +0 -1
  79. package/dist/clients/dispatch/runners/helm-lint.js +16 -4
  80. package/dist/clients/dispatch/runners/helm-render.js +54 -10
  81. package/dist/clients/dispatch/runners/htmlhint.js +0 -1
  82. package/dist/clients/dispatch/runners/index.js +1 -3
  83. package/dist/clients/dispatch/runners/javac.js +0 -1
  84. package/dist/clients/dispatch/runners/ktlint.js +0 -1
  85. package/dist/clients/dispatch/runners/lsp.js +37 -2
  86. package/dist/clients/dispatch/runners/markdownlint.js +4 -2
  87. package/dist/clients/dispatch/runners/mypy.js +0 -1
  88. package/dist/clients/dispatch/runners/oxlint.js +24 -22
  89. package/dist/clients/dispatch/runners/php-lint.js +0 -1
  90. package/dist/clients/dispatch/runners/phpstan.js +0 -1
  91. package/dist/clients/dispatch/runners/prisma-validate.js +0 -1
  92. package/dist/clients/dispatch/runners/psscriptanalyzer.js +4 -1
  93. package/dist/clients/dispatch/runners/pyright.js +7 -4
  94. package/dist/clients/dispatch/runners/rubocop.js +0 -1
  95. package/dist/clients/dispatch/runners/ruff.js +0 -1
  96. package/dist/clients/dispatch/runners/rust-clippy.js +9 -6
  97. package/dist/clients/dispatch/runners/shellcheck.js +0 -1
  98. package/dist/clients/dispatch/runners/shfmt.js +0 -1
  99. package/dist/clients/dispatch/runners/spellcheck.js +0 -1
  100. package/dist/clients/dispatch/runners/spotbugs.js +0 -1
  101. package/dist/clients/dispatch/runners/sqlfluff.js +0 -1
  102. package/dist/clients/dispatch/runners/stylelint.js +0 -1
  103. package/dist/clients/dispatch/runners/swiftlint.js +0 -1
  104. package/dist/clients/dispatch/runners/taplo.js +0 -1
  105. package/dist/clients/dispatch/runners/terragrunt.js +0 -1
  106. package/dist/clients/dispatch/runners/tflint.js +0 -1
  107. package/dist/clients/dispatch/runners/tree-sitter.js +0 -1
  108. package/dist/clients/dispatch/runners/trivy-config.js +0 -1
  109. package/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
  110. package/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -4
  111. package/dist/clients/dispatch/runners/utils/runner-helpers.js +270 -105
  112. package/dist/clients/dispatch/runners/utils/spawn-outcome.js +4 -0
  113. package/dist/clients/dispatch/runners/utils/tool-failure.js +19 -7
  114. package/dist/clients/dispatch/runners/utils/toolchain-availability.js +89 -26
  115. package/dist/clients/dispatch/runners/vale.js +0 -1
  116. package/dist/clients/dispatch/runners/yaml-rule-parser.js +51 -17
  117. package/dist/clients/dispatch/runners/yamllint.js +0 -1
  118. package/dist/clients/dispatch/runners/zig-check.js +0 -1
  119. package/dist/clients/disposition-logger.js +3 -3
  120. package/dist/clients/effective-config.js +403 -0
  121. package/dist/clients/error-class.js +23 -0
  122. package/dist/clients/event-loop-hold.js +274 -0
  123. package/dist/clients/event-loop-monitor.js +66 -1
  124. package/dist/clients/extension-log.js +2 -2
  125. package/dist/clients/feature-hints.js +2 -1
  126. package/dist/clients/file-time.js +6 -1
  127. package/dist/clients/file-utils.js +266 -13
  128. package/dist/clients/finding-delivery-gate.js +55 -13
  129. package/dist/clients/format-service.js +6 -1
  130. package/dist/clients/formatters.js +303 -104
  131. package/dist/clients/freshness-cadence.js +17 -0
  132. package/dist/clients/generated-artifacts.js +114 -19
  133. package/dist/clients/generation-guard.js +4 -9
  134. package/dist/clients/git-guard.js +198 -48
  135. package/dist/clients/git-tracked-ignore.js +32 -1
  136. package/dist/clients/go-client.js +39 -0
  137. package/dist/clients/govulncheck-client.js +5 -1
  138. package/dist/clients/gradle-ktfmt-style.js +252 -0
  139. package/dist/clients/gzip-stage-write.js +7 -0
  140. package/dist/clients/hashline-anchor.js +424 -0
  141. package/dist/clients/hook-budgets.js +76 -0
  142. package/dist/clients/host-edit-normalize.js +5 -2
  143. package/dist/clients/host-ports.js +1 -1
  144. package/dist/clients/installer/index.js +200 -84
  145. package/dist/clients/installer/managed-tool-refresh.js +3 -1
  146. package/dist/clients/instance-reaper.js +108 -243
  147. package/dist/clients/instance-registry-lock.js +145 -0
  148. package/dist/clients/instance-registry.js +435 -122
  149. package/dist/clients/jscpd-client.js +6 -1
  150. package/dist/clients/language-profile.js +4 -2
  151. package/dist/clients/language-registry.js +595 -0
  152. package/dist/clients/latency-logger.js +179 -3
  153. package/dist/clients/ledger-bounds.js +13 -0
  154. package/dist/clients/lens-config.js +141 -30
  155. package/dist/clients/lens-engine.js +30 -37
  156. package/dist/clients/lens-flag-registry.js +67 -6
  157. package/dist/clients/lens-map.js +3 -1
  158. package/dist/clients/log-cleanup.js +2 -2
  159. package/dist/clients/lsp/aggregation.js +3 -1
  160. package/dist/clients/lsp/cascade-tier.js +4 -0
  161. package/dist/clients/lsp/client.js +672 -49
  162. package/dist/clients/lsp/config.js +504 -156
  163. package/dist/clients/lsp/diagnostic-binding.js +10 -5
  164. package/dist/clients/lsp/edits.js +15 -2
  165. package/dist/clients/lsp/index.js +1179 -247
  166. package/dist/clients/lsp/jvm-runtime.js +5 -0
  167. package/dist/clients/lsp/language.js +18 -169
  168. package/dist/clients/lsp/launch.js +38 -9
  169. package/dist/clients/lsp/pending-aux-coverage.js +113 -12
  170. package/dist/clients/lsp/server.js +426 -160
  171. package/dist/clients/lsp/session-roots.js +192 -0
  172. package/dist/clients/lsp/spawn-history.js +2 -7
  173. package/dist/clients/lsp/tsserver-sync.js +9 -2
  174. package/dist/clients/lsp/workspace-diagnostics-cache.js +17 -7
  175. package/dist/clients/lsp/workspace-sweep-hold.js +32 -23
  176. package/dist/clients/lsp-mutation.js +179 -23
  177. package/dist/clients/map-with-concurrency.js +36 -0
  178. package/dist/clients/mcp/analyze.js +45 -10
  179. package/dist/clients/mcp/session.js +19 -17
  180. package/dist/clients/memory-sampler.js +28 -1
  181. package/dist/clients/message-end-attribution.js +30 -0
  182. package/dist/clients/metrics-history.js +21 -4
  183. package/dist/clients/middle-man-analysis.js +2 -4
  184. package/dist/clients/module-report.js +20 -45
  185. package/dist/clients/mutating-tool.js +651 -0
  186. package/dist/clients/mutation-attribution.js +368 -0
  187. package/dist/clients/mutation-bridge.js +240 -0
  188. package/dist/clients/ndjson-logger.js +247 -20
  189. package/dist/clients/observed-mutation-sources.js +101 -0
  190. package/dist/clients/observed-mutation.js +1215 -0
  191. package/dist/clients/opaque-mutation-scan.js +300 -40
  192. package/dist/clients/opengrep-config.js +4 -0
  193. package/dist/clients/package-manager.js +201 -44
  194. package/dist/clients/partial-edit-apply.js +359 -80
  195. package/dist/clients/path-attribution-telemetry.js +23 -7
  196. package/dist/clients/path-keyed-map.js +35 -3
  197. package/dist/clients/path-utils.js +460 -13
  198. package/dist/clients/persist-debounce.js +8 -1
  199. package/dist/clients/php-cs-fixer-config.js +114 -0
  200. package/dist/clients/pipeline.js +77 -24
  201. package/dist/clients/probe-home-state.js +228 -0
  202. package/dist/clients/process-bridge.js +66 -0
  203. package/dist/clients/process-singletons.js +210 -0
  204. package/dist/clients/process-snapshot.js +68 -0
  205. package/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
  206. package/dist/clients/project-diagnostics/runner-adapters/madge.js +2 -1
  207. package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +27 -3
  208. package/dist/clients/project-diagnostics/scanner.js +61 -24
  209. package/dist/clients/project-lens-config.js +393 -122
  210. package/dist/clients/project-snapshot.js +85 -35
  211. package/dist/clients/python-environment.js +217 -0
  212. package/dist/clients/python-provenance.js +639 -0
  213. package/dist/clients/quiet-window.js +6 -1
  214. package/dist/clients/read-bridge.js +13 -21
  215. package/dist/clients/read-expansion.js +9 -46
  216. package/dist/clients/read-guard-logger.js +39 -6
  217. package/dist/clients/read-guard-tool-lines.js +364 -212
  218. package/dist/clients/read-guard.js +95 -7
  219. package/dist/clients/recent-touches.js +17 -5
  220. package/dist/clients/resource-sampler.js +195 -96
  221. package/dist/clients/review-graph/builder.js +752 -132
  222. package/dist/clients/review-graph/service.js +55 -3
  223. package/dist/clients/review-graph/shared-extraction-ir.js +7 -15
  224. package/dist/clients/review-graph/tsconfig-paths.js +5 -1
  225. package/dist/clients/review-graph/workspace-modules.js +10 -74
  226. package/dist/clients/review-graph-logger.js +30 -4
  227. package/dist/clients/runtime-agent-end.js +18 -2
  228. package/dist/clients/runtime-context.js +49 -16
  229. package/dist/clients/runtime-coordinator.js +93 -6
  230. package/dist/clients/runtime-session.js +169 -34
  231. package/dist/clients/runtime-tool-call.js +183 -35
  232. package/dist/clients/runtime-tool-result.js +894 -241
  233. package/dist/clients/runtime-turn.js +1264 -74
  234. package/dist/clients/rust-client.js +31 -0
  235. package/dist/clients/safe-spawn.js +191 -28
  236. package/dist/clients/sanitize.js +7 -1
  237. package/dist/clients/scratch-tree-policy.js +1 -3
  238. package/dist/clients/security-scan-client.js +51 -17
  239. package/dist/clients/session-lifecycle.js +238 -42
  240. package/dist/clients/session-start-observability.js +79 -0
  241. package/dist/clients/sessionstart-logger.js +12 -3
  242. package/dist/clients/sg-runner.js +27 -15
  243. package/dist/clients/sgconfig.js +6 -1
  244. package/dist/clients/shared-checkout-guard.js +338 -0
  245. package/dist/clients/skills-resolver.js +105 -0
  246. package/dist/clients/smells-rollup.js +2 -2
  247. package/dist/clients/spawn-output-cap.js +37 -0
  248. package/dist/clients/spawn-timeout-cooldown.js +15 -1
  249. package/dist/clients/startup-scan.js +6 -7
  250. package/dist/clients/startup-timing.js +69 -4
  251. package/dist/clients/string-utils.js +33 -0
  252. package/dist/clients/subagent-mode.js +17 -4
  253. package/dist/clients/test-runner-client.js +586 -118
  254. package/dist/clients/test-runner-delivery.js +239 -0
  255. package/dist/clients/tool-definition.js +41 -1
  256. package/dist/clients/tool-policy.js +92 -22
  257. package/dist/clients/tree-sitter-cache.js +40 -40
  258. package/dist/clients/tree-sitter-client.js +97 -53
  259. package/dist/clients/tree-sitter-logger.js +19 -3
  260. package/dist/clients/tree-sitter-query-loader.js +81 -1
  261. package/dist/clients/tree-sitter-shared.js +51 -46
  262. package/dist/clients/tree-sitter-symbol-extractor.js +28 -0
  263. package/dist/clients/typos-config.js +5 -0
  264. package/dist/clients/user-notify.js +6 -2
  265. package/dist/clients/warm-attach.js +8 -10
  266. package/dist/clients/widget-state.js +191 -26
  267. package/dist/clients/word-index-logger.js +15 -3
  268. package/dist/clients/word-index-store.js +463 -0
  269. package/dist/clients/word-index.js +815 -125
  270. package/dist/clients/workspace-topology.js +25 -1
  271. package/dist/clients/zizmor-config.js +29 -6
  272. package/dist/index.js +68577 -53950
  273. package/dist/mcp/analyze-cli.js +3 -1
  274. package/dist/mcp/build-staleness.js +8 -7
  275. package/dist/mcp/cli.js +3 -1
  276. package/dist/mcp/server.js +133 -6
  277. package/dist/scripts/lib/process-scan.mjs +583 -0
  278. package/dist/scripts/lib/skills-predicate.mjs +129 -0
  279. package/dist/tools/effective-config.js +89 -0
  280. package/dist/tools/lens-diagnostics.js +99 -24
  281. package/dist/tools/lsp-diagnostics.js +81 -85
  282. package/dist/tools/lsp-navigation.js +40 -17
  283. package/dist/tools/shared.js +0 -1
  284. package/docs/agent-guide.md +68 -3
  285. package/docs/ast-grep_rules_catalog.md +41 -10
  286. package/docs/astplayground.md +26 -17
  287. package/docs/configuration.md +222 -0
  288. package/docs/dependencies.md +3 -3
  289. package/docs/features.md +44 -5
  290. package/docs/language-coverage.md +2 -2
  291. package/docs/pi-lens-fixer.md +25 -0
  292. package/docs/pi-lens-investigator.md +25 -0
  293. package/docs/pi-lens-reviewer.md +27 -0
  294. package/docs/pi-lens-subagent.md +38 -0
  295. package/docs/pi-lens-warden.md +55 -0
  296. package/docs/public-api-stability.md +359 -0
  297. package/docs/release-qa-baseline.md +182 -0
  298. package/docs/subagent-compat.md +110 -30
  299. package/docs/tree-sitter_rules_catalog.md +2 -2
  300. package/package.json +17 -21
  301. package/rules/ast-grep-rules/rule-tests/c-no-malloc-free-test.yml +20 -0
  302. package/rules/ast-grep-rules/rule-tests/cpp-no-malloc-free-test.yml +25 -0
  303. package/rules/ast-grep-rules/rule-tests/cpp-no-printf-test.yml +19 -0
  304. package/rules/ast-grep-rules/rule-tests/go-no-fmt-println-test.yml +19 -0
  305. package/rules/ast-grep-rules/rule-tests/go-no-panic-in-lib-test.yml +17 -0
  306. package/rules/ast-grep-rules/rule-tests/go-no-underscore-func-name-test.yml +18 -0
  307. package/rules/ast-grep-rules/rule-tests/go-prefer-errors-is-test.yml +15 -0
  308. package/rules/ast-grep-rules/rule-tests/go-prefer-string-builder-test.yml +21 -0
  309. package/rules/ast-grep-rules/rule-tests/hyphenated-svg-attribute-test.yml +11 -0
  310. package/rules/ast-grep-rules/rule-tests/kotlin-no-lateinit-test.yml +17 -0
  311. package/rules/ast-grep-rules/rule-tests/kotlin-no-nullable-boolean-test.yml +15 -0
  312. package/rules/ast-grep-rules/rule-tests/kotlin-no-println-test.yml +19 -0
  313. package/rules/ast-grep-rules/rule-tests/kotlin-no-unnecessary-let-test.yml +7 -0
  314. package/rules/ast-grep-rules/rule-tests/kotlin-no-unsafe-cast-test.yml +7 -0
  315. package/rules/ast-grep-rules/rule-tests/kotlin-no-var-test.yml +7 -0
  316. package/rules/ast-grep-rules/rule-tests/kotlin-prefer-data-class-test.yml +25 -0
  317. package/rules/ast-grep-rules/rule-tests/kotlin-prefer-is-empty-test.yml +11 -0
  318. package/rules/ast-grep-rules/rule-tests/no-important-test.yml +21 -0
  319. package/rules/ast-grep-rules/rule-tests/no-raw-types-test.yml +30 -0
  320. package/rules/ast-grep-rules/rule-tests/no-string-concat-in-loop-test.yml +100 -0
  321. package/rules/ast-grep-rules/rule-tests/no-system-out-println-test.yml +7 -0
  322. package/rules/ast-grep-rules/rule-tests/prefer-string-is-empty-test.yml +7 -0
  323. package/rules/ast-grep-rules/rule-tests/python-optional-type-test.yml +9 -0
  324. package/rules/ast-grep-rules/rule-tests/reducible-list-comprehension-test.yml +11 -0
  325. package/rules/ast-grep-rules/rule-tests/ruby-symbol-to-proc-candidate-test.yml +10 -0
  326. package/rules/ast-grep-rules/rule-tests/rust-no-chars-enumerate-test.yml +7 -0
  327. package/rules/ast-grep-rules/rules/c-no-malloc-free.yml +39 -0
  328. package/rules/ast-grep-rules/rules/cpp-no-malloc-free.yml +45 -0
  329. package/rules/ast-grep-rules/rules/cpp-no-printf.yml +41 -0
  330. package/rules/ast-grep-rules/rules/go-no-fmt-println.yml +43 -0
  331. package/rules/ast-grep-rules/rules/go-no-panic-in-lib.yml +41 -0
  332. package/rules/ast-grep-rules/rules/go-no-underscore-func-name.yml +35 -0
  333. package/rules/ast-grep-rules/rules/go-prefer-errors-is.yml +40 -0
  334. package/rules/ast-grep-rules/rules/go-prefer-string-builder.yml +43 -0
  335. package/rules/ast-grep-rules/rules/hyphenated-svg-attribute.yml +32 -0
  336. package/rules/ast-grep-rules/rules/kotlin-no-lateinit.yml +37 -0
  337. package/rules/ast-grep-rules/rules/kotlin-no-nullable-boolean.yml +33 -0
  338. package/rules/ast-grep-rules/rules/kotlin-no-println.yml +42 -0
  339. package/rules/ast-grep-rules/rules/kotlin-no-unnecessary-let.yml +28 -0
  340. package/rules/ast-grep-rules/rules/kotlin-no-unsafe-cast.yml +28 -0
  341. package/rules/ast-grep-rules/rules/kotlin-no-var.yml +35 -0
  342. package/rules/ast-grep-rules/rules/kotlin-prefer-data-class.yml +66 -0
  343. package/rules/ast-grep-rules/rules/kotlin-prefer-is-empty.yml +35 -0
  344. package/rules/ast-grep-rules/rules/no-important.yml +21 -0
  345. package/rules/ast-grep-rules/rules/no-raw-types.yml +33 -0
  346. package/rules/ast-grep-rules/rules/no-string-concat-in-loop.yml +90 -0
  347. package/rules/ast-grep-rules/rules/no-system-out-println.yml +16 -0
  348. package/rules/ast-grep-rules/rules/prefer-string-is-empty.yml +11 -0
  349. package/rules/ast-grep-rules/rules/python-optional-type.yml +22 -0
  350. package/rules/ast-grep-rules/rules/reducible-list-comprehension.yml +29 -0
  351. package/rules/ast-grep-rules/rules/ruby-symbol-to-proc-candidate.yml +25 -0
  352. package/rules/ast-grep-rules/rules/rust-no-chars-enumerate.yml +19 -0
  353. package/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
  354. package/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
  355. package/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
  356. package/scripts/analyze-pi-lens-logs.mjs +192 -1
  357. package/scripts/install-selftest.mjs +58 -2
  358. package/scripts/lib/skills-predicate.mjs +129 -0
  359. package/scripts/rpc-load-check.mjs +3 -1
@@ -0,0 +1,25 @@
1
+ # Investigator contract
2
+
3
+ Root-cause runtime behavior from reproducible and durable evidence.
4
+
5
+ Define the symptom as a question that evidence can answer. Name the time window,
6
+ sessions, and build in scope. Prefer a tight reproduction loop before code
7
+ reading. Correlate records by stable identifiers, not time alone. Read each
8
+ record's producer before trusting its labels. Count a representative population,
9
+ and separate worker behavior from daemon behavior.
10
+
11
+ Keep the investigation read-only. Rank falsifiable hypotheses with evidence for
12
+ and against each hypothesis and the observation that would settle it. Sweep the
13
+ tree for the root-cause pattern and every member of the affected population.
14
+ State the blast radius and any missing or unbounded observability.
15
+
16
+ Deliver a proven diagnosis and a concrete next step. If the task expands to an
17
+ implementation, stop and return it to the orchestrator for a fixer delegation.
18
+ Use concise, active, plain prose.
19
+
20
+ ## Tautological tests considered harmful
21
+
22
+ Treat a probe as evidence only when it can distinguish the competing hypotheses.
23
+ Do not seed the asserted outcome, mirror the production predicate, or rely on a
24
+ mock where the real in-process seam is available. Record the observation that
25
+ would turn the hypothesis red, and preserve that distinction in the handoff.
@@ -0,0 +1,27 @@
1
+ # Reviewer contract
2
+
3
+ Adversarially verify a change before merge and report proven findings.
4
+
5
+ Assume the implementation's claims are incomplete. Read the issue, full diff,
6
+ repository instructions, shared delegated worker contract, PR body, and merge
7
+ state. Keep the review read-only.
8
+
9
+ Reproduce the build and targeted tests. Verify quoted red-first evidence by
10
+ keeping the tests and removing the source fix. Mutate every new guard and demand
11
+ a red test. Probe inversions, concurrency, input channels, trust boundaries,
12
+ strict consumers, and durable-record compatibility. Repeat the pattern and
13
+ population sweeps. Check the stated blast radius, bounded observability,
14
+ changelog fragment, commit shape, and PR conventions.
15
+
16
+ Report `CRITICAL`, `HIGH`, `MEDIUM`, `LOW`, then `NITPICK` findings. Give the
17
+ file and line, a concrete failure, evidence, and a suggested fix. Separate issue
18
+ acceptance findings from repository-standard findings. List cleared categories,
19
+ then record one verdict: merge as-is, merge after fixes, or redesign. Never
20
+ merge or silently repair the author's branch. Use short, active, plain prose.
21
+
22
+ ## Tautological tests considered harmful
23
+
24
+ Check that each regression test reaches the real seam and observes an independent
25
+ effect. Remove or mutate the claimed guard and require the test to fail for the
26
+ intended reason. Flag tests that restate the implementation, assert setup data,
27
+ or swap a real in-process store, sink, coordinator, or registry for a fake.
@@ -0,0 +1,38 @@
1
+ # Delegated worker delivery contract
2
+
3
+ This contract applies to every delegated pi-lens worker, regardless of the
4
+ agent runner or model. Pair it with exactly one role contract: fixer, reviewer,
5
+ or investigator.
6
+
7
+ Work only in the assigned worktree. Before editing, verify its absolute path,
8
+ registered worktree entry, branch, and base. Preserve junctioned dependencies.
9
+ Never switch another checkout's branch, and never use `git stash`. Save a patch
10
+ before temporarily reverting work.
11
+
12
+ Treat the acceptance criteria as the contract. For a regression, prove the new
13
+ test red before the fix and green after it. Mutating or removing a new guard
14
+ must make at least one test fail. Sweep the whole tree for the same code pattern
15
+ and every member of any enumerable population. Record both sweeps.
16
+
17
+ State the blast radius, including callers, durable shapes, strict parsers, and
18
+ tool surfaces. Add bounded observability for every new failure path. Include the
19
+ required changelog fragment for a code change. Report verification honestly.
20
+ Write active, direct prose with short sentences and consistent terms.
21
+
22
+ ## Tautological tests considered harmful
23
+
24
+ A test must observe behavior through the real seam, not repeat the implementation
25
+ or feed the expected answer in through setup. Keep the red-first failure tied to
26
+ the defect, and mutate the guard or filter to prove the test can detect its loss.
27
+ Mocks belong only at true process or host boundaries. When a test can use the
28
+ real store, sink, coordinator, or registry, use it and assert the durable result.
29
+
30
+ Git authority is separate from the role. Commit, push, or open a PR only when
31
+ the delegation explicitly grants that authority after worktree verification.
32
+ Otherwise, edit and test with the assigned worktree as the command working
33
+ directory, then return the patch and evidence to the orchestrator. Never merge.
34
+
35
+ When Git authority is granted, use one logical commit with an imperative,
36
+ conventional-prefix subject of at most 50 characters, a blank line, and a
37
+ 72-column body that states what and why. Reference the issue. Open a PR, do not
38
+ merge it, and report its URL.
@@ -0,0 +1,55 @@
1
+ # PR warden contract
2
+
3
+ Keep every pull request moving through review, fix, verification, and merge.
4
+
5
+ The warden is a read-only workflow controller. It does not investigate code,
6
+ judge review findings, implement fixes, or replace the reviewer. Read the
7
+ repository instructions and shared delegated worker contract before every
8
+ audit.
9
+
10
+ Build the ledger from GitHub and registered worktree evidence. For every pull
11
+ request, record the exact head SHA, matching worktree, dirty or unpushed state,
12
+ mergeability, required checks on that SHA, review outcome, active owner, and
13
+ next action. Use one state from this closed set:
14
+
15
+ - `UNREVIEWED`
16
+ - `REVIEW_FINDINGS`
17
+ - `FIXING`
18
+ - `AWAITING_VERIFY`
19
+ - `AWAITING_COMMIT_PUSH`
20
+ - `CI_PENDING`
21
+ - `CI_REAL_FAILURE`
22
+ - `CI_INFRA_FAILURE`
23
+ - `READY_AUTOMERGE`
24
+ - `MERGED`
25
+
26
+ A worker result becomes durable workflow evidence only when the orchestrator
27
+ records its role, exact head or working-tree identity, verdict, dispositions,
28
+ and next owner on the pull request or another shared ledger. Chat-only results
29
+ cannot drive a later audit. Flag a missing durable handoff record instead of
30
+ guessing that review passed.
31
+
32
+ Assign an owner and next action whenever the state changes. Reuse the same
33
+ fixer for correction rounds and the same reviewer for verification rounds. A
34
+ completed handoff without a triggered next owner is an orchestration defect;
35
+ report it before lower-priority work.
36
+
37
+ Run the audit after every worker completion, push, review verdict, CI verdict,
38
+ merge, and user status request. Poll GitHub and persistent external-worker
39
+ handles when completion notifications are unavailable. Never infer completion
40
+ from a quiet worker or clean worktree.
41
+
42
+ Classify required CI from its log and exact head. Treat assertion failures and
43
+ `[mem-watch] done. exitCode=1` as real. Apply the repository's exit-137 rules
44
+ before calling a failure infrastructure. Do not treat SonarCloud or another
45
+ advisory lane as a merge gate. A skipped required job is not green.
46
+
47
+ Mark `READY_AUTOMERGE` only when the actual final head has passed required CI
48
+ and adversarial review, every material finding has a disposition, and every
49
+ substantive fix has passed the same-reviewer verification loop. The
50
+ orchestrator, not the warden, commits, pushes, comments, enables automerge, or
51
+ merges.
52
+
53
+ Return a compact transition table followed by a priority queue. Name
54
+ orchestration breaches separately. Provide exact safe commands when they help
55
+ the orchestrator, but make no repository or GitHub mutation.
@@ -0,0 +1,359 @@
1
+ # Public API stability and versioning policy
2
+
3
+ **Status:** normative. Landed by #2418; gates #2416.
4
+ **Enforced by:** `clients/config-diagnostic-codes.ts` (the data),
5
+ `tests/support/schema-stability.ts`, `tests/config/schema-stability-tiers.test.ts`,
6
+ `tests/clients/config-diagnostic-codes.test.ts`,
7
+ `tests/clients/config-deprecation-registry.test.ts` (the tests).
8
+
9
+ pi-lens ships to roughly 28k installs a month. A config field, a warning a user
10
+ greps for, or a tool id becomes a compatibility obligation the moment it ships —
11
+ whether or not anyone wrote that obligation down. This document writes it down,
12
+ and every clause below is backed by a test rather than by convention, because a
13
+ policy nobody can fail is not a policy.
14
+
15
+ Scope: the unified config schema (#2415/#2416/#2383/#195), the capability
16
+ facades, the MCP tool mirror, and the versioned `PiLensApi` (#1358). It does not
17
+ define any catalog schema; it constrains how those schemas evolve.
18
+
19
+ ## 1. Field stability tiers
20
+
21
+ Every property in a published pi-lens schema carries an `x-stability`
22
+ annotation, whose value is one of a closed vocabulary:
23
+
24
+ | Tier | Meaning |
25
+ | --- | --- |
26
+ | `experimental` | May change shape, semantics, or disappear in a **minor** release. Not covered by the compatibility guarantee. |
27
+ | `stable` | Covered by the guarantee. Shape and semantics change only in a **major**, through the checklist in section 4. |
28
+
29
+ Rules:
30
+
31
+ - **New fields default to `experimental`.** Shipping a field straight to
32
+ `stable` is a deliberate act, not a default.
33
+ - **Promotion `experimental` → `stable` is changelogged** under `Changed`,
34
+ naming the field. Demotion `stable` → `experimental` is a breaking change and
35
+ follows section 4.
36
+ - **A property with no tier fails CI.** `assertSchemaStabilityTiers` walks the
37
+ whole schema — `properties`, `patternProperties`, `items`, `prefixItems`,
38
+ `additionalProperties`, `oneOf`/`anyOf`/`allOf`, `not`, `if`/`then`/`else`,
39
+ `$defs`/`definitions` — so a field cannot hide from the tier requirement by
40
+ living inside a composition keyword.
41
+ - The **root schema** is not itself a property and carries no tier. Entries
42
+ under `$defs`/`definitions` are reusable subschemas, not published fields;
43
+ they need no tier, but every property *inside* them does.
44
+
45
+ The vocabulary lives in `STABILITY_TIERS` and the annotation key in
46
+ `STABILITY_TIER_KEY` (`clients/config-diagnostic-codes.ts`). #2416's first real
47
+ catalog schema asserts itself with the same two exported functions rather than
48
+ writing a second walker.
49
+
50
+ ## 2. Stable config diagnostic codes
51
+
52
+ Every user-facing config validation or migration warning carries a code from a
53
+ closed, **append-only** namespace, `PILENS_CFG_NNNN`, registered once in
54
+ `CONFIG_DIAGNOSTIC_CODES`.
55
+
56
+ - **The prose is not API; the code is.** Message text may be rewritten in any
57
+ release. A code is never renumbered, never removed, and a retired number is
58
+ never reused — a retired code keeps its registry entry with an amended
59
+ description.
60
+ - The code is threaded through the one durable choke point
61
+ (`recordDegradationOnce` / `incrementDegradationCount` in
62
+ `clients/degradation-ledger.ts`) and through `notifyUserDegradation`, so the
63
+ same code appears in the user-visible message, in `extension.log`, and in the
64
+ durable `latency.log` degradation row.
65
+ - The durable row writes `code` **after** the bounded caller metadata, so the
66
+ ledger's `MAX_METADATA_KEYS` cap can never evict the one field a user greps
67
+ on.
68
+ - A new `notifyUserDegradation` call from any `clients/**/*config*.ts` file
69
+ without a registered code fails CI
70
+ (`tests/clients/config-diagnostic-codes.test.ts` scans for it; it does not
71
+ keep a hand-maintained list of call sites).
72
+
73
+ ### How a user matches or suppresses a warning
74
+
75
+ **The match key is the bracketed suffix, not the prose.** Every coded warning is
76
+ rendered as:
77
+
78
+ ```
79
+ pi-lens: ignoring invalid LSP config .pi-lens/lsp.json: Unexpected token } [PILENS_CFG_0001]
80
+ ```
81
+
82
+ The trailing ` [PILENS_CFG_NNNN]` marker is appended by
83
+ `withConfigDiagnosticCode`, is idempotent, and is always last. Match on it:
84
+
85
+ ```sh
86
+ # every ignored-config warning this session, from the durable degradation log
87
+ grep 'PILENS_CFG_0001' ~/.pi-lens/latency.log
88
+
89
+ # suppress one code while keeping every other pi-lens warning
90
+ pi ... 2>&1 | grep -v 'PILENS_CFG_0001'
91
+ ```
92
+
93
+ The extraction pattern is exported as `CONFIG_DIAGNOSTIC_MARKER_PATTERN`
94
+ (capture group 1 is the code) so tooling need not re-derive it. Anything that
95
+ filters on the prose instead — `"ignoring invalid"` — is filtering on a string
96
+ this policy explicitly reserves the right to change.
97
+
98
+ ### Registered codes
99
+
100
+ | Code | Meaning | Emitter |
101
+ | --- | --- | --- |
102
+ | `PILENS_CFG_0001` | A config file exists but could not be read or parsed, so it is ignored. | `warnIgnoredConfigOnce` (`clients/config-warn.ts`), the single choke point behind the LSP, global, and project config loaders. |
103
+ | `PILENS_CFG_0002` | A deprecated config **key** was accepted inside its deprecation window. | `deprecationRecords` (`clients/config-resolve.ts`), one record per `(file, key)`, delivered by `reportPiLensConfigRecords` (#2426). |
104
+ | `PILENS_CFG_0003` | A deprecated config **file location** was read inside its window. | Same producer and same delivery path as `PILENS_CFG_0002`. |
105
+ | `PILENS_CFG_0004` | A config field no schema property claims was dropped. | `validate()` (`clients/config-core/normalize.ts`) produces the record; `reportPiLensConfigRecords` (`clients/config-resolve.ts`) delivers it through `warnIgnoredConfigOnce` (#2426). |
106
+ | `PILENS_CFG_0005` | A config field's value did not match its schema and was dropped. One FIELD; the rest of the file is in effect. | Same producer and same delivery path as `PILENS_CFG_0004`. |
107
+ | `PILENS_CFG_0006` | A config key that would modify an object's prototype (`__proto__`, `constructor`, `prototype`) was refused. | Both halves of the config core, through the shared policy in `clients/config-core/safe-object.ts`. |
108
+ | `PILENS_CFG_0007` | Further config notices were suppressed by a bound, and this one carries the count — the WHOLE count, including anything an earlier bound in the same pipeline dropped. Nothing about the config is wrong; the notice list was truncated. | `MigrationRecordCollector.finalize` (`clients/config-core/records.ts`) — the ONE producer, reached through `finalizeRecords` by every record list: the shared resolution, the global loader's unknown-key scan, the project loader's unknown-key scan, and its legacy-document enumeration. Rendered with neutral prose and recorded under the `config-notice-suppressed` degradation kind, never `config-ignored`. |
109
+ | `PILENS_CFG_0008` | Resolving a config failed internally, so the WHOLE file was ignored and pi-lens ran on defaults. | The two guards under the pipeline: `resolveConfig` (`clients/config-core/resolve.ts`) and the global loader's post-parse catch (`clients/lens-config.ts`). Carries the error class only, never its message. |
110
+
111
+ A reserved code is registered and referenced by the deprecation registry, but
112
+ nothing emits it today. That is deliberate: the number must be pinned before the
113
+ migration warning ships, because append-only means the number cannot be chosen
114
+ later.
115
+
116
+ ## 3. Config-envelope identity anchor
117
+
118
+ The unified config format reserves a `$schema` URL from its first published
119
+ version. Both halves are pinned in `clients/config-diagnostic-codes.ts`:
120
+
121
+ - `CONFIG_SCHEMA_ID` — the canonical schema URL. The published schema's own
122
+ `$id` must equal it.
123
+ - `CONFIG_SCHEMA_ANCHOR_KEY` (`"$schema"`) — the key a user's config file uses
124
+ to name the schema it was written against.
125
+
126
+ `assertSchemaIdentityAnchor` checks all three facts: the schema's `$id` matches,
127
+ the schema declares a meta-schema, and the root declares a `$schema` **instance**
128
+ property so a user's file can carry the anchor. Pinning the URL in one module is
129
+ what stops it drifting between the schema, the validator, and the docs.
130
+
131
+ ## 4. Deprecation window and removal checklist
132
+
133
+ ### The maintainer stance
134
+
135
+ **A legacy source is read for exactly one deprecation window, and then it is
136
+ actually removed.** pi-lens does not carry legacy config surfaces forever, and
137
+ it does not silently drop them either. Both failure modes are ruled out by the
138
+ same rule: while a surface is inside its window it is read and honored exactly
139
+ as before, with a bounded coded warning; at the next major it is removed through
140
+ the checklist below, announced in `Removed`. Nothing is ever dropped without an
141
+ announced window that preceded it.
142
+
143
+ ### The data
144
+
145
+ Every deprecated key or file location is a row in `DEPRECATED_CONFIG_SURFACES`
146
+ carrying `surface`, `kind`, `code`, `deprecatedSince`, `removeNotBefore`, and a
147
+ `reason`. The registry test enforces:
148
+
149
+ - `deprecatedSince` names the release that **announces** the deprecation — for a
150
+ row announced only in an unreleased `.changelog/` fragment, that must be a
151
+ version later than the newest release in `CHANGELOG.md` (you cannot back-date
152
+ a deprecation into a version that already shipped without it);
153
+ - `removeNotBefore` is a **later major**, `X.0.0` — removal never happens in a
154
+ minor;
155
+ - the row's code is registered, and matches its kind;
156
+ - the surface is announced in a Changelog `Deprecated` section as a delimited
157
+ token (`` `pi-lens.json` ``), so a substring of a longer filename does not
158
+ count as an announcement;
159
+ - FILE rows name a location a loader actually reads, and KEY rows name a key the
160
+ `LSPConfig` interface actually declares — both checked against the exported
161
+ constants and the real interface body, never a hand-copied list.
162
+
163
+ Note that a canonical file is not deprecated because some of its keys are.
164
+ `.pi-lens.json` is a canonical location (#2426); the deprecated surfaces are the
165
+ legacy top-level LSP keys read from it, which are `kind: "key"` rows.
166
+
167
+ ### The removal checklist
168
+
169
+ This is the checklist #2372 slice 5's "separately approved breaking-change plan"
170
+ instantiates. It does not invent a second process; slice 5 is one execution of
171
+ this list.
172
+
173
+ 1. **Window elapsed.** The current version is at or past the row's
174
+ `removeNotBefore`, and that version is a major.
175
+ 2. **Announced.** The surface has been in a shipped `Deprecated` changelog
176
+ section since `deprecatedSince`, continuously.
177
+ 3. **Warned in-product.** The migration warning has been emitting its stable
178
+ code for the whole window — the user has had a coded, greppable signal, not
179
+ only a release note.
180
+ 4. **Migration path documented and reachable.** The replacement surface exists,
181
+ is `stable`, and the `reason` field names it.
182
+ 5. **Canonical-wins collision behavior verified.** For the whole window, a
183
+ config setting both the legacy and the canonical surface resolved to the
184
+ canonical one, with the coded warning naming the ignored legacy value.
185
+ 6. **Removal PR does all four:** deletes the reader, deletes the registry row,
186
+ adds a `Removed` changelog entry naming the surface and the replacement, and
187
+ keeps the diagnostic code registered (codes outlive the surfaces they
188
+ described).
189
+ 7. **Approved as a breaking change.** A major-version bump plus explicit
190
+ maintainer approval on the plan; a removal never rides in on an unrelated PR.
191
+
192
+ Removing a row from `DEPRECATED_CONFIG_SURFACES` while the reader still exists,
193
+ or removing the reader while the row still exists, fails the registry test. The
194
+ two move together or not at all.
195
+
196
+ ## 5. The config core
197
+
198
+ `clients/config-core/` is the one place a pi-lens configuration is validated,
199
+ merged, and explained. Every loader, catalog, and selector resolves through it
200
+ (#2425); a fourth merge semantics is a defect, not a design choice.
201
+
202
+ The pipeline is `RawConfig -> validate(schema) -> NormalizedConfig ->
203
+ merge(sources) -> Resolved<T>`, and `resolveConfig` runs both halves. It is
204
+ pure: no file reads, no logging, no ledger writes. Reporting is the separate,
205
+ explicit `reportPiLensConfigRecords` step (`clients/config-resolve.ts`, which is
206
+ where the loaders share it — never inside the core), so the warn-once latch
207
+ stays with the loaders rather than with the library.
208
+
209
+ Every loader reports **every** record its own resolution produced — it does not
210
+ filter to the records it "owns". Ownership is a property of the RECORD, not of
211
+ the caller: `reportPiLensConfigRecords` derives the reporting subsystem from the
212
+ record's own owner and tier, so an `lsp.*` key always reports as an LSP setting
213
+ and a pi-lens key always reports under the loader for its tier, whichever loader
214
+ happened to open the file. A `(file, key)` that three loaders all resolve is
215
+ reported three times and the warn-once latch — keyed on
216
+ `(subsystem, file, key, reason)` — collapses those into the one notice the user
217
+ sees. Filtering by caller instead is what left a record no loader claimed
218
+ reported by nobody at all (#2426 review round 3, F1).
219
+
220
+ ### Source tiers
221
+
222
+ Seven tiers, lowest value-precedence first. A later tier's value replaces an
223
+ earlier one for the same leaf.
224
+
225
+ | Tier | Class | Meaning |
226
+ | --- | --- | --- |
227
+ | `builtin` | **default** | pi-lens's own shipped defaults. |
228
+ | `global` | operator | The user's machine-global config. |
229
+ | `project` | **repo** | A config file inside the checkout. |
230
+ | `nested-project` | **repo** | A config file in a nested package. |
231
+ | `env` | operator | Environment variables. |
232
+ | `cli` | operator | Command-line arguments. |
233
+ | `host` | operator | The host application's decision. |
234
+
235
+ The class column is a second, independent axis, and it has **three** values, not
236
+ two. `repo` tiers carry content that arrived with a checkout — content a user
237
+ may never have read. `operator` tiers are a deliberate act by the person running
238
+ pi-lens. `default` is pi-lens's own shipped opinion, which nobody chose. Only the
239
+ class decides who may lift a denial; `builtin` being its own class is what keeps
240
+ a shipped default overridable by the operator while still out of reach of
241
+ repository content.
242
+
243
+ ### Monotonic deny precedence
244
+
245
+ A schema node marked `x-deny` resolves by denial rules instead of
246
+ last-tier-wins:
247
+
248
+ - `x-deny: "boolean-false"` — a `false` from an **operator** tier is never
249
+ lifted, by anything. A `false` from a `default` or `repo` tier is lifted only
250
+ by an explicit `true` from an **operator** tier of higher precedence. A repo
251
+ tier never lifts a denial at all, its own class included.
252
+ - `x-deny: "array-union"` — the resolved list is the union of every tier's
253
+ members. There is no vocabulary for un-denying a member, so a nearer tier that
254
+ omits one is expressing nothing. This outranks the node's own
255
+ `x-merge-strategy`: a denial a merge strategy could erase would not be
256
+ monotonic.
257
+
258
+ Provenance for a denied leaf names the tier that **made** the denial, not the
259
+ last tier to restate it — the answer to "why can I not turn this back on".
260
+
261
+ Two consequences are deliberate rulings rather than accidents of the algorithm,
262
+ and both are load-bearing:
263
+
264
+ **A built-in denial is a default, not a law.** `builtin: false` plus
265
+ `global: true` resolves to `true`, attributed to `global`; `builtin: false` plus
266
+ `project: true` stays `false`, attributed to `builtin`. When `builtin` sat in the
267
+ operator class, a conservative default pi-lens shipped — an `enabled: false`, or
268
+ any member of a built-in deny list — could never be lifted by anyone, including
269
+ the person who installed pi-lens, and the only escape was editing pi-lens's
270
+ source. A default the operator cannot override is not a default.
271
+
272
+ **An operator denial is not liftable by a nearer operator tier.** `global: false`
273
+ plus `cli: true` stays `false`. This is the spec letter and it is kept on
274
+ purpose: a denial is a security decision, and letting one operator surface
275
+ out-shout another would make the guarantee depend on which surface an attacker
276
+ could reach (an inherited `PILENS_*` environment variable, a wrapper script's
277
+ argv) rather than on what the operator decided. The escape hatch is an
278
+ operator-tier **change** — edit the global config, unset the variable — never a
279
+ repo-tier one.
280
+
281
+ ### Prototype-safe keys
282
+
283
+ `__proto__`, `constructor`, and `prototype` are refused wherever a config
284
+ supplies a key, in both halves of the pipeline, with a `PILENS_CFG_0006` record
285
+ naming the key. No pi-lens setting is spelled that way, so there is nothing to
286
+ preserve, and assigning one would change an object's behavior rather than its
287
+ contents — a document that serializes as `{}` while answering an attacker's
288
+ value on every field read.
289
+
290
+ Both halves also bound their own recursion at `MAX_CONFIG_DEPTH` (32) and
291
+ `resolveConfig` never throws: a config that cannot be resolved degrades to
292
+ absent with records, never to a failed session.
293
+
294
+ A schema node that declares no `type` — or a `type` keyword the core does not
295
+ recognize — is **opaque**, and an opaque node is walked by the value's own
296
+ shape rather than passed through. Its children are kept (that is what an opaque
297
+ node means), but they are copied, depth-counted, key-checked, and recorded like
298
+ any other. A schema that wants a genuinely free-form subtree should still say
299
+ `additionalProperties: true`, which states the intent instead of relying on an
300
+ omission.
301
+
302
+ ### Merge strategies
303
+
304
+ Objects are always merged field-wise; a nearer tier setting one key never erases
305
+ its siblings. Arrays follow the node's `x-merge-strategy`:
306
+
307
+ | Value | Behavior |
308
+ | --- | --- |
309
+ | `replace` (default) | The highest-precedence tier that sets the array supplies all of it. |
310
+ | `append` | Every tier's entries, concatenated lowest precedence first. |
311
+ | `keyed:<field>` | Entries matched across tiers by `<field>` and merged field-wise; unmatched entries appended. |
312
+
313
+ ### The trust-gated `ProcessSpec`
314
+
315
+ A `ProcessSpec` carries a non-empty argv, a bounded env (count and bytes), a
316
+ closed `cwdMode`/`inputMode`, a timeout, its provenance, and the trust decision
317
+ that applied when it was read. `toSpawnArgs(spec)` is the only way to get
318
+ spawnable arguments out of one, and for a `project` or `nested-project` spec it
319
+ refuses unless **both** the spec's recorded trust and the host's current
320
+ `isProjectTrusted()` decision are `"trusted"`. Two conditions, because a session
321
+ can revoke trust after the config was read; one condition would make a spec a
322
+ permanent capability token.
323
+
324
+ `unknown` fails closed here, unlike `isToolInstallAllowedByTrust`. That gate
325
+ governs pi-lens's own managed tools; this one governs a command string a
326
+ repository wrote.
327
+
328
+ Refusals record under the existing `trust-refusal` degradation kind through
329
+ `incrementDegradationCount`, carrying the tier, `argv[0]`, and the trust
330
+ generation — never an argument or an env value.
331
+
332
+ ### Redaction
333
+
334
+ `redactProcessSpec` is the only projection of a spec for a diagnostic or
335
+ telemetry surface. It strips every env **value** and every argv entry after
336
+ `argv[0]`; env names survive, because a name is a label and "which variables did
337
+ this server get" is the question an operator asks.
338
+
339
+ `provenanceView(resolved)` is redacted by construction: it is built from the
340
+ provenance map alone and never reads the resolved value, so no un-redacted view
341
+ exists. Validation records are bounded and structural — a reason names a key, a
342
+ type, and a count, never a value or a source snippet.
343
+
344
+ ## Where each policy point is enforced
345
+
346
+ | Policy point | Data | Test |
347
+ | --- | --- | --- |
348
+ | 1. `x-stability` on every published field | `STABILITY_TIER_KEY`, `STABILITY_TIERS` | `tests/config/schema-stability-tiers.test.ts` via `assertSchemaStabilityTiers` |
349
+ | 2. Append-only `PILENS_CFG_*` codes | `CONFIG_DIAGNOSTIC_CODES` | `tests/clients/config-diagnostic-codes.test.ts` |
350
+ | 3. Reserved `$schema` identity anchor | `CONFIG_SCHEMA_ID`, `CONFIG_SCHEMA_ANCHOR_KEY` | `assertSchemaIdentityAnchor` |
351
+ | 4. Deprecation window + removal checklist | `DEPRECATED_CONFIG_SURFACES` | `tests/clients/config-deprecation-registry.test.ts` |
352
+ | 5. Config core: tiers, deny precedence, ProcessSpec trust | `clients/config-core/` | `tests/clients/config-core/*.test.ts`, `tests/config/config-core-schema-stability.test.ts` |
353
+
354
+ ## Related
355
+
356
+ #2415 (shared config core), #2416/#2372 (catalog schema and its compat
357
+ template), #2383, #195 (selector semantics), #1358 (facade versioning — a
358
+ separate version axis this policy only has to compose with), #2426 (canonical
359
+ config locations).