@herbertgao/pi-extensions 2026.8.6 → 2026.8.7
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/README.md +5 -5
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +272 -22
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +14 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
- package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
- package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +17 -13
- package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
- package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
- package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
- package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
- package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +21 -6
- package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +20 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +1 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +26 -10
- package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +2 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +30 -7
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +30 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +26 -5
- package/node_modules/@narumitw/pi-btw/README.md +120 -111
- package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
- package/node_modules/@narumitw/pi-btw/package.json +10 -6
- package/node_modules/@narumitw/pi-btw/src/btw.ts +36 -40
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +45 -2
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
- package/node_modules/pi-lens/CHANGELOG.md +909 -0
- package/node_modules/pi-lens/README.md +11 -3
- package/node_modules/pi-lens/config/ruff/core.toml +42 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +126 -13
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +67 -11
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
- package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +285 -8
- package/node_modules/pi-lens/dist/clients/biome-client.js +61 -30
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
- package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +119 -0
- package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +192 -0
- package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
- package/node_modules/pi-lens/dist/clients/cascade-format.js +20 -1
- package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
- package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +123 -31
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
- package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +62 -13
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +25 -3
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +147 -33
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +52 -2
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +256 -88
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +60 -53
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +23 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +20 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +10 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +18 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +275 -13
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +21 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +734 -64
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +72 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +9 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +9 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
- package/node_modules/pi-lens/dist/clients/extension-log.js +22 -1
- package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +294 -0
- package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
- package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
- package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
- package/node_modules/pi-lens/dist/clients/git-guard.js +31 -0
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +109 -9
- package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
- package/node_modules/pi-lens/dist/clients/host-ports.js +8 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +1114 -100
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
- package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +82 -6
- package/node_modules/pi-lens/dist/clients/knip-client.js +310 -10
- package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +2 -0
- package/node_modules/pi-lens/dist/clients/latency-logger.js +440 -11
- package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +102 -7
- package/node_modules/pi-lens/dist/clients/lsp/client.js +1516 -159
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +83 -1
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2274 -147
- package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
- package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
- package/node_modules/pi-lens/dist/clients/lsp/server.js +277 -16
- package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +79 -3
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +43 -0
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
- package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
- package/node_modules/pi-lens/dist/clients/mcp/session.js +10 -2
- package/node_modules/pi-lens/dist/clients/module-report.js +8 -0
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
- package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
- package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
- package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +154 -33
- package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +84 -14
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -0
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
- package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
- package/node_modules/pi-lens/dist/clients/read-guard.js +184 -10
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
- package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +52 -4
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +96 -7
- package/node_modules/pi-lens/dist/clients/runtime-context.js +8 -1
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +308 -45
- package/node_modules/pi-lens/dist/clients/runtime-session.js +332 -9
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +212 -79
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +575 -67
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
- package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +157 -24
- package/node_modules/pi-lens/dist/clients/sg-runner.js +116 -22
- package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
- package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
- package/node_modules/pi-lens/dist/clients/startup-timing.js +19 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +151 -16
- package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
- package/node_modules/pi-lens/dist/clients/tool-policy.js +125 -20
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
- package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
- package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
- package/node_modules/pi-lens/dist/index.js +67738 -55739
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +466 -54
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +132 -43
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
- package/node_modules/pi-lens/docs/agent-guide.md +6 -1
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +515 -506
- package/node_modules/pi-lens/docs/dispositions.md +21 -0
- package/node_modules/pi-lens/docs/features.md +19 -5
- package/node_modules/pi-lens/docs/globalconfig.md +3 -0
- package/node_modules/pi-lens/docs/language-coverage.md +71 -5
- package/node_modules/pi-lens/docs/mcp.md +2 -2
- package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
- package/node_modules/pi-lens/docs/settings.md +19 -1
- package/node_modules/pi-lens/docs/usage.md +1 -0
- package/node_modules/pi-lens/package.json +10 -6
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +15 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +2 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +2 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +57 -12
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +22 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +23 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +33 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +34 -8
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +6 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +53 -1
- package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
- package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +2 -0
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +80 -3
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +41 -0
- package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +36 -0
- package/node_modules/pi-mcp-adapter/README.md +47 -5
- package/node_modules/pi-mcp-adapter/cli.js +163 -1
- package/node_modules/pi-mcp-adapter/commands.ts +128 -22
- package/node_modules/pi-mcp-adapter/config.ts +25 -7
- package/node_modules/pi-mcp-adapter/direct-tools.ts +5 -4
- package/node_modules/pi-mcp-adapter/index.ts +219 -32
- package/node_modules/pi-mcp-adapter/init.ts +36 -4
- package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
- package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
- package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +32 -21
- package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
- package/node_modules/pi-mcp-adapter/package.json +3 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +31 -37
- package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
- package/node_modules/pi-mcp-adapter/server-manager.ts +140 -2
- package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
- package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
- package/node_modules/pi-mcp-adapter/types.ts +2 -0
- package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
- package/node_modules/pi-mcp-adapter/utils.ts +70 -0
- package/node_modules/pi-web-access/CHANGELOG.md +23 -0
- package/node_modules/pi-web-access/README.md +27 -9
- package/node_modules/pi-web-access/anysearch.ts +4 -2
- package/node_modules/pi-web-access/brave.ts +16 -4
- package/node_modules/pi-web-access/curator-page.ts +6 -3
- package/node_modules/pi-web-access/curator-server.ts +4 -1
- package/node_modules/pi-web-access/exa.ts +18 -7
- package/node_modules/pi-web-access/extract.ts +28 -3
- package/node_modules/pi-web-access/gemini-search.ts +16 -4
- package/node_modules/pi-web-access/index.ts +41 -57
- package/node_modules/pi-web-access/package.json +1 -1
- package/node_modules/pi-web-access/page-query.ts +4 -2
- package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
- package/node_modules/pi-web-access/parallel.ts +2 -2
- package/node_modules/pi-web-access/query-rewrite.ts +51 -0
- package/node_modules/pi-web-access/serper.ts +211 -0
- package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
- package/node_modules/pi-web-access/summary-review.ts +44 -22
- package/node_modules/pi-web-access/tavily.ts +16 -4
- package/node_modules/pi-web-access/utils.ts +87 -0
- package/node_modules/pi-web-access/valyu.ts +199 -0
- package/package.json +13 -13
|
@@ -19,16 +19,17 @@ import * as crypto from "node:crypto";
|
|
|
19
19
|
import * as fs from "node:fs";
|
|
20
20
|
import { createRequire } from "node:module";
|
|
21
21
|
import * as path from "node:path";
|
|
22
|
+
import { getDegradationLedgerGeneration, incrementDegradationCount, recordDegradation, recordDegradationOnce, } from "./degradation-ledger.js";
|
|
22
23
|
import { loadWebTreeSitter } from "./deps/web-tree-sitter.js";
|
|
24
|
+
import { transientRetryDelayMs } from "./dispatch/runners/utils/availability-policy.js";
|
|
23
25
|
import { getProjectIgnoreMatcher, isExcludedDirName } from "./file-utils.js";
|
|
24
|
-
import {
|
|
26
|
+
import { downloadGrammarDetailed, fileHasWasmMagic, grammarBlockReason, grammarFileSha256, isVendoredGrammar, LANGUAGE_TO_GRAMMAR, pinnedGrammarHash, vendoredGrammarRefusal, vendoredGrammarsDir, } from "./grammar-source.js";
|
|
25
27
|
import { resolvePackagePath } from "./package-root.js";
|
|
26
|
-
import { recordDegradation } from "./degradation-ledger.js";
|
|
27
28
|
import { assertInstallAllowed, getProjectTrustGeneration, } from "./project-trust.js";
|
|
28
29
|
import { logTreeSitterDiagnostic } from "./tree-sitter-logger.js";
|
|
29
30
|
import { notifyUserDegradation } from "./user-notify.js";
|
|
30
31
|
const _require = createRequire(import.meta.url);
|
|
31
|
-
import { createTreeCacheCounters, TreeCache, } from "./tree-sitter-cache.js";
|
|
32
|
+
import { createTreeCacheCounters, deriveScanTreeCacheCapacity, TreeCache, } from "./tree-sitter-cache.js";
|
|
32
33
|
import { TreeSitterNavigator } from "./tree-sitter-navigator.js";
|
|
33
34
|
import { TreeSitterQueryLoader, } from "./tree-sitter-query-loader.js";
|
|
34
35
|
// Hard cap on a single structural-search file walk. Bounds a misrooted scan so
|
|
@@ -53,21 +54,215 @@ function createParserCounters() {
|
|
|
53
54
|
parserFailures: 0,
|
|
54
55
|
};
|
|
55
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* `size:mtimeMs` identity for a grammar candidate, or undefined when the path
|
|
59
|
+
* is absent or not a regular file. Doubles as the existence check on the
|
|
60
|
+
* resolve path and as the invalidation key for the verified-preamble memo
|
|
61
|
+
* (#1548) — `size` + `mtimeMs` is the codebase's standard cheap stamp.
|
|
62
|
+
*/
|
|
63
|
+
function grammarFileStamp(filePath) {
|
|
64
|
+
try {
|
|
65
|
+
const stat = fs.statSync(filePath);
|
|
66
|
+
return stat.isFile() ? `${stat.size}:${stat.mtimeMs}` : undefined;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
56
72
|
export function isTreeSitterWasmAbortError(error) {
|
|
57
73
|
const message = error instanceof Error ? error.message : String(error);
|
|
58
74
|
return message.includes("Aborted") || message.includes("abort()");
|
|
59
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Positively-identified RESOLUTION-failure codes for a dynamic `import()`:
|
|
78
|
+
* the specifier couldn't be found/resolved, or a transient fs error hit
|
|
79
|
+
* before `import()` got as far as evaluating the target module. All of
|
|
80
|
+
* these recover once whatever was missing/busy appears — they say nothing
|
|
81
|
+
* durable about the module itself (mirrors the errno allowlist in
|
|
82
|
+
* clients/dispatch/runners/ast-grep-napi.ts's `classifyAstGrepLoadFailure`,
|
|
83
|
+
* kept local here rather than shared because that file is owned by an
|
|
84
|
+
* in-flight PR, #1701, at the time of writing).
|
|
85
|
+
*/
|
|
86
|
+
const RESOLUTION_ERROR_CODES = new Set([
|
|
87
|
+
"ERR_MODULE_NOT_FOUND",
|
|
88
|
+
"MODULE_NOT_FOUND",
|
|
89
|
+
"ERR_INVALID_MODULE_SPECIFIER",
|
|
90
|
+
"ERR_UNSUPPORTED_DIR_IMPORT",
|
|
91
|
+
"ENOENT",
|
|
92
|
+
"EMFILE",
|
|
93
|
+
"EBUSY",
|
|
94
|
+
"EAGAIN",
|
|
95
|
+
"EPERM",
|
|
96
|
+
"ETXTBSY",
|
|
97
|
+
]);
|
|
98
|
+
/** Walk an error's `.cause` chain, collecting every `code` seen along the
|
|
99
|
+
* way — a resolution failure's `code` can be wrapped in a cause rather than
|
|
100
|
+
* sitting on the top-level thrown Error. */
|
|
101
|
+
function collectErrorCodes(err) {
|
|
102
|
+
const codes = [];
|
|
103
|
+
let current = err;
|
|
104
|
+
const seen = new Set();
|
|
105
|
+
while (current instanceof Error && !seen.has(current)) {
|
|
106
|
+
seen.add(current);
|
|
107
|
+
const code = current.code;
|
|
108
|
+
if (typeof code === "string")
|
|
109
|
+
codes.push(code);
|
|
110
|
+
current = current.cause;
|
|
111
|
+
}
|
|
112
|
+
return codes;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Classify a `loadWebTreeSitter()` rejection (#1592). RESOLUTION failures
|
|
116
|
+
* (a positively-identified code above) are recoverable: the module gets a
|
|
117
|
+
* fresh `import()` attempt on the very next `init()` call, same as before
|
|
118
|
+
* this fix, because whatever blocked resolution can plausibly clear on its
|
|
119
|
+
* own. Everything else is treated as EVALUATION-shaped and latches — an
|
|
120
|
+
* unrecognized error is far more likely to be the target module's own
|
|
121
|
+
* top-level code throwing (which Node's ESM loader then memoizes as a
|
|
122
|
+
* permanently-rejected module record for that URL) than an unclassified
|
|
123
|
+
* resolution hiccup, so "unknown" defaults to the case a retry cannot fix.
|
|
124
|
+
*/
|
|
125
|
+
function classifyWebTreeSitterLoadFailure(err) {
|
|
126
|
+
const codes = collectErrorCodes(err);
|
|
127
|
+
return codes.some((code) => RESOLUTION_ERROR_CODES.has(code))
|
|
128
|
+
? "resolution"
|
|
129
|
+
: "evaluation";
|
|
130
|
+
}
|
|
60
131
|
// --- Parser Manager ---
|
|
61
132
|
export class TreeSitterClient {
|
|
62
133
|
initialized = false;
|
|
63
134
|
initPromise = null;
|
|
135
|
+
/**
|
|
136
|
+
* Set when `loadWebTreeSitter()` itself rejects with an EVALUATION-shaped
|
|
137
|
+
* error (#1592, review round 2 F1/F2) — `classifyWebTreeSitterLoadFailure`
|
|
138
|
+
* above draws that line. That call is a dynamic `import()` of a fixed
|
|
139
|
+
* resolved URL, and Node's ESM loader permanently memoizes a module
|
|
140
|
+
* record that threw during evaluation — a later `import()` of the SAME
|
|
141
|
+
* URL from a later `init()` call would just replay the cached rejection,
|
|
142
|
+
* not re-attempt the load. Without this latch, every `withTreeSitterRoot()`
|
|
143
|
+
* call (one per file parse) would re-invoke `init()`, see `initPromise`
|
|
144
|
+
* cleared by the previous attempt's `finally`, and dynamically re-import —
|
|
145
|
+
* a dead retry on the hot path. A RESOLUTION-shaped rejection does NOT
|
|
146
|
+
* set this: it leaves `initPromise` cleared as before, so the next
|
|
147
|
+
* `init()` call retries for real, because that class of failure can
|
|
148
|
+
* plausibly clear before the next call arrives.
|
|
149
|
+
*
|
|
150
|
+
* SESSION-scoped, not process-lifetime (round 2 F2, the #1567/#1575
|
|
151
|
+
* `sgSessionHold` precedent): re-armed by `resetLoadStateForSession()`,
|
|
152
|
+
* wired into `resetDispatchBaselines()` via `tree-sitter-shared.ts`'s
|
|
153
|
+
* `resetTreeSitterClientLoadState()`. Re-arming does not guarantee the
|
|
154
|
+
* next attempt succeeds — if the process itself didn't restart between
|
|
155
|
+
* sessions, Node's module cache is unchanged and the replay will just
|
|
156
|
+
* fail fast again — but it gives a fresh degradation record for the new
|
|
157
|
+
* session (the ledger's own `onceKeys` are cleared at session_start too)
|
|
158
|
+
* instead of silently reusing a stale verdict forever, and it does let a
|
|
159
|
+
* genuinely fixed install (process WAS restarted) recover.
|
|
160
|
+
*
|
|
161
|
+
* Distinct from `wasmAborted`, which stays process-lifetime and is
|
|
162
|
+
* deliberately NOT included in the session reset: that flag means the
|
|
163
|
+
* Emscripten WASM heap itself aborted mid-use — a runtime that DID load
|
|
164
|
+
* and then corrupted its own memory. Reusing that heap after a "session"
|
|
165
|
+
* boundary that isn't an actual process restart is not a retry, it's
|
|
166
|
+
* operating on data already documented (tree-sitter-shared.ts) as
|
|
167
|
+
* requiring a real restart to recover. `webTreeSitterLoadFailed` only
|
|
168
|
+
* ever covers a runtime that never loaded in the first place, so nothing
|
|
169
|
+
* corrupted survives a re-arm.
|
|
170
|
+
*/
|
|
171
|
+
webTreeSitterLoadFailed = false;
|
|
64
172
|
languages = new Map();
|
|
65
173
|
parsers = new Map();
|
|
66
174
|
treeCache;
|
|
67
175
|
navigator = new TreeSitterNavigator();
|
|
68
176
|
grammarsDir;
|
|
69
|
-
/** In-flight
|
|
177
|
+
/** In-flight lazy grammar fetches, keyed by wasm filename. Evicted on
|
|
178
|
+
* settle (#1536) — a rejected/false attempt must not be remembered as the
|
|
179
|
+
* permanent answer for the session; only concurrent demands during the
|
|
180
|
+
* SAME probe share it. */
|
|
70
181
|
grammarEnsurePromises = new Map();
|
|
182
|
+
/** Epoch ms before which a failed grammar download is not retried
|
|
183
|
+
* (#1536). A transient download failure — offline, DNS hiccup, CDN blip —
|
|
184
|
+
* says nothing durable about the grammar, so it gets a bounded cooldown
|
|
185
|
+
* (the `transientRetryDelayMs` shape from availability-policy.ts) instead
|
|
186
|
+
* of latching for the process lifetime. */
|
|
187
|
+
grammarRetryAtMs = new Map();
|
|
188
|
+
/**
|
|
189
|
+
* Grammar path → the `size:mtimeMs` stamp at which its wasm preamble was
|
|
190
|
+
* verified (#1548). A POSITIVE-only memo, so the steady-state resolve costs
|
|
191
|
+
* one `stat` (which the old `existsSync` already paid) and re-reads the
|
|
192
|
+
* header only when the stamp moves. Stamped rather than a bare `Set`
|
|
193
|
+
* because a memo that never expires is defect-shape 6: the file it vouches
|
|
194
|
+
* for can be replaced under it. Failures are deliberately NOT memoized — a
|
|
195
|
+
* poisoned path must be re-examined on the next resolve so a successful
|
|
196
|
+
* re-download over it is picked up immediately.
|
|
197
|
+
*/
|
|
198
|
+
verifiedGrammarPaths = new Map();
|
|
199
|
+
/**
|
|
200
|
+
* Grammar path → the `size:mtimeMs` stamp at which `Language.load` failed
|
|
201
|
+
* on it despite passing the wasm-preamble check (#1564) — e.g. a truncated
|
|
202
|
+
* download whose first four bytes are a genuine `\0asm` preamble but whose
|
|
203
|
+
* body decodes short ("Code section extends past end of the module"). The
|
|
204
|
+
* preamble check alone can't see this; only a real decode attempt can.
|
|
205
|
+
* `resolveGrammarFile` treats a path recorded here (at the SAME stamp) as
|
|
206
|
+
* absent, so the next demand re-fetches instead of reusing the same broken
|
|
207
|
+
* file forever. Stamped so a fresh download (new mtime) is re-examined
|
|
208
|
+
* rather than permanently distrusted — same shape as `verifiedGrammarPaths`.
|
|
209
|
+
*/
|
|
210
|
+
decodeFailedGrammarPaths = new Map();
|
|
211
|
+
/**
|
|
212
|
+
* Grammar path → the `size:mtimeMs` stamp at which its bytes were verified
|
|
213
|
+
* to match the CURRENTLY PINNED sha256 manifest (#1760). Positive-only,
|
|
214
|
+
* same discipline as `verifiedGrammarPaths`: the hash is computed once per
|
|
215
|
+
* stamp (not per parse), and a stamp change — a fresh download — forces a
|
|
216
|
+
* re-check rather than trusting a memo made for different bytes.
|
|
217
|
+
*/
|
|
218
|
+
verifiedGrammarVersionAt = new Map();
|
|
219
|
+
/**
|
|
220
|
+
* Grammar path → the `size:mtimeMs` stamp at which it was found to no
|
|
221
|
+
* longer match the pinned manifest hash — a version bump
|
|
222
|
+
* (`TREE_SITTER_WASMS_VERSION`, or a `SOURCE_OVERRIDES` entry), or on-disk
|
|
223
|
+
* corruption (#1760). `resolveGrammarFile` treats a path recorded here (at
|
|
224
|
+
* the SAME stamp) as absent, exactly like `decodeFailedGrammarPaths`, so
|
|
225
|
+
* the next demand re-fetches instead of serving the stale/corrupt file for
|
|
226
|
+
* the rest of the process's life. A fresh download (new stamp) is
|
|
227
|
+
* re-examined rather than permanently distrusted.
|
|
228
|
+
*/
|
|
229
|
+
staleGrammarVersionAt = new Map();
|
|
230
|
+
/**
|
|
231
|
+
* Paths already reported as version-stale THIS SESSION, to log/record them
|
|
232
|
+
* once each (#1801 review F1). Deliberately separate from
|
|
233
|
+
* `staleGrammarVersionAt`: that map is a pure hash-verdict memo whose whole
|
|
234
|
+
* point is to persist for the process's life (re-hashing an unchanged file
|
|
235
|
+
* is exactly what it exists to avoid), so it must never double as a report
|
|
236
|
+
* gate — a gate riding on process-lifetime state can't re-arm at a session
|
|
237
|
+
* boundary. Cleared in `refreshGrammarSessionLatches`, mirroring
|
|
238
|
+
* `poisonedGrammarPaths`.
|
|
239
|
+
*/
|
|
240
|
+
staleReportedGrammarPaths = new Set();
|
|
241
|
+
/** Paths already reported as non-wasm, to log/record them once each. */
|
|
242
|
+
poisonedGrammarPaths = new Set();
|
|
243
|
+
/** Consecutive download failures per grammar, for the exponential
|
|
244
|
+
* cooldown; reset on success. */
|
|
245
|
+
grammarFailureAttempts = new Map();
|
|
246
|
+
/**
|
|
247
|
+
* Retry delay (ms) last SHOWN to the user for this grammar, or `-1` for a
|
|
248
|
+
* durable (non-retryable) failure already announced. The cooldown lets
|
|
249
|
+
* the ensure loop retry silently in the background — only the delay's
|
|
250
|
+
* FIRST appearance, and any later escalation the user was never told
|
|
251
|
+
* about, needs to interrupt them (#1536 review F6): a fresh 30s cooldown
|
|
252
|
+
* re-notifies the same as an escalated 300s one once it's a genuinely new
|
|
253
|
+
* number, but two consecutive 30s cooldowns (identical, nothing new to
|
|
254
|
+
* say) do not. Absence means "not yet notified this streak."
|
|
255
|
+
*
|
|
256
|
+
* Session-scoped (#1536 review F5): cleared whenever the degradation
|
|
257
|
+
* ledger's own generation moves past `grammarNotificationsLedgerGen`,
|
|
258
|
+
* mirroring `trustBlockedGrammarNotifications` below — a lazy
|
|
259
|
+
* compare-at-use-time against a monotonic counter, not a listener. Tied to
|
|
260
|
+
* the LEDGER's generation (bumped by `resetDegradationLedger`, which
|
|
261
|
+
* `handleSessionStart` calls first thing) rather than trust, since this
|
|
262
|
+
* is a session boundary, not a trust transition.
|
|
263
|
+
*/
|
|
264
|
+
grammarLastNotifiedDelayMs = new Map();
|
|
265
|
+
grammarNotificationsLedgerGen = getDegradationLedgerGeneration();
|
|
71
266
|
trustBlockedGrammarNotifications = new Set();
|
|
72
267
|
trustNotificationsGeneration = getProjectTrustGeneration();
|
|
73
268
|
// biome-ignore lint/suspicious/noExplicitAny: Optional dependency loaded dynamically
|
|
@@ -180,6 +375,19 @@ export class TreeSitterClient {
|
|
|
180
375
|
...this.parserCounters,
|
|
181
376
|
};
|
|
182
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* Grow the tree cache to span a full-project scan's working set (#1715).
|
|
380
|
+
* The interactive default (50 entries) can't hold a mid/large project's
|
|
381
|
+
* file count, so a second scan re-parses everything the first scan's LRU
|
|
382
|
+
* evicted — live dogfood evidence showed every miss on a second 110-file
|
|
383
|
+
* scan was a `capacityMisses` one. Bounded by
|
|
384
|
+
* `TREE_CACHE_SCAN_CAPACITY_CEILING` (see `deriveScanTreeCacheCapacity`'s
|
|
385
|
+
* heap-cost note) and monotonic — a smaller scan never shrinks a capacity
|
|
386
|
+
* an earlier, larger one already grew.
|
|
387
|
+
*/
|
|
388
|
+
ensureTreeCacheCapacity(fileCount) {
|
|
389
|
+
this.treeCache.setMaxSize(deriveScanTreeCacheCapacity(fileCount, this.treeCache.getMaxSize()));
|
|
390
|
+
}
|
|
183
391
|
async withParseCacheMeasurement(work, onComplete) {
|
|
184
392
|
const measurement = {
|
|
185
393
|
...createTreeCacheCounters(),
|
|
@@ -283,10 +491,33 @@ export class TreeSitterClient {
|
|
|
283
491
|
return undefined;
|
|
284
492
|
}
|
|
285
493
|
}
|
|
494
|
+
_vendoredGrammarsDir;
|
|
495
|
+
/**
|
|
496
|
+
* The committed `vendor/grammars` dir, if it exists. Cached only on a hit,
|
|
497
|
+
* mirroring `bundledGrammarsDir`.
|
|
498
|
+
*/
|
|
499
|
+
vendoredGrammarsDir() {
|
|
500
|
+
if (this._vendoredGrammarsDir)
|
|
501
|
+
return this._vendoredGrammarsDir;
|
|
502
|
+
try {
|
|
503
|
+
const dir = vendoredGrammarsDir();
|
|
504
|
+
if (fs.existsSync(dir))
|
|
505
|
+
this._vendoredGrammarsDir = dir;
|
|
506
|
+
return this._vendoredGrammarsDir;
|
|
507
|
+
}
|
|
508
|
+
catch {
|
|
509
|
+
return undefined;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
286
512
|
/**
|
|
287
513
|
* All directories that may hold grammar wasms, in precedence order: the
|
|
288
|
-
*
|
|
289
|
-
* grammars dir (the lazy-fetch
|
|
514
|
+
* committed vendor dir, the bundled core dir, the resolved
|
|
515
|
+
* `this.grammarsDir`, and the web-tree-sitter grammars dir (the lazy-fetch
|
|
516
|
+
* write target). Deduped.
|
|
517
|
+
*
|
|
518
|
+
* `vendor/grammars` comes first because it is the ONLY source for a grammar
|
|
519
|
+
* we build ourselves (`VENDORED_GRAMMARS`) — nothing downloads into the
|
|
520
|
+
* later dirs for it, so a miss here is a miss everywhere.
|
|
290
521
|
*/
|
|
291
522
|
grammarSourceDirs() {
|
|
292
523
|
const dirs = [];
|
|
@@ -294,20 +525,184 @@ export class TreeSitterClient {
|
|
|
294
525
|
if (d && !dirs.includes(d))
|
|
295
526
|
dirs.push(d);
|
|
296
527
|
};
|
|
528
|
+
push(this.vendoredGrammarsDir());
|
|
297
529
|
push(this.bundledGrammarsDir());
|
|
298
530
|
push(this.grammarsDir || undefined);
|
|
299
531
|
push(this.resolveWebTreeSitterAsset("grammars"));
|
|
300
532
|
return dirs;
|
|
301
533
|
}
|
|
302
|
-
/**
|
|
534
|
+
/**
|
|
535
|
+
* Absolute path to `grammarFile` across all source dirs, else undefined.
|
|
536
|
+
*
|
|
537
|
+
* Existence is not enough (#1548): a file whose first four bytes aren't the
|
|
538
|
+
* wasm preamble is not a grammar, and returning it would report the language
|
|
539
|
+
* as available while every `Language.load` fails — the permanent-poisoning
|
|
540
|
+
* shape this issue is about. #1548 stops NEW poisoned files from being
|
|
541
|
+
* written, but a file poisoned before that shipped is already on disk, so
|
|
542
|
+
* the resolve path has to reject it too: a rejected candidate is treated as
|
|
543
|
+
* absent, which lets `ensureGrammar` re-download over it.
|
|
544
|
+
*/
|
|
303
545
|
resolveGrammarFile(grammarFile) {
|
|
304
546
|
for (const dir of this.grammarSourceDirs()) {
|
|
305
547
|
const candidate = path.join(dir, grammarFile);
|
|
306
|
-
|
|
307
|
-
|
|
548
|
+
const stamp = grammarFileStamp(candidate);
|
|
549
|
+
if (!stamp)
|
|
550
|
+
continue;
|
|
551
|
+
// #1564: a file that passed the preamble check but failed
|
|
552
|
+
// `Language.load` at this exact stamp is known-bad — treat it as
|
|
553
|
+
// absent so `ensureGrammar` re-fetches over it, same as the
|
|
554
|
+
// non-wasm-magic case below. A stamp mismatch (the file changed —
|
|
555
|
+
// re-downloaded) falls through to re-examine it fresh.
|
|
556
|
+
if (this.decodeFailedGrammarPaths.get(candidate) === stamp)
|
|
557
|
+
continue;
|
|
558
|
+
if (this.verifiedGrammarPaths.get(candidate) === stamp) {
|
|
559
|
+
if (this.isGrammarVersionCurrent(candidate, grammarFile, stamp)) {
|
|
560
|
+
return candidate;
|
|
561
|
+
}
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
if (fileHasWasmMagic(candidate)) {
|
|
565
|
+
this.verifiedGrammarPaths.set(candidate, stamp);
|
|
566
|
+
if (this.isGrammarVersionCurrent(candidate, grammarFile, stamp)) {
|
|
567
|
+
return candidate;
|
|
568
|
+
}
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
this.verifiedGrammarPaths.delete(candidate);
|
|
572
|
+
this.reportPoisonedGrammarFile(candidate, grammarFile);
|
|
308
573
|
}
|
|
309
574
|
return undefined;
|
|
310
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* Clear the once-per-session grammar report gates when the degradation
|
|
578
|
+
* ledger's own generation has moved (#1536 review F5). Lazy
|
|
579
|
+
* compare-at-use-time against a monotonic counter, bumped by
|
|
580
|
+
* `resetDegradationLedger`, which `handleSessionStart` calls first thing —
|
|
581
|
+
* no listener, no retention. Every gate keyed to a SESSION rather than to
|
|
582
|
+
* the process lives here, so a new one cannot forget to re-arm: a gate that
|
|
583
|
+
* outlives the ledger it guards silently swallows the record it was only
|
|
584
|
+
* ever meant to de-duplicate (#1560 review F1).
|
|
585
|
+
*/
|
|
586
|
+
refreshGrammarSessionLatches() {
|
|
587
|
+
const ledgerGen = getDegradationLedgerGeneration();
|
|
588
|
+
if (ledgerGen === this.grammarNotificationsLedgerGen)
|
|
589
|
+
return;
|
|
590
|
+
this.grammarNotificationsLedgerGen = ledgerGen;
|
|
591
|
+
this.grammarLastNotifiedDelayMs.clear();
|
|
592
|
+
this.poisonedGrammarPaths.clear();
|
|
593
|
+
this.staleReportedGrammarPaths.clear();
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Log + record a grammar file on disk that isn't a wasm module, once per
|
|
597
|
+
* path per session. No user notification here: the caller goes on to attempt
|
|
598
|
+
* a re-download, and `recordGrammarFailure` is what interrupts the user if
|
|
599
|
+
* that also fails. A silent recovery should stay silent.
|
|
600
|
+
*/
|
|
601
|
+
reportPoisonedGrammarFile(candidate, grammarFile) {
|
|
602
|
+
// Per SESSION, not per process (#1560 review F1). The file is still
|
|
603
|
+
// poisoned after a session boundary and still degrades the language, so
|
|
604
|
+
// the new session's ledger has to carry the entry — a client-lifetime
|
|
605
|
+
// gate would leave `resetDegradationLedger` with a permanently empty
|
|
606
|
+
// grammar-blocked group and no record of why the language is degraded.
|
|
607
|
+
this.refreshGrammarSessionLatches();
|
|
608
|
+
if (this.poisonedGrammarPaths.has(candidate))
|
|
609
|
+
return;
|
|
610
|
+
this.poisonedGrammarPaths.add(candidate);
|
|
611
|
+
logTreeSitterDiagnostic({
|
|
612
|
+
subsystem: "tree-sitter-client",
|
|
613
|
+
level: "warn",
|
|
614
|
+
message: `ignoring ${candidate}: the file is not a wasm module (missing the \\0asm preamble) — ` +
|
|
615
|
+
`most likely a captive-portal or proxy page written by an earlier download (#1548). ` +
|
|
616
|
+
`pi-lens will treat the grammar as missing and try to fetch it again.`,
|
|
617
|
+
metadata: { grammarFile, path: candidate, outcome: "not-wasm" },
|
|
618
|
+
});
|
|
619
|
+
incrementDegradationCount({
|
|
620
|
+
kind: "grammar-blocked",
|
|
621
|
+
subject: grammarFile,
|
|
622
|
+
reason: "on-disk grammar file is not a wasm module — ignored, re-fetching",
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Is the cached grammar at `candidate` (whose wasm preamble already passed)
|
|
627
|
+
* still current against the pinned sha256 manifest (#1760)?
|
|
628
|
+
*
|
|
629
|
+
* A grammar downloaded once is never revisited when this repo bumps
|
|
630
|
+
* `TREE_SITTER_WASMS_VERSION` or changes a `SOURCE_OVERRIDES` entry — the
|
|
631
|
+
* cached file's name carries no version, so a stale build serves forever.
|
|
632
|
+
* The check is cheap and NEVER touches the network: it compares the
|
|
633
|
+
* on-disk sha256 (hashed once per `size:mtimeMs` stamp, memoized exactly
|
|
634
|
+
* like `verifiedGrammarPaths` above so a hot parse loop never re-hashes an
|
|
635
|
+
* unchanged file) against `pinnedGrammarHash`, which itself is a pure
|
|
636
|
+
* in-memory manifest lookup. A mismatch also catches on-disk corruption,
|
|
637
|
+
* which nothing detected before this.
|
|
638
|
+
*
|
|
639
|
+
* Vendored grammars (`VENDORED_GRAMMARS`) are skipped: they have no CDN
|
|
640
|
+
* pin to drift against, and their bytes are already guarded by the
|
|
641
|
+
* separate build-provenance check (`scripts/check-grammar-provenance.mjs`).
|
|
642
|
+
* No pinned hash for this filename (manifest missing, or a grammar added
|
|
643
|
+
* before `--write-manifest` was re-run) is treated as "can't verify" and
|
|
644
|
+
* trusted, the same fallback `downloadGrammarDetailed`'s own hash check
|
|
645
|
+
* already uses — never as a forced, unbounded refetch loop.
|
|
646
|
+
*/
|
|
647
|
+
isGrammarVersionCurrent(candidate, grammarFile, stamp) {
|
|
648
|
+
if (isVendoredGrammar(grammarFile))
|
|
649
|
+
return true;
|
|
650
|
+
if (this.staleGrammarVersionAt.get(candidate) === stamp) {
|
|
651
|
+
// #1801 review F1: the hash-verdict memo above is process-lifetime by
|
|
652
|
+
// design (re-hashing an unchanged file on every resolve is the exact
|
|
653
|
+
// cost this whole check exists to avoid), so an early return here must
|
|
654
|
+
// NOT skip the report — a still-stale grammar has to keep showing up
|
|
655
|
+
// in every NEW session's ledger, not just the session that first
|
|
656
|
+
// discovered the mismatch.
|
|
657
|
+
this.reportStaleGrammarVersion(candidate, grammarFile);
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
if (this.verifiedGrammarVersionAt.get(candidate) === stamp)
|
|
661
|
+
return true;
|
|
662
|
+
const pinnedHash = pinnedGrammarHash(grammarFile);
|
|
663
|
+
if (!pinnedHash) {
|
|
664
|
+
this.verifiedGrammarVersionAt.set(candidate, stamp);
|
|
665
|
+
return true;
|
|
666
|
+
}
|
|
667
|
+
const actualHash = grammarFileSha256(candidate);
|
|
668
|
+
if (actualHash === pinnedHash) {
|
|
669
|
+
this.verifiedGrammarVersionAt.set(candidate, stamp);
|
|
670
|
+
return true;
|
|
671
|
+
}
|
|
672
|
+
this.verifiedGrammarVersionAt.delete(candidate);
|
|
673
|
+
this.staleGrammarVersionAt.set(candidate, stamp);
|
|
674
|
+
this.reportStaleGrammarVersion(candidate, grammarFile);
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Log + record a version-stale (or corrupt) cached grammar, once per path
|
|
679
|
+
* per SESSION (#1801 review F1). Called from BOTH the memoized-stale path
|
|
680
|
+
* and the fresh-mismatch path in `isGrammarVersionCurrent`, so the report
|
|
681
|
+
* gate is independent of `staleGrammarVersionAt`'s own process-lifetime
|
|
682
|
+
* memo — a grammar that is STILL stale in a later session must still emit
|
|
683
|
+
* a fresh record into that session's ledger, exactly like
|
|
684
|
+
* `reportPoisonedGrammarFile` already does for the non-wasm case.
|
|
685
|
+
*/
|
|
686
|
+
reportStaleGrammarVersion(candidate, grammarFile) {
|
|
687
|
+
this.refreshGrammarSessionLatches();
|
|
688
|
+
if (this.staleReportedGrammarPaths.has(candidate))
|
|
689
|
+
return;
|
|
690
|
+
this.staleReportedGrammarPaths.add(candidate);
|
|
691
|
+
logTreeSitterDiagnostic({
|
|
692
|
+
subsystem: "tree-sitter-client",
|
|
693
|
+
level: "warn",
|
|
694
|
+
message: `ignoring ${candidate}: its sha256 no longer matches the pinned grammar ` +
|
|
695
|
+
`manifest — the pinned tree-sitter-wasms version (or a SOURCE_OVERRIDES entry) ` +
|
|
696
|
+
`moved since this file was downloaded. pi-lens will treat the grammar as ` +
|
|
697
|
+
`missing and re-fetch the current build (#1760).`,
|
|
698
|
+
metadata: { grammarFile, path: candidate, outcome: "stale-version" },
|
|
699
|
+
});
|
|
700
|
+
incrementDegradationCount({
|
|
701
|
+
kind: "grammar-blocked",
|
|
702
|
+
subject: grammarFile,
|
|
703
|
+
reason: "cached grammar no longer matches the pinned manifest hash — ignored, re-fetching",
|
|
704
|
+
});
|
|
705
|
+
}
|
|
311
706
|
/** Find tree-sitter grammar directory */
|
|
312
707
|
findGrammarsDir() {
|
|
313
708
|
const grammarsDir = this.resolveWebTreeSitterAsset("grammars");
|
|
@@ -392,65 +787,284 @@ export class TreeSitterClient {
|
|
|
392
787
|
const inflight = this.grammarEnsurePromises.get(grammarFile);
|
|
393
788
|
if (inflight)
|
|
394
789
|
return inflight;
|
|
790
|
+
// A failed download only stays refused for its cooldown window (#1536):
|
|
791
|
+
// past that, fall through and retry rather than remembering an offline
|
|
792
|
+
// moment for the life of the process. No download is attempted while the
|
|
793
|
+
// cooldown is live, so a hard-down CDN is not re-hit on every parse. A
|
|
794
|
+
// DURABLE failure (grammarBlockDurable below) sets this to +Infinity,
|
|
795
|
+
// which reads the same as "still cooling down" here — it just never
|
|
796
|
+
// expires, matching the issue's "a genuinely unavailable grammar may
|
|
797
|
+
// still latch" allowance.
|
|
798
|
+
const retryAt = this.grammarRetryAtMs.get(grammarFile);
|
|
799
|
+
if (retryAt !== undefined && Date.now() < retryAt) {
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
395
802
|
const task = (async () => {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
: this.grammarsWriteDir();
|
|
399
|
-
if (!dir)
|
|
400
|
-
return false;
|
|
401
|
-
// Reuse the shared single-file downloader (same CDN/source as the
|
|
402
|
-
// postinstall) — see clients/grammar-source.ts.
|
|
403
|
-
const ok = await downloadGrammar(dir, grammarFile);
|
|
404
|
-
if (ok) {
|
|
405
|
-
if (!this.grammarsDir)
|
|
406
|
-
this.grammarsDir = dir;
|
|
407
|
-
logTreeSitterDiagnostic({
|
|
408
|
-
subsystem: "tree-sitter-client",
|
|
409
|
-
level: "warn",
|
|
410
|
-
message: `fetched missing tree-sitter grammar ${grammarFile} at runtime (install scripts were skipped by the package manager)`,
|
|
411
|
-
metadata: { grammarFile, outcome: "fetched" },
|
|
412
|
-
});
|
|
803
|
+
try {
|
|
804
|
+
return await this.fetchGrammar(grammarFile);
|
|
413
805
|
}
|
|
414
|
-
|
|
415
|
-
//
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
metadata: { grammarFile, outcome: "unavailable" },
|
|
427
|
-
});
|
|
428
|
-
recordDegradation({
|
|
429
|
-
kind: "grammar-blocked",
|
|
430
|
-
subject: grammarFile,
|
|
431
|
-
reason: "runtime grammar download failed",
|
|
432
|
-
});
|
|
433
|
-
// HUMAN-audience: an offline grammar fetch silently degrades this
|
|
434
|
-
// language's features, so it reaches the user through the HOST's
|
|
435
|
-
// render path (#1333) rather than a raw terminal write.
|
|
436
|
-
notifyUserDegradation(`pi-lens: ${unavailable}`);
|
|
806
|
+
catch (err) {
|
|
807
|
+
// Defensive (#1548): nothing in `fetchGrammar` throws today —
|
|
808
|
+
// `downloadGrammarDetailed` catches internally. But a thrown error
|
|
809
|
+
// here used to skip the whole `else` arm, so NO cooldown was armed
|
|
810
|
+
// and the rejection escaped through `task.finally` — the exact
|
|
811
|
+
// pre-#1536 shape (no cooldown, possible unhandled rejection). Funnel
|
|
812
|
+
// it into the same retryable failure path instead, so a future
|
|
813
|
+
// refactor that lets an exception out cannot regress to it.
|
|
814
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
815
|
+
this.recordGrammarFailure(grammarFile, `The runtime grammar fetch threw an unexpected error (${message}).`,
|
|
816
|
+
/* retryable */ true);
|
|
817
|
+
return false;
|
|
437
818
|
}
|
|
438
|
-
return ok;
|
|
439
819
|
})();
|
|
440
820
|
this.grammarEnsurePromises.set(grammarFile, task);
|
|
821
|
+
// Evict on settle regardless of outcome (#1536): a resolved TRUE is
|
|
822
|
+
// superseded by resolveGrammarFile() finding the file on disk on the
|
|
823
|
+
// next call, and a resolved FALSE must not be remembered as the
|
|
824
|
+
// permanent verdict — only concurrent callers during the SAME in-flight
|
|
825
|
+
// attempt should ever observe this promise.
|
|
826
|
+
//
|
|
827
|
+
// The trailing `.catch` is UNFALSIFIABLE scaffolding, not verified
|
|
828
|
+
// defence (#1560 review F3): `finally` returns a DERIVED promise that
|
|
829
|
+
// rejects whenever `task` does, but the task's own try/catch above means
|
|
830
|
+
// it cannot reject, so no test can make this handler fire and removing it
|
|
831
|
+
// changes nothing today. It is here for the day the try/catch above stops
|
|
832
|
+
// being exhaustive — or the eviction callback itself throws — because at
|
|
833
|
+
// that point the fork becomes an unhandled rejection nobody is awaiting.
|
|
834
|
+
void task
|
|
835
|
+
.finally(() => {
|
|
836
|
+
if (this.grammarEnsurePromises.get(grammarFile) === task) {
|
|
837
|
+
this.grammarEnsurePromises.delete(grammarFile);
|
|
838
|
+
}
|
|
839
|
+
})
|
|
840
|
+
.catch(() => {
|
|
841
|
+
/* see above: the task's own catch is the real handler */
|
|
842
|
+
});
|
|
441
843
|
return task;
|
|
442
844
|
}
|
|
845
|
+
/**
|
|
846
|
+
* One grammar-fetch attempt: locate a writable dir, download, and record the
|
|
847
|
+
* outcome. Split out of `ensureGrammar` so the whole body sits under one
|
|
848
|
+
* try/catch that funnels any throw into `recordGrammarFailure` (#1548).
|
|
849
|
+
*/
|
|
850
|
+
async fetchGrammar(grammarFile) {
|
|
851
|
+
// BEFORE resolving a write directory: a vendored grammar is never
|
|
852
|
+
// downloaded, so the write dir has no bearing on its verdict. Resolving
|
|
853
|
+
// first let the "no writable grammars directory" branch below answer
|
|
854
|
+
// RETRYABLE for a missing vendored wasm on any host where
|
|
855
|
+
// web-tree-sitter isn't locatable — reporting a packaging fault as a
|
|
856
|
+
// transient download failure, and arming a cooldown for a fetch that can
|
|
857
|
+
// never happen.
|
|
858
|
+
if (isVendoredGrammar(grammarFile)) {
|
|
859
|
+
const { reason } = vendoredGrammarRefusal(grammarFile);
|
|
860
|
+
this.recordGrammarFailure(grammarFile, reason ?? `${grammarFile} is missing from vendor/grammars/.`,
|
|
861
|
+
/* retryable */ false);
|
|
862
|
+
return false;
|
|
863
|
+
}
|
|
864
|
+
const dir = this.grammarsDir && fs.existsSync(this.grammarsDir)
|
|
865
|
+
? this.grammarsDir
|
|
866
|
+
: this.grammarsWriteDir();
|
|
867
|
+
if (!dir) {
|
|
868
|
+
// No writable grammars directory could be located (e.g. pi compiled
|
|
869
|
+
// this module to a temp dir and web-tree-sitter isn't resolvable
|
|
870
|
+
// from there yet). This is an environment condition, not a CDN
|
|
871
|
+
// verdict — #1536 review F2: it must get the same cooldown +
|
|
872
|
+
// notification treatment as a download failure, not a silent
|
|
873
|
+
// unmemoized `false` that re-does the same failing resolution
|
|
874
|
+
// sweep on every single demand.
|
|
875
|
+
this.recordGrammarFailure(grammarFile, "No writable grammars directory could be located for the runtime fetch.",
|
|
876
|
+
/* retryable */ true);
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
// Reuse the shared single-file downloader (same CDN/source as the
|
|
880
|
+
// postinstall) — see clients/grammar-source.ts.
|
|
881
|
+
const { ok, retryable, reason } = await downloadGrammarDetailed(dir, grammarFile);
|
|
882
|
+
if (ok) {
|
|
883
|
+
if (!this.grammarsDir)
|
|
884
|
+
this.grammarsDir = dir;
|
|
885
|
+
this.grammarRetryAtMs.delete(grammarFile);
|
|
886
|
+
this.grammarFailureAttempts.delete(grammarFile);
|
|
887
|
+
this.grammarLastNotifiedDelayMs.delete(grammarFile);
|
|
888
|
+
logTreeSitterDiagnostic({
|
|
889
|
+
subsystem: "tree-sitter-client",
|
|
890
|
+
level: "warn",
|
|
891
|
+
message: `fetched missing tree-sitter grammar ${grammarFile} at runtime (install scripts were skipped by the package manager)`,
|
|
892
|
+
metadata: { grammarFile, outcome: "fetched" },
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
// `retryable` distinguishes a durable CDN verdict (404/410 — a
|
|
897
|
+
// retry hits the same answer) from everything else (offline, DNS,
|
|
898
|
+
// a down CDN, a 5xx, a timeout), which says nothing durable about
|
|
899
|
+
// the grammar (#1536 review F4). Only the retryable case gets the
|
|
900
|
+
// bounded cooldown; a durable failure keeps the pre-#1536 latched
|
|
901
|
+
// behavior, matching the issue's own allowance.
|
|
902
|
+
//
|
|
903
|
+
// `reason`, when the downloader supplies one, names the actual failure
|
|
904
|
+
// shape (#1548: "returned an HTML page, not a wasm module") instead of
|
|
905
|
+
// the generic guess — the difference between a diagnosable log line
|
|
906
|
+
// and one that sends the user to inspect their package manager while a
|
|
907
|
+
// captive portal is the real cause.
|
|
908
|
+
this.recordGrammarFailure(grammarFile, reason ??
|
|
909
|
+
"The package manager skipped install scripts and the runtime download failed.", retryable);
|
|
910
|
+
}
|
|
911
|
+
return ok;
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Record one failed grammar-ensure attempt: arm (or extend) the retry
|
|
915
|
+
* cooldown, record the degradation, and notify the user at most once per
|
|
916
|
+
* session per DISTINCT retry delay (#1536 review F1/F2/F4/F5/F6). Shared
|
|
917
|
+
* by the "no writable directory" and "download failed" arms of
|
|
918
|
+
* `ensureGrammar` — both are failures of the SAME ensure attempt, just
|
|
919
|
+
* with a different point of failure.
|
|
920
|
+
*/
|
|
921
|
+
recordGrammarFailure(grammarFile, detail, retryable) {
|
|
922
|
+
const attempts = (this.grammarFailureAttempts.get(grammarFile) ?? 0) + 1;
|
|
923
|
+
this.grammarFailureAttempts.set(grammarFile, attempts);
|
|
924
|
+
// A durable verdict (404/410) never expires -- matches the issue's "a
|
|
925
|
+
// genuinely unavailable grammar may still latch" allowance. Everything
|
|
926
|
+
// else gets the bounded exponential cooldown.
|
|
927
|
+
const retryDelayMs = retryable
|
|
928
|
+
? transientRetryDelayMs(attempts, "probe-timeout")
|
|
929
|
+
: undefined;
|
|
930
|
+
this.grammarRetryAtMs.set(grammarFile, retryDelayMs === undefined
|
|
931
|
+
? Number.POSITIVE_INFINITY
|
|
932
|
+
: Date.now() + retryDelayMs);
|
|
933
|
+
const unavailable = retryable
|
|
934
|
+
? `tree-sitter grammar '${grammarFile}' is unavailable — symbol search, ` +
|
|
935
|
+
`module reports and structural rules for this language will be degraded. ` +
|
|
936
|
+
`${detail} pi-lens will retry automatically in ${Math.round(retryDelayMs / 1000)}s; ` +
|
|
937
|
+
`if the problem persists, allow the package manager's build scripts ` +
|
|
938
|
+
`(pnpm approve-builds / bun trustedDependencies) or restore network access.`
|
|
939
|
+
: `tree-sitter grammar '${grammarFile}' is unavailable — symbol search, ` +
|
|
940
|
+
`module reports and structural rules for this language will be degraded. ` +
|
|
941
|
+
`${detail} The grammar source reports it does not exist (not a network problem), so this will not ` +
|
|
942
|
+
`resolve on retry. Fix: reinstall with a manager that runs postinstall, allow its build scripts ` +
|
|
943
|
+
`(pnpm approve-builds / bun trustedDependencies), or restore network access.`;
|
|
944
|
+
logTreeSitterDiagnostic({
|
|
945
|
+
subsystem: "tree-sitter-client",
|
|
946
|
+
message: unavailable,
|
|
947
|
+
metadata: {
|
|
948
|
+
grammarFile,
|
|
949
|
+
outcome: "unavailable",
|
|
950
|
+
retryable,
|
|
951
|
+
retryDelayMs,
|
|
952
|
+
attempts,
|
|
953
|
+
},
|
|
954
|
+
});
|
|
955
|
+
// #1536 review F1: incrementDegradationCount keeps ONE ring-buffer slot
|
|
956
|
+
// per subject (bumping its count in place) instead of recordDegradation's
|
|
957
|
+
// one-new-entry-per-call — a grammar retrying every cooldown window would
|
|
958
|
+
// otherwise flood the shared "grammar-blocked" kind's 20-slot ring and
|
|
959
|
+
// evict unrelated entries (e.g. a different grammar's V8-crash block
|
|
960
|
+
// reason from loadLanguage/grammarBlockReason) after ~20 cycles.
|
|
961
|
+
incrementDegradationCount({
|
|
962
|
+
kind: "grammar-blocked",
|
|
963
|
+
subject: grammarFile,
|
|
964
|
+
// #1548: carry `detail` into the RETRYABLE reason too. All retryable
|
|
965
|
+
// failures used to share one string, so the ledger could not tell an
|
|
966
|
+
// offline laptop from a captive portal serving HTML — the two need
|
|
967
|
+
// different user actions, and the ledger is what a bug report shows.
|
|
968
|
+
reason: retryable
|
|
969
|
+
? `runtime grammar download failed — retryable (${detail.trim()})`
|
|
970
|
+
: `runtime grammar download failed — durable (${detail.trim()})`,
|
|
971
|
+
});
|
|
972
|
+
this.refreshGrammarSessionLatches();
|
|
973
|
+
// HUMAN-audience: an offline grammar fetch silently degrades this
|
|
974
|
+
// language's features, so it reaches the user through the HOST's render
|
|
975
|
+
// path (#1333) rather than a raw terminal write. Notify once per DISTINCT
|
|
976
|
+
// retry delay this session (#1536 review F6) -- a fresh failure streak or
|
|
977
|
+
// an escalated backoff is new information worth surfacing; an unchanged
|
|
978
|
+
// repeat during the same cooldown tier is not.
|
|
979
|
+
const notifyKey = retryDelayMs ?? -1;
|
|
980
|
+
if (this.grammarLastNotifiedDelayMs.get(grammarFile) !== notifyKey) {
|
|
981
|
+
this.grammarLastNotifiedDelayMs.set(grammarFile, notifyKey);
|
|
982
|
+
notifyUserDegradation(`pi-lens: ${unavailable}`);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Record a `Language.load` failure on a file `resolveGrammarFile` had just
|
|
987
|
+
* vouched for (#1564) — the diagnosis gap #1548 left open: the preamble
|
|
988
|
+
* check proves the first four bytes are `\0asm`, not that the whole body
|
|
989
|
+
* decodes. A decode error here (a truncated download, on-disk corruption)
|
|
990
|
+
* is evidence the FILE is bad, so it gets the exact same treatment as an
|
|
991
|
+
* ensure-time download failure: invalidate the resolve memo so the next
|
|
992
|
+
* demand re-fetches instead of reusing the same broken file forever, and
|
|
993
|
+
* reuse `recordGrammarFailure`'s cooldown/degradation/notification
|
|
994
|
+
* machinery rather than hand-rolling a parallel one — a persistently
|
|
995
|
+
* truncated CDN response would otherwise re-download and re-fail on every
|
|
996
|
+
* single parse.
|
|
997
|
+
*
|
|
998
|
+
* A DURABLE decode failure (the bytes are complete and correct, but ABI-
|
|
999
|
+
* incompatible with the installed `web-tree-sitter` — a version drift, not
|
|
1000
|
+
* a truncation) is classified `retryable` here too, same as everything
|
|
1001
|
+
* else this method sees: there is no way to tell "truncated" from "wrong
|
|
1002
|
+
* ABI" from the error message alone, so it re-downloads and re-fails once
|
|
1003
|
+
* per cooldown tier instead of latching forever. That is bounded by the
|
|
1004
|
+
* cooldown ladder (this never re-fetches faster than #1536's backoff) and
|
|
1005
|
+
* by `BLOCKED_GRAMMARS` for the narrower case that's fatal to the process
|
|
1006
|
+
* rather than just wrong (`grammar-source.ts`). The redownload only helps
|
|
1007
|
+
* if a fresh fetch could actually be a DIFFERENT (compatible) build,
|
|
1008
|
+
* which is exactly what the `grammar-source.test.ts` version/lock sync
|
|
1009
|
+
* guard (`lock.version === TREE_SITTER_WASMS_VERSION`) exists to keep
|
|
1010
|
+
* true — without it, a version bump with a stale lock would make every
|
|
1011
|
+
* redownload land the identical bytes and spin the ladder against a file
|
|
1012
|
+
* that can never pass.
|
|
1013
|
+
*/
|
|
1014
|
+
recordGrammarLoadFailure(grammarPath, grammarFile, err) {
|
|
1015
|
+
const stamp = grammarFileStamp(grammarPath);
|
|
1016
|
+
this.verifiedGrammarPaths.delete(grammarPath);
|
|
1017
|
+
if (stamp)
|
|
1018
|
+
this.decodeFailedGrammarPaths.set(grammarPath, stamp);
|
|
1019
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1020
|
+
logTreeSitterDiagnostic({
|
|
1021
|
+
subsystem: "tree-sitter-client",
|
|
1022
|
+
level: "warn",
|
|
1023
|
+
message: `Language.load failed for ${grammarPath} even though the wasm preamble check ` +
|
|
1024
|
+
`passed (${message}) — most likely a truncated download (#1564). Treating the ` +
|
|
1025
|
+
`grammar as missing so the next demand re-fetches it.`,
|
|
1026
|
+
metadata: { grammarFile, path: grammarPath, outcome: "load-failed" },
|
|
1027
|
+
});
|
|
1028
|
+
this.recordGrammarFailure(grammarFile, `Language.load failed on a resolved grammar file (${message}).`,
|
|
1029
|
+
/* retryable */ true);
|
|
1030
|
+
}
|
|
443
1031
|
/** Initialize tree-sitter WASM runtime */
|
|
444
1032
|
async init() {
|
|
445
1033
|
if (this.wasmAborted)
|
|
446
1034
|
return false;
|
|
447
1035
|
if (this.initialized)
|
|
448
1036
|
return true;
|
|
1037
|
+
if (this.webTreeSitterLoadFailed)
|
|
1038
|
+
return false;
|
|
449
1039
|
if (this.initPromise)
|
|
450
1040
|
return this.initPromise;
|
|
451
1041
|
this.initPromise = (async () => {
|
|
452
1042
|
try {
|
|
453
|
-
|
|
1043
|
+
let mod;
|
|
1044
|
+
try {
|
|
1045
|
+
mod = await loadWebTreeSitter();
|
|
1046
|
+
}
|
|
1047
|
+
catch (err) {
|
|
1048
|
+
// See webTreeSitterLoadFailed's doc: an EVALUATION-shaped
|
|
1049
|
+
// rejection is a dead retry, so latch instead of leaving
|
|
1050
|
+
// initPromise clearable (the finally below would otherwise let
|
|
1051
|
+
// the very next parse call re-import the same doomed URL). A
|
|
1052
|
+
// RESOLUTION-shaped rejection is left alone — initPromise still
|
|
1053
|
+
// clears below, so the next init() call retries for real.
|
|
1054
|
+
const classification = classifyWebTreeSitterLoadFailure(err);
|
|
1055
|
+
if (classification === "evaluation") {
|
|
1056
|
+
this.webTreeSitterLoadFailed = true;
|
|
1057
|
+
}
|
|
1058
|
+
recordDegradationOnce({
|
|
1059
|
+
kind: "web-tree-sitter-load-failed",
|
|
1060
|
+
subject: "web-tree-sitter",
|
|
1061
|
+
reason: (err instanceof Error ? err.message : String(err)) +
|
|
1062
|
+
(classification === "resolution"
|
|
1063
|
+
? " (resolution failure — retryable)"
|
|
1064
|
+
: " (evaluation failure — latched for the session)"),
|
|
1065
|
+
});
|
|
1066
|
+
throw err;
|
|
1067
|
+
}
|
|
454
1068
|
// biome-ignore lint/suspicious/noExplicitAny: web-tree-sitter module shape varies (Parser direct / default-wrapped)
|
|
455
1069
|
const anyMod = mod;
|
|
456
1070
|
const ParserClass = anyMod.Parser || anyMod.default || anyMod;
|
|
@@ -492,6 +1106,17 @@ export class TreeSitterClient {
|
|
|
492
1106
|
})();
|
|
493
1107
|
return this.initPromise;
|
|
494
1108
|
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Re-arm the web-tree-sitter load latch for a new session (#1592 review
|
|
1111
|
+
* round 2 F2). Deliberately does NOT touch `wasmAborted` or `initialized`
|
|
1112
|
+
* — see `webTreeSitterLoadFailed`'s doc for why the abort case stays
|
|
1113
|
+
* process-lifetime, and a successful init has nothing to re-arm. Called
|
|
1114
|
+
* from `resetTreeSitterClientLoadState()` (tree-sitter-shared.ts), wired
|
|
1115
|
+
* into `resetDispatchBaselines()` beside `resetAstGrepNapiLoadState()`.
|
|
1116
|
+
*/
|
|
1117
|
+
resetLoadStateForSession() {
|
|
1118
|
+
this.webTreeSitterLoadFailed = false;
|
|
1119
|
+
}
|
|
495
1120
|
/** Load language grammar */
|
|
496
1121
|
async loadLanguage(languageId) {
|
|
497
1122
|
if (this.wasmAborted)
|
|
@@ -552,7 +1177,16 @@ export class TreeSitterClient {
|
|
|
552
1177
|
return language;
|
|
553
1178
|
}
|
|
554
1179
|
catch (err) {
|
|
555
|
-
|
|
1180
|
+
// An uncatchable-shaped WASM abort poisons the whole process (#402) —
|
|
1181
|
+
// `reportWasmAbort` already records + handles it. Everything else
|
|
1182
|
+
// (#1564) is a DECODE failure on a file `resolveGrammarFile` just
|
|
1183
|
+
// vouched for — e.g. "Code section extends past end of the module" on
|
|
1184
|
+
// a truncated-but-magic-valid download. That is evidence the file
|
|
1185
|
+
// itself is bad, not the runtime, so record it and stop vouching for
|
|
1186
|
+
// this exact file so the next demand can re-fetch it.
|
|
1187
|
+
if (!this.reportWasmAbort(err)) {
|
|
1188
|
+
this.recordGrammarLoadFailure(grammarPath, grammarFile, err);
|
|
1189
|
+
}
|
|
556
1190
|
this.dbg(`Language load error: ${err}`);
|
|
557
1191
|
return null;
|
|
558
1192
|
}
|
|
@@ -2350,20 +2984,18 @@ export class TreeSitterClient {
|
|
|
2350
2984
|
return !hasExceptionSpec;
|
|
2351
2985
|
}
|
|
2352
2986
|
case "eq_mod_fn": {
|
|
2353
|
-
//
|
|
2354
|
-
//
|
|
2355
|
-
// query captures @MOD, @FN but the predicates aren't enforced
|
|
2356
|
-
// (see evaluatePredicates in clients/tree-sitter-client.ts).
|
|
2987
|
+
// Belt-and-braces re-check of the #eq? predicates that
|
|
2988
|
+
// evaluatePredicates already enforces (clients/tree-sitter-client.ts).
|
|
2357
2989
|
// This filter re-applies the #eq? checks at post_filter time.
|
|
2358
2990
|
const mod = captures.MOD?.text ?? "";
|
|
2359
2991
|
const fn = captures.FN?.text ?? "";
|
|
2360
2992
|
return mod === "threading" && fn === "Thread";
|
|
2361
2993
|
}
|
|
2362
2994
|
case "regex_first_arg_identifier": {
|
|
2363
|
-
//
|
|
2364
|
-
//
|
|
2365
|
-
// This post_filter re-applies
|
|
2366
|
-
// the first-argument check:
|
|
2995
|
+
// Belt-and-braces re-check of the #eq?/#match? predicates that
|
|
2996
|
+
// evaluatePredicates already enforces, plus the first-argument
|
|
2997
|
+
// check that predicates can't express. This post_filter re-applies
|
|
2998
|
+
// both predicate checks AND the first-argument check:
|
|
2367
2999
|
// 1. MOD must be "re" (would-be #eq? @MOD "re")
|
|
2368
3000
|
// 2. FUNC must match the regex method pattern (#match? @FUNC ...)
|
|
2369
3001
|
// 3. First arg must be an identifier (dynamic pattern)
|
|
@@ -3135,12 +3767,83 @@ export class TreeSitterClient {
|
|
|
3135
3767
|
});
|
|
3136
3768
|
}
|
|
3137
3769
|
/**
|
|
3138
|
-
*
|
|
3139
|
-
*
|
|
3140
|
-
*
|
|
3770
|
+
* Latch so the "textPredicates is malformed" diagnostic log line fires once
|
|
3771
|
+
* per `TreeSitterClient` instance for the life of the process (this client is
|
|
3772
|
+
* a process-wide singleton in production — see clients/tree-sitter-shared.ts),
|
|
3773
|
+
* not once per call. This gates ONLY the log line: process-wide log volume is
|
|
3774
|
+
* the concern here, not the user-facing degradation record below, which must
|
|
3775
|
+
* re-arm every session (see recordDegradationOnce) — including for a Query
|
|
3776
|
+
* object the LRU query cache (queryCache/queryBatchCache above) kept alive
|
|
3777
|
+
* across a session boundary, since the cache is evicted, never cleared.
|
|
3778
|
+
*/
|
|
3779
|
+
textPredicatesInvalidLogged = false;
|
|
3780
|
+
/**
|
|
3781
|
+
* Typed accessor for `query.textPredicates`. If the property is missing or the
|
|
3782
|
+
* wrong shape — e.g. a future web-tree-sitter upgrade renames or removes it —
|
|
3783
|
+
* `query.textPredicates?.[i] ?? []` would silently mean "no predicates for this
|
|
3784
|
+
* pattern," passing every match through unfiltered. That's a silent fail-open on
|
|
3785
|
+
* #match?/#eq? predicates that rules rely on for correctness, and because this
|
|
3786
|
+
* runs for every match of every query, it would zero out ALL structural matches
|
|
3787
|
+
* across every language while pi-lens reports "no issues found." Fail loud (log
|
|
3788
|
+
* once per client instance, record a degradation so it reaches the user) and
|
|
3789
|
+
* closed (report the query has no usable predicates) instead of guessing.
|
|
3790
|
+
*
|
|
3791
|
+
* No WeakSet short-circuit for already-known-bad queries here (#1523 review
|
|
3792
|
+
* R1): `Array.isArray` is O(1), so memoizing it buys nothing, and a
|
|
3793
|
+
* process-lifetime WeakSet would make the SAME cached Query object (the query
|
|
3794
|
+
* cache is LRU-evicted, never cleared) skip straight past
|
|
3795
|
+
* `recordDegradationOnce` on every call after the first — which is exactly
|
|
3796
|
+
* what made the degradation record fail to re-arm across a session boundary
|
|
3797
|
+
* for a query the cache kept warm. `recordDegradationOnce` itself is called
|
|
3798
|
+
* unconditionally on every invalid check; the ledger's own once-per-kind/
|
|
3799
|
+
* subject dedupe (cleared by resetDegradationLedger, which handleSessionStart
|
|
3800
|
+
* calls first thing — clients/runtime-session.ts) is the single source of
|
|
3801
|
+
* truth for "how often does this actually get recorded."
|
|
3802
|
+
*/
|
|
3803
|
+
// biome-ignore lint/suspicious/noExplicitAny: web-tree-sitter Query instances
|
|
3804
|
+
hasValidTextPredicates(query) {
|
|
3805
|
+
const valid = Array.isArray(query?.textPredicates);
|
|
3806
|
+
if (!valid) {
|
|
3807
|
+
if (!this.textPredicatesInvalidLogged) {
|
|
3808
|
+
this.textPredicatesInvalidLogged = true;
|
|
3809
|
+
logTreeSitterDiagnostic({
|
|
3810
|
+
subsystem: "tree-sitter-client",
|
|
3811
|
+
message: "web-tree-sitter Query.textPredicates is missing or not an array — " +
|
|
3812
|
+
"#match?/#eq? predicates cannot be evaluated. Failing CLOSED: matches " +
|
|
3813
|
+
"for this query are dropped rather than reported unfiltered.",
|
|
3814
|
+
metadata: { textPredicatesType: typeof query?.textPredicates },
|
|
3815
|
+
});
|
|
3816
|
+
}
|
|
3817
|
+
// User-facing signal (#1523 review F1). Called unconditionally on every
|
|
3818
|
+
// invalid check (not gated by a client-lifetime latch) so it re-arms
|
|
3819
|
+
// every session, per the R1 fix above.
|
|
3820
|
+
recordDegradationOnce({
|
|
3821
|
+
kind: "query-predicates-invalid",
|
|
3822
|
+
subject: "web-tree-sitter",
|
|
3823
|
+
reason: "Query.textPredicates is missing or not an array — #match?/#eq? " +
|
|
3824
|
+
"predicates cannot be evaluated; structural matches relying on them " +
|
|
3825
|
+
"are dropped fail-closed",
|
|
3826
|
+
});
|
|
3827
|
+
}
|
|
3828
|
+
return valid;
|
|
3829
|
+
}
|
|
3830
|
+
/**
|
|
3831
|
+
* Evaluate text predicates (#match?, #eq?) for a query match. web-tree-sitter
|
|
3832
|
+
* 0.25's `Query.matches()` DOES apply these predicates itself (probed on the
|
|
3833
|
+
* shipped grammars — see clients/tree-sitter-symbol-extractor.ts), so this is a
|
|
3834
|
+
* belt-and-braces re-filter, not a workaround for missing enforcement. It also
|
|
3835
|
+
* doubles as the fail-closed guard (#1523): if `query.textPredicates` is ever
|
|
3836
|
+
* missing or malformed, this drops the match instead of assuming it already
|
|
3837
|
+
* passed upstream.
|
|
3141
3838
|
*/
|
|
3142
3839
|
// biome-ignore lint/suspicious/noExplicitAny: web-tree-sitter types
|
|
3143
3840
|
evaluatePredicates(query, match) {
|
|
3841
|
+
if (!this.hasValidTextPredicates(query))
|
|
3842
|
+
return false;
|
|
3843
|
+
// `?.` guards a post-validation removal of the property (e.g. a later
|
|
3844
|
+
// mutation strips it after the first successful check) so the read can't
|
|
3845
|
+
// throw here — an uncaught throw would be swallowed by the outer try/catch
|
|
3846
|
+
// in searchFileWithQuery and silently zero out matches for the whole file.
|
|
3144
3847
|
const predicates = query.textPredicates?.[match.patternIndex] ?? [];
|
|
3145
3848
|
return predicates.every((fn) => fn(match.captures));
|
|
3146
3849
|
}
|
|
@@ -3161,7 +3864,9 @@ export class TreeSitterClient {
|
|
|
3161
3864
|
captures[capture.name] = capture.node;
|
|
3162
3865
|
}
|
|
3163
3866
|
}
|
|
3164
|
-
//
|
|
3867
|
+
// Belt-and-braces re-check of #match?/#eq? predicates (web-tree-sitter
|
|
3868
|
+
// 0.25's Query.matches() already applies them) plus the fail-closed
|
|
3869
|
+
// guard for a malformed query.textPredicates (see evaluatePredicates).
|
|
3165
3870
|
if (!this.evaluatePredicates(query, match)) {
|
|
3166
3871
|
continue;
|
|
3167
3872
|
}
|