pi-lens 4.2.1 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1562 -0
- package/README.md +157 -24
- package/config/dependency-cruiser-eager-allowlist.json +6 -2
- package/dist/chunk-22AVIGXS.js +560 -0
- package/dist/chunk-27BBHSZC.js +106 -0
- package/dist/chunk-2BEMIWEW.js +51 -0
- package/dist/chunk-2EECBNC5.js +89 -0
- package/dist/chunk-34U7PLGE.js +23 -0
- package/dist/chunk-3I5QFT3N.js +181 -0
- package/dist/chunk-3MED75HT.js +37 -0
- package/dist/chunk-47D2BO4S.js +87 -0
- package/dist/chunk-4RERDEXR.js +348 -0
- package/dist/chunk-4W2PLC4J.js +27 -0
- package/dist/chunk-52H5B5TC.js +202 -0
- package/dist/chunk-54B42YRX.js +27 -0
- package/dist/chunk-5RXBNSW2.js +266 -0
- package/dist/chunk-5THXD6X5.js +201 -0
- package/dist/chunk-5W3R6LFT.js +24 -0
- package/dist/chunk-5ZEJHV35.js +341 -0
- package/dist/chunk-6P62QVEA.js +118 -0
- package/dist/chunk-6QIUX4EP.js +459 -0
- package/dist/chunk-6VCD4VKZ.js +78 -0
- package/dist/chunk-7F72PXY2.js +326 -0
- package/dist/chunk-7FBF7NPR.js +998 -0
- package/dist/chunk-7U2IDNXN.js +37 -0
- package/dist/chunk-ABBOA7UD.js +157 -0
- package/dist/chunk-AHDW3GPE.js +50 -0
- package/dist/chunk-AJ3QXTRQ.js +756 -0
- package/dist/chunk-AJVLWNFN.js +1610 -0
- package/dist/chunk-AJXODZY6.js +112 -0
- package/dist/chunk-ARFCXG7S.js +84 -0
- package/dist/chunk-AS44CFQ4.js +708 -0
- package/dist/chunk-AWE5TMPO.js +38 -0
- package/dist/chunk-B3I2UUT4.js +4692 -0
- package/dist/chunk-BRJKOBZP.js +331 -0
- package/dist/chunk-BW5KFIQC.js +59 -0
- package/dist/chunk-C2AUYXQH.js +149 -0
- package/dist/chunk-CGGAIP7J.js +29 -0
- package/dist/chunk-D6QS3U6M.js +30 -0
- package/dist/chunk-DFVMIKMQ.js +349 -0
- package/dist/chunk-DIULIWKU.js +169 -0
- package/dist/chunk-DQ2NZ7C4.js +2317 -0
- package/dist/chunk-E2WFBRHM.js +213 -0
- package/dist/chunk-E4MAEDPM.js +50 -0
- package/dist/chunk-EOKRQ3SA.js +30 -0
- package/dist/chunk-F3AU2TMM.js +2626 -0
- package/dist/chunk-F3P7VQ7H.js +170 -0
- package/dist/chunk-FC4RP4I2.js +12 -0
- package/dist/chunk-FJNKANOS.js +32 -0
- package/dist/chunk-GUOGVTCQ.js +146 -0
- package/dist/chunk-GVXA6CCE.js +18 -0
- package/dist/chunk-HHYAELLJ.js +2334 -0
- package/dist/chunk-HMFXILPV.js +83 -0
- package/dist/chunk-HVUZXSWK.js +421 -0
- package/dist/chunk-HWBZIGMJ.js +62 -0
- package/dist/chunk-I3R7GKDR.js +17 -0
- package/dist/chunk-I7SPGOWI.js +55 -0
- package/dist/chunk-IIXOAGHR.js +38 -0
- package/dist/chunk-IVP5KAB7.js +2250 -0
- package/dist/chunk-IXEBJSUE.js +22 -0
- package/dist/chunk-JOLITGCC.js +27 -0
- package/dist/chunk-K6VYLB2U.js +145 -0
- package/dist/chunk-LJDODBBZ.js +78 -0
- package/dist/chunk-LNAKEO63.js +78 -0
- package/dist/chunk-LSMJ6KBE.js +4139 -0
- package/dist/chunk-LW6XKDZQ.js +5565 -0
- package/dist/chunk-N3YQJI6O.js +899 -0
- package/dist/chunk-NABLC7W3.js +148 -0
- package/dist/chunk-NKOUFPQF.js +659 -0
- package/dist/chunk-NL5GLMFI.js +211 -0
- package/dist/chunk-NMABWBZN.js +327 -0
- package/dist/chunk-NTHH2J74.js +27 -0
- package/dist/chunk-NTUJEGDZ.js +59 -0
- package/dist/chunk-NXL4FFQQ.js +47 -0
- package/dist/chunk-NXPS2L6U.js +466 -0
- package/dist/chunk-O5WILGMI.js +42 -0
- package/dist/chunk-O6TQT6RI.js +48 -0
- package/dist/chunk-OAXNDZJ7.js +323 -0
- package/dist/chunk-ODEEOXPH.js +44 -0
- package/dist/chunk-OQGFCZJ4.js +4533 -0
- package/dist/chunk-ORTMY4Q4.js +282 -0
- package/dist/chunk-OS32RNR2.js +442 -0
- package/dist/chunk-PAK6IDME.js +189 -0
- package/dist/chunk-PWFHBTSF.js +406 -0
- package/dist/chunk-Q5U6FMDI.js +2352 -0
- package/dist/chunk-QOZXJ4NA.js +3364 -0
- package/dist/chunk-QYF5S6P6.js +45 -0
- package/dist/chunk-RIISMHKN.js +36 -0
- package/dist/chunk-RPD4ZSB7.js +25 -0
- package/dist/chunk-RPD5K5NC.js +79 -0
- package/dist/chunk-RUOG5D2C.js +262 -0
- package/dist/chunk-T4ERT73Y.js +599 -0
- package/dist/chunk-T6D4Q4AQ.js +54 -0
- package/dist/chunk-TGPXLY2J.js +391 -0
- package/dist/chunk-TLGFBM3F.js +12116 -0
- package/dist/chunk-TO6WL3GM.js +88 -0
- package/dist/chunk-TT22N74J.js +517 -0
- package/dist/chunk-TVN5N6GY.js +21216 -0
- package/dist/chunk-U3VGR4VU.js +308 -0
- package/dist/chunk-UK3CMEAL.js +1250 -0
- package/dist/chunk-UOPVJQ4N.js +103 -0
- package/dist/chunk-UXVNL3D7.js +23 -0
- package/dist/chunk-UY752QSN.js +45 -0
- package/dist/chunk-V5PXFSZK.js +188 -0
- package/dist/chunk-V7R425XG.js +398 -0
- package/dist/chunk-VCSRSYLF.js +81 -0
- package/dist/chunk-VD4JQCQC.js +162 -0
- package/dist/chunk-VDBPVIKD.js +62 -0
- package/dist/chunk-VHQLPIRJ.js +114 -0
- package/dist/chunk-VN2AXLEX.js +5414 -0
- package/dist/chunk-VNMT7C64.js +478 -0
- package/dist/chunk-WONERJTP.js +309 -0
- package/dist/chunk-XECLWQZN.js +85 -0
- package/dist/chunk-XIMYZ3JA.js +27 -0
- package/dist/chunk-XK4CIILI.js +144 -0
- package/dist/chunk-Y4T27COV.js +562 -0
- package/dist/chunk-YGVYKNSN.js +1860 -0
- package/dist/chunk-YLF3PPT5.js +308 -0
- package/dist/chunk-YSNFMJKN.js +14 -0
- package/dist/chunk-Z3CCVDUB.js +76 -0
- package/dist/chunk-Z54ZSOBZ.js +61 -0
- package/dist/chunk-ZAWW3AF6.js +97 -0
- package/dist/chunk-ZMOL6RQC.js +167 -0
- package/dist/index.js +19039 -8956
- package/dist/mcp/analyze-cli.js +186 -228
- package/dist/mcp/cli.js +137 -98
- package/dist/mcp/server.js +22592 -1564
- package/dist/mcp/worker.js +97 -38
- package/dist/probes/installer.js +135 -0
- package/dist/workers/project-snapshot-persist-worker.js +22 -0
- package/dist/workers/review-graph-persist-worker.js +15 -0
- package/docs/adr/0001-stale-advisory-live-arm.md +29 -0
- package/docs/adr/0002-workspace-hash-rendezvous.md +30 -0
- package/docs/adr/0003-git-guard-latch-writer.md +203 -0
- package/docs/adr/0004-disposition-policy-seam.md +38 -0
- package/docs/adr/0005-tool-availability-enforcement-seam.md +29 -0
- package/docs/adr/0006-derived-state-benchmark-first.md +31 -0
- package/docs/adr/0007-end-to-end-witness-per-seam-slice.md +38 -0
- package/docs/adr/0008-turn-end-lane-interface.md +165 -0
- package/docs/adr/0009-reported-path-attribution.md +114 -0
- package/docs/adr/README.md +26 -0
- package/docs/agent-guide.md +13 -4
- package/docs/agent-tools.md +60 -1
- package/docs/ast-grep_rules_catalog.md +6 -4
- package/docs/configuration.md +75 -2
- package/docs/engineering-principles.md +202 -0
- package/docs/environment-variables.md +113 -6
- package/docs/features.md +10 -6
- package/docs/globalconfig.md +7 -1
- package/docs/io-bridge-v2-migration.md +94 -0
- package/docs/language-coverage.md +17 -0
- package/docs/lsp-capability-matrix.md +131 -32
- package/docs/lsp-idle-eviction.md +124 -0
- package/docs/mcp.md +21 -5
- package/docs/merge-queue-rollout.md +77 -0
- package/docs/pi-lens-fixer.md +161 -39
- package/docs/pi-lens-investigator.md +77 -20
- package/docs/pi-lens-merge-policy.md +694 -0
- package/docs/pi-lens-monitor.md +73 -7
- package/docs/pi-lens-retro.md +6 -4
- package/docs/pi-lens-reviewer.md +190 -34
- package/docs/pi-lens-subagent.md +247 -37
- package/docs/pi-lens-warden.md +18 -7
- package/docs/public-api-stability.md +56 -0
- package/docs/release-qa-baseline.md +86 -17
- package/docs/rfcs/3654-unified-io-bridge-v2.md +316 -0
- package/docs/servercapabilities.md +66 -52
- package/docs/settings.md +13 -3
- package/docs/subagent-compat.md +1 -1
- package/docs/usage.md +5 -1
- package/grammars/tree-sitter-bash.wasm +0 -0
- package/grammars/tree-sitter-bash.wasm.json +3 -3
- package/package.json +23 -9
- package/rules/ast-grep-rules/rule-tests/advisory-registry-subset-test.yml +41 -0
- package/rules/ast-grep-rules/rule-tests/no-assert-tuple-test.yml +13 -0
- package/rules/ast-grep-rules/rule-tests/no-boolean-in-except-test.yml +26 -0
- package/rules/ast-grep-rules/rule-tests/no-http-headers-bracket-access-test.yml +10 -0
- package/rules/ast-grep-rules/rule-tests/unchecked-numeric-parse-python-test.yml +18 -0
- package/rules/ast-grep-rules/rule-tests/unchecked-throwing-call-python-test.yml +4 -3
- package/rules/ast-grep-rules/rules/advisory-registry-subset.yml +50 -0
- package/rules/ast-grep-rules/rules/go-no-fmt-println.yml +2 -2
- package/rules/ast-grep-rules/rules/no-assert-tuple.yml +23 -13
- package/rules/ast-grep-rules/rules/no-boolean-in-except.yml +7 -3
- package/rules/ast-grep-rules/rules/no-console-except-error-js.yml +2 -2
- package/rules/ast-grep-rules/rules/no-console-except-error.yml +2 -2
- package/rules/ast-grep-rules/rules/no-http-headers-bracket-access.yml +12 -7
- package/rules/ast-grep-rules/rules/unchecked-numeric-parse-python.yml +44 -0
- package/rules/ast-grep-rules/rules/unchecked-throwing-call-python.yml +5 -3
- package/rules/tree-sitter-queries/typescript/console-statement.yml +2 -2
- package/scripts/analyze-pi-lens-logs.mjs +1275 -62
- package/scripts/download-grammars.js +5 -0
- package/scripts/grammars.lock.json +6 -1
- package/scripts/install-selftest.mjs +38 -35
- package/scripts/lib/host-provided-deps.mjs +13 -4
- package/scripts/lib/web-tree-sitter-dir.mjs +141 -0
- package/dist/clients/actionable-warnings-logger.js +0 -35
- package/dist/clients/actionable-warnings.js +0 -1594
- package/dist/clients/advisory-provenance.js +0 -457
- package/dist/clients/agent-behavior-client.js +0 -147
- package/dist/clients/agent-nudge.js +0 -325
- package/dist/clients/analysed-root.js +0 -1
- package/dist/clients/ast-grep-client.js +0 -731
- package/dist/clients/ast-grep-rule-manager.js +0 -146
- package/dist/clients/ast-grep-tool-logger.js +0 -118
- package/dist/clients/ast-grep-types.js +0 -10
- package/dist/clients/ast-grep-yaml-synth.js +0 -154
- package/dist/clients/atomic-write-staging.js +0 -75
- package/dist/clients/atomic-write.js +0 -189
- package/dist/clients/availability-probe-flight.js +0 -12
- package/dist/clients/bash-file-access.js +0 -1056
- package/dist/clients/biome-client.js +0 -357
- package/dist/clients/blocker-freshness.js +0 -837
- package/dist/clients/blocker-past-eof.js +0 -129
- package/dist/clients/bootstrap.js +0 -569
- package/dist/clients/bounded-cache.js +0 -190
- package/dist/clients/bounded-pid-file-lock.js +0 -202
- package/dist/clients/bounded-telemetry.js +0 -295
- package/dist/clients/build-identity.js +0 -95
- package/dist/clients/bundled-resource-health.js +0 -113
- package/dist/clients/bus-events-logger.js +0 -105
- package/dist/clients/bus-publish.js +0 -171
- package/dist/clients/cache/rule-cache.js +0 -190
- package/dist/clients/cache-manager.js +0 -465
- package/dist/clients/cache-observability.js +0 -1313
- package/dist/clients/call-graph.js +0 -852
- package/dist/clients/cargo-manifest.js +0 -422
- package/dist/clients/cascade-budget.js +0 -196
- package/dist/clients/cascade-format.js +0 -315
- package/dist/clients/cascade-logger.js +0 -31
- package/dist/clients/cascade-types.js +0 -9
- package/dist/clients/child-unref.js +0 -115
- package/dist/clients/code-quality-warnings.js +0 -286
- package/dist/clients/codebase-model.js +0 -173
- package/dist/clients/collateral-test-role.js +0 -40
- package/dist/clients/complexity-client.js +0 -462
- package/dist/clients/config-core/deny.js +0 -221
- package/dist/clients/config-core/index.js +0 -50
- package/dist/clients/config-core/merge.js +0 -357
- package/dist/clients/config-core/normalize.js +0 -340
- package/dist/clients/config-core/process-spec.js +0 -248
- package/dist/clients/config-core/provenance.js +0 -164
- package/dist/clients/config-core/records.js +0 -218
- package/dist/clients/config-core/resolve.js +0 -125
- package/dist/clients/config-core/safe-object.js +0 -78
- package/dist/clients/config-core/schema.js +0 -165
- package/dist/clients/config-diagnostic-codes.js +0 -283
- package/dist/clients/config-locations.js +0 -160
- package/dist/clients/config-resolve.js +0 -792
- package/dist/clients/config-schema.js +0 -251
- package/dist/clients/config-warn.js +0 -407
- package/dist/clients/console-guard-install.js +0 -26
- package/dist/clients/cooperative-budget.js +0 -42
- package/dist/clients/dead-code-client.js +0 -510
- package/dist/clients/dead-code-logger.js +0 -30
- package/dist/clients/deadline-utils.js +0 -280
- package/dist/clients/debug-handles.js +0 -211
- package/dist/clients/debug-heap.js +0 -174
- package/dist/clients/deferred-lsp-work.js +0 -106
- package/dist/clients/degradation-ledger.js +0 -396
- package/dist/clients/demoted-finding-render.js +0 -169
- package/dist/clients/dependency-checker.js +0 -862
- package/dist/clients/deps/ast-grep-napi.js +0 -22
- package/dist/clients/deps/js-yaml.js +0 -9
- package/dist/clients/deps/minimatch.js +0 -4
- package/dist/clients/deps/pi-tui.js +0 -14
- package/dist/clients/deps/typebox.js +0 -14
- package/dist/clients/deps/vscode-jsonrpc.js +0 -1
- package/dist/clients/deps/web-tree-sitter.js +0 -24
- package/dist/clients/diagnostic-dispositions.js +0 -594
- package/dist/clients/diagnostic-line-freshness.js +0 -305
- package/dist/clients/diagnostic-logger.js +0 -73
- package/dist/clients/diagnostic-tracker.js +0 -105
- package/dist/clients/diagnostics-publish.js +0 -230
- package/dist/clients/dispatch/ast-grep-catalog.js +0 -65
- package/dist/clients/dispatch/auxiliary-lsp.js +0 -343
- package/dist/clients/dispatch/collect-later-tier.js +0 -35
- package/dist/clients/dispatch/diagnostic-taxonomy.js +0 -66
- package/dist/clients/dispatch/dispatcher.js +0 -1269
- package/dist/clients/dispatch/fact-provider-types.js +0 -1
- package/dist/clients/dispatch/fact-rule-runner.js +0 -17
- package/dist/clients/dispatch/fact-runner.js +0 -30
- package/dist/clients/dispatch/fact-scheduler.js +0 -68
- package/dist/clients/dispatch/fact-store.js +0 -377
- package/dist/clients/dispatch/facts/comment-facts.js +0 -22
- package/dist/clients/dispatch/facts/file-content.js +0 -19
- package/dist/clients/dispatch/facts/function-facts.js +0 -348
- package/dist/clients/dispatch/facts/import-facts.js +0 -204
- package/dist/clients/dispatch/facts/tree-sitter-facts.js +0 -34
- package/dist/clients/dispatch/facts/try-catch-facts.js +0 -119
- package/dist/clients/dispatch/finding-policy.js +0 -222
- package/dist/clients/dispatch/indent-detect.js +0 -411
- package/dist/clients/dispatch/inline-suppressions.js +0 -70
- package/dist/clients/dispatch/integration.js +0 -2377
- package/dist/clients/dispatch/lazy.js +0 -11
- package/dist/clients/dispatch/pending-runner-findings.js +0 -87
- package/dist/clients/dispatch/plan.js +0 -278
- package/dist/clients/dispatch/priorities.js +0 -20
- package/dist/clients/dispatch/rule-id-normalize.js +0 -50
- package/dist/clients/dispatch/rule-ignores.js +0 -62
- package/dist/clients/dispatch/rule-policy.js +0 -135
- package/dist/clients/dispatch/rules/async-noise.js +0 -40
- package/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +0 -27
- package/dist/clients/dispatch/rules/cors-wildcard.js +0 -54
- package/dist/clients/dispatch/rules/error-obscuring.js +0 -32
- package/dist/clients/dispatch/rules/error-swallowing.js +0 -31
- package/dist/clients/dispatch/rules/framework-call-noise.js +0 -67
- package/dist/clients/dispatch/rules/high-complexity.js +0 -72
- package/dist/clients/dispatch/rules/high-fan-out.js +0 -74
- package/dist/clients/dispatch/rules/high-import-coupling.js +0 -37
- package/dist/clients/dispatch/rules/missing-error-propagation.js +0 -62
- package/dist/clients/dispatch/rules/no-commented-credentials.js +0 -52
- package/dist/clients/dispatch/rules/pass-through-wrappers.js +0 -42
- package/dist/clients/dispatch/rules/placeholder-comments.js +0 -40
- package/dist/clients/dispatch/rules/unsafe-boundary.js +0 -126
- package/dist/clients/dispatch/runner-context.js +0 -52
- package/dist/clients/dispatch/runners/actionlint.js +0 -113
- package/dist/clients/dispatch/runners/ast-grep-napi.js +0 -1196
- package/dist/clients/dispatch/runners/biome-check.js +0 -283
- package/dist/clients/dispatch/runners/cpp-check.js +0 -236
- package/dist/clients/dispatch/runners/credo.js +0 -80
- package/dist/clients/dispatch/runners/cue-vet.js +0 -398
- package/dist/clients/dispatch/runners/dart-analyze.js +0 -202
- package/dist/clients/dispatch/runners/detekt.js +0 -169
- package/dist/clients/dispatch/runners/dotnet-build.js +0 -159
- package/dist/clients/dispatch/runners/elixir-check.js +0 -210
- package/dist/clients/dispatch/runners/eslint.js +0 -154
- package/dist/clients/dispatch/runners/fact-rules.js +0 -29
- package/dist/clients/dispatch/runners/fish-indent.js +0 -69
- package/dist/clients/dispatch/runners/gleam-check.js +0 -87
- package/dist/clients/dispatch/runners/go-vet.js +0 -90
- package/dist/clients/dispatch/runners/golangci-lint.js +0 -120
- package/dist/clients/dispatch/runners/hadolint.js +0 -79
- package/dist/clients/dispatch/runners/helm-lint.js +0 -171
- package/dist/clients/dispatch/runners/helm-render.js +0 -940
- package/dist/clients/dispatch/runners/htmlhint.js +0 -108
- package/dist/clients/dispatch/runners/index.js +0 -108
- package/dist/clients/dispatch/runners/javac.js +0 -89
- package/dist/clients/dispatch/runners/ktlint.js +0 -136
- package/dist/clients/dispatch/runners/lsp.js +0 -378
- package/dist/clients/dispatch/runners/markdownlint.js +0 -160
- package/dist/clients/dispatch/runners/mypy.js +0 -101
- package/dist/clients/dispatch/runners/oxlint.js +0 -398
- package/dist/clients/dispatch/runners/php-lint.js +0 -62
- package/dist/clients/dispatch/runners/phpstan.js +0 -120
- package/dist/clients/dispatch/runners/prisma-validate.js +0 -62
- package/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -445
- package/dist/clients/dispatch/runners/pyright.js +0 -169
- package/dist/clients/dispatch/runners/rubocop.js +0 -90
- package/dist/clients/dispatch/runners/ruff.js +0 -89
- package/dist/clients/dispatch/runners/rust-clippy.js +0 -251
- package/dist/clients/dispatch/runners/shellcheck.js +0 -169
- package/dist/clients/dispatch/runners/shfmt.js +0 -102
- package/dist/clients/dispatch/runners/spellcheck.js +0 -121
- package/dist/clients/dispatch/runners/spotbugs.js +0 -259
- package/dist/clients/dispatch/runners/sqlfluff.js +0 -174
- package/dist/clients/dispatch/runners/stylelint.js +0 -158
- package/dist/clients/dispatch/runners/swiftlint.js +0 -154
- package/dist/clients/dispatch/runners/taplo.js +0 -168
- package/dist/clients/dispatch/runners/terragrunt.js +0 -168
- package/dist/clients/dispatch/runners/tflint.js +0 -110
- package/dist/clients/dispatch/runners/tree-sitter.js +0 -620
- package/dist/clients/dispatch/runners/trivy-config.js +0 -247
- package/dist/clients/dispatch/runners/utils/availability-policy.js +0 -496
- package/dist/clients/dispatch/runners/utils/candidate-probe.js +0 -90
- package/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -85
- package/dist/clients/dispatch/runners/utils/lazy-installer.js +0 -274
- package/dist/clients/dispatch/runners/utils/runner-helpers.js +0 -1701
- package/dist/clients/dispatch/runners/utils/spawn-outcome.js +0 -101
- package/dist/clients/dispatch/runners/utils/tool-failure.js +0 -235
- package/dist/clients/dispatch/runners/utils/toolchain-availability.js +0 -169
- package/dist/clients/dispatch/runners/utils.js +0 -15
- package/dist/clients/dispatch/runners/vale.js +0 -116
- package/dist/clients/dispatch/runners/yaml-rule-parser.js +0 -242
- package/dist/clients/dispatch/runners/yamllint.js +0 -85
- package/dist/clients/dispatch/runners/zig-check.js +0 -91
- package/dist/clients/dispatch/suppress-writer.js +0 -69
- package/dist/clients/dispatch/tool-profile.js +0 -30
- package/dist/clients/dispatch/types.js +0 -21
- package/dist/clients/dispatch/utils/format-utils.js +0 -45
- package/dist/clients/dispatch/utils/lsp-diagnostics.js +0 -35
- package/dist/clients/disposition-logger.js +0 -32
- package/dist/clients/disposition-publish.js +0 -124
- package/dist/clients/durable-store.js +0 -97
- package/dist/clients/effective-config.js +0 -413
- package/dist/clients/env-utils.js +0 -83
- package/dist/clients/error-class.js +0 -23
- package/dist/clients/eval-timestamp.js +0 -17
- package/dist/clients/event-loop-hold.js +0 -274
- package/dist/clients/event-loop-monitor.js +0 -250
- package/dist/clients/extension-log.js +0 -504
- package/dist/clients/extension-mode.js +0 -73
- package/dist/clients/feature-hints.js +0 -103
- package/dist/clients/file-kinds.js +0 -412
- package/dist/clients/file-role.js +0 -134
- package/dist/clients/file-time.js +0 -160
- package/dist/clients/file-utils.js +0 -1032
- package/dist/clients/finding-delivery-gate.js +0 -463
- package/dist/clients/finding-identity.js +0 -88
- package/dist/clients/fix-worklog.js +0 -103
- package/dist/clients/format-events-publish.js +0 -343
- package/dist/clients/format-service.js +0 -222
- package/dist/clients/formatters-lazy.js +0 -9
- package/dist/clients/formatters.js +0 -1994
- package/dist/clients/freshness-cadence.js +0 -17
- package/dist/clients/freshness.js +0 -38
- package/dist/clients/generated-artifacts.js +0 -394
- package/dist/clients/generation-guard.js +0 -215
- package/dist/clients/git-guard.js +0 -923
- package/dist/clients/git-tracked-ignore.js +0 -243
- package/dist/clients/gitleaks-client.js +0 -509
- package/dist/clients/go-client.js +0 -104
- package/dist/clients/govulncheck-client.js +0 -605
- package/dist/clients/gradle-ktfmt-style.js +0 -252
- package/dist/clients/grammar-source.js +0 -465
- package/dist/clients/gzip-stage-write.js +0 -135
- package/dist/clients/hashline-anchor.js +0 -424
- package/dist/clients/hook-budgets.js +0 -76
- package/dist/clients/host-edit-normalize.js +0 -140
- package/dist/clients/host-ports.js +0 -39
- package/dist/clients/indent-retarget.js +0 -154
- package/dist/clients/install-diagnostics.js +0 -143
- package/dist/clients/installer/index.js +0 -5167
- package/dist/clients/installer/managed-tool-refresh-session.js +0 -58
- package/dist/clients/installer/managed-tool-refresh.js +0 -783
- package/dist/clients/instance-reaper-state.js +0 -7
- package/dist/clients/instance-reaper.js +0 -1137
- package/dist/clients/instance-registry-lock.js +0 -145
- package/dist/clients/instance-registry.js +0 -773
- package/dist/clients/jscpd-client.js +0 -370
- package/dist/clients/json-cache-read.js +0 -65
- package/dist/clients/knip-client.js +0 -788
- package/dist/clients/language-policy.js +0 -252
- package/dist/clients/language-profile.js +0 -361
- package/dist/clients/language-registry.js +0 -603
- package/dist/clients/latency-logger.js +0 -719
- package/dist/clients/lazy-import.js +0 -21
- package/dist/clients/ledger-bounds.js +0 -54
- package/dist/clients/lens-config.js +0 -346
- package/dist/clients/lens-engine.js +0 -358
- package/dist/clients/lens-events.js +0 -125
- package/dist/clients/lens-flag-registry.js +0 -467
- package/dist/clients/lens-map.js +0 -1156
- package/dist/clients/live-bus-emitter.js +0 -74
- package/dist/clients/log-cleanup.js +0 -222
- package/dist/clients/lsp/aggregation.js +0 -178
- package/dist/clients/lsp/cascade-tier.js +0 -421
- package/dist/clients/lsp/client.js +0 -4465
- package/dist/clients/lsp/config.js +0 -689
- package/dist/clients/lsp/diagnostic-binding.js +0 -225
- package/dist/clients/lsp/document-drift.js +0 -467
- package/dist/clients/lsp/edits.js +0 -1219
- package/dist/clients/lsp/index.js +0 -8305
- package/dist/clients/lsp/inferred-project.js +0 -216
- package/dist/clients/lsp/jvm-runtime.js +0 -298
- package/dist/clients/lsp/language.js +0 -63
- package/dist/clients/lsp/launch.js +0 -725
- package/dist/clients/lsp/lombok.js +0 -158
- package/dist/clients/lsp/path-utils.js +0 -5
- package/dist/clients/lsp/pending-aux-coverage.js +0 -251
- package/dist/clients/lsp/position-encoding.js +0 -61
- package/dist/clients/lsp/ruby-drive-dirs.js +0 -83
- package/dist/clients/lsp/server.js +0 -3209
- package/dist/clients/lsp/session-roots.js +0 -192
- package/dist/clients/lsp/spawn-history.js +0 -17
- package/dist/clients/lsp/sync-kind.js +0 -42
- package/dist/clients/lsp/tsserver-sync.js +0 -332
- package/dist/clients/lsp/wait-policy/capability-snapshot.js +0 -1
- package/dist/clients/lsp/wait-policy/classification.js +0 -85
- package/dist/clients/lsp/wait-policy/index.js +0 -10
- package/dist/clients/lsp/wait-policy/strategies.js +0 -303
- package/dist/clients/lsp/watch-queue.js +0 -61
- package/dist/clients/lsp/workspace-diagnostics-cache.js +0 -639
- package/dist/clients/lsp/workspace-diagnostics-session.js +0 -30
- package/dist/clients/lsp/workspace-sweep-hold.js +0 -184
- package/dist/clients/lsp-budget.js +0 -186
- package/dist/clients/lsp-document-symbols.js +0 -110
- package/dist/clients/lsp-lazy.js +0 -9
- package/dist/clients/lsp-mutation.js +0 -396
- package/dist/clients/lsp-status.js +0 -59
- package/dist/clients/map-with-concurrency.js +0 -40
- package/dist/clients/mcp/analyze.js +0 -420
- package/dist/clients/mcp/host-shim.js +0 -31
- package/dist/clients/mcp/ipc.js +0 -335
- package/dist/clients/mcp/review.js +0 -151
- package/dist/clients/mcp/session.js +0 -412
- package/dist/clients/memory-sampler.js +0 -208
- package/dist/clients/message-end-attribution.js +0 -30
- package/dist/clients/metrics-client.js +0 -107
- package/dist/clients/metrics-history.js +0 -327
- package/dist/clients/middle-man-analysis.js +0 -263
- package/dist/clients/model-provider.js +0 -53
- package/dist/clients/module-report-lsp.js +0 -189
- package/dist/clients/module-report.js +0 -2150
- package/dist/clients/mutating-tool.js +0 -651
- package/dist/clients/mutation-attribution.js +0 -368
- package/dist/clients/mutation-bridge.js +0 -240
- package/dist/clients/ndjson-logger.js +0 -734
- package/dist/clients/observed-mutation-sources.js +0 -101
- package/dist/clients/observed-mutation.js +0 -1253
- package/dist/clients/oldtext-autopatch.js +0 -128
- package/dist/clients/opaque-mutation-scan.js +0 -496
- package/dist/clients/opengrep-client.js +0 -328
- package/dist/clients/opengrep-config.js +0 -76
- package/dist/clients/package-manager.js +0 -607
- package/dist/clients/package-root.js +0 -38
- package/dist/clients/partial-edit-apply.js +0 -439
- package/dist/clients/path-attribution-telemetry.js +0 -43
- package/dist/clients/path-keyed-map.js +0 -108
- package/dist/clients/path-utils.js +0 -1244
- package/dist/clients/performance-report.js +0 -331
- package/dist/clients/persist-debounce.js +0 -70
- package/dist/clients/persistent-reverify.js +0 -271
- package/dist/clients/php-cs-fixer-config.js +0 -119
- package/dist/clients/pipeline.js +0 -1425
- package/dist/clients/post-autofix-notice.js +0 -37
- package/dist/clients/print-mode.js +0 -21
- package/dist/clients/probe-home-state.js +0 -228
- package/dist/clients/process-bridge.js +0 -66
- package/dist/clients/process-singletons.js +0 -210
- package/dist/clients/process-snapshot.js +0 -68
- package/dist/clients/project-changes.js +0 -181
- package/dist/clients/project-conventions.js +0 -171
- package/dist/clients/project-diagnostics/cache.js +0 -137
- package/dist/clients/project-diagnostics/extractors.js +0 -109
- package/dist/clients/project-diagnostics/fresh-fetch.js +0 -650
- package/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +0 -118
- package/dist/clients/project-diagnostics/runner-adapters/dead-code.js +0 -47
- package/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +0 -40
- package/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +0 -34
- package/dist/clients/project-diagnostics/runner-adapters/jscpd.js +0 -38
- package/dist/clients/project-diagnostics/runner-adapters/knip.js +0 -47
- package/dist/clients/project-diagnostics/runner-adapters/madge.js +0 -48
- package/dist/clients/project-diagnostics/runner-adapters/opengrep.js +0 -40
- package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +0 -127
- package/dist/clients/project-diagnostics/runner-adapters/trivy.js +0 -81
- package/dist/clients/project-diagnostics/scanner.js +0 -520
- package/dist/clients/project-diagnostics/types.js +0 -1
- package/dist/clients/project-lens-config.js +0 -799
- package/dist/clients/project-report.js +0 -701
- package/dist/clients/project-scale.js +0 -237
- package/dist/clients/project-scan-policy.js +0 -11
- package/dist/clients/project-snapshot-fingerprint.js +0 -48
- package/dist/clients/project-snapshot-persist-worker.js +0 -20
- package/dist/clients/project-snapshot.js +0 -1653
- package/dist/clients/project-trust.js +0 -181
- package/dist/clients/python-environment.js +0 -217
- package/dist/clients/python-provenance.js +0 -639
- package/dist/clients/quiet-window-config.js +0 -47
- package/dist/clients/quiet-window.js +0 -236
- package/dist/clients/read-bridge.js +0 -139
- package/dist/clients/read-expansion.js +0 -266
- package/dist/clients/read-guard-logger.js +0 -251
- package/dist/clients/read-guard-tool-lines.js +0 -1306
- package/dist/clients/read-guard.js +0 -1561
- package/dist/clients/recent-touches.js +0 -240
- package/dist/clients/redact/secrets.js +0 -449
- package/dist/clients/resource-sampler.js +0 -628
- package/dist/clients/reverse-deps.js +0 -270
- package/dist/clients/review-graph/builder.js +0 -5118
- package/dist/clients/review-graph/format.js +0 -33
- package/dist/clients/review-graph/git-identity.js +0 -157
- package/dist/clients/review-graph/import-resolvers.js +0 -481
- package/dist/clients/review-graph/persist-worker.js +0 -10
- package/dist/clients/review-graph/query.js +0 -244
- package/dist/clients/review-graph/revision-drift.js +0 -21
- package/dist/clients/review-graph/service.js +0 -96
- package/dist/clients/review-graph/shared-extraction-ir.js +0 -82
- package/dist/clients/review-graph/symbol-id.js +0 -103
- package/dist/clients/review-graph/tsconfig-paths.js +0 -299
- package/dist/clients/review-graph/types.js +0 -1
- package/dist/clients/review-graph/workspace-modules.js +0 -449
- package/dist/clients/review-graph-logger.js +0 -81
- package/dist/clients/ruff-client.js +0 -152
- package/dist/clients/rules-scanner.js +0 -131
- package/dist/clients/run-duration.js +0 -55
- package/dist/clients/runtime-agent-end.js +0 -746
- package/dist/clients/runtime-config.js +0 -77
- package/dist/clients/runtime-context.js +0 -211
- package/dist/clients/runtime-coordinator.js +0 -1290
- package/dist/clients/runtime-session.js +0 -2485
- package/dist/clients/runtime-tool-call.js +0 -1284
- package/dist/clients/runtime-tool-result.js +0 -2034
- package/dist/clients/runtime-turn.js +0 -3600
- package/dist/clients/rust-client.js +0 -97
- package/dist/clients/safe-spawn.js +0 -2166
- package/dist/clients/sanitize.js +0 -286
- package/dist/clients/scan-utils.js +0 -25
- package/dist/clients/scratch-tree-policy.js +0 -165
- package/dist/clients/search-read-registration.js +0 -94
- package/dist/clients/secret-findings.js +0 -114
- package/dist/clients/security-scan-client.js +0 -352
- package/dist/clients/session-event-guard.js +0 -297
- package/dist/clients/session-lifecycle.js +0 -484
- package/dist/clients/session-start-observability.js +0 -94
- package/dist/clients/session-state-store.js +0 -101
- package/dist/clients/session-summary.js +0 -15
- package/dist/clients/sessionstart-logger.js +0 -27
- package/dist/clients/sg-runner.js +0 -876
- package/dist/clients/sgconfig.js +0 -347
- package/dist/clients/shared-checkout-guard.js +0 -338
- package/dist/clients/single-flight.js +0 -158
- package/dist/clients/situational-tool-telemetry.js +0 -108
- package/dist/clients/skills-resolver.js +0 -141
- package/dist/clients/slow-fs.js +0 -138
- package/dist/clients/smells-rollup.js +0 -231
- package/dist/clients/source-filter.js +0 -596
- package/dist/clients/source-walker.js +0 -211
- package/dist/clients/spawn-output-cap.js +0 -37
- package/dist/clients/spawn-timeout-cooldown.js +0 -110
- package/dist/clients/stale-marker.js +0 -17
- package/dist/clients/startup-scan.js +0 -400
- package/dist/clients/startup-timing.js +0 -130
- package/dist/clients/string-utils.js +0 -33
- package/dist/clients/subagent-mode.js +0 -111
- package/dist/clients/symbol-containment.js +0 -24
- package/dist/clients/symbol-types.js +0 -5
- package/dist/clients/test-runner-client.js +0 -2507
- package/dist/clients/test-runner-delivery.js +0 -287
- package/dist/clients/todo-scanner.js +0 -229
- package/dist/clients/tool-agreement.js +0 -423
- package/dist/clients/tool-config.js +0 -249
- package/dist/clients/tool-cwd.js +0 -270
- package/dist/clients/tool-definition.js +0 -47
- package/dist/clients/tool-event.js +0 -105
- package/dist/clients/tool-policy.js +0 -2755
- package/dist/clients/tool-probe.js +0 -53
- package/dist/clients/tool-render.js +0 -156
- package/dist/clients/tool-set-policy.js +0 -118
- package/dist/clients/tree-sitter-cache.js +0 -364
- package/dist/clients/tree-sitter-client.js +0 -4134
- package/dist/clients/tree-sitter-logger.js +0 -100
- package/dist/clients/tree-sitter-navigator.js +0 -269
- package/dist/clients/tree-sitter-query-loader.js +0 -569
- package/dist/clients/tree-sitter-shared.js +0 -158
- package/dist/clients/tree-sitter-symbol-extractor.js +0 -1265
- package/dist/clients/trivy-client.js +0 -467
- package/dist/clients/tui-fit.js +0 -50
- package/dist/clients/turn-context.js +0 -52
- package/dist/clients/turn-summary-render.js +0 -72
- package/dist/clients/turn-summary.js +0 -132
- package/dist/clients/typos-config.js +0 -29
- package/dist/clients/user-notify.js +0 -51
- package/dist/clients/vanished-instance-marker.js +0 -57
- package/dist/clients/warm-attach.js +0 -246
- package/dist/clients/widget-state.js +0 -1677
- package/dist/clients/word-index-logger.js +0 -49
- package/dist/clients/word-index-store.js +0 -459
- package/dist/clients/word-index.js +0 -2080
- package/dist/clients/workspace-topology.js +0 -389
- package/dist/clients/write-ordering-guard.js +0 -65
- package/dist/clients/zizmor-config.js +0 -327
- package/dist/i18n.js +0 -69
- package/dist/mcp/build-staleness.js +0 -124
- package/dist/scripts/lib/process-scan.mjs +0 -583
- package/dist/scripts/lib/skills-predicate.mjs +0 -129
- package/dist/tools/activate-tools.js +0 -87
- package/dist/tools/ast-grep-outline.js +0 -137
- package/dist/tools/ast-grep-replace.js +0 -244
- package/dist/tools/ast-grep-search.js +0 -762
- package/dist/tools/effective-config.js +0 -90
- package/dist/tools/lens-diagnostic-mark.js +0 -375
- package/dist/tools/lens-diagnostics.js +0 -2544
- package/dist/tools/lsp-diagnostics.js +0 -1634
- package/dist/tools/lsp-navigation.js +0 -1367
- package/dist/tools/lsp-structured-output.js +0 -294
- package/dist/tools/module-report.js +0 -382
- package/dist/tools/project-report.js +0 -86
- package/dist/tools/render-compact.js +0 -374
- package/dist/tools/scan-progress.js +0 -53
- package/dist/tools/shared.js +0 -29
- package/dist/tools/symbol-search.js +0 -155
|
@@ -1,923 +0,0 @@
|
|
|
1
|
-
import * as nodeFs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { isPathIgnoredByProject } from "./file-utils.js";
|
|
4
|
-
import { tokenizeShellCommand } from "./bash-file-access.js";
|
|
5
|
-
import { logLatency } from "./latency-logger.js";
|
|
6
|
-
import { resolveLensToolName } from "./tool-config.js";
|
|
7
|
-
import { advisoryFileHash, advisoryPathKey, MAX_ADVISORY_AFFECTED_FILES, snapshotAdvisoryProvenance, } from "./advisory-provenance.js";
|
|
8
|
-
function resolveGuardPath(filePath, cwd) {
|
|
9
|
-
return path.resolve(cwd, filePath);
|
|
10
|
-
}
|
|
11
|
-
function guardPathKey(filePath, cwd) {
|
|
12
|
-
return advisoryPathKey(filePath, cwd);
|
|
13
|
-
}
|
|
14
|
-
function fileFingerprint(filePath) {
|
|
15
|
-
return advisoryFileHash(filePath);
|
|
16
|
-
}
|
|
17
|
-
function currentFileFingerprint(filePath) {
|
|
18
|
-
try {
|
|
19
|
-
nodeFs.statSync(filePath);
|
|
20
|
-
return fileFingerprint(filePath);
|
|
21
|
-
}
|
|
22
|
-
catch (err) {
|
|
23
|
-
return err.code === "ENOENT"
|
|
24
|
-
? "missing"
|
|
25
|
-
: `unreadable:${err.code ?? "unknown"}`;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function capAffectedFiles(files, cwd) {
|
|
29
|
-
const unique = [...new Set(files.map((file) => resolveGuardPath(file, cwd)))];
|
|
30
|
-
return {
|
|
31
|
-
files: unique.slice(0, MAX_ADVISORY_AFFECTED_FILES),
|
|
32
|
-
truncated: unique.length > MAX_ADVISORY_AFFECTED_FILES,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
/** Recovery is safe only when blocker content and its file provenance agree. */
|
|
36
|
-
function hasCompleteBlockingProvenance(blockerContent, blockingFiles, cwd) {
|
|
37
|
-
if (typeof blockerContent !== "string" || blockerContent.length === 0)
|
|
38
|
-
return false;
|
|
39
|
-
if (!Array.isArray(blockingFiles) || blockingFiles.length === 0)
|
|
40
|
-
return false;
|
|
41
|
-
if (blockingFiles.some((file) => typeof file !== "string" || file.trim().length === 0)) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
const provenanceKeys = blockingFiles.map((file) => guardPathKey(file, cwd));
|
|
45
|
-
if (new Set(provenanceKeys).size !== provenanceKeys.length)
|
|
46
|
-
return false;
|
|
47
|
-
const blockerKeys = blockerContent.split("\n").map((line) => {
|
|
48
|
-
const separator = line.indexOf(": ");
|
|
49
|
-
if (separator <= 0)
|
|
50
|
-
return undefined;
|
|
51
|
-
const file = line.slice(0, separator).trim();
|
|
52
|
-
return file.length > 0 ? guardPathKey(file, cwd) : undefined;
|
|
53
|
-
});
|
|
54
|
-
if (blockerKeys.some((key) => key === undefined))
|
|
55
|
-
return false;
|
|
56
|
-
const uniqueBlockerKeys = new Set(blockerKeys);
|
|
57
|
-
return (uniqueBlockerKeys.size === blockerKeys.length &&
|
|
58
|
-
uniqueBlockerKeys.size === provenanceKeys.length &&
|
|
59
|
-
[...uniqueBlockerKeys].every((key) => provenanceKeys.includes(key)));
|
|
60
|
-
}
|
|
61
|
-
function getShellCommand(input) {
|
|
62
|
-
if (!input || typeof input !== "object")
|
|
63
|
-
return "";
|
|
64
|
-
const raw = input;
|
|
65
|
-
if (typeof raw.command === "string" && raw.command.trim())
|
|
66
|
-
return raw.command;
|
|
67
|
-
if (typeof raw.cmd === "string" && raw.cmd.trim())
|
|
68
|
-
return raw.cmd;
|
|
69
|
-
if (typeof raw.command === "string")
|
|
70
|
-
return raw.command;
|
|
71
|
-
return "";
|
|
72
|
-
}
|
|
73
|
-
function executableName(value) {
|
|
74
|
-
const normalized = value.replace(/\\/g, "/");
|
|
75
|
-
let name = (normalized.slice(normalized.lastIndexOf("/") + 1) ?? "").toLowerCase();
|
|
76
|
-
// Shell launchers are commonly supplied as resolved Windows paths or as
|
|
77
|
-
// PATHEXT-qualified names. Guard classification must happen after the same
|
|
78
|
-
// basename/extension normalization for every wrapper family.
|
|
79
|
-
return name.replace(/\.(?:exe|com|bat|cmd)$/i, "");
|
|
80
|
-
}
|
|
81
|
-
function isShellWrapper(value) {
|
|
82
|
-
return new Set([
|
|
83
|
-
"sh",
|
|
84
|
-
"bash",
|
|
85
|
-
"dash",
|
|
86
|
-
"zsh",
|
|
87
|
-
"ash",
|
|
88
|
-
"cmd",
|
|
89
|
-
"pwsh",
|
|
90
|
-
"powershell",
|
|
91
|
-
]).has(executableName(value));
|
|
92
|
-
}
|
|
93
|
-
function isGitExecutable(value) {
|
|
94
|
-
return new Set(["git", "git.exe", "git.cmd", "git.bat"]).has(executableName(value));
|
|
95
|
-
}
|
|
96
|
-
const COMMAND_STRING_WRAPPERS = new Set(["busybox", "toybox", "nix-shell"]);
|
|
97
|
-
function isCommandStringWrapper(value) {
|
|
98
|
-
return COMMAND_STRING_WRAPPERS.has(executableName(value));
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Canonicalize shell parameter separators before command classification. The
|
|
102
|
-
* quote-aware pass deliberately leaves literal arguments alone; the lexer
|
|
103
|
-
* then supplies the command-position boundaries used by the guard.
|
|
104
|
-
*/
|
|
105
|
-
function canonicalizeGuardCommand(command) {
|
|
106
|
-
let result = "";
|
|
107
|
-
let quote;
|
|
108
|
-
let escaped = false;
|
|
109
|
-
for (let i = 0; i < command.length; i++) {
|
|
110
|
-
const ch = command[i];
|
|
111
|
-
if (quote === "single") {
|
|
112
|
-
result += ch;
|
|
113
|
-
if (ch === "'")
|
|
114
|
-
quote = undefined;
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
if (quote === "double") {
|
|
118
|
-
result += ch;
|
|
119
|
-
if (escaped)
|
|
120
|
-
escaped = false;
|
|
121
|
-
else if (ch === "\\")
|
|
122
|
-
escaped = true;
|
|
123
|
-
else if (ch === '"')
|
|
124
|
-
quote = undefined;
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
if (ch === "'" || ch === '"') {
|
|
128
|
-
quote = ch === "'" ? "single" : "double";
|
|
129
|
-
result += ch;
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
const parameter = command.slice(i).match(/^\$\{IFS[^}]*\}/)?.[0];
|
|
133
|
-
const positional = command.slice(i).match(/^\$IFS(?:\$[0-9]+)?/)?.[0];
|
|
134
|
-
if (parameter || positional) {
|
|
135
|
-
result += " ";
|
|
136
|
-
i += (parameter ?? positional ?? "").length - 1;
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
result += ch;
|
|
140
|
-
}
|
|
141
|
-
let collapsed = "";
|
|
142
|
-
let pendingSpace = false;
|
|
143
|
-
quote = undefined;
|
|
144
|
-
for (const ch of result) {
|
|
145
|
-
if (!quote && ch === "\n") {
|
|
146
|
-
collapsed += ch;
|
|
147
|
-
pendingSpace = false;
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
if (!quote && /\s/.test(ch)) {
|
|
151
|
-
pendingSpace = collapsed.length > 0;
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
if (pendingSpace)
|
|
155
|
-
collapsed += " ";
|
|
156
|
-
pendingSpace = false;
|
|
157
|
-
collapsed += ch;
|
|
158
|
-
if (!quote && (ch === "'" || ch === '"')) {
|
|
159
|
-
quote = ch === "'" ? "single" : "double";
|
|
160
|
-
}
|
|
161
|
-
else if ((quote === "single" && ch === "'") ||
|
|
162
|
-
(quote === "double" && ch === '"')) {
|
|
163
|
-
quote = undefined;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return collapsed.trim();
|
|
167
|
-
}
|
|
168
|
-
/** Normalize only a command-position token; never apply this to path args. */
|
|
169
|
-
function normalizeGuardVerbToken(value) {
|
|
170
|
-
return value
|
|
171
|
-
.replace(/\\(?=[A-Za-z0-9_])/g, "")
|
|
172
|
-
.replace(/`(?=.)/g, "")
|
|
173
|
-
.replace(/\^(?=.)/g, "");
|
|
174
|
-
}
|
|
175
|
-
function expandGuardVerbToken(value) {
|
|
176
|
-
return normalizeGuardVerbToken(value).trim().split(/\s+/).filter(Boolean);
|
|
177
|
-
}
|
|
178
|
-
function delimitedSubstitutionBody(command, start, opener, closer) {
|
|
179
|
-
let depth = 1;
|
|
180
|
-
let quote;
|
|
181
|
-
let escaped = false;
|
|
182
|
-
for (let i = start + opener.length; i < command.length; i++) {
|
|
183
|
-
const ch = command[i];
|
|
184
|
-
if (quote === "single") {
|
|
185
|
-
if (ch === "'")
|
|
186
|
-
quote = undefined;
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
if (quote === "double" && ch === '"') {
|
|
190
|
-
quote = undefined;
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
if (escaped) {
|
|
194
|
-
escaped = false;
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
if (ch === "\\") {
|
|
198
|
-
escaped = true;
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
if (ch === "'" || ch === '"') {
|
|
202
|
-
quote = ch === "'" ? "single" : "double";
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
if (command.startsWith(opener, i)) {
|
|
206
|
-
depth += 1;
|
|
207
|
-
i += opener.length - 1;
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
if (command.startsWith(closer, i) && --depth === 0) {
|
|
211
|
-
return { body: command.slice(start + opener.length, i), end: i };
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return undefined;
|
|
215
|
-
}
|
|
216
|
-
const COMMIT_PUSH_MATCHER = {
|
|
217
|
-
id: "commit-push",
|
|
218
|
-
matchesVerb: (verb) => verb === "commit" || verb === "push",
|
|
219
|
-
indirectAlwaysMatches: true,
|
|
220
|
-
suppressPostVerbHelp: false,
|
|
221
|
-
};
|
|
222
|
-
function containsGuardedSubstitution(command, depth, matcher) {
|
|
223
|
-
if (depth > 3)
|
|
224
|
-
return false;
|
|
225
|
-
let quote;
|
|
226
|
-
let escaped = false;
|
|
227
|
-
for (let i = 0; i < command.length; i++) {
|
|
228
|
-
const ch = command[i];
|
|
229
|
-
if (quote === "single") {
|
|
230
|
-
if (ch === "'")
|
|
231
|
-
quote = undefined;
|
|
232
|
-
continue;
|
|
233
|
-
}
|
|
234
|
-
if (quote === "double" && ch === '"') {
|
|
235
|
-
quote = undefined;
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
if (escaped) {
|
|
239
|
-
escaped = false;
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
if (ch === "\\") {
|
|
243
|
-
escaped = true;
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
if (ch === '"' || ch === "'") {
|
|
247
|
-
quote = ch === "'" ? "single" : "double";
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
const substitution = command.startsWith("$(", i)
|
|
251
|
-
? delimitedSubstitutionBody(command, i, "$(", ")")
|
|
252
|
-
: command.startsWith("<(", i)
|
|
253
|
-
? delimitedSubstitutionBody(command, i, "<(", ")")
|
|
254
|
-
: command.startsWith(">(", i)
|
|
255
|
-
? delimitedSubstitutionBody(command, i, ">(", ")")
|
|
256
|
-
: ch === "`"
|
|
257
|
-
? {
|
|
258
|
-
body: command.slice(i + 1, command.indexOf("`", i + 1)),
|
|
259
|
-
end: command.indexOf("`", i + 1),
|
|
260
|
-
}
|
|
261
|
-
: undefined;
|
|
262
|
-
if (substitution && substitution.end >= 0) {
|
|
263
|
-
const nested = canonicalizeGuardCommand(substitution.body);
|
|
264
|
-
if (containsGuardedSubstitution(nested, depth + 1, matcher) ||
|
|
265
|
-
tokenizeShellCommand(nested).some((segment) => containsGuardedGitVerb(segment.tokens, depth + 1, matcher))) {
|
|
266
|
-
return true;
|
|
267
|
-
}
|
|
268
|
-
i = substitution.end;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
return false;
|
|
272
|
-
}
|
|
273
|
-
/** Git global options that consume the following token as their value. */
|
|
274
|
-
const GIT_GLOBAL_OPTIONS_WITH_VALUE = new Set([
|
|
275
|
-
"-C",
|
|
276
|
-
"-c",
|
|
277
|
-
"--config-env",
|
|
278
|
-
"--git-dir",
|
|
279
|
-
"--work-tree",
|
|
280
|
-
"--exec-path",
|
|
281
|
-
"--namespace",
|
|
282
|
-
]);
|
|
283
|
-
const GIT_GLOBAL_OPTIONS_WITH_INLINE_VALUE = [
|
|
284
|
-
"--config-env",
|
|
285
|
-
"--git-dir",
|
|
286
|
-
"--work-tree",
|
|
287
|
-
"--exec-path",
|
|
288
|
-
"--namespace",
|
|
289
|
-
];
|
|
290
|
-
/**
|
|
291
|
-
* Index of git's subcommand token in `gitTokens` (which starts at the `git`
|
|
292
|
-
* executable), after skipping global options and their values. Returns
|
|
293
|
-
* `undefined` when the invocation has no subcommand at all — `git --help`,
|
|
294
|
-
* `git --version`, or a trailing option with no verb behind it.
|
|
295
|
-
*
|
|
296
|
-
* Shared by the direct and indirect paths so both ask the same question about
|
|
297
|
-
* the same position (#2007).
|
|
298
|
-
*/
|
|
299
|
-
function gitVerbIndex(gitTokens) {
|
|
300
|
-
let i = 1;
|
|
301
|
-
while (i < gitTokens.length && gitTokens[i].startsWith("-")) {
|
|
302
|
-
const option = gitTokens[i];
|
|
303
|
-
if (["--help", "-h", "--version", "-v", "-V"].includes(option)) {
|
|
304
|
-
return undefined;
|
|
305
|
-
}
|
|
306
|
-
if (option === "--")
|
|
307
|
-
return i + 1 < gitTokens.length ? i + 1 : undefined;
|
|
308
|
-
if (["-C", "-c"].some((prefix) => option.startsWith(prefix) && option.length > prefix.length)) {
|
|
309
|
-
i += 1;
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
if (GIT_GLOBAL_OPTIONS_WITH_INLINE_VALUE.some((prefix) => option.startsWith(`${prefix}=`))) {
|
|
313
|
-
i += 1;
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
i += GIT_GLOBAL_OPTIONS_WITH_VALUE.has(option) ? 2 : 1;
|
|
317
|
-
}
|
|
318
|
-
return i < gitTokens.length ? i : undefined;
|
|
319
|
-
}
|
|
320
|
-
/**
|
|
321
|
-
* True when the subcommand in git's command position is one the matcher
|
|
322
|
-
* governs. `gitTokens[0]` is the `git` executable token.
|
|
323
|
-
*/
|
|
324
|
-
export function matchGitVerbAtCommandPosition(gitTokens, matcher) {
|
|
325
|
-
const index = gitVerbIndex(gitTokens);
|
|
326
|
-
if (index === undefined)
|
|
327
|
-
return false;
|
|
328
|
-
const argsAfterVerb = gitTokens.slice(index + 1);
|
|
329
|
-
// `git checkout --help` prints documentation and touches nothing, and the
|
|
330
|
-
// global-option walk above cannot see it, because it sits AFTER the verb.
|
|
331
|
-
//
|
|
332
|
-
// Only the FIRST argument counts, and only for matchers that opt in. `-h`
|
|
333
|
-
// deeper in the argv may be an option VALUE rather than a help request —
|
|
334
|
-
// `git commit -m -h` commits with the message `-h`, `git clean -e -h`
|
|
335
|
-
// excludes a pattern named `-h`. Reading any position would need a
|
|
336
|
-
// per-verb table of value-taking options for every governed verb, which is
|
|
337
|
-
// exactly the hand-maintained parallel list this repo forbids, and every
|
|
338
|
-
// gap in it would silently UNDER-block. Leading-position-only needs no
|
|
339
|
-
// table, covers how help is actually invoked, and every other spelling
|
|
340
|
-
// falls through to a match, which is the safe direction for a guard.
|
|
341
|
-
if (matcher.suppressPostVerbHelp &&
|
|
342
|
-
(argsAfterVerb[0] === "--help" || argsAfterVerb[0] === "-h")) {
|
|
343
|
-
return false;
|
|
344
|
-
}
|
|
345
|
-
const verbs = expandGuardVerbToken(gitTokens[index] ?? "");
|
|
346
|
-
return verbs.length === 1 && matcher.matchesVerb(verbs[0], argsAfterVerb);
|
|
347
|
-
}
|
|
348
|
-
/** Strip one layer of shell quoting the lexer preserved on a value token. */
|
|
349
|
-
function unquoteGuardValue(value) {
|
|
350
|
-
const trimmed = value.trim();
|
|
351
|
-
if (trimmed.length >= 2) {
|
|
352
|
-
const first = trimmed[0];
|
|
353
|
-
const last = trimmed[trimmed.length - 1];
|
|
354
|
-
if ((first === '"' || first === "'") && first === last) {
|
|
355
|
-
return trimmed.slice(1, -1);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
return trimmed;
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* The directory a git invocation actually targets (#2007).
|
|
362
|
-
*
|
|
363
|
-
* `git -C <dir>` and `--work-tree` retarget the command at a DIFFERENT
|
|
364
|
-
* directory, so a guard that evaluates the caller's cwd would inspect the
|
|
365
|
-
* wrong working tree and allow a destructive command against a shared one.
|
|
366
|
-
* `-C` composes cumulatively, exactly as git applies it; `--work-tree` names
|
|
367
|
-
* the working tree directly and therefore wins.
|
|
368
|
-
*
|
|
369
|
-
* Returns the resolved absolute directory, or `cwd` when the invocation
|
|
370
|
-
* carries no retargeting option.
|
|
371
|
-
*/
|
|
372
|
-
export function resolveGitTargetDirectory(gitTokens, cwd) {
|
|
373
|
-
let base = cwd;
|
|
374
|
-
let workTree;
|
|
375
|
-
let i = 1;
|
|
376
|
-
while (i < gitTokens.length && gitTokens[i].startsWith("-")) {
|
|
377
|
-
const option = gitTokens[i];
|
|
378
|
-
if (["--help", "-h", "--version", "-v", "-V"].includes(option))
|
|
379
|
-
break;
|
|
380
|
-
if (option === "--")
|
|
381
|
-
break;
|
|
382
|
-
if (option === "-C" && i + 1 < gitTokens.length) {
|
|
383
|
-
base = path.resolve(base, unquoteGuardValue(gitTokens[i + 1]));
|
|
384
|
-
i += 2;
|
|
385
|
-
continue;
|
|
386
|
-
}
|
|
387
|
-
if (option.startsWith("-C") && option.length > 2) {
|
|
388
|
-
base = path.resolve(base, unquoteGuardValue(option.slice(2)));
|
|
389
|
-
i += 1;
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
if (option === "--work-tree" && i + 1 < gitTokens.length) {
|
|
393
|
-
workTree = unquoteGuardValue(gitTokens[i + 1]);
|
|
394
|
-
i += 2;
|
|
395
|
-
continue;
|
|
396
|
-
}
|
|
397
|
-
if (option.startsWith("--work-tree=")) {
|
|
398
|
-
workTree = unquoteGuardValue(option.slice("--work-tree=".length));
|
|
399
|
-
i += 1;
|
|
400
|
-
continue;
|
|
401
|
-
}
|
|
402
|
-
if (option.startsWith("-c") &&
|
|
403
|
-
option.length > 2 &&
|
|
404
|
-
!option.startsWith("--")) {
|
|
405
|
-
i += 1;
|
|
406
|
-
continue;
|
|
407
|
-
}
|
|
408
|
-
if (GIT_GLOBAL_OPTIONS_WITH_INLINE_VALUE.some((prefix) => option.startsWith(`${prefix}=`))) {
|
|
409
|
-
i += 1;
|
|
410
|
-
continue;
|
|
411
|
-
}
|
|
412
|
-
i += GIT_GLOBAL_OPTIONS_WITH_VALUE.has(option) ? 2 : 1;
|
|
413
|
-
}
|
|
414
|
-
return workTree === undefined ? base : path.resolve(base, workTree);
|
|
415
|
-
}
|
|
416
|
-
/**
|
|
417
|
-
* Every git invocation the command contains, as token arrays starting at the
|
|
418
|
-
* `git` executable. Used by callers that must know WHICH directory a matched
|
|
419
|
-
* command targets, not merely that it matched (#2007).
|
|
420
|
-
*/
|
|
421
|
-
export function collectGitInvocations(toolName, input) {
|
|
422
|
-
if (toolName !== "bash")
|
|
423
|
-
return [];
|
|
424
|
-
const command = getShellCommand(input);
|
|
425
|
-
if (!command)
|
|
426
|
-
return [];
|
|
427
|
-
const invocations = [];
|
|
428
|
-
for (const segment of tokenizeShellCommand(canonicalizeGuardCommand(command))) {
|
|
429
|
-
const gitIndex = segment.tokens.findIndex((token) => isGitExecutable(token));
|
|
430
|
-
if (gitIndex >= 0)
|
|
431
|
-
invocations.push(segment.tokens.slice(gitIndex));
|
|
432
|
-
}
|
|
433
|
-
return invocations;
|
|
434
|
-
}
|
|
435
|
-
function containsGuardedGitVerb(tokens, depth, matcher) {
|
|
436
|
-
if (depth > 3 || tokens.length === 0)
|
|
437
|
-
return false;
|
|
438
|
-
let commandTokens = tokens;
|
|
439
|
-
while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(commandTokens[0] ?? "")) {
|
|
440
|
-
commandTokens = commandTokens.slice(1);
|
|
441
|
-
}
|
|
442
|
-
if (commandTokens.length === 0)
|
|
443
|
-
return false;
|
|
444
|
-
const expandedHead = expandGuardVerbToken(commandTokens[0] ?? "");
|
|
445
|
-
if (expandedHead.length > 1) {
|
|
446
|
-
commandTokens = [...expandedHead, ...commandTokens.slice(1)];
|
|
447
|
-
}
|
|
448
|
-
if (isCommandStringWrapper(commandTokens[0] ?? "")) {
|
|
449
|
-
const lower = commandTokens.slice(1).map((token) => token.toLowerCase());
|
|
450
|
-
const runIndex = lower.findIndex((token) => token === "--run");
|
|
451
|
-
if (runIndex >= 0 && runIndex + 1 < commandTokens.length) {
|
|
452
|
-
const nestedCommand = commandTokens.slice(runIndex + 2).join(" ");
|
|
453
|
-
return tokenizeShellCommand(canonicalizeGuardCommand(nestedCommand)).some((segment) => containsGuardedGitVerb(segment.tokens, depth + 1, matcher));
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
// These commands consume their following words as text/patterns; a bare
|
|
457
|
-
// git token in their arguments is not an indirect executable invocation.
|
|
458
|
-
// `$(git push)` is execution and must block; `"git push"` as literal text
|
|
459
|
-
// is allowed. Substitutions are screened before this text-consumer escape.
|
|
460
|
-
if (["echo", "printf", "grep"].includes(executableName(commandTokens[0] ?? ""))) {
|
|
461
|
-
return false;
|
|
462
|
-
}
|
|
463
|
-
const gitIndex = commandTokens.findIndex((token) => isGitExecutable(token));
|
|
464
|
-
if (gitIndex >= 0) {
|
|
465
|
-
// Any non-leading git invocation is indirect. Do not maintain a wrapper
|
|
466
|
-
// or flag allowlist: unknown launchers are the security boundary here.
|
|
467
|
-
if (gitIndex > 0 && matcher.indirectAlwaysMatches)
|
|
468
|
-
return true;
|
|
469
|
-
// #2007: a guard that protects the agent from its own accident must not
|
|
470
|
-
// decline `xargs git status`, so the indirect path stays armed only when
|
|
471
|
-
// a governed verb sits in git's real COMMAND POSITION. Scanning every
|
|
472
|
-
// token instead would fire on `find . -name checkout | xargs git add`,
|
|
473
|
-
// where `checkout` is a positional value and not a subcommand at all.
|
|
474
|
-
return matchGitVerbAtCommandPosition(commandTokens.slice(gitIndex), matcher);
|
|
475
|
-
}
|
|
476
|
-
const leadingExecutable = commandTokens[0] ?? "";
|
|
477
|
-
const knownCommandStringWrapper = isShellWrapper(leadingExecutable) ||
|
|
478
|
-
isCommandStringWrapper(leadingExecutable);
|
|
479
|
-
if (!knownCommandStringWrapper) {
|
|
480
|
-
// Unknown launchers are a fail-closed boundary only when they explicitly
|
|
481
|
-
// accept a command string. Re-tokenizing the value keeps literal mentions
|
|
482
|
-
// such as `myprog -c "echo git push"` out of the guarded-command path.
|
|
483
|
-
const unknownSwitchIndex = commandTokens
|
|
484
|
-
.slice(1)
|
|
485
|
-
.findIndex((token) => (token.startsWith("-") || token.startsWith("/")) && token.length > 1);
|
|
486
|
-
if (unknownSwitchIndex < 0)
|
|
487
|
-
return false;
|
|
488
|
-
const commandIndex = unknownSwitchIndex + 2;
|
|
489
|
-
if (commandIndex >= commandTokens.length)
|
|
490
|
-
return false;
|
|
491
|
-
const nestedCommand = commandTokens.slice(commandIndex).join(" ");
|
|
492
|
-
return tokenizeShellCommand(canonicalizeGuardCommand(nestedCommand)).some((segment) => containsGuardedGitVerb(segment.tokens, depth + 1, matcher));
|
|
493
|
-
}
|
|
494
|
-
const lower = commandTokens.slice(1).map((token) => token.toLowerCase());
|
|
495
|
-
const switchIndex = lower.findIndex((token) => token === "-c" ||
|
|
496
|
-
token === "--run" ||
|
|
497
|
-
token === "-lc" ||
|
|
498
|
-
(/^-[^-]*c$/.test(token) && !token.startsWith("--")) ||
|
|
499
|
-
token === "/c" ||
|
|
500
|
-
token === "-command" ||
|
|
501
|
-
token === "-command:" ||
|
|
502
|
-
token === "-encodedcommand");
|
|
503
|
-
if (switchIndex < 0 || switchIndex + 1 >= commandTokens.length)
|
|
504
|
-
return false;
|
|
505
|
-
// Encoded PowerShell is intentionally unsupported: decoding it here would
|
|
506
|
-
// be a second shell/parser and could turn an ambiguous command into a false
|
|
507
|
-
// allow. Plain -Command/-c is safely handed back to the shared lexer.
|
|
508
|
-
if (lower[switchIndex] === "-encodedcommand")
|
|
509
|
-
return false;
|
|
510
|
-
// switchIndex is relative to commandTokens.slice(1), so +2 addresses the
|
|
511
|
-
// command token after the switch for both cmd and PowerShell. This also
|
|
512
|
-
// handles cmd options preceding /C (for example `/S /C`).
|
|
513
|
-
let commandIndex = switchIndex + 2;
|
|
514
|
-
if (commandTokens[commandIndex] === "--")
|
|
515
|
-
commandIndex += 1;
|
|
516
|
-
const nestedCommand = commandTokens.slice(commandIndex).join(" ");
|
|
517
|
-
return tokenizeShellCommand(canonicalizeGuardCommand(nestedCommand)).some((segment) => containsGuardedGitVerb(segment.tokens, depth + 1, matcher));
|
|
518
|
-
}
|
|
519
|
-
/**
|
|
520
|
-
* Analyze actual executable invocations, not substrings in shell text.
|
|
521
|
-
*
|
|
522
|
-
* Shared entry point for every guard that needs "is this bash input really a
|
|
523
|
-
* git invocation of a verb I govern" (#2007). The verb question is the only
|
|
524
|
-
* parameter; wrapper, substitution, and text-consumer analysis are identical
|
|
525
|
-
* for all callers by construction.
|
|
526
|
-
*/
|
|
527
|
-
export function detectGuardedGitVerb(toolName, input, matcher) {
|
|
528
|
-
if (toolName !== "bash")
|
|
529
|
-
return false;
|
|
530
|
-
const command = getShellCommand(input);
|
|
531
|
-
if (!command)
|
|
532
|
-
return false;
|
|
533
|
-
const canonical = canonicalizeGuardCommand(command);
|
|
534
|
-
if (containsGuardedSubstitution(canonical, 0, matcher))
|
|
535
|
-
return true;
|
|
536
|
-
return tokenizeShellCommand(canonical).some((segment) => containsGuardedGitVerb(segment.tokens, 0, matcher));
|
|
537
|
-
}
|
|
538
|
-
/** The #1063 commit gate's own question. */
|
|
539
|
-
export function isGitCommitOrPushAttempt(toolName, input) {
|
|
540
|
-
return detectGuardedGitVerb(toolName, input, COMMIT_PUSH_MATCHER);
|
|
541
|
-
}
|
|
542
|
-
function isTurnEndFindingsCache(value) {
|
|
543
|
-
if (!value || typeof value !== "object")
|
|
544
|
-
return false;
|
|
545
|
-
const record = value;
|
|
546
|
-
return (typeof record.content === "string" &&
|
|
547
|
-
typeof record.hasBlockers === "boolean" &&
|
|
548
|
-
Array.isArray(record.affectedFiles) &&
|
|
549
|
-
record.affectedFiles.every((file) => typeof file === "string") &&
|
|
550
|
-
typeof record.sessionId === "string" &&
|
|
551
|
-
typeof record.projectSeqStart === "number" &&
|
|
552
|
-
typeof record.projectSeqEnd === "number" &&
|
|
553
|
-
Number.isFinite(record.projectSeqStart) &&
|
|
554
|
-
Number.isFinite(record.projectSeqEnd) &&
|
|
555
|
-
!!record.fileSeqByPath &&
|
|
556
|
-
typeof record.fileSeqByPath === "object" &&
|
|
557
|
-
Object.values(record.fileSeqByPath).every((seq) => typeof seq === "number" && Number.isFinite(seq)) &&
|
|
558
|
-
!!record.fileContentHashes &&
|
|
559
|
-
typeof record.fileContentHashes === "object" &&
|
|
560
|
-
Object.values(record.fileContentHashes).every((hash) => typeof hash === "string") &&
|
|
561
|
-
(record.affectedFilesTruncated === undefined ||
|
|
562
|
-
typeof record.affectedFilesTruncated === "boolean") &&
|
|
563
|
-
(record.blockingFiles === undefined || Array.isArray(record.blockingFiles)));
|
|
564
|
-
}
|
|
565
|
-
function cacheRecord(cacheManager, cwd) {
|
|
566
|
-
const entry = cacheManager.readCache("turn-end-findings", cwd);
|
|
567
|
-
return entry && isTurnEndFindingsCache(entry.data) ? entry.data : undefined;
|
|
568
|
-
}
|
|
569
|
-
function markCacheUnknown(runtime, reason) {
|
|
570
|
-
runtime.markGitGuardCacheUnknown(reason);
|
|
571
|
-
}
|
|
572
|
-
/** Persist a complete, bounded, content-bound guard record. */
|
|
573
|
-
export function writeGitGuardRecord(cacheManager, runtime, cwd, record) {
|
|
574
|
-
const capped = capAffectedFiles(Array.isArray(record.affectedFiles) ? record.affectedFiles : [], cwd);
|
|
575
|
-
const fileSeqByPath = { ...record.fileSeqByPath };
|
|
576
|
-
for (const file of capped.files) {
|
|
577
|
-
const key = guardPathKey(file, cwd);
|
|
578
|
-
if (fileSeqByPath[key] === undefined) {
|
|
579
|
-
fileSeqByPath[key] = runtime.getFileSeq(resolveGuardPath(file, cwd));
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
const currentProvenance = snapshotAdvisoryProvenance({
|
|
583
|
-
cwd,
|
|
584
|
-
runtime,
|
|
585
|
-
generation: 0,
|
|
586
|
-
files: capped.files.map((file) => ({
|
|
587
|
-
path: file,
|
|
588
|
-
role: "affected",
|
|
589
|
-
})),
|
|
590
|
-
truncated: capped.truncated,
|
|
591
|
-
});
|
|
592
|
-
const fileContentHashes = Object.fromEntries(currentProvenance.files.map((file) => [
|
|
593
|
-
guardPathKey(file.path, cwd),
|
|
594
|
-
file.sha256,
|
|
595
|
-
]));
|
|
596
|
-
const data = {
|
|
597
|
-
...record,
|
|
598
|
-
affectedFiles: capped.files,
|
|
599
|
-
affectedFilesTruncated: capped.truncated,
|
|
600
|
-
fileSeqByPath,
|
|
601
|
-
blockingFiles: Array.isArray(record.blockingFiles)
|
|
602
|
-
? capAffectedFiles(record.blockingFiles, cwd).files
|
|
603
|
-
: undefined,
|
|
604
|
-
fileContentHashes,
|
|
605
|
-
provenance: record.provenance ?? currentProvenance,
|
|
606
|
-
};
|
|
607
|
-
try {
|
|
608
|
-
cacheManager.writeCache("turn-end-findings", data, cwd);
|
|
609
|
-
runtime.clearGitGuardCacheUnknown();
|
|
610
|
-
return true;
|
|
611
|
-
}
|
|
612
|
-
catch {
|
|
613
|
-
markCacheUnknown(runtime, "cache_write_failed");
|
|
614
|
-
return false;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
function logDecision(cwd, decision, reasonCategory, metadata = {}) {
|
|
618
|
-
logLatency({
|
|
619
|
-
type: "phase",
|
|
620
|
-
toolName: "git-guard",
|
|
621
|
-
filePath: cwd,
|
|
622
|
-
phase: "decision",
|
|
623
|
-
durationMs: 0,
|
|
624
|
-
result: decision,
|
|
625
|
-
metadata: { decision, reasonCategory, ...metadata },
|
|
626
|
-
});
|
|
627
|
-
}
|
|
628
|
-
function unknown(cwd, reasonCategory, metadata = {}) {
|
|
629
|
-
logDecision(cwd, "unknown", reasonCategory, metadata);
|
|
630
|
-
return {
|
|
631
|
-
block: true,
|
|
632
|
-
unknown: true,
|
|
633
|
-
reason: `🔴 COMMIT BLOCKED (--lens-guard): blocker state is unknown (${reasonCategory}). Re-run pi-lens checks or start a fresh session, then retry.`,
|
|
634
|
-
};
|
|
635
|
-
}
|
|
636
|
-
/**
|
|
637
|
-
* Reconcile the one persisted record after a per-file dispatch. This is called
|
|
638
|
-
* on tool_result, never tool_call, and therefore does not add disk I/O to the
|
|
639
|
-
* edit preflight path.
|
|
640
|
-
*/
|
|
641
|
-
/**
|
|
642
|
-
* Retire one file's inline blocker on a confirmed-clean verdict, then bring the
|
|
643
|
-
* commit gate back in line with the map (#1561 F2).
|
|
644
|
-
*
|
|
645
|
-
* Retiring the map entry alone is not enough and claiming otherwise was wrong:
|
|
646
|
-
* the gate reads a LATCH (`gitGuardHasBlockers`) and a PERSISTED record, and
|
|
647
|
-
* `evaluateGitGuard` short-circuits on the latch before it ever looks at the
|
|
648
|
-
* record. A retire that touched neither left the commit blocked and quoted the
|
|
649
|
-
* retired blocker back as the reason.
|
|
650
|
-
*
|
|
651
|
-
* Both are recomputed exactly the way a clean dispatch recomputes them:
|
|
652
|
-
* `updateGitGuardStatus(false, "")` re-derives the latch from the blocker map
|
|
653
|
-
* — it cannot clear a latch another file's live blocker still justifies,
|
|
654
|
-
* because that file's entry is still in the map — and `syncGitGuardRecord`
|
|
655
|
-
* rewrites the persisted record from the same source of truth.
|
|
656
|
-
*
|
|
657
|
-
* Colocated with the gate rather than inlined at the wiring site so the claim
|
|
658
|
-
* is testable without booting the extension.
|
|
659
|
-
*
|
|
660
|
-
* @returns true when an entry was retired (the caller logs it — #1432 Gap 1).
|
|
661
|
-
*/
|
|
662
|
-
export function retireInlineBlockerAndResyncGuard(args) {
|
|
663
|
-
const retired = args.runtime.retireInlineBlockerOnConfirmedClean(args.filePath, args.writeIndex, args.coveredSources);
|
|
664
|
-
if (!retired)
|
|
665
|
-
return false;
|
|
666
|
-
args.runtime.updateGitGuardStatus(false, "");
|
|
667
|
-
if (args.lensGuardEnabled) {
|
|
668
|
-
syncGitGuardRecord(args.runtime, args.cacheManager, args.cwd, args.filePath);
|
|
669
|
-
}
|
|
670
|
-
return true;
|
|
671
|
-
}
|
|
672
|
-
export function syncGitGuardRecord(runtime, cacheManager, cwd, editedFilePath) {
|
|
673
|
-
const entries = runtime.getInlineBlockersSnapshot?.() ?? [];
|
|
674
|
-
const inspection = cacheManager.inspectCache("turn-end-findings", cwd);
|
|
675
|
-
const existing = cacheRecord(cacheManager, cwd);
|
|
676
|
-
if (existing &&
|
|
677
|
-
existing.sessionId !== runtime.telemetrySessionId &&
|
|
678
|
-
entries.length === 0) {
|
|
679
|
-
markCacheUnknown(runtime, "session_mismatch");
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
if (!existing && inspection !== "missing" && entries.length === 0) {
|
|
683
|
-
markCacheUnknown(runtime, `cache_${inspection}`);
|
|
684
|
-
return;
|
|
685
|
-
}
|
|
686
|
-
const fileSeqByPath = {};
|
|
687
|
-
for (const [filePath, seq] of runtime.getFileSeqEntries?.() ?? []) {
|
|
688
|
-
fileSeqByPath[guardPathKey(filePath, cwd)] = seq;
|
|
689
|
-
}
|
|
690
|
-
const inlineFiles = entries.map((entry) => resolveGuardPath(entry.filePath, cwd));
|
|
691
|
-
const existingBlockingFiles = existing?.blockingFiles ?? [];
|
|
692
|
-
const provenanceComplete = existing?.blockerContent
|
|
693
|
-
? hasCompleteBlockingProvenance(existing.blockerContent, existingBlockingFiles, cwd)
|
|
694
|
-
: true;
|
|
695
|
-
if (existing?.blockerContent && !provenanceComplete) {
|
|
696
|
-
markCacheUnknown(runtime, "blocking_provenance_untrusted");
|
|
697
|
-
return;
|
|
698
|
-
}
|
|
699
|
-
const editedKey = editedFilePath
|
|
700
|
-
? guardPathKey(editedFilePath, cwd)
|
|
701
|
-
: undefined;
|
|
702
|
-
const testFiles = existing?.testFailureFiles ?? [];
|
|
703
|
-
const remainingBlockingFiles = editedKey && existingBlockingFiles.length > 0
|
|
704
|
-
? existingBlockingFiles.filter((file) => guardPathKey(file, cwd) !== editedKey)
|
|
705
|
-
: existingBlockingFiles;
|
|
706
|
-
let affectedFiles = [...(existing?.affectedFiles ?? []), ...inlineFiles];
|
|
707
|
-
if (!entries.length && editedKey && existing) {
|
|
708
|
-
const isStillTestFailure = testFiles.some((file) => guardPathKey(file, cwd) === editedKey);
|
|
709
|
-
const isUnknownBlockingPath = existingBlockingFiles.length === 0 && !!existing.blockerContent;
|
|
710
|
-
if (!isStillTestFailure && !isUnknownBlockingPath) {
|
|
711
|
-
affectedFiles = affectedFiles.filter((file) => guardPathKey(file, cwd) !== editedKey);
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
// A clean per-file dispatch is authoritative for that file. When the
|
|
715
|
-
// persisted record has explicit blocking-file provenance and the last such
|
|
716
|
-
// file just reconciled clean, retaining blockerContent would resurrect a
|
|
717
|
-
// stale blocker on every later git-guard lookup. Records without that
|
|
718
|
-
// provenance remain fail-closed: they cannot be safely cleared here.
|
|
719
|
-
const clearedLastKnownBlocker = !entries.length &&
|
|
720
|
-
!!editedKey &&
|
|
721
|
-
provenanceComplete &&
|
|
722
|
-
existingBlockingFiles.length > 0 &&
|
|
723
|
-
remainingBlockingFiles.length === 0;
|
|
724
|
-
const blockerContent = entries.length > 0
|
|
725
|
-
? entries.map((entry) => `${entry.filePath}: ${entry.summary}`).join("\n")
|
|
726
|
-
: clearedLastKnownBlocker
|
|
727
|
-
? undefined
|
|
728
|
-
: existing?.blockerContent;
|
|
729
|
-
const hasTestFailures = existing?.testFailures === true;
|
|
730
|
-
const hasBlockers = !!blockerContent || hasTestFailures;
|
|
731
|
-
const content = [
|
|
732
|
-
blockerContent,
|
|
733
|
-
hasTestFailures ? existing?.testFailureContent : undefined,
|
|
734
|
-
]
|
|
735
|
-
.filter((value) => !!value)
|
|
736
|
-
.join("\n\n");
|
|
737
|
-
if (!hasBlockers && !content) {
|
|
738
|
-
cacheManager.clearCache("turn-end-findings", cwd);
|
|
739
|
-
return;
|
|
740
|
-
}
|
|
741
|
-
writeGitGuardRecord(cacheManager, runtime, cwd, {
|
|
742
|
-
content: content || existing?.content || "",
|
|
743
|
-
blockerContent,
|
|
744
|
-
blockingFiles: entries.length > 0 ? inlineFiles : remainingBlockingFiles,
|
|
745
|
-
hasBlockers,
|
|
746
|
-
affectedFiles,
|
|
747
|
-
sessionId: runtime.telemetrySessionId,
|
|
748
|
-
projectSeqStart: runtime.turnStartProjectSeq,
|
|
749
|
-
projectSeqEnd: runtime.projectSeq,
|
|
750
|
-
fileSeqByPath,
|
|
751
|
-
fileContentHashes: {},
|
|
752
|
-
consumed: false,
|
|
753
|
-
testFailures: existing?.testFailures,
|
|
754
|
-
testFailureContent: existing?.testFailureContent,
|
|
755
|
-
testFailureFiles: existing?.testFailureFiles,
|
|
756
|
-
});
|
|
757
|
-
}
|
|
758
|
-
/** Add blocking test failures to the same turn-end record. */
|
|
759
|
-
export function mergeGitGuardTestFailure(cacheManager, cwd, runtime, content, files) {
|
|
760
|
-
const existing = cacheRecord(cacheManager, cwd);
|
|
761
|
-
const fileSeqByPath = {};
|
|
762
|
-
for (const [filePath, seq] of runtime.getFileSeqEntries?.() ?? []) {
|
|
763
|
-
fileSeqByPath[guardPathKey(filePath, cwd)] = seq;
|
|
764
|
-
}
|
|
765
|
-
const failedFiles = files.map((file) => resolveGuardPath(file, cwd));
|
|
766
|
-
const blockerContent = existing?.blockerContent;
|
|
767
|
-
const testFailureFiles = [
|
|
768
|
-
...(existing?.testFailureFiles ?? []),
|
|
769
|
-
...failedFiles,
|
|
770
|
-
].filter((file, index, all) => all.findIndex((candidate) => guardPathKey(candidate, cwd) === guardPathKey(file, cwd)) === index);
|
|
771
|
-
writeGitGuardRecord(cacheManager, runtime, cwd, {
|
|
772
|
-
content: [blockerContent, content].filter(Boolean).join("\n\n"),
|
|
773
|
-
blockerContent,
|
|
774
|
-
blockingFiles: existing?.blockingFiles ?? [],
|
|
775
|
-
testFailureContent: content,
|
|
776
|
-
testFailureFiles: testFailureFiles,
|
|
777
|
-
hasBlockers: true,
|
|
778
|
-
affectedFiles: [...(existing?.affectedFiles ?? []), ...failedFiles],
|
|
779
|
-
sessionId: runtime.telemetrySessionId,
|
|
780
|
-
projectSeqStart: runtime.turnStartProjectSeq,
|
|
781
|
-
projectSeqEnd: runtime.projectSeq,
|
|
782
|
-
fileSeqByPath,
|
|
783
|
-
fileContentHashes: {},
|
|
784
|
-
consumed: false,
|
|
785
|
-
testFailures: true,
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
/** A passing test run resolves only its own previous test failures. */
|
|
789
|
-
export function clearGitGuardTestFailure(cacheManager, cwd, runtime, passedFiles = []) {
|
|
790
|
-
const existing = cacheRecord(cacheManager, cwd);
|
|
791
|
-
if (!existing?.testFailures)
|
|
792
|
-
return;
|
|
793
|
-
const passedKeys = new Set((passedFiles.length > 0
|
|
794
|
-
? passedFiles
|
|
795
|
-
: (existing.testFailureFiles ?? [])).map((file) => guardPathKey(file, cwd)));
|
|
796
|
-
const remainingFiles = (existing.testFailureFiles ?? []).filter((file) => !passedKeys.has(guardPathKey(file, cwd)));
|
|
797
|
-
const blockerContent = existing.blockerContent ?? "";
|
|
798
|
-
if (!blockerContent && remainingFiles.length === 0) {
|
|
799
|
-
cacheManager.clearCache("turn-end-findings", cwd);
|
|
800
|
-
return;
|
|
801
|
-
}
|
|
802
|
-
const blockingKeys = new Set((existing.blockingFiles ?? []).map((file) => guardPathKey(file, cwd)));
|
|
803
|
-
const affectedFiles = existing.affectedFiles.filter((file) => blockingKeys.has(guardPathKey(file, cwd)) ||
|
|
804
|
-
remainingFiles.some((testFile) => guardPathKey(testFile, cwd) === guardPathKey(file, cwd)));
|
|
805
|
-
writeGitGuardRecord(cacheManager, runtime, cwd, {
|
|
806
|
-
...existing,
|
|
807
|
-
content: blockerContent,
|
|
808
|
-
blockerContent,
|
|
809
|
-
affectedFiles,
|
|
810
|
-
testFailures: remainingFiles.length > 0,
|
|
811
|
-
testFailureContent: remainingFiles.length > 0 ? existing.testFailureContent : undefined,
|
|
812
|
-
testFailureFiles: remainingFiles.length > 0 ? remainingFiles : undefined,
|
|
813
|
-
hasBlockers: !!blockerContent || remainingFiles.length > 0,
|
|
814
|
-
sessionId: runtime.telemetrySessionId,
|
|
815
|
-
projectSeqStart: runtime.turnStartProjectSeq,
|
|
816
|
-
projectSeqEnd: runtime.projectSeq,
|
|
817
|
-
fileSeqByPath: Object.fromEntries(runtime
|
|
818
|
-
.getFileSeqEntries()
|
|
819
|
-
.map(([filePath, seq]) => [guardPathKey(filePath, cwd), seq])),
|
|
820
|
-
fileContentHashes: {},
|
|
821
|
-
});
|
|
822
|
-
}
|
|
823
|
-
export function evaluateGitGuard(runtime, cacheManager, cwd, host = "pi") {
|
|
824
|
-
// #2535 F3: a known tool with no mapping on this host resolves to
|
|
825
|
-
// undefined — rephrase rather than naming a dead tool. `lens_diagnostics`
|
|
826
|
-
// is pinned available on both hosts, so the fallback is defensive only.
|
|
827
|
-
const diagnosticsTool = resolveLensToolName("lens_diagnostics", host);
|
|
828
|
-
const inspectLine = diagnosticsTool
|
|
829
|
-
? `Run ${diagnosticsTool} mode=all for full details, then commit again.`
|
|
830
|
-
: "Inspect the full diagnostics, then commit again.";
|
|
831
|
-
if (runtime.gitGuardHasBlockers) {
|
|
832
|
-
logDecision(cwd, "blocked", "runtime_blockers", {
|
|
833
|
-
projectSeq: runtime.projectSeq,
|
|
834
|
-
});
|
|
835
|
-
const detail = runtime.gitGuardSummary
|
|
836
|
-
? `\n${runtime.gitGuardSummary}`
|
|
837
|
-
: "";
|
|
838
|
-
return {
|
|
839
|
-
block: true,
|
|
840
|
-
reason: `🔴 COMMIT BLOCKED (--lens-guard): unresolved blockers must be fixed before commit/push.${detail}\n${inspectLine}`,
|
|
841
|
-
};
|
|
842
|
-
}
|
|
843
|
-
if (runtime.gitGuardCacheUnknownReason) {
|
|
844
|
-
return unknown(cwd, runtime.gitGuardCacheUnknownReason);
|
|
845
|
-
}
|
|
846
|
-
const inspection = cacheManager.inspectCache("turn-end-findings", cwd);
|
|
847
|
-
if (inspection === "missing") {
|
|
848
|
-
logDecision(cwd, "allowed", "no_record");
|
|
849
|
-
return { block: false };
|
|
850
|
-
}
|
|
851
|
-
if (inspection !== "fresh")
|
|
852
|
-
return unknown(cwd, `cache_${inspection}`);
|
|
853
|
-
const pending = cacheManager.readCache("turn-end-findings", cwd);
|
|
854
|
-
if (!pending || !isTurnEndFindingsCache(pending.data)) {
|
|
855
|
-
return unknown(cwd, "cache_malformed");
|
|
856
|
-
}
|
|
857
|
-
const record = pending.data;
|
|
858
|
-
if (!record.hasBlockers) {
|
|
859
|
-
logDecision(cwd, "allowed", "advisory_only");
|
|
860
|
-
return { block: false };
|
|
861
|
-
}
|
|
862
|
-
if (record.affectedFilesTruncated) {
|
|
863
|
-
return unknown(cwd, "affected_files_truncated");
|
|
864
|
-
}
|
|
865
|
-
if (record.sessionId !== runtime.telemetrySessionId) {
|
|
866
|
-
return unknown(cwd, "session_mismatch");
|
|
867
|
-
}
|
|
868
|
-
if (record.projectSeqEnd !== runtime.projectSeq) {
|
|
869
|
-
return unknown(cwd, "project_sequence_mismatch", {
|
|
870
|
-
recordedProjectSeq: record.projectSeqEnd,
|
|
871
|
-
currentProjectSeq: runtime.projectSeq,
|
|
872
|
-
});
|
|
873
|
-
}
|
|
874
|
-
const liveFiles = [];
|
|
875
|
-
for (const file of record.affectedFiles) {
|
|
876
|
-
const resolved = resolveGuardPath(file, cwd);
|
|
877
|
-
const key = guardPathKey(resolved, cwd);
|
|
878
|
-
const recordedSeq = record.fileSeqByPath[key];
|
|
879
|
-
if (recordedSeq === undefined ||
|
|
880
|
-
recordedSeq !== (runtime.getFileSeq?.(resolved) ?? 0)) {
|
|
881
|
-
return unknown(cwd, "file_sequence_mismatch", { file: resolved });
|
|
882
|
-
}
|
|
883
|
-
try {
|
|
884
|
-
if (!isPathIgnoredByProject(resolved, runtime.projectRoot || cwd, false)) {
|
|
885
|
-
const currentHash = currentFileFingerprint(resolved);
|
|
886
|
-
if (currentHash !== "missing") {
|
|
887
|
-
liveFiles.push(resolved);
|
|
888
|
-
const expectedHash = record.fileContentHashes[key];
|
|
889
|
-
if (!expectedHash ||
|
|
890
|
-
expectedHash.startsWith("unreadable:") ||
|
|
891
|
-
currentHash.startsWith("unreadable:") ||
|
|
892
|
-
expectedHash !== currentHash) {
|
|
893
|
-
return unknown(cwd, "file_content_changed", { file: resolved });
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
catch {
|
|
899
|
-
return unknown(cwd, "file_unreadable", { file: resolved });
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
if (record.hasBlockers && record.affectedFiles.length === 0) {
|
|
903
|
-
return unknown(cwd, "blocker_without_affected_file");
|
|
904
|
-
}
|
|
905
|
-
// A deleted/ignored affected file has been resolved; no stale blocker is
|
|
906
|
-
// allowed to survive solely because its old path remains in the record.
|
|
907
|
-
if (record.hasBlockers &&
|
|
908
|
-
liveFiles.length === 0 &&
|
|
909
|
-
record.affectedFiles.length > 0) {
|
|
910
|
-
logDecision(cwd, "allowed", "affected_files_resolved", {
|
|
911
|
-
projectSeq: record.projectSeqEnd,
|
|
912
|
-
});
|
|
913
|
-
return { block: false };
|
|
914
|
-
}
|
|
915
|
-
logDecision(cwd, "blocked", "cache_blockers", {
|
|
916
|
-
projectSeq: record.projectSeqEnd,
|
|
917
|
-
affectedFileCount: record.affectedFiles.length,
|
|
918
|
-
});
|
|
919
|
-
return {
|
|
920
|
-
block: true,
|
|
921
|
-
reason: `🔴 COMMIT BLOCKED (--lens-guard): unresolved blockers must be fixed before commit/push.\n${inspectLine}`,
|
|
922
|
-
};
|
|
923
|
-
}
|