@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.
- package/node_modules/@czottmann/pi-automode/CHANGELOG.md +13 -1
- package/node_modules/@czottmann/pi-automode/README.md +20 -0
- package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +1 -1
- package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +2 -1
- package/node_modules/@czottmann/pi-automode/docs/defaults.md +3 -2
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +38 -2
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +5 -3
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +11 -3
- package/node_modules/@czottmann/pi-automode/package.json +1 -1
- package/node_modules/@pi-plugins/fast-mode/README.md +5 -3
- package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +20 -55
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
- package/node_modules/pi-lens/CHANGELOG.md +988 -0
- package/node_modules/pi-lens/README.md +3 -0
- package/node_modules/pi-lens/config/biome/core.jsonc +11 -2
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +13 -1
- package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +927 -80
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +13 -4
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +123 -3
- package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +60 -5
- package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +1 -3
- package/node_modules/pi-lens/dist/clients/biome-client.js +9 -2
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +14 -0
- package/node_modules/pi-lens/dist/clients/bootstrap.js +509 -73
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +152 -12
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +59 -4
- package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +113 -0
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +31 -4
- package/node_modules/pi-lens/dist/clients/cache-manager.js +105 -6
- package/node_modules/pi-lens/dist/clients/cache-observability.js +44 -18
- package/node_modules/pi-lens/dist/clients/cargo-manifest.js +422 -0
- package/node_modules/pi-lens/dist/clients/cascade-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +13 -3
- package/node_modules/pi-lens/dist/clients/complexity-client.js +16 -5
- package/node_modules/pi-lens/dist/clients/config-core/deny.js +221 -0
- package/node_modules/pi-lens/dist/clients/config-core/index.js +47 -0
- package/node_modules/pi-lens/dist/clients/config-core/merge.js +357 -0
- package/node_modules/pi-lens/dist/clients/config-core/normalize.js +340 -0
- package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +248 -0
- package/node_modules/pi-lens/dist/clients/config-core/provenance.js +171 -0
- package/node_modules/pi-lens/dist/clients/config-core/records.js +218 -0
- package/node_modules/pi-lens/dist/clients/config-core/resolve.js +125 -0
- package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +78 -0
- package/node_modules/pi-lens/dist/clients/config-core/schema.js +167 -0
- package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +281 -0
- package/node_modules/pi-lens/dist/clients/config-locations.js +160 -0
- package/node_modules/pi-lens/dist/clients/config-resolve.js +789 -0
- package/node_modules/pi-lens/dist/clients/config-schema.js +197 -0
- package/node_modules/pi-lens/dist/clients/config-warn.js +407 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +19 -14
- package/node_modules/pi-lens/dist/clients/dead-code-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +178 -0
- package/node_modules/pi-lens/dist/clients/debug-handles.js +2 -2
- package/node_modules/pi-lens/dist/clients/debug-heap.js +3 -3
- package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +106 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +113 -7
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +8 -19
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +12 -9
- package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +14 -4
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +162 -18
- package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +9 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +380 -26
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +1 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +13 -18
- package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +7 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +1 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +194 -80
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +84 -15
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/disposition-logger.js +3 -3
- package/node_modules/pi-lens/dist/clients/effective-config.js +403 -0
- package/node_modules/pi-lens/dist/clients/error-class.js +23 -0
- package/node_modules/pi-lens/dist/clients/event-loop-hold.js +274 -0
- package/node_modules/pi-lens/dist/clients/extension-log.js +2 -2
- package/node_modules/pi-lens/dist/clients/feature-hints.js +2 -1
- package/node_modules/pi-lens/dist/clients/file-time.js +6 -1
- package/node_modules/pi-lens/dist/clients/file-utils.js +112 -9
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +50 -13
- package/node_modules/pi-lens/dist/clients/format-service.js +6 -1
- package/node_modules/pi-lens/dist/clients/formatters.js +276 -102
- package/node_modules/pi-lens/dist/clients/freshness-cadence.js +17 -0
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +114 -19
- package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -9
- package/node_modules/pi-lens/dist/clients/git-guard.js +1 -1
- package/node_modules/pi-lens/dist/clients/go-client.js +39 -0
- package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +252 -0
- package/node_modules/pi-lens/dist/clients/hashline-anchor.js +424 -0
- package/node_modules/pi-lens/dist/clients/hook-budgets.js +76 -0
- package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +5 -2
- package/node_modules/pi-lens/dist/clients/host-ports.js +1 -1
- package/node_modules/pi-lens/dist/clients/installer/index.js +71 -52
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +104 -230
- package/node_modules/pi-lens/dist/clients/instance-registry.js +6 -3
- package/node_modules/pi-lens/dist/clients/language-registry.js +595 -0
- package/node_modules/pi-lens/dist/clients/latency-logger.js +160 -2
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +13 -0
- package/node_modules/pi-lens/dist/clients/lens-config.js +141 -30
- package/node_modules/pi-lens/dist/clients/lens-engine.js +30 -37
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +59 -6
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -2
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +3 -1
- package/node_modules/pi-lens/dist/clients/lsp/client.js +168 -30
- package/node_modules/pi-lens/dist/clients/lsp/config.js +483 -158
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +15 -2
- package/node_modules/pi-lens/dist/clients/lsp/index.js +676 -71
- package/node_modules/pi-lens/dist/clients/lsp/language.js +18 -169
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +5 -16
- package/node_modules/pi-lens/dist/clients/lsp/server.js +284 -147
- package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +116 -23
- package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +2 -7
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +9 -2
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +163 -21
- package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +36 -0
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +33 -4
- package/node_modules/pi-lens/dist/clients/mcp/session.js +5 -16
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +8 -3
- package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +2 -4
- package/node_modules/pi-lens/dist/clients/module-report.js +20 -45
- package/node_modules/pi-lens/dist/clients/mutating-tool.js +651 -0
- package/node_modules/pi-lens/dist/clients/mutation-attribution.js +368 -0
- package/node_modules/pi-lens/dist/clients/mutation-bridge.js +240 -0
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +247 -20
- package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +101 -0
- package/node_modules/pi-lens/dist/clients/observed-mutation.js +1215 -0
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +68 -22
- package/node_modules/pi-lens/dist/clients/opengrep-config.js +4 -0
- package/node_modules/pi-lens/dist/clients/package-manager.js +195 -26
- package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +359 -80
- package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +23 -7
- package/node_modules/pi-lens/dist/clients/path-keyed-map.js +21 -2
- package/node_modules/pi-lens/dist/clients/path-utils.js +428 -13
- package/node_modules/pi-lens/dist/clients/persist-debounce.js +8 -1
- package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +114 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +77 -16
- package/node_modules/pi-lens/dist/clients/probe-home-state.js +228 -0
- package/node_modules/pi-lens/dist/clients/process-bridge.js +66 -0
- package/node_modules/pi-lens/dist/clients/process-snapshot.js +68 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +27 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +51 -20
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +393 -122
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +52 -24
- package/node_modules/pi-lens/dist/clients/python-environment.js +217 -0
- package/node_modules/pi-lens/dist/clients/python-provenance.js +639 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +6 -1
- package/node_modules/pi-lens/dist/clients/read-bridge.js +13 -21
- package/node_modules/pi-lens/dist/clients/read-expansion.js +9 -46
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +15 -4
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +364 -212
- package/node_modules/pi-lens/dist/clients/read-guard.js +21 -0
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +195 -96
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +152 -109
- package/node_modules/pi-lens/dist/clients/review-graph/service.js +55 -3
- package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +7 -15
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +10 -74
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +9 -3
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +18 -2
- package/node_modules/pi-lens/dist/clients/runtime-context.js +49 -16
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +25 -6
- package/node_modules/pi-lens/dist/clients/runtime-session.js +139 -22
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +168 -35
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +797 -243
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +1028 -53
- package/node_modules/pi-lens/dist/clients/rust-client.js +31 -0
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +33 -10
- package/node_modules/pi-lens/dist/clients/sanitize.js +7 -1
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +1 -3
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +3 -0
- package/node_modules/pi-lens/dist/clients/session-lifecycle.js +1 -1
- package/node_modules/pi-lens/dist/clients/session-start-observability.js +79 -0
- package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +12 -3
- package/node_modules/pi-lens/dist/clients/sgconfig.js +6 -1
- package/node_modules/pi-lens/dist/clients/skills-resolver.js +105 -0
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +2 -2
- package/node_modules/pi-lens/dist/clients/string-utils.js +13 -0
- package/node_modules/pi-lens/dist/clients/subagent-mode.js +17 -4
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +176 -39
- package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +239 -0
- package/node_modules/pi-lens/dist/clients/tool-definition.js +41 -1
- package/node_modules/pi-lens/dist/clients/tool-policy.js +91 -21
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +40 -40
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +86 -48
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +81 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +51 -46
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +28 -0
- package/node_modules/pi-lens/dist/clients/typos-config.js +5 -0
- package/node_modules/pi-lens/dist/clients/user-notify.js +6 -2
- package/node_modules/pi-lens/dist/clients/widget-state.js +191 -26
- package/node_modules/pi-lens/dist/clients/word-index-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/word-index-store.js +20 -5
- package/node_modules/pi-lens/dist/clients/word-index.js +243 -31
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +8 -1
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +3 -0
- package/node_modules/pi-lens/dist/index.js +64335 -54300
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -1
- package/node_modules/pi-lens/dist/mcp/server.js +127 -5
- package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +583 -0
- package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +129 -0
- package/node_modules/pi-lens/dist/tools/effective-config.js +89 -0
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +95 -24
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +67 -80
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +37 -16
- package/node_modules/pi-lens/dist/tools/shared.js +0 -1
- package/node_modules/pi-lens/docs/agent-guide.md +64 -1
- package/node_modules/pi-lens/docs/configuration.md +222 -0
- package/node_modules/pi-lens/docs/dependencies.md +3 -3
- package/node_modules/pi-lens/docs/features.md +44 -5
- package/node_modules/pi-lens/docs/language-coverage.md +2 -2
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +25 -0
- package/node_modules/pi-lens/docs/pi-lens-investigator.md +25 -0
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +27 -0
- package/node_modules/pi-lens/docs/pi-lens-subagent.md +38 -0
- package/node_modules/pi-lens/docs/pi-lens-warden.md +55 -0
- package/node_modules/pi-lens/docs/public-api-stability.md +359 -0
- package/node_modules/pi-lens/docs/release-qa-baseline.md +182 -0
- package/node_modules/pi-lens/docs/subagent-compat.md +110 -30
- package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +2 -2
- package/node_modules/pi-lens/package.json +15 -23
- package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
- package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
- package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +192 -1
- package/node_modules/pi-lens/scripts/install-selftest.mjs +58 -2
- package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +129 -0
- package/node_modules/pi-lens/scripts/rpc-load-check.mjs +3 -1
- package/node_modules/pi-web-access/CHANGELOG.md +28 -0
- package/node_modules/pi-web-access/README.md +62 -15
- package/node_modules/pi-web-access/curator-page.ts +3 -1
- package/node_modules/pi-web-access/curator-server.ts +5 -1
- package/node_modules/pi-web-access/gemini-search.ts +8 -4
- package/node_modules/pi-web-access/github-extract.ts +242 -1
- package/node_modules/pi-web-access/index.ts +118 -64
- package/node_modules/pi-web-access/mistral-search.ts +281 -0
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/perplexity.ts +14 -1
- package/node_modules/pi-web-access/utils.ts +23 -6
- package/node_modules/pi-web-access/xai-search.ts +96 -33
- package/package.json +5 -5
|
@@ -57,37 +57,28 @@
|
|
|
57
57
|
* patterns apply to files inside that package, in addition to (and with
|
|
58
58
|
* higher precedence than) the root config's `ignore` patterns.
|
|
59
59
|
*/
|
|
60
|
-
import {
|
|
61
|
-
import { notifyUserDegradation } from "./user-notify.js";
|
|
60
|
+
import { resetIgnoredConfigWarnCache, warnIgnoredConfigOnce, } from "./config-warn.js";
|
|
62
61
|
import * as fs from "node:fs";
|
|
63
62
|
import * as os from "node:os";
|
|
64
63
|
import * as path from "node:path";
|
|
65
64
|
import { toPositiveFinite } from "./env-utils.js";
|
|
66
|
-
import {
|
|
67
|
-
import {
|
|
65
|
+
import { FRESHNESS_CADENCE_MS } from "./freshness-cadence.js";
|
|
66
|
+
import { assignFlagConfigSection, flagConfigSectionKeys, GLOBAL_NON_FLAG_CONFIG_SECTIONS, LENS_FLAGS, PROJECT_FOREIGN_CONFIG_NAMESPACES, PROJECT_FOREIGN_NAMESPACE_HONORED_KEYS, PROJECT_NON_FLAG_CONFIG_SECTIONS, PROJECT_SCOPED_LENS_FLAGS, readFlagConfigValue, } from "./lens-flag-registry.js";
|
|
67
|
+
import { configSearchDirs, getPiLensGlobalConfigPath, PROJECT_CONFIG_BASENAMES, PROJECT_CONFIG_LOCATIONS, } from "./config-locations.js";
|
|
68
|
+
import { deprecationRecords, ignoredRecordCollector, projectLocationFor, readConfigDocument, reportConfigReadFailure, reportPiLensConfigRecords, resolveOnePiLensConfigDocument, } from "./config-resolve.js";
|
|
69
|
+
import { finalizeRecords, } from "./config-core/records.js";
|
|
70
|
+
import { homeRelativePath, isAtOrAboveHomeDir, walkUpDirs, } from "./path-utils.js";
|
|
68
71
|
import { findPiLensConfigMarkerInDir } from "./workspace-topology.js";
|
|
69
|
-
const PROJECT_CONFIG_BASENAMES = [".pi-lens.json", "pi-lens.json"];
|
|
70
72
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* file also carries live in `PROJECT_FOREIGN_CONFIG_NAMESPACES` beside the
|
|
79
|
-
* registry.
|
|
73
|
+
* Project config basenames, in DESCENDING precedence (first match wins), for
|
|
74
|
+
* this loader's own discovery.
|
|
75
|
+
*
|
|
76
|
+
* Re-exported from `config-locations.ts`, which owns the derivation, so this
|
|
77
|
+
* loader's long-standing export keeps working while there is exactly ONE list
|
|
78
|
+
* (#2426 review round 2: `workspace-topology.ts` was still carrying a third
|
|
79
|
+
* copy).
|
|
80
80
|
*/
|
|
81
|
-
|
|
82
|
-
"ignore",
|
|
83
|
-
"rules",
|
|
84
|
-
"maxProjectFiles",
|
|
85
|
-
"reviewGraph",
|
|
86
|
-
"trivy",
|
|
87
|
-
// `helm.renderValidation.enabled` — the opt-in for rendered-manifest
|
|
88
|
-
// validation, read via `.raw` in the helm-render runner (#1283).
|
|
89
|
-
"helm",
|
|
90
|
-
];
|
|
81
|
+
export { PROJECT_CONFIG_BASENAMES } from "./config-locations.js";
|
|
91
82
|
/** Clamp bounds for `reviewGraph.maxFiles` — see the field's doc comment above. */
|
|
92
83
|
const REVIEW_GRAPH_MAX_FILES_MIN = 100;
|
|
93
84
|
const REVIEW_GRAPH_MAX_FILES_MAX = 20_000;
|
|
@@ -102,34 +93,86 @@ export const EMPTY_PROJECT_CONFIG = {
|
|
|
102
93
|
/** Cache by absolute config path; we read each candidate's mtime before reuse. */
|
|
103
94
|
const configCache = new Map();
|
|
104
95
|
const discoveryCache = new Map();
|
|
105
|
-
|
|
96
|
+
/**
|
|
97
|
+
* The shared body of `loadPiLensProjectConfig` and `loadPiLensConfigInDir`:
|
|
98
|
+
* reuse a fresh cache entry, or parse and cache one, and — EITHER WAY (#2426
|
|
99
|
+
* review round 3, F2) — report the resolution's migration records. Content
|
|
100
|
+
* caching (skip re-reading unchanged bytes off disk) and per-session
|
|
101
|
+
* reporting (tell the user once a session about a deprecated setting) are two
|
|
102
|
+
* different lifetimes; folding "already cached" into "already reported"
|
|
103
|
+
* silently dropped the report the moment the file stopped changing.
|
|
104
|
+
*/
|
|
105
|
+
function loadCachedConfigFile(info) {
|
|
106
|
+
const cached = configCache.get(info.path);
|
|
107
|
+
if (cached && cached.mtimeMs === info.mtimeMs && cached.size === info.size) {
|
|
108
|
+
replayConfigNotices(cached);
|
|
109
|
+
return cached.config;
|
|
110
|
+
}
|
|
111
|
+
const parsed = parseConfigFile(info.path);
|
|
112
|
+
const entry = {
|
|
113
|
+
mtimeMs: info.mtimeMs,
|
|
114
|
+
size: info.size,
|
|
115
|
+
config: parsed.config,
|
|
116
|
+
records: parsed.records,
|
|
117
|
+
ignored: parsed.ignored,
|
|
118
|
+
};
|
|
119
|
+
configCache.set(info.path, entry);
|
|
120
|
+
replayConfigNotices(entry);
|
|
121
|
+
return parsed.config;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Report BOTH classes of notice a parse produced (#2426 review round 4, F3).
|
|
125
|
+
*
|
|
126
|
+
* The resolution's records go through the shared seam, which derives each
|
|
127
|
+
* one's subsystem from the record. This loader's own records do not: they are
|
|
128
|
+
* `project-lens-config`'s by construction — it composed their prose — so
|
|
129
|
+
* routing them through that derivation would relabel `"lsp.enabled" is a
|
|
130
|
+
* global-only pi-lens setting` as an LSP-config problem, which is the opposite
|
|
131
|
+
* of what it says.
|
|
132
|
+
*/
|
|
133
|
+
function replayConfigNotices(entry) {
|
|
134
|
+
reportPiLensConfigRecords(entry.records);
|
|
135
|
+
for (const record of entry.ignored) {
|
|
136
|
+
warnIgnoredConfigOnce({
|
|
137
|
+
subsystem: "project-lens-config",
|
|
138
|
+
file: record.file,
|
|
139
|
+
reason: record.reason,
|
|
140
|
+
code: record.code,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
106
144
|
/**
|
|
107
145
|
* Walk up from `startDir` looking for a `.pi-lens.json` or `pi-lens.json`.
|
|
108
146
|
* Returns the parsed config, or an empty config if none was found.
|
|
109
147
|
*/
|
|
110
|
-
export function loadPiLensProjectConfig(startDir, preloadedInfo
|
|
111
|
-
|
|
148
|
+
export function loadPiLensProjectConfig(startDir, preloadedInfo) {
|
|
149
|
+
// ONE discovery per load (#2426 review round 5, F-C). The legacy-document
|
|
150
|
+
// enumeration below and the config-file lookup are two READS of the same
|
|
151
|
+
// cache entry, and resolving that entry twice — the default argument once,
|
|
152
|
+
// the record replay again — doubled the freshness stats every warm load
|
|
153
|
+
// pays, on a path called per dispatch. The entry is resolved here and both
|
|
154
|
+
// halves read it.
|
|
155
|
+
const entry = discoveryEntryFor(startDir);
|
|
156
|
+
// #2426 review round 4, F4. The half-migrated notices — a legacy
|
|
157
|
+
// `pi-lens.json` sitting BESIDE or ABOVE the canonical `.pi-lens.json` this
|
|
158
|
+
// walk stops at — used to be produced only by `loadLSPConfig`, whose
|
|
159
|
+
// discovery collects from every bearing directory rather than stopping at
|
|
160
|
+
// the first. Under `--no-lsp`, `lsp.enabled: false`, or a subagent session
|
|
161
|
+
// (#449, routine) that loader never runs, and the pi-lens-OWNED records for
|
|
162
|
+
// a pi-lens-owned file were then produced by nobody at all. The pi-lens
|
|
163
|
+
// loader now enumerates those documents itself, for records only: nothing
|
|
164
|
+
// here is merged, and which file supplies a VALUE is unchanged.
|
|
165
|
+
reportPiLensConfigRecords(entry.legacyRecords);
|
|
166
|
+
const configInfo = preloadedInfo ?? freshInfoFor(entry);
|
|
112
167
|
if (!configInfo)
|
|
113
168
|
return EMPTY_PROJECT_CONFIG;
|
|
114
|
-
|
|
115
|
-
if (cached &&
|
|
116
|
-
cached.mtimeMs === configInfo.mtimeMs &&
|
|
117
|
-
cached.size === configInfo.size) {
|
|
118
|
-
return cached.config;
|
|
119
|
-
}
|
|
120
|
-
const config = parseConfigFile(configInfo.path);
|
|
121
|
-
configCache.set(configInfo.path, {
|
|
122
|
-
mtimeMs: configInfo.mtimeMs,
|
|
123
|
-
size: configInfo.size,
|
|
124
|
-
config,
|
|
125
|
-
});
|
|
126
|
-
return config;
|
|
169
|
+
return loadCachedConfigFile(configInfo);
|
|
127
170
|
}
|
|
128
171
|
/** For tests + callers that need to force a re-read (e.g. config-watcher hooks). */
|
|
129
172
|
export function resetProjectLensConfigCache() {
|
|
130
173
|
configCache.clear();
|
|
131
174
|
discoveryCache.clear();
|
|
132
|
-
|
|
175
|
+
resetIgnoredConfigWarnCache("project-lens-config");
|
|
133
176
|
}
|
|
134
177
|
/**
|
|
135
178
|
* Look for a `.pi-lens.json`/`pi-lens.json` directly IN `dir` — no upward
|
|
@@ -189,31 +232,37 @@ export function loadPiLensConfigInDir(dir) {
|
|
|
189
232
|
const info = findPiLensConfigInDir(dir);
|
|
190
233
|
if (!info)
|
|
191
234
|
return EMPTY_PROJECT_CONFIG;
|
|
192
|
-
|
|
193
|
-
if (cached && cached.mtimeMs === info.mtimeMs && cached.size === info.size) {
|
|
194
|
-
return cached.config;
|
|
195
|
-
}
|
|
196
|
-
const config = parseConfigFile(info.path);
|
|
197
|
-
configCache.set(info.path, {
|
|
198
|
-
mtimeMs: info.mtimeMs,
|
|
199
|
-
size: info.size,
|
|
200
|
-
config,
|
|
201
|
-
});
|
|
202
|
-
return config;
|
|
235
|
+
return loadCachedConfigFile(info);
|
|
203
236
|
}
|
|
204
|
-
|
|
237
|
+
/** The cached discovery for `startDir`, recomputed when it is no longer fresh. */
|
|
238
|
+
function discoveryEntryFor(startDir) {
|
|
205
239
|
const cacheKey = path.resolve(startDir);
|
|
206
240
|
const cached = discoveryCache.get(cacheKey);
|
|
207
|
-
if (cached && discoveryCacheStillFresh(cached))
|
|
208
|
-
|
|
209
|
-
return undefined;
|
|
210
|
-
const stat = safeFileStat(cached.info.path);
|
|
211
|
-
if (stat?.isFile())
|
|
212
|
-
return { ...cached.info, mtimeMs: stat.mtimeMs, size: stat.size };
|
|
213
|
-
}
|
|
241
|
+
if (cached && discoveryCacheStillFresh(cached))
|
|
242
|
+
return cached;
|
|
214
243
|
const discovered = discoverPiLensProjectConfig(cacheKey);
|
|
215
244
|
discoveryCache.set(cacheKey, discovered);
|
|
216
|
-
return discovered
|
|
245
|
+
return discovered;
|
|
246
|
+
}
|
|
247
|
+
export function findPiLensProjectConfig(startDir) {
|
|
248
|
+
return freshInfoFor(discoveryEntryFor(startDir));
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* The winning config file's info, re-statted.
|
|
252
|
+
*
|
|
253
|
+
* Split out of `findPiLensProjectConfig` so a caller that already holds the
|
|
254
|
+
* discovery entry does not resolve it a second time to get here (#2426 review
|
|
255
|
+
* round 5, F-C). The re-stat itself stays: the entry can be fresh by directory
|
|
256
|
+
* mtime while the config file was rewritten in place, and the caller keys its
|
|
257
|
+
* content cache on the mtime/size this returns.
|
|
258
|
+
*/
|
|
259
|
+
function freshInfoFor(entry) {
|
|
260
|
+
if (!entry.info)
|
|
261
|
+
return undefined;
|
|
262
|
+
const stat = safeFileStat(entry.info.path);
|
|
263
|
+
if (!stat?.isFile())
|
|
264
|
+
return entry.info;
|
|
265
|
+
return { ...entry.info, mtimeMs: stat.mtimeMs, size: stat.size };
|
|
217
266
|
}
|
|
218
267
|
function safeFileStat(filePath) {
|
|
219
268
|
try {
|
|
@@ -232,48 +281,192 @@ function safeDirMtimeMs(dir) {
|
|
|
232
281
|
}
|
|
233
282
|
}
|
|
234
283
|
function discoveryCacheStillFresh(entry) {
|
|
235
|
-
|
|
284
|
+
if (!entry.dirMtimes.every((cached) => safeDirMtimeMs(cached.dir) === cached.mtimeMs)) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
// #2483 round 2. When no config was found anywhere, `bearingDirMtimes`'s
|
|
288
|
+
// no-`info` branch scopes `dirMtimes` to `startDir` alone, so the `.every`
|
|
289
|
+
// above can only ever see churn AT `startDir` — a config file created at
|
|
290
|
+
// any ancestor above it moves nothing this entry tracks, and the entry
|
|
291
|
+
// would then read as fresh for the rest of the process (no production
|
|
292
|
+
// caller resets it, and a same-`startDir` load never rediscovers). The
|
|
293
|
+
// issue's acceptance line rules that out ("a new config file created in
|
|
294
|
+
// the chain IS noticed (bounded staleness stated)"), so a no-`info` entry
|
|
295
|
+
// is force-expired at most once per `FRESHNESS_CADENCE_MS` regardless of
|
|
296
|
+
// what `dirMtimes` says — the same negative-cache cadence bound
|
|
297
|
+
// `file-utils.ts`'s `patternsForDir` already applies to its own no-match
|
|
298
|
+
// case, reused rather than a second cadence mechanism invented here. The
|
|
299
|
+
// forced re-walk that follows re-stats the FULL ancestor chain (see
|
|
300
|
+
// `discoverPiLensProjectConfig`), so a config created anywhere in it is
|
|
301
|
+
// found — bounded to at most one cadence window of staleness, not
|
|
302
|
+
// unbounded.
|
|
303
|
+
if (entry.info === undefined &&
|
|
304
|
+
Date.now() - entry.checkedAtMs >= FRESHNESS_CADENCE_MS) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
// A legacy document can be EDITED in place without its directory's mtime
|
|
308
|
+
// moving, and its top-level key set is what `legacyRecords` describes — so
|
|
309
|
+
// those files carry their own freshness axis, the same (mtime, size) pair
|
|
310
|
+
// `CacheEntry` uses.
|
|
311
|
+
return entry.legacySources.every((source) => {
|
|
312
|
+
const stat = safeFileStat(source.path);
|
|
313
|
+
return (stat?.isFile() === true &&
|
|
314
|
+
stat.mtimeMs === source.mtimeMs &&
|
|
315
|
+
stat.size === source.size);
|
|
316
|
+
});
|
|
236
317
|
}
|
|
318
|
+
const LEGACY_PROJECT_LOCATIONS = PROJECT_CONFIG_LOCATIONS.filter((location) => location.legacy);
|
|
237
319
|
function discoverPiLensProjectConfig(startDir) {
|
|
238
320
|
const dirMtimes = [];
|
|
239
|
-
|
|
321
|
+
const legacyDocuments = [];
|
|
322
|
+
const legacySources = [];
|
|
323
|
+
let info;
|
|
324
|
+
// #2426: ceiling-bounded, same rule and same shared primitive as the LSP
|
|
325
|
+
// loader and as `findNestedProjectMutationValue` below. This walk ran to the
|
|
326
|
+
// filesystem root before, so a `.pi-lens.json` in `$HOME` (or at `C:\`) was
|
|
327
|
+
// adopted by every project on the machine — the #622/#625 class.
|
|
328
|
+
//
|
|
329
|
+
// It no longer STOPS at the first bearing directory (#2426 review round 4,
|
|
330
|
+
// F4): the first hit still decides `info`, and therefore still decides every
|
|
331
|
+
// value this loader returns, but the walk continues to the ceiling so the
|
|
332
|
+
// legacy documents a user is being asked to migrate are enumerated wherever
|
|
333
|
+
// they sit — beside the winning file or above it.
|
|
334
|
+
for (const dir of configSearchDirs(startDir)) {
|
|
240
335
|
dirMtimes.push({ dir, mtimeMs: safeDirMtimeMs(dir) });
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
info
|
|
336
|
+
if (!info) {
|
|
337
|
+
for (const name of PROJECT_CONFIG_BASENAMES) {
|
|
338
|
+
const candidate = path.join(dir, name);
|
|
339
|
+
const stat = safeFileStat(candidate);
|
|
340
|
+
if (stat?.isFile()) {
|
|
341
|
+
info = {
|
|
247
342
|
path: candidate,
|
|
248
343
|
dir,
|
|
249
344
|
mtimeMs: stat.mtimeMs,
|
|
250
345
|
size: stat.size,
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
}
|
|
346
|
+
};
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
254
349
|
}
|
|
255
350
|
}
|
|
351
|
+
for (const location of LEGACY_PROJECT_LOCATIONS) {
|
|
352
|
+
const file = path.join(dir, location.relativePath);
|
|
353
|
+
const stat = safeFileStat(file);
|
|
354
|
+
if (!stat?.isFile())
|
|
355
|
+
continue;
|
|
356
|
+
legacySources.push({
|
|
357
|
+
path: file,
|
|
358
|
+
mtimeMs: stat.mtimeMs,
|
|
359
|
+
size: stat.size,
|
|
360
|
+
});
|
|
361
|
+
const outcome = readConfigDocument(file);
|
|
362
|
+
if (outcome.status === "error") {
|
|
363
|
+
reportConfigReadFailure({
|
|
364
|
+
file,
|
|
365
|
+
location,
|
|
366
|
+
tier: "project",
|
|
367
|
+
error: outcome.error,
|
|
368
|
+
sourceText: outcome.sourceText,
|
|
369
|
+
});
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (outcome.status !== "ok")
|
|
373
|
+
continue;
|
|
374
|
+
legacyDocuments.push({
|
|
375
|
+
tier: "project",
|
|
376
|
+
file,
|
|
377
|
+
location,
|
|
378
|
+
value: outcome.value,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
256
381
|
}
|
|
257
|
-
return {
|
|
382
|
+
return {
|
|
383
|
+
info,
|
|
384
|
+
dirMtimes: bearingDirMtimes(dirMtimes, info),
|
|
385
|
+
legacyRecords: legacyDeprecationRecords(legacyDocuments),
|
|
386
|
+
legacySources,
|
|
387
|
+
checkedAtMs: Date.now(),
|
|
388
|
+
};
|
|
258
389
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
390
|
+
/**
|
|
391
|
+
* The legacy documents' deprecation records, BOUNDED per document (#2426
|
|
392
|
+
* review round 5, F-A/F-B).
|
|
393
|
+
*
|
|
394
|
+
* This path shipped its records raw. A legacy file's recognized top-level key
|
|
395
|
+
* count is user input, so one 28-key `pi-lens.json` produced 28 notifications
|
|
396
|
+
* through this loader while the LSP loader — which finalizes the same records —
|
|
397
|
+
* produced the bound plus a count for the identical file, and five nested
|
|
398
|
+
* legacy files produced 145 with no suppression record anywhere. Every producer
|
|
399
|
+
* now goes through the one \`finalizeRecords\`.
|
|
400
|
+
*
|
|
401
|
+
* Per DOCUMENT rather than per walk, because these are not one resolution: each
|
|
402
|
+
* file is separately actionable ("move these keys out of THIS file"), and a
|
|
403
|
+
* per-walk bound would silently drop whole files' advice while naming only the
|
|
404
|
+
* last one. Per document also makes the two loaders agree on the count for a
|
|
405
|
+
* given file, which is the parity `config-notice-ownership` asserts.
|
|
406
|
+
*/
|
|
407
|
+
function legacyDeprecationRecords(documents) {
|
|
408
|
+
const home = os.homedir();
|
|
409
|
+
return documents.flatMap((document) => finalizeRecords(deprecationRecords([document], home), {
|
|
410
|
+
file: document.file,
|
|
411
|
+
tier: document.tier,
|
|
412
|
+
}));
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* The directory mtimes this entry's freshness is keyed on: the BEARING chain —
|
|
416
|
+
* `startDir` up to and including the directory that supplied `info` (#2426
|
|
417
|
+
* review round 5, F-C).
|
|
418
|
+
*
|
|
419
|
+
* The walk itself no longer stops at the first bearing directory, because the
|
|
420
|
+
* legacy documents a user is asked to migrate can sit above it. Keying
|
|
421
|
+
* freshness on every directory it VISITS is a different decision, and a bad
|
|
422
|
+
* one: the walk runs to just below `$HOME`, so the entry was invalidated by any
|
|
423
|
+
* churn in an ancestor like `~/Desktop` — a full re-walk, re-read and re-parse
|
|
424
|
+
* of every legacy document, on a hot path, triggered by an unrelated directory.
|
|
425
|
+
*
|
|
426
|
+
* Scoping it back to the bearing chain restores the pre-#2426 invalidation
|
|
427
|
+
* surface exactly. Nothing that decides a VALUE is outside that chain, and an
|
|
428
|
+
* already-discovered legacy document above it carries its own (mtime, size)
|
|
429
|
+
* stamp in `legacySources`, so an EDIT to one is still noticed. What is given
|
|
430
|
+
* up is noticing a legacy file NEWLY CREATED above the bearing directory
|
|
431
|
+
* mid-process — records-only, and strictly more than the pre-#2426 loader saw,
|
|
432
|
+
* since it never looked above that directory at all.
|
|
433
|
+
*
|
|
434
|
+
* When NO config file is found anywhere (#2483), `info` is undefined and there
|
|
435
|
+
* is no bearing directory to stop at — but the majority of projects have no
|
|
436
|
+
* config at all, so this is the COMMON case, not an edge one. Falling back to
|
|
437
|
+
* the full chain here (the pre-fix behavior) keyed freshness on every
|
|
438
|
+
* directory up to just below `$HOME`: unrelated churn in `~/Desktop` or
|
|
439
|
+
* `~/projects` invalidated the entry and forced a full re-walk on the very
|
|
440
|
+
* next load, every load, forever.
|
|
441
|
+
*
|
|
442
|
+
* The nearest directory a config COULD have appeared in is `startDir` itself
|
|
443
|
+
* — `dirMtimes[0]`, first in the innermost-first walk order (`configSearchDirs`)
|
|
444
|
+
* — so that is what per-call freshness is scoped to, the same bearing-directory
|
|
445
|
+
* principle as the presence case (a directory always includes itself as the
|
|
446
|
+
* degenerate bearing chain). A config file created directly in `startDir`
|
|
447
|
+
* bumps `startDir`'s own mtime and is found on the next load.
|
|
448
|
+
*
|
|
449
|
+
* A config file created further up the chain does not move `startDir`'s own
|
|
450
|
+
* mtime, so it is invisible to THIS check alone (round 1 of #2483 stopped
|
|
451
|
+
* here, leaving staleness unbounded — no production caller ever resets the
|
|
452
|
+
* cache and a same-`startDir` load never rediscovers). `discoveryCacheStillFresh`
|
|
453
|
+
* closes that gap: a no-`info` entry is force-expired at most once per
|
|
454
|
+
* `FRESHNESS_CADENCE_MS`, independent of what `dirMtimes` says, which sends
|
|
455
|
+
* the NEXT load through a full re-walk of the ancestor chain regardless — so
|
|
456
|
+
* a config created anywhere in it is noticed within one cadence window.
|
|
457
|
+
* Bounded staleness, and strictly narrower than the pre-fix "any ancestor up
|
|
458
|
+
* to $HOME invalidates on every load".
|
|
459
|
+
*/
|
|
460
|
+
function bearingDirMtimes(dirMtimes, info) {
|
|
461
|
+
if (info) {
|
|
462
|
+
const bearing = dirMtimes.findIndex((entry) => entry.dir === info.dir);
|
|
463
|
+
return bearing < 0 ? [...dirMtimes] : dirMtimes.slice(0, bearing + 1);
|
|
464
|
+
}
|
|
465
|
+
return dirMtimes.slice(0, 1);
|
|
273
466
|
}
|
|
274
|
-
function parseRulePolicyList(
|
|
467
|
+
function parseRulePolicyList(note, ruleId, key, value) {
|
|
275
468
|
if (!Array.isArray(value)) {
|
|
276
|
-
|
|
469
|
+
note(`rules.${ruleId}.${key} must be an array of strings`);
|
|
277
470
|
return { list: [], invalid: true };
|
|
278
471
|
}
|
|
279
472
|
const list = [];
|
|
@@ -290,34 +483,78 @@ function parseRulePolicyList(configPath, ruleId, key, value) {
|
|
|
290
483
|
// but none were usable strings (all blank / non-string), which is a real
|
|
291
484
|
// authoring mistake that must not fail silently.
|
|
292
485
|
if (value.length > 0) {
|
|
293
|
-
|
|
486
|
+
note(`rules.${ruleId}.${key} must contain at least one non-empty string`);
|
|
294
487
|
return { list: [], invalid: true };
|
|
295
488
|
}
|
|
296
489
|
return { list: [], invalid: false };
|
|
297
490
|
}
|
|
298
491
|
return { list, invalid: false };
|
|
299
492
|
}
|
|
493
|
+
const NO_RECORDS = [];
|
|
300
494
|
function parseConfigFile(configPath) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
495
|
+
const { note, records: ignoredRecords } = ignoredRecordCollector(configPath, "project");
|
|
496
|
+
const outcome = readConfigDocument(configPath);
|
|
497
|
+
if (outcome.status === "error") {
|
|
498
|
+
// `{ parseError }`, not a pre-stringified message (#2431): the raw error
|
|
499
|
+
// reaches one seam, which decides how much of a `JSON.parse`
|
|
500
|
+
// `SyntaxError#message` (which embeds a snippet of the file being parsed)
|
|
501
|
+
// survives into the sinks.
|
|
502
|
+
note({ parseError: outcome.error, sourceText: outcome.sourceText });
|
|
503
|
+
return {
|
|
504
|
+
config: EMPTY_PROJECT_CONFIG,
|
|
505
|
+
records: NO_RECORDS,
|
|
506
|
+
ignored: ignoredRecords(),
|
|
507
|
+
};
|
|
305
508
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
509
|
+
// The file was stat'd as present by the discovery above, so `missing` here is
|
|
510
|
+
// a delete that raced the read. Nothing to warn about — the next call
|
|
511
|
+
// re-discovers and finds no config at all.
|
|
512
|
+
if (outcome.status === "missing") {
|
|
513
|
+
return {
|
|
514
|
+
config: EMPTY_PROJECT_CONFIG,
|
|
515
|
+
records: NO_RECORDS,
|
|
516
|
+
ignored: NO_RECORDS,
|
|
517
|
+
};
|
|
309
518
|
}
|
|
310
|
-
if (!
|
|
311
|
-
|
|
312
|
-
|
|
519
|
+
if (!outcome.value ||
|
|
520
|
+
typeof outcome.value !== "object" ||
|
|
521
|
+
Array.isArray(outcome.value)) {
|
|
522
|
+
note("top-level value must be an object");
|
|
523
|
+
return {
|
|
524
|
+
config: EMPTY_PROJECT_CONFIG,
|
|
525
|
+
records: NO_RECORDS,
|
|
526
|
+
ignored: ignoredRecords(),
|
|
527
|
+
};
|
|
313
528
|
}
|
|
314
|
-
|
|
529
|
+
// Through the #2425 core (#2426): the value is validated against the
|
|
530
|
+
// canonical schema before this loader's own field parsing sees it, so the
|
|
531
|
+
// depth bound, the prototype-key policy and the declared `lsp.*` types apply
|
|
532
|
+
// here exactly as they do in the LSP loader. `raw` is the VALIDATED value
|
|
533
|
+
// rather than the parse output, which is the one place the field's
|
|
534
|
+
// documented "the parsed JSON as-is" narrows: a `__proto__` key or a
|
|
535
|
+
// 4000-deep blob no longer rides into `.raw`'s consumers (`trivy-client.ts`,
|
|
536
|
+
// the helm-render runner).
|
|
537
|
+
const resolved = resolveOnePiLensConfigDocument({
|
|
538
|
+
tier: "project",
|
|
539
|
+
file: configPath,
|
|
540
|
+
location: projectLocationFor(configPath),
|
|
541
|
+
value: outcome.value,
|
|
542
|
+
});
|
|
543
|
+
// The records are RETURNED, not reported here (#2426 review round 3, F2):
|
|
544
|
+
// this function's result is what the caller caches, and a cache HIT must
|
|
545
|
+
// still report them every session — a call this function never makes on a
|
|
546
|
+
// hit. Reporting here would fire only on a cache MISS (session 1), and
|
|
547
|
+
// `resetProjectLensConfigCache` has no production caller, so every session
|
|
548
|
+
// after the first silently lost the `config-deprecated` ledger row despite
|
|
549
|
+
// the file still being the exact deprecated one on disk.
|
|
550
|
+
const raw = resolved.value;
|
|
551
|
+
const obj = resolved.value;
|
|
315
552
|
const ignore = Array.isArray(obj.ignore)
|
|
316
553
|
? obj.ignore.filter((p) => typeof p === "string")
|
|
317
554
|
: [];
|
|
318
555
|
const mutations = {};
|
|
319
556
|
for (const spec of PROJECT_SCOPED_LENS_FLAGS) {
|
|
320
|
-
assignFlagConfigSection(obj, mutations, spec.configKey,
|
|
557
|
+
assignFlagConfigSection(obj, mutations, spec.configKey, note);
|
|
321
558
|
}
|
|
322
559
|
const rules = {};
|
|
323
560
|
if (obj.rules && typeof obj.rules === "object" && !Array.isArray(obj.rules)) {
|
|
@@ -327,7 +564,7 @@ function parseConfigFile(configPath) {
|
|
|
327
564
|
// disable`), which lands here as an array and would otherwise be
|
|
328
565
|
// dropped without a word — the one shape a user is most likely to try.
|
|
329
566
|
if (!ruleCfg || typeof ruleCfg !== "object" || Array.isArray(ruleCfg)) {
|
|
330
|
-
|
|
567
|
+
note(`rules.${ruleId} must be an object with threshold, disable, or select; ignored`);
|
|
331
568
|
continue;
|
|
332
569
|
}
|
|
333
570
|
const r = ruleCfg;
|
|
@@ -338,17 +575,17 @@ function parseConfigFile(configPath) {
|
|
|
338
575
|
entry.threshold = r.threshold;
|
|
339
576
|
}
|
|
340
577
|
else if ("threshold" in r) {
|
|
341
|
-
|
|
578
|
+
note(`rules.${ruleId}.threshold must be a positive finite number`);
|
|
342
579
|
}
|
|
343
580
|
if ("disable" in r) {
|
|
344
|
-
const parsed = parseRulePolicyList(
|
|
581
|
+
const parsed = parseRulePolicyList(note, ruleId, "disable", r.disable);
|
|
345
582
|
// #1087: an explicitly empty list is valid-but-empty (no warning);
|
|
346
583
|
// don't store a pointless no-op entry for it.
|
|
347
584
|
if (!parsed.invalid && parsed.list.length > 0)
|
|
348
585
|
entry.disable = parsed.list;
|
|
349
586
|
}
|
|
350
587
|
if ("select" in r) {
|
|
351
|
-
const parsed = parseRulePolicyList(
|
|
588
|
+
const parsed = parseRulePolicyList(note, ruleId, "select", r.select);
|
|
352
589
|
if (!parsed.invalid && parsed.list.length > 0)
|
|
353
590
|
entry.select = parsed.list;
|
|
354
591
|
}
|
|
@@ -361,7 +598,7 @@ function parseConfigFile(configPath) {
|
|
|
361
598
|
rules[ruleId] = entry;
|
|
362
599
|
}
|
|
363
600
|
else if (!("threshold" in r) && !("disable" in r) && !("select" in r)) {
|
|
364
|
-
|
|
601
|
+
note(`rules.${ruleId} has no recognized setting (threshold, disable, select); ignored`);
|
|
365
602
|
}
|
|
366
603
|
}
|
|
367
604
|
}
|
|
@@ -373,7 +610,7 @@ function parseConfigFile(configPath) {
|
|
|
373
610
|
maxProjectFiles = obj.maxProjectFiles;
|
|
374
611
|
}
|
|
375
612
|
else {
|
|
376
|
-
|
|
613
|
+
note("maxProjectFiles must be a positive finite number");
|
|
377
614
|
}
|
|
378
615
|
}
|
|
379
616
|
let reviewGraph;
|
|
@@ -381,7 +618,7 @@ function parseConfigFile(configPath) {
|
|
|
381
618
|
if (!obj.reviewGraph ||
|
|
382
619
|
typeof obj.reviewGraph !== "object" ||
|
|
383
620
|
Array.isArray(obj.reviewGraph)) {
|
|
384
|
-
|
|
621
|
+
note("reviewGraph must be an object");
|
|
385
622
|
}
|
|
386
623
|
else {
|
|
387
624
|
const rg = obj.reviewGraph;
|
|
@@ -392,7 +629,7 @@ function parseConfigFile(configPath) {
|
|
|
392
629
|
reviewGraph = { maxFiles: clamped };
|
|
393
630
|
}
|
|
394
631
|
else {
|
|
395
|
-
|
|
632
|
+
note("reviewGraph.maxFiles must be a positive finite number");
|
|
396
633
|
}
|
|
397
634
|
}
|
|
398
635
|
}
|
|
@@ -407,7 +644,7 @@ function parseConfigFile(configPath) {
|
|
|
407
644
|
// than being lumped in with typos — docs previously called this "silently
|
|
408
645
|
// ignored".
|
|
409
646
|
const knownProjectKeys = new Set([
|
|
410
|
-
...
|
|
647
|
+
...PROJECT_NON_FLAG_CONFIG_SECTIONS,
|
|
411
648
|
...flagConfigSectionKeys(PROJECT_SCOPED_LENS_FLAGS),
|
|
412
649
|
...PROJECT_FOREIGN_CONFIG_NAMESPACES,
|
|
413
650
|
]);
|
|
@@ -415,23 +652,57 @@ function parseConfigFile(configPath) {
|
|
|
415
652
|
...flagConfigSectionKeys(LENS_FLAGS),
|
|
416
653
|
...GLOBAL_NON_FLAG_CONFIG_SECTIONS,
|
|
417
654
|
].filter((key) => !knownProjectKeys.has(key)));
|
|
655
|
+
// The full DOTTED config keys of the flags a project file cannot set, for the
|
|
656
|
+
// sub-key scan below. Section-level keys are too coarse for a namespace that
|
|
657
|
+
// mixes scopes: `lsp` holds four project-scoped settings AND the global-only
|
|
658
|
+
// `lsp.enabled`.
|
|
659
|
+
const projectScoped = new Set(PROJECT_SCOPED_LENS_FLAGS.map((spec) => spec.configKey));
|
|
660
|
+
const globalScopeOnlyFlagKeys = LENS_FLAGS.map((spec) => spec.configKey).filter((configKey) => !projectScoped.has(configKey));
|
|
661
|
+
// The literal path is derived, not hand-typed (#2426 review round 3, S1):
|
|
662
|
+
// under `PI_LENS_CONFIG_PATH` the canonical global config is NOT
|
|
663
|
+
// `~/.pi-lens/config.json`, and a hardcoded string in this notice would name
|
|
664
|
+
// a file the resolver never reads.
|
|
665
|
+
const globalConfigLabel = homeRelativePath(getPiLensGlobalConfigPath(), os.homedir());
|
|
666
|
+
const warnUnhonoredKey = (label, globalOnly) => {
|
|
667
|
+
note(globalOnly
|
|
668
|
+
? `"${label}" is a global-only pi-lens setting and is not honored in a project .pi-lens.json (set it in ${globalConfigLabel} or pass the matching CLI flag); ignored`
|
|
669
|
+
: `unknown key "${label}" is not a recognized pi-lens setting (check for a typo); ignored`);
|
|
670
|
+
};
|
|
418
671
|
for (const key of Object.keys(obj)) {
|
|
419
672
|
if (knownProjectKeys.has(key))
|
|
420
673
|
continue;
|
|
421
|
-
|
|
422
|
-
|
|
674
|
+
warnUnhonoredKey(key, globalScopeOnlyKeys.has(key));
|
|
675
|
+
}
|
|
676
|
+
// A tolerated foreign namespace is tolerated KEY BY KEY (#2426 review round
|
|
677
|
+
// 2, F3). `lsp` is read out of this file by the LSP loader, but only four of
|
|
678
|
+
// its keys are project-scoped; `lsp.enabled` is the `--no-lsp` flag, which is
|
|
679
|
+
// `scope: "global"`. Blanket namespace tolerance swallowed it silently, which
|
|
680
|
+
// `docs/configuration.md`'s "nothing is ignored silently" forbids — so every
|
|
681
|
+
// other sub-key gets the same global-only-vs-typo signal a top-level key does,
|
|
682
|
+
// named with its DOTTED path so the notice says which setting is meant.
|
|
683
|
+
for (const [namespace, honored] of PROJECT_FOREIGN_NAMESPACE_HONORED_KEYS) {
|
|
684
|
+
const section = obj[namespace];
|
|
685
|
+
if (!section || typeof section !== "object" || Array.isArray(section)) {
|
|
686
|
+
continue;
|
|
423
687
|
}
|
|
424
|
-
|
|
425
|
-
|
|
688
|
+
for (const key of Object.keys(section)) {
|
|
689
|
+
if (honored.includes(key))
|
|
690
|
+
continue;
|
|
691
|
+
const dotted = `${namespace}.${key}`;
|
|
692
|
+
warnUnhonoredKey(dotted, globalScopeOnlyFlagKeys.some((configKey) => configKey === dotted || configKey.startsWith(`${dotted}.`)));
|
|
426
693
|
}
|
|
427
694
|
}
|
|
428
695
|
return {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
696
|
+
config: {
|
|
697
|
+
ignore,
|
|
698
|
+
rules,
|
|
699
|
+
...mutations,
|
|
700
|
+
maxProjectFiles,
|
|
701
|
+
reviewGraph,
|
|
702
|
+
raw,
|
|
703
|
+
configPath,
|
|
704
|
+
},
|
|
705
|
+
records: resolved.records,
|
|
706
|
+
ignored: ignoredRecords(),
|
|
436
707
|
};
|
|
437
708
|
}
|