@herbertgao/pi-extensions 2026.8.6 → 2026.8.8
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 +8 -6
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
- 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 +320 -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 +79 -17
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +27 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +66 -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 +5 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +174 -100
- 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 +5 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +10 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +10 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/tool-schema.md +11 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +5 -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 +140 -97
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +63 -28
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/projections.ts +22 -15
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/state-reducer.ts +11 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/tool/response-envelope.ts +11 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/tool/types.ts +11 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/multi-select-view.ts +31 -17
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-block-renderer.ts +15 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/wrapping-select.ts +15 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +86 -38
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +76 -9
- 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-plugins/fast-mode/dist/index.mjs +44 -1856
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/package.json +4 -6
- package/node_modules/pi-lens/CHANGELOG.md +999 -0
- package/node_modules/pi-lens/README.md +117 -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 +381 -13
- 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 +129 -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 +206 -0
- package/node_modules/pi-lens/dist/clients/cache-observability.js +353 -76
- 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 +84 -14
- package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +155 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +25 -3
- package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +8 -2
- package/node_modules/pi-lens/dist/clients/deps/typebox.js +8 -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/detekt.js +9 -6
- 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/hadolint.js +9 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +8 -6
- 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 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +25 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +20 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +44 -11
- 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 +17 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +32 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +47 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +14 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +127 -28
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +12 -7
- 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 +188 -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 +21 -10
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -2
- 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 +316 -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 +87 -8
- 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 +9 -2
- 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 +128 -8
- package/node_modules/pi-lens/dist/clients/lsp/client.js +1660 -164
- 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 +2423 -151
- 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 +22 -1
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +114 -23
- 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 +23 -9
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
- package/node_modules/pi-lens/dist/clients/project-report.js +62 -27
- 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 +8 -3
- package/node_modules/pi-lens/dist/clients/read-guard.js +253 -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/builder.js +194 -31
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +52 -4
- package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +21 -0
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +6 -3
- 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 +352 -45
- package/node_modules/pi-lens/dist/clients/runtime-session.js +373 -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 +620 -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/session-event-guard.js +184 -0
- 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/source-filter.js +6 -2
- package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
- package/node_modules/pi-lens/dist/clients/startup-scan.js +5 -2
- 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-logger.js +1 -0
- 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 +61407 -48577
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +476 -57
- 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 +27 -10
- 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/scripts/install-selftest.mjs +99 -11
- package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +75 -0
- package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +285 -0
- package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +233 -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 +37 -0
- package/node_modules/pi-web-access/README.md +32 -13
- 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/chrome-cookies.ts +86 -15
- 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/data-uri-sanitize.ts +406 -0
- package/node_modules/pi-web-access/exa.ts +18 -7
- package/node_modules/pi-web-access/extract.ts +40 -5
- package/node_modules/pi-web-access/firecrawl.ts +18 -2
- package/node_modules/pi-web-access/gemini-search.ts +16 -4
- package/node_modules/pi-web-access/github-extract.ts +65 -36
- package/node_modules/pi-web-access/index.ts +60 -61
- package/node_modules/pi-web-access/openai-search.ts +17 -6
- 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/pdf-extract.ts +18 -5
- 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/ssrf-protection.ts +12 -2
- 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 +14 -14
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for `scripts/warm-loader-cache.mjs` (#1926).
|
|
3
|
+
*
|
|
4
|
+
* pi loads an extension through jiti (`createJiti(..., { moduleCache: false,
|
|
5
|
+
* alias: … })` in `@earendil-works/pi-coding-agent`'s
|
|
6
|
+
* `dist/core/extensions/loader.js`). jiti transforms the entry with Babel and
|
|
7
|
+
* stores the result in a filesystem cache. pi-lens's `dist/index.js` is a ~4MB
|
|
8
|
+
* esbuild bundle, so that transform costs seconds: the first session after a
|
|
9
|
+
* `git:` install or update measured 4847ms of `module import`, against a 138ms
|
|
10
|
+
* steady state once the cache is warm (#1926 field report).
|
|
11
|
+
*
|
|
12
|
+
* The fix is to pay that transform at install time instead of in the first
|
|
13
|
+
* interactive session. `prepare` already builds `dist/`, so the last step of
|
|
14
|
+
* the chain runs the same transform through the same jiti, writing the same
|
|
15
|
+
* cache file pi will later read.
|
|
16
|
+
*
|
|
17
|
+
* WHY THE WARM PRODUCES A HIT FOR THE REAL LOADER
|
|
18
|
+
*
|
|
19
|
+
* jiti keys a cache entry on the transformed file, not on the jiti instance
|
|
20
|
+
* (see `getCache` in `jiti/dist/jiti.cjs`):
|
|
21
|
+
*
|
|
22
|
+
* <basename(dirname(file))>-<basename(file) up to the first dot>
|
|
23
|
+
* + "+map" if sourceMaps + ".i" if interopDefault
|
|
24
|
+
* + "." + md5(file).slice(0, 8)
|
|
25
|
+
* + ".mjs" for an async import, ".cjs" otherwise
|
|
26
|
+
*
|
|
27
|
+
* and it validates the stored body against a trailing
|
|
28
|
+
* ` /* v<TRANSFORM_VERSION>-<md5(source).slice(0,16)> *\/` marker. So the warm
|
|
29
|
+
* has to agree with pi on three things only: the absolute path of the entry,
|
|
30
|
+
* the cache directory, and the transform itself. Everything else — the alias
|
|
31
|
+
* map, `moduleCache`, which package the jiti instance was created from — is
|
|
32
|
+
* outside the key AND outside the transform. Measured, not assumed: running
|
|
33
|
+
* this warm over the dogfood install's `dist/index.js` produced a cache file
|
|
34
|
+
* byte-identical to the one pi had written, both with pi's alias map and with
|
|
35
|
+
* no alias map at all.
|
|
36
|
+
*
|
|
37
|
+
* The marker is also the safety net. If pi ships a jiti whose transform version
|
|
38
|
+
* or Babel output differs, pi recomputes the marker, does not match, and simply
|
|
39
|
+
* re-transforms. A mismatched warm costs the install a few seconds and buys
|
|
40
|
+
* nothing; it can never hand pi the wrong code.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import { createHash, getFips } from "node:crypto";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Mirror of jiti's `utils_hash`: md5 truncated to `length` hex characters, or
|
|
47
|
+
* sha256 where FIPS mode forbids md5. jiti makes the same substitution, so the
|
|
48
|
+
* two agree on every machine.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} text
|
|
51
|
+
* @param {number} [length]
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
export function jitiHash(text, length = 8) {
|
|
55
|
+
let fips = false;
|
|
56
|
+
try {
|
|
57
|
+
fips = Boolean(getFips?.());
|
|
58
|
+
} catch {
|
|
59
|
+
fips = false;
|
|
60
|
+
}
|
|
61
|
+
return createHash(fips ? "sha256" : "md5")
|
|
62
|
+
.update(text)
|
|
63
|
+
.digest("hex")
|
|
64
|
+
.slice(0, length);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The cache file jiti writes for an async import of `entry`.
|
|
69
|
+
*
|
|
70
|
+
* Mirror of jiti's `getCache` naming:
|
|
71
|
+
* `<basename(dirname(file))>-<basename up to the first dot>.<hash(file)>.mjs`.
|
|
72
|
+
* jiti hashes the POSIX-normalised absolute path, so the separators are
|
|
73
|
+
* converted here. The drive-letter case is left exactly as the caller resolved
|
|
74
|
+
* it, because jiti does not touch it either. Verified against pi's own cache
|
|
75
|
+
* directory: the md5 of the dogfood install's forward-slashed entry path,
|
|
76
|
+
* truncated to 8 characters, is the `db18768f` in the
|
|
77
|
+
* `dist-index.db18768f.mjs` pi had already written.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} entry absolute path to the extension entry
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
export function expectedCacheFileName(entry) {
|
|
83
|
+
const posix = entry.split("\\").join("/");
|
|
84
|
+
const segments = posix.split("/");
|
|
85
|
+
const base = segments.pop() ?? posix;
|
|
86
|
+
const parent = segments.pop() ?? "";
|
|
87
|
+
const dot = base.indexOf(".");
|
|
88
|
+
const stem = dot <= 0 ? base : base.slice(0, dot);
|
|
89
|
+
return `${parent}-${stem}.${jitiHash(posix)}.mjs`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Did the warm actually leave a cache entry pi can use?
|
|
94
|
+
*
|
|
95
|
+
* Elapsed time is not evidence. jiti can return from an import having written
|
|
96
|
+
* nothing, and every such path exits 0:
|
|
97
|
+
*
|
|
98
|
+
* - the cache directory is not writable. jiti's `prepareCacheDir` catches
|
|
99
|
+
* that, sets `fsCache` to false, and transforms in memory from then on.
|
|
100
|
+
* - the entry imported natively and nothing needed transforming. For an
|
|
101
|
+
* async-imported ESM `.js` file jiti's `evalModule` attempts a native
|
|
102
|
+
* import UNCONDITIONALLY, and only falls back to the transform when that
|
|
103
|
+
* import rejects. So the cache exists at all because native import fails in
|
|
104
|
+
* a real `--omit=dev` install, where the host-provided specifiers do not
|
|
105
|
+
* resolve. That is the same reason pi's own load transforms the bundle,
|
|
106
|
+
* which is why the warm and pi agree — but it is a property of the
|
|
107
|
+
* environment, not of this script.
|
|
108
|
+
* - a cache entry exists but does not match the source. jiti validates the
|
|
109
|
+
* body against a trailing version-and-source-hash marker, so a stale or
|
|
110
|
+
* truncated file is a miss for pi and the session pays the transform.
|
|
111
|
+
*
|
|
112
|
+
* Checking the file is what turns "the import returned" into "the warm's own
|
|
113
|
+
* jiti cached this entry, with these versions". It is not a promise about pi:
|
|
114
|
+
* pi-side drift shows up as a version delta between this record and pi's, which
|
|
115
|
+
* is a thing to read from the log, not something an install can detect.
|
|
116
|
+
* An absent entry always warrants a look, never a shrug.
|
|
117
|
+
*
|
|
118
|
+
* @param {object} args
|
|
119
|
+
* @param {string} args.cacheDir
|
|
120
|
+
* @param {string} args.fileName
|
|
121
|
+
* @param {string} args.source contents of the entry, for the marker check
|
|
122
|
+
* @param {object} args.fsDeps
|
|
123
|
+
* @param {(p: string) => boolean} args.fsDeps.existsSync
|
|
124
|
+
* @param {(p: string) => string} args.fsDeps.readFileSync
|
|
125
|
+
* @param {(p: string) => boolean} args.fsDeps.isWritable
|
|
126
|
+
* @returns {{ok: boolean, reason: string | null, transformVersion: string | null}}
|
|
127
|
+
*/
|
|
128
|
+
export function verifyCacheEntry({ cacheDir, fileName, source, fsDeps }) {
|
|
129
|
+
const file = `${cacheDir}/${fileName}`;
|
|
130
|
+
if (!fsDeps.existsSync(file)) {
|
|
131
|
+
if (!fsDeps.isWritable(cacheDir)) {
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
reason: `cache directory is not writable: ${cacheDir}`,
|
|
135
|
+
transformVersion: null,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// Deliberately generic. Several routes end here — a native import that
|
|
139
|
+
// needed no transform, a transform that threw before the write — and this
|
|
140
|
+
// check cannot tell them apart, so it does not name one.
|
|
141
|
+
return {
|
|
142
|
+
ok: false,
|
|
143
|
+
reason: `no cache entry was written: ${fileName}`,
|
|
144
|
+
transformVersion: null,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
let body;
|
|
148
|
+
try {
|
|
149
|
+
body = fsDeps.readFileSync(file);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
152
|
+
return {
|
|
153
|
+
ok: false,
|
|
154
|
+
reason: `cache entry unreadable: ${message}`,
|
|
155
|
+
transformVersion: null,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const marker = /\/\* v([^-\s]+)-([0-9a-f]+) \*\/\s*$/.exec(body);
|
|
159
|
+
if (!marker) {
|
|
160
|
+
return {
|
|
161
|
+
ok: false,
|
|
162
|
+
reason: "cache entry has no jiti version marker — pi will re-transform",
|
|
163
|
+
transformVersion: null,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (marker[2] !== jitiHash(source, marker[2].length)) {
|
|
167
|
+
return {
|
|
168
|
+
ok: false,
|
|
169
|
+
reason:
|
|
170
|
+
"cache entry does not match the built entry — pi will re-transform",
|
|
171
|
+
transformVersion: marker[1],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return { ok: true, reason: null, transformVersion: marker[1] };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Mirror of jiti's `prepareCacheDir` fallback branch.
|
|
179
|
+
*
|
|
180
|
+
* jiti picks `<dir of the file that created the jiti instance>/node_modules/
|
|
181
|
+
* .cache/jiti` when that `node_modules` exists, and `<tmpdir>/jiti` otherwise.
|
|
182
|
+
* pi creates its instance from `dist/core/extensions/loader.js` inside the
|
|
183
|
+
* installed `@earendil-works/pi-coding-agent`, which has no `node_modules`
|
|
184
|
+
* sibling, so pi always lands on the tmpdir branch. The warm therefore passes
|
|
185
|
+
* `<tmpdir>/jiti` EXPLICITLY rather than letting jiti derive a directory from
|
|
186
|
+
* this repo's layout: `scripts/` has no `node_modules` sibling today, but a
|
|
187
|
+
* script moved one level up would silently start filling a private cache that
|
|
188
|
+
* pi never reads.
|
|
189
|
+
*
|
|
190
|
+
* The TMPDIR dance below is jiti's, kept verbatim so the two agree on machines
|
|
191
|
+
* where TMPDIR is set to the current directory.
|
|
192
|
+
*
|
|
193
|
+
* @param {object} deps
|
|
194
|
+
* @param {() => string} deps.tmpdir
|
|
195
|
+
* @param {Record<string, string | undefined>} deps.env
|
|
196
|
+
* @param {() => string} deps.cwd
|
|
197
|
+
* @param {(a: string, b: string) => string} deps.join
|
|
198
|
+
* @returns {string}
|
|
199
|
+
*/
|
|
200
|
+
export function resolveJitiCacheDir({ tmpdir, env, cwd, join }) {
|
|
201
|
+
let dir = tmpdir();
|
|
202
|
+
if (env.TMPDIR && dir === cwd() && !env.JITI_RESPECT_TMPDIR_ENV) {
|
|
203
|
+
const saved = env.TMPDIR;
|
|
204
|
+
delete env.TMPDIR;
|
|
205
|
+
dir = tmpdir();
|
|
206
|
+
env.TMPDIR = saved;
|
|
207
|
+
}
|
|
208
|
+
return join(dir, "jiti");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Alias every host-provided specifier to a path that cannot exist.
|
|
213
|
+
*
|
|
214
|
+
* The warm only needs the TRANSFORM of `dist/index.js`; jiti writes the cache
|
|
215
|
+
* entry before it evaluates the module. Evaluation then walks the bundle's
|
|
216
|
+
* external imports, none of which resolve outside pi. Pointing the
|
|
217
|
+
* host-provided ones at a stub makes that walk stop at the first one instead of
|
|
218
|
+
* resolving unrelated packages first, so the warm does the least work it can
|
|
219
|
+
* and always ends the same way. Derived from the shared host-provided list so
|
|
220
|
+
* it cannot drift from what `bundle-dist.mjs` keeps external.
|
|
221
|
+
*
|
|
222
|
+
* @param {readonly string[]} hostProvidedPackages
|
|
223
|
+
* @returns {Record<string, string>}
|
|
224
|
+
*/
|
|
225
|
+
export function buildStubAliases(hostProvidedPackages) {
|
|
226
|
+
/** @type {Record<string, string>} */
|
|
227
|
+
const alias = {};
|
|
228
|
+
for (const name of hostProvidedPackages) {
|
|
229
|
+
alias[name] = STUB_TARGET;
|
|
230
|
+
}
|
|
231
|
+
return alias;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** A path no filesystem resolves, so aliased imports fail immediately. */
|
|
235
|
+
export const STUB_TARGET = "/__pi-lens-warm-cache-stub__";
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Decide whether the warm can run, and say why not when it cannot.
|
|
239
|
+
*
|
|
240
|
+
* Every reason is a skip, never a failure: `prepare` also builds `dist/` and
|
|
241
|
+
* downloads grammars, and those steps MUST fail loudly. Cache warming is an
|
|
242
|
+
* optimisation, so it runs last and reports rather than throws — the same
|
|
243
|
+
* posture as `scripts/setup-git-hooks.mjs` (#1804).
|
|
244
|
+
*
|
|
245
|
+
* @param {object} state
|
|
246
|
+
* @param {Record<string, string | undefined>} state.env
|
|
247
|
+
* @param {boolean} state.distEntryExists
|
|
248
|
+
* @param {boolean} state.jitiResolvable
|
|
249
|
+
* @returns {string | null} skip reason, or null to proceed
|
|
250
|
+
*/
|
|
251
|
+
export function warmSkipReason({ env, distEntryExists, jitiResolvable }) {
|
|
252
|
+
const optOut = env.PI_LENS_SKIP_WARM_CACHE;
|
|
253
|
+
if (typeof optOut === "string" && optOut.length > 0) {
|
|
254
|
+
return "PI_LENS_SKIP_WARM_CACHE is set";
|
|
255
|
+
}
|
|
256
|
+
if (!distEntryExists) {
|
|
257
|
+
return "dist/index.js is missing — nothing to warm";
|
|
258
|
+
}
|
|
259
|
+
if (!jitiResolvable) {
|
|
260
|
+
// jiti is an optionalDependency, so `--omit=optional` (or a failed
|
|
261
|
+
// optional install) legitimately leaves it absent.
|
|
262
|
+
return "jiti is not installed — install ran without optional dependencies";
|
|
263
|
+
}
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Keep the install log bounded: one line per install, newest last.
|
|
269
|
+
*
|
|
270
|
+
* @param {string[]} existingLines
|
|
271
|
+
* @param {string} line
|
|
272
|
+
* @param {number} [max]
|
|
273
|
+
* @returns {string[]}
|
|
274
|
+
*/
|
|
275
|
+
export function appendBounded(
|
|
276
|
+
existingLines,
|
|
277
|
+
line,
|
|
278
|
+
max = INSTALL_LOG_MAX_LINES,
|
|
279
|
+
) {
|
|
280
|
+
const lines = [...existingLines.filter((l) => l.trim().length > 0), line];
|
|
281
|
+
return lines.slice(Math.max(0, lines.length - max));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Cap for `~/.pi-lens/install.log`. */
|
|
285
|
+
export const INSTALL_LOG_MAX_LINES = 100;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/warm-loader-cache.mjs (#1926)
|
|
3
|
+
//
|
|
4
|
+
// Pay pi's jiti transform of dist/index.js at install time, so no interactive
|
|
5
|
+
// session pays it.
|
|
6
|
+
//
|
|
7
|
+
// pi loads an extension with jiti, which Babel-transforms the entry and caches
|
|
8
|
+
// the result under <tmpdir>/jiti. pi-lens's entry is a ~4MB esbuild bundle, so
|
|
9
|
+
// the transform is the dominant startup cost exactly once per build: the first
|
|
10
|
+
// session after a `git:` install or update measured 4847ms of `module import`,
|
|
11
|
+
// against 138ms once the cache is warm. `prepare` has just built that bundle,
|
|
12
|
+
// so this script transforms it through the same jiti and writes the same cache
|
|
13
|
+
// entry pi will read.
|
|
14
|
+
//
|
|
15
|
+
// See scripts/lib/warm-loader-cache.mjs for why the entry this writes is the
|
|
16
|
+
// entry pi reads — the cache key, the transform-version marker that makes a
|
|
17
|
+
// mismatch a miss instead of a corruption, and the measurement that proved the
|
|
18
|
+
// output byte-identical to pi's own.
|
|
19
|
+
//
|
|
20
|
+
// POSTURE: best-effort, last in the chain, always exit 0. `prepare` also runs
|
|
21
|
+
// `build:dist` and `download-grammars`, which consumers depend on and which
|
|
22
|
+
// MUST fail loudly. A cache warm is an optimisation and must never share their
|
|
23
|
+
// failure path — the same split `scripts/setup-git-hooks.mjs` makes (#1804).
|
|
24
|
+
import * as fs from "node:fs";
|
|
25
|
+
import { createRequire } from "node:module";
|
|
26
|
+
import * as os from "node:os";
|
|
27
|
+
import * as path from "node:path";
|
|
28
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
29
|
+
import { HOST_PROVIDED_PACKAGES } from "./lib/host-provided-deps.mjs";
|
|
30
|
+
import {
|
|
31
|
+
appendBounded,
|
|
32
|
+
buildStubAliases,
|
|
33
|
+
expectedCacheFileName,
|
|
34
|
+
resolveJitiCacheDir,
|
|
35
|
+
verifyCacheEntry,
|
|
36
|
+
warmSkipReason,
|
|
37
|
+
} from "./lib/warm-loader-cache.mjs";
|
|
38
|
+
|
|
39
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
40
|
+
const require = createRequire(import.meta.url);
|
|
41
|
+
|
|
42
|
+
function log(message) {
|
|
43
|
+
process.stderr.write(`[warm-loader-cache] ${message}\n`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Append one JSONL record to ~/.pi-lens/install.log.
|
|
48
|
+
*
|
|
49
|
+
* pi's install output scrolls away, so without this there is no record that the
|
|
50
|
+
* warm ran, was skipped, or failed. One bounded line per install keeps the
|
|
51
|
+
* question answerable after the fact. `PI_LENS_INSTALL_LOG` redirects the file,
|
|
52
|
+
* which is how the tests read the record back.
|
|
53
|
+
*/
|
|
54
|
+
function record(entry) {
|
|
55
|
+
try {
|
|
56
|
+
const override = process.env.PI_LENS_INSTALL_LOG;
|
|
57
|
+
const file =
|
|
58
|
+
typeof override === "string" && override.length > 0
|
|
59
|
+
? override
|
|
60
|
+
: path.join(os.homedir(), ".pi-lens", "install.log");
|
|
61
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
62
|
+
const existing = fs.existsSync(file)
|
|
63
|
+
? fs.readFileSync(file, "utf8").split("\n")
|
|
64
|
+
: [];
|
|
65
|
+
const line = JSON.stringify({
|
|
66
|
+
ts: new Date().toISOString(),
|
|
67
|
+
event: "warm_loader_cache",
|
|
68
|
+
...entry,
|
|
69
|
+
});
|
|
70
|
+
fs.writeFileSync(file, `${appendBounded(existing, line).join("\n")}\n`);
|
|
71
|
+
} catch {
|
|
72
|
+
// The log is diagnostic. Losing it must not change the install outcome.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Entries pi will load, read from the manifest rather than hardcoded. */
|
|
77
|
+
function extensionEntries() {
|
|
78
|
+
try {
|
|
79
|
+
const pkg = JSON.parse(
|
|
80
|
+
fs.readFileSync(path.join(root, "package.json"), "utf8"),
|
|
81
|
+
);
|
|
82
|
+
return (pkg.pi?.extensions ?? []).map((entry) => path.resolve(root, entry));
|
|
83
|
+
} catch {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function loadCreateJiti() {
|
|
89
|
+
// `jiti/static` is the subpath pi's loader imports, and it is export-mapped
|
|
90
|
+
// for `import` only — `require.resolve` reports it as not exported. Resolve
|
|
91
|
+
// it the ESM way, and fall back to the package root for older runtimes.
|
|
92
|
+
for (const specifier of ["jiti/static", "jiti"]) {
|
|
93
|
+
try {
|
|
94
|
+
const url = import.meta.resolve
|
|
95
|
+
? import.meta.resolve(specifier)
|
|
96
|
+
: pathToFileURL(require.resolve(specifier)).href;
|
|
97
|
+
const mod = await import(url);
|
|
98
|
+
if (typeof mod.createJiti === "function") return mod.createJiti;
|
|
99
|
+
} catch {
|
|
100
|
+
// Try the next specifier; absence is a skip, not a failure.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The installed jiti's version, so a host jiti bump — which changes the marker
|
|
108
|
+
* and silently turns every warm into a cold session — is diagnosable from the
|
|
109
|
+
* record rather than from a bisect.
|
|
110
|
+
*/
|
|
111
|
+
function jitiVersion() {
|
|
112
|
+
try {
|
|
113
|
+
const url = import.meta.resolve
|
|
114
|
+
? import.meta.resolve("jiti/package.json")
|
|
115
|
+
: pathToFileURL(require.resolve("jiti/package.json")).href;
|
|
116
|
+
return JSON.parse(fs.readFileSync(fileURLToPath(url), "utf8")).version;
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Real-filesystem probes for `verifyCacheEntry`. */
|
|
123
|
+
const fsDeps = {
|
|
124
|
+
existsSync: (p) => fs.existsSync(p),
|
|
125
|
+
readFileSync: (p) => fs.readFileSync(p, "utf8"),
|
|
126
|
+
isWritable: (p) => {
|
|
127
|
+
try {
|
|
128
|
+
fs.accessSync(p, fs.constants.W_OK);
|
|
129
|
+
return true;
|
|
130
|
+
} catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export async function main() {
|
|
137
|
+
const entries = extensionEntries();
|
|
138
|
+
const createJiti = await loadCreateJiti();
|
|
139
|
+
const skip = warmSkipReason({
|
|
140
|
+
env: process.env,
|
|
141
|
+
distEntryExists:
|
|
142
|
+
entries.length > 0 && entries.every((e) => fs.existsSync(e)),
|
|
143
|
+
jitiResolvable: typeof createJiti === "function",
|
|
144
|
+
});
|
|
145
|
+
if (skip) {
|
|
146
|
+
log(`skipped: ${skip}`);
|
|
147
|
+
record({ status: "skipped", reason: skip });
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const cacheDir = resolveJitiCacheDir({
|
|
152
|
+
tmpdir: os.tmpdir,
|
|
153
|
+
env: process.env,
|
|
154
|
+
cwd: process.cwd,
|
|
155
|
+
join: path.join,
|
|
156
|
+
});
|
|
157
|
+
const alias = buildStubAliases(HOST_PROVIDED_PACKAGES);
|
|
158
|
+
const version = jitiVersion();
|
|
159
|
+
|
|
160
|
+
for (const entry of entries) {
|
|
161
|
+
const started = Date.now();
|
|
162
|
+
const cacheFile = expectedCacheFileName(entry);
|
|
163
|
+
const jiti = createJiti(pathToFileURL(entry).href, {
|
|
164
|
+
// Mirror pi's loader: no in-process module cache, aliases present.
|
|
165
|
+
// Neither affects the transform or the cache key, but staying close to
|
|
166
|
+
// pi's call keeps the two easy to compare.
|
|
167
|
+
moduleCache: false,
|
|
168
|
+
fsCache: cacheDir,
|
|
169
|
+
alias,
|
|
170
|
+
});
|
|
171
|
+
try {
|
|
172
|
+
// jiti writes the cache entry BEFORE it evaluates the module, so the
|
|
173
|
+
// warm is complete even though evaluation then fails on the first
|
|
174
|
+
// host-provided import — those only resolve inside pi.
|
|
175
|
+
await jiti.import(entry, { default: true });
|
|
176
|
+
} catch {
|
|
177
|
+
// Expected. Evaluation is not the goal; the transform is.
|
|
178
|
+
}
|
|
179
|
+
const ms = Date.now() - started;
|
|
180
|
+
const relative = path.relative(root, entry);
|
|
181
|
+
|
|
182
|
+
// The import returning proves nothing. Several exit-0 paths leave no usable
|
|
183
|
+
// entry — an unwritable cache directory, an import that never reached the
|
|
184
|
+
// transform, a stale file that fails jiti's marker check — so read the file
|
|
185
|
+
// back before claiming anything was cached.
|
|
186
|
+
const verdict = verifyCacheEntry({
|
|
187
|
+
cacheDir,
|
|
188
|
+
fileName: cacheFile,
|
|
189
|
+
source: fs.readFileSync(entry, "utf8"),
|
|
190
|
+
fsDeps,
|
|
191
|
+
});
|
|
192
|
+
const common = {
|
|
193
|
+
entry: relative,
|
|
194
|
+
ms,
|
|
195
|
+
cacheDir,
|
|
196
|
+
cacheFile,
|
|
197
|
+
jitiVersion: version,
|
|
198
|
+
};
|
|
199
|
+
if (verdict.ok) {
|
|
200
|
+
log(`warmed ${relative} in ${ms}ms (cache: ${cacheDir}/${cacheFile})`);
|
|
201
|
+
record({
|
|
202
|
+
status: "warmed",
|
|
203
|
+
...common,
|
|
204
|
+
transformVersion: verdict.transformVersion,
|
|
205
|
+
});
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
log(`not cached: ${relative} after ${ms}ms — ${verdict.reason}`);
|
|
209
|
+
record({ status: "not_cached", ...common, reason: verdict.reason });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Run `work`, absorbing any failure. Exported so a test can inject a throwing
|
|
215
|
+
* body and assert the process still exits 0 and still leaves a record.
|
|
216
|
+
*/
|
|
217
|
+
export async function run(work = main) {
|
|
218
|
+
try {
|
|
219
|
+
await work();
|
|
220
|
+
} catch (err) {
|
|
221
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
222
|
+
log(`failed: ${message}`);
|
|
223
|
+
record({ status: "failed", reason: message });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const invokedPath = process.argv[1];
|
|
228
|
+
const invokedDirectly =
|
|
229
|
+
typeof invokedPath === "string" &&
|
|
230
|
+
pathToFileURL(path.resolve(invokedPath)).href === import.meta.url;
|
|
231
|
+
if (invokedDirectly) {
|
|
232
|
+
await run();
|
|
233
|
+
}
|
|
@@ -62,6 +62,8 @@ Use these instead of writing raw YAML:
|
|
|
62
62
|
|
|
63
63
|
`hasKind` and `hasDescendantKind` are mutually exclusive on both tools — combining them errors.
|
|
64
64
|
|
|
65
|
+
⚠ `insideKind` searches ALL ancestors (`stopBy: end`) with no boundary of its own — on a deeply nested file it can escalate past the enclosing function you meant and match against an unrelated outer scope; scope with `paths` or a raw YAML `rule:` with its own `stopBy` boundary if that matters.
|
|
66
|
+
|
|
65
67
|
```
|
|
66
68
|
# console.log only inside functions
|
|
67
69
|
ast_grep_search pattern="console.log($MSG)" lang="typescript" insideKind="function_declaration"
|
|
@@ -13,7 +13,7 @@ Same `id` as a built-in overrides it. Multiple rules per file: separate with `--
|
|
|
13
13
|
```yaml
|
|
14
14
|
id: no-foo-bar
|
|
15
15
|
language: TypeScript # PascalCase — see languages below
|
|
16
|
-
severity: warning # error | warning | info
|
|
16
|
+
severity: warning # error | warning | hint | info — pick by evidence, see below
|
|
17
17
|
message: "Avoid foo.bar() — use baz() instead"
|
|
18
18
|
note: |
|
|
19
19
|
Longer explanation / fix guidance here.
|
|
@@ -21,6 +21,39 @@ rule:
|
|
|
21
21
|
pattern: foo.bar($ARG)
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
**Pick the severity by the evidence behind the rule, not by feel:**
|
|
25
|
+
|
|
26
|
+
- **`error`** — only with a documented zero-false-positive audit in the `note`.
|
|
27
|
+
Only `error` maps to semantic `blocking` and stops a turn.
|
|
28
|
+
- **`warning`** — a real finding with a known, bounded false-positive rate.
|
|
29
|
+
- **`hint` / `info`** — style opinions. They render as advisory text, never
|
|
30
|
+
block, and lose the report budget to warnings when a report is capped.
|
|
31
|
+
|
|
32
|
+
Full policy and the `error`-promotion procedure: AGENTS.md's "Severity policy
|
|
33
|
+
(#1777)" section. Read it before shipping anything above `warning`.
|
|
34
|
+
|
|
35
|
+
## Before promoting to error
|
|
36
|
+
|
|
37
|
+
Three things must all be true, and the rule's `note` must record them:
|
|
38
|
+
|
|
39
|
+
1. **A multi-corpus false-positive census**, not a single-tree count. Run the
|
|
40
|
+
rule over pi-lens's `clients/`/`tests/` plus at least one real external
|
|
41
|
+
codebase of the kind the rule targets, classify every hit, and put the
|
|
42
|
+
table in the note.
|
|
43
|
+
2. **Structural narrowing before exemption.** Suppress a legitimate idiom
|
|
44
|
+
with a relational constraint (`inside`/`has`/`follows`, scoped path
|
|
45
|
+
globs), not with prose telling readers to ignore the hit. Narrow first —
|
|
46
|
+
don't reach for a lower tier as the easy way out. `no-non-null-assertion`
|
|
47
|
+
was demoted to `hint` (`0124608a`), then reverted back to `warning` once
|
|
48
|
+
the same false positives were closed with a structural exclusion instead
|
|
49
|
+
(`b3e1fd79`).
|
|
50
|
+
3. **Self-scan wiring.** Tag the rule `metadata.category: pi-lens-self-scan`
|
|
51
|
+
so `npm run astgrep:self-scan` holds this tree at zero in CI. An `error`
|
|
52
|
+
rule that never runs against pi-lens's own source is an unaudited claim.
|
|
53
|
+
|
|
54
|
+
If the post-narrowing residual is still tens of legitimate hits, stop and
|
|
55
|
+
report the numbers instead of shipping at `error`.
|
|
56
|
+
|
|
24
57
|
## Language values
|
|
25
58
|
|
|
26
59
|
`TypeScript` `JavaScript` `Python` `Go` `Rust` `Java` `C` `Cpp` `CSharp` `Kotlin` `Ruby` `Php`
|
|
@@ -69,6 +102,13 @@ constraints: # metavariable regex constraints work
|
|
|
69
102
|
recursive descendant/ancestor search add `stopBy: end`. This is the #1 migration
|
|
70
103
|
gotcha — see the `has` note in `reference.md`.
|
|
71
104
|
|
|
105
|
+
⚠ **`stopBy: end` alone is not a boundary.** It's a search-depth control — how far
|
|
106
|
+
the walk searches — not a stop condition; the `any:` kind list only decides what
|
|
107
|
+
CAN satisfy the match, not where the walk halts. To scope a relation to the
|
|
108
|
+
nearest enclosing function (or any other boundary), give `stopBy` its own rule.
|
|
109
|
+
See "Scoping to the nearest enclosing X" in `reference.md` (#1794 F1 — this bug
|
|
110
|
+
shipped twice in one window).
|
|
111
|
+
|
|
72
112
|
## YAML quoting — REQUIRED (js-yaml will reject the rule otherwise)
|
|
73
113
|
|
|
74
114
|
The parser is a real YAML parser, so unquoted special chars throw and the rule is
|
|
@@ -104,13 +144,50 @@ The parser is a real YAML parser, so unquoted special chars throw and the rule i
|
|
|
104
144
|
|
|
105
145
|
`reference.md` (same directory) covers: ReDoS-safe regex authoring, node-text
|
|
106
146
|
string-escape quirks, `has`/`inside` `stopBy` defaults (and when to override
|
|
107
|
-
them), the
|
|
108
|
-
|
|
147
|
+
them), scoping a relation to the nearest enclosing node with a `stopBy`
|
|
148
|
+
boundary rule, the `-js` twin dedup behavior (#657), boolean-parameter
|
|
149
|
+
matching across the TS/JS grammars, and precision-over-recall heuristics for
|
|
109
150
|
denylist-shaped rules. Read it when a rule isn't matching (or over-matching)
|
|
110
151
|
the way you expect, or before shipping a `regex`/`has`-heavy rule.
|
|
111
152
|
|
|
153
|
+
## Testing a suppression
|
|
154
|
+
|
|
155
|
+
Every relational suppression (a `has`/`inside`/`not` exclusion for a
|
|
156
|
+
legitimate idiom) needs two fixtures per bound metavariable, not one:
|
|
157
|
+
|
|
158
|
+
1. **A same-binding valid case** — the idiom the exclusion is meant to
|
|
159
|
+
suppress, with the metavariable bound consistently (same receiver, same
|
|
160
|
+
key).
|
|
161
|
+
2. **A mutation-guard invalid case** — the same shape with the binding
|
|
162
|
+
broken: a different key, a different receiver, or a boundary-crossing
|
|
163
|
+
lookalike (the guard lives in the wrong scope). This proves the
|
|
164
|
+
exclusion checks the BINDING, not just the pattern's presence somewhere
|
|
165
|
+
in the file.
|
|
166
|
+
|
|
167
|
+
`rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml` is the model: alongside the
|
|
168
|
+
get-after-has/pop-after-length valid cases, it fixtures a different-key
|
|
169
|
+
guard, a different-map guard, and an outer-function guard that must NOT
|
|
170
|
+
suppress an inner closure — each one red if the corresponding binding check
|
|
171
|
+
is deleted.
|
|
172
|
+
|
|
173
|
+
Corpus silence is not evidence. A valve with zero corpus hits needs
|
|
174
|
+
adversarial fixtures MORE, not less — `redundant-unsafe-function` targets
|
|
175
|
+
Rust, so it has no pi-lens corpus to census against, and its `# Safety`
|
|
176
|
+
valve still shipped an unbounded backward scan that over-suppressed real
|
|
177
|
+
detections, caught only by adversarial fixtures in review (`00284bcc`). A
|
|
178
|
+
documented blind spot (an idiom the rule knowingly can't distinguish from a
|
|
179
|
+
bug) becomes an `invalid`-direction fixture with a comment naming the gap,
|
|
180
|
+
never silence.
|
|
181
|
+
|
|
182
|
+
Regenerate the catalog doc with `npm run docs:rule-catalogs` after adding or
|
|
183
|
+
changing a rule — never hand-edit the generated catalog.
|
|
184
|
+
|
|
112
185
|
## Validating a candidate rule against the REAL engine (not the warm MCP cache)
|
|
113
186
|
|
|
187
|
+
Live-binary discipline (AGENTS.md shape 16) applies here too: verify parsing
|
|
188
|
+
and match behavior against a real `ast-grep` run before you write it into a
|
|
189
|
+
rule note or a test fixture — a hand-written fixture pins a guess, not a fact.
|
|
190
|
+
|
|
114
191
|
```
|
|
115
192
|
|
|
116
193
|
# inspect how a PATTERN parses → find the node kind you actually need
|
|
@@ -92,6 +92,47 @@ grammars, and precision-over-recall heuristics for denylist-shaped rules.
|
|
|
92
92
|
the partition unique; flag only a single quantified atom inside the group).
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
+
## Scoping to the nearest enclosing X (#1794 F1)
|
|
96
|
+
|
|
97
|
+
`stopBy` controls how far the ancestor/descendant walk searches. It is not a
|
|
98
|
+
stop CONDITION. An `any:` kind list inside `inside`/`has` decides which
|
|
99
|
+
nodes the walk is ALLOWED to match — it does not tell the walk where to
|
|
100
|
+
halt. `stopBy: end` alone searches every ancestor up to the file root, so a
|
|
101
|
+
guard several scopes out can suppress a finding inside an unrelated closure.
|
|
102
|
+
|
|
103
|
+
To scope a relation to the nearest enclosing function (or any other
|
|
104
|
+
boundary), give `stopBy` its OWN rule — the same kind list used as the
|
|
105
|
+
match target — so the walk stops at the first satisfying ancestor and can
|
|
106
|
+
never escalate past it:
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
inside:
|
|
110
|
+
stopBy: # the BOUNDARY: halts the walk here
|
|
111
|
+
any:
|
|
112
|
+
- kind: function_declaration
|
|
113
|
+
- kind: method_definition
|
|
114
|
+
- kind: arrow_function
|
|
115
|
+
- kind: function_expression
|
|
116
|
+
any: # the MATCH TARGET: same list, different job
|
|
117
|
+
- kind: function_declaration
|
|
118
|
+
- kind: method_definition
|
|
119
|
+
- kind: arrow_function
|
|
120
|
+
- kind: function_expression
|
|
121
|
+
has:
|
|
122
|
+
stopBy: end
|
|
123
|
+
pattern: $M.has($K)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This is the fix for #1794 F1: an earlier `no-non-null-assertion` exclusion
|
|
127
|
+
used `stopBy: end` with only the `any:` kind list as the match target, so a
|
|
128
|
+
`.has()`/`.length` guard in an OUTER function suppressed a closure's `!`
|
|
129
|
+
several scopes in. The same defect shape — `stopBy: end` mistaken for a
|
|
130
|
+
boundary — shipped twice in the same 2026-08-20 window: once here, and once
|
|
131
|
+
in `redundant-unsafe-function`'s `# Safety` valve (an unbounded backward
|
|
132
|
+
comment scan, fixed in `00284bcc`). See
|
|
133
|
+
`rules/ast-grep-rules/rules/no-non-null-assertion.yml` for the full working
|
|
134
|
+
rule.
|
|
135
|
+
|
|
95
136
|
## Matching things a pattern can't express (#305)
|
|
96
137
|
|
|
97
138
|
```
|
|
Binary file
|