@herbertgao/pi-extensions 2026.9.0 → 2026.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/node_modules/@czottmann/pi-automode/CHANGELOG.md +13 -1
- package/node_modules/@czottmann/pi-automode/README.md +20 -0
- package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +1 -1
- package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +2 -1
- package/node_modules/@czottmann/pi-automode/docs/defaults.md +3 -2
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +38 -2
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +5 -3
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +11 -3
- package/node_modules/@czottmann/pi-automode/package.json +1 -1
- package/node_modules/@narumitw/pi-btw/README.md +57 -141
- package/node_modules/@narumitw/pi-btw/dist/index.ts +466 -41
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
- package/node_modules/@narumitw/pi-btw/docs/workflows.md +74 -0
- package/node_modules/@narumitw/pi-btw/package.json +2 -1
- package/node_modules/@narumitw/pi-btw/src/btw.ts +15 -1
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +32 -2
- package/node_modules/@narumitw/pi-btw/src/keybindings.ts +330 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +136 -19
- package/node_modules/@narumitw/pi-btw/src/settings.ts +32 -0
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +57 -5
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +52 -30
- package/node_modules/@pi-plugins/fast-mode/README.md +5 -3
- package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +20 -55
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
- package/node_modules/pi-lens/CHANGELOG.md +1072 -0
- package/node_modules/pi-lens/README.md +3 -0
- package/node_modules/pi-lens/config/biome/core.jsonc +11 -2
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +13 -1
- package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +2 -9
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +927 -80
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +1 -1
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +13 -4
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +123 -3
- package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +60 -5
- package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +2 -9
- package/node_modules/pi-lens/dist/clients/ast-grep-types.js +2 -0
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +133 -3
- package/node_modules/pi-lens/dist/clients/biome-client.js +9 -2
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +14 -6
- package/node_modules/pi-lens/dist/clients/bootstrap.js +509 -73
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +152 -12
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +1 -1
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +59 -9
- package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +113 -0
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +3 -11
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +31 -4
- package/node_modules/pi-lens/dist/clients/cache-manager.js +105 -6
- package/node_modules/pi-lens/dist/clients/cache-observability.js +40 -19
- package/node_modules/pi-lens/dist/clients/cargo-manifest.js +422 -0
- package/node_modules/pi-lens/dist/clients/cascade-logger.js +2 -9
- package/node_modules/pi-lens/dist/clients/child-unref.js +1 -19
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +13 -3
- package/node_modules/pi-lens/dist/clients/complexity-client.js +16 -5
- package/node_modules/pi-lens/dist/clients/config-core/deny.js +221 -0
- package/node_modules/pi-lens/dist/clients/config-core/index.js +50 -0
- package/node_modules/pi-lens/dist/clients/config-core/merge.js +357 -0
- package/node_modules/pi-lens/dist/clients/config-core/normalize.js +340 -0
- package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +248 -0
- package/node_modules/pi-lens/dist/clients/config-core/provenance.js +164 -0
- package/node_modules/pi-lens/dist/clients/config-core/records.js +218 -0
- package/node_modules/pi-lens/dist/clients/config-core/resolve.js +125 -0
- package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +78 -0
- package/node_modules/pi-lens/dist/clients/config-core/schema.js +165 -0
- package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +281 -0
- package/node_modules/pi-lens/dist/clients/config-locations.js +160 -0
- package/node_modules/pi-lens/dist/clients/config-resolve.js +789 -0
- package/node_modules/pi-lens/dist/clients/config-schema.js +234 -0
- package/node_modules/pi-lens/dist/clients/config-warn.js +407 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +19 -14
- package/node_modules/pi-lens/dist/clients/dead-code-logger.js +2 -6
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +178 -0
- package/node_modules/pi-lens/dist/clients/debug-handles.js +2 -2
- package/node_modules/pi-lens/dist/clients/debug-heap.js +3 -3
- package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +106 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +113 -7
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +8 -19
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +1 -1
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +12 -9
- package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +15 -5
- package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -4
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +162 -44
- package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +27 -8
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -6
- package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -3
- package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +2 -2
- package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +383 -27
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +4 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +1 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +1 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +14 -18
- package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +6 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +7 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +1 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +1 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +1 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +4 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -67
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +194 -80
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +84 -15
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -21
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +2 -20
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +2 -2
- package/node_modules/pi-lens/dist/clients/disposition-logger.js +3 -10
- package/node_modules/pi-lens/dist/clients/effective-config.js +403 -0
- package/node_modules/pi-lens/dist/clients/error-class.js +23 -0
- package/node_modules/pi-lens/dist/clients/event-loop-hold.js +274 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +2 -2
- package/node_modules/pi-lens/dist/clients/extension-log.js +2 -2
- package/node_modules/pi-lens/dist/clients/feature-hints.js +2 -1
- package/node_modules/pi-lens/dist/clients/file-kinds.js +1 -38
- package/node_modules/pi-lens/dist/clients/file-time.js +7 -2
- package/node_modules/pi-lens/dist/clients/file-utils.js +117 -10
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +50 -13
- package/node_modules/pi-lens/dist/clients/format-service.js +6 -2
- package/node_modules/pi-lens/dist/clients/formatters.js +385 -104
- package/node_modules/pi-lens/dist/clients/freshness-cadence.js +17 -0
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +116 -22
- package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -9
- package/node_modules/pi-lens/dist/clients/git-guard.js +6 -1
- package/node_modules/pi-lens/dist/clients/go-client.js +39 -0
- package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +252 -0
- package/node_modules/pi-lens/dist/clients/grammar-source.js +3 -3
- package/node_modules/pi-lens/dist/clients/hashline-anchor.js +424 -0
- package/node_modules/pi-lens/dist/clients/hook-budgets.js +76 -0
- package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +5 -2
- package/node_modules/pi-lens/dist/clients/host-ports.js +1 -1
- package/node_modules/pi-lens/dist/clients/installer/index.js +289 -59
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +84 -18
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +110 -236
- package/node_modules/pi-lens/dist/clients/instance-registry.js +6 -3
- package/node_modules/pi-lens/dist/clients/language-profile.js +0 -9
- package/node_modules/pi-lens/dist/clients/language-registry.js +599 -0
- package/node_modules/pi-lens/dist/clients/latency-logger.js +165 -9
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +34 -1
- package/node_modules/pi-lens/dist/clients/lens-config.js +160 -30
- package/node_modules/pi-lens/dist/clients/lens-engine.js +33 -41
- package/node_modules/pi-lens/dist/clients/lens-events.js +1 -1
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +77 -6
- package/node_modules/pi-lens/dist/clients/lens-map.js +1 -1
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -38
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +3 -1
- package/node_modules/pi-lens/dist/clients/lsp/client.js +172 -33
- package/node_modules/pi-lens/dist/clients/lsp/config.js +484 -158
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +18 -5
- package/node_modules/pi-lens/dist/clients/lsp/index.js +892 -96
- package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/language.js +18 -177
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +5 -18
- package/node_modules/pi-lens/dist/clients/lsp/server.js +284 -147
- package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +116 -23
- package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +2 -7
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -1
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +14 -7
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +6 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +163 -21
- package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +36 -0
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +33 -4
- package/node_modules/pi-lens/dist/clients/mcp/session.js +5 -16
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +8 -3
- package/node_modules/pi-lens/dist/clients/metrics-history.js +28 -107
- package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +3 -5
- package/node_modules/pi-lens/dist/clients/module-report.js +21 -46
- package/node_modules/pi-lens/dist/clients/mutating-tool.js +651 -0
- package/node_modules/pi-lens/dist/clients/mutation-attribution.js +368 -0
- package/node_modules/pi-lens/dist/clients/mutation-bridge.js +240 -0
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +247 -24
- package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +101 -0
- package/node_modules/pi-lens/dist/clients/observed-mutation.js +1215 -0
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +70 -28
- package/node_modules/pi-lens/dist/clients/opengrep-config.js +5 -1
- package/node_modules/pi-lens/dist/clients/package-manager.js +195 -26
- package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +359 -80
- package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +23 -7
- package/node_modules/pi-lens/dist/clients/path-keyed-map.js +21 -2
- package/node_modules/pi-lens/dist/clients/path-utils.js +451 -15
- package/node_modules/pi-lens/dist/clients/performance-report.js +2 -2
- package/node_modules/pi-lens/dist/clients/persist-debounce.js +8 -1
- package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +114 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +79 -18
- package/node_modules/pi-lens/dist/clients/probe-home-state.js +228 -0
- package/node_modules/pi-lens/dist/clients/process-bridge.js +66 -0
- package/node_modules/pi-lens/dist/clients/process-snapshot.js +68 -0
- package/node_modules/pi-lens/dist/clients/project-changes.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +28 -4
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +51 -20
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +438 -122
- package/node_modules/pi-lens/dist/clients/project-scan-policy.js +3 -51
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +52 -24
- package/node_modules/pi-lens/dist/clients/python-environment.js +217 -0
- package/node_modules/pi-lens/dist/clients/python-provenance.js +639 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +6 -1
- package/node_modules/pi-lens/dist/clients/read-bridge.js +13 -21
- package/node_modules/pi-lens/dist/clients/read-expansion.js +9 -46
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +16 -5
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +364 -212
- package/node_modules/pi-lens/dist/clients/read-guard.js +26 -2
- package/node_modules/pi-lens/dist/clients/recent-touches.js +2 -2
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +195 -96
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +155 -114
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +1 -1
- package/node_modules/pi-lens/dist/clients/review-graph/service.js +55 -3
- package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +7 -15
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +10 -74
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +9 -3
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +19 -3
- package/node_modules/pi-lens/dist/clients/runtime-context.js +49 -16
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +25 -6
- package/node_modules/pi-lens/dist/clients/runtime-session.js +184 -32
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +179 -36
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +797 -243
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +1028 -53
- package/node_modules/pi-lens/dist/clients/rust-client.js +31 -0
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +33 -10
- package/node_modules/pi-lens/dist/clients/sanitize.js +7 -12
- package/node_modules/pi-lens/dist/clients/scan-utils.js +1 -56
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +1 -3
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +3 -0
- package/node_modules/pi-lens/dist/clients/session-lifecycle.js +1 -1
- package/node_modules/pi-lens/dist/clients/session-start-observability.js +79 -0
- package/node_modules/pi-lens/dist/clients/session-summary.js +0 -24
- package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +12 -3
- package/node_modules/pi-lens/dist/clients/sgconfig.js +7 -6
- package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +1 -1
- package/node_modules/pi-lens/dist/clients/skills-resolver.js +105 -0
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +4 -4
- package/node_modules/pi-lens/dist/clients/startup-scan.js +2 -10
- package/node_modules/pi-lens/dist/clients/string-utils.js +13 -0
- package/node_modules/pi-lens/dist/clients/subagent-mode.js +17 -4
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +176 -39
- package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +244 -0
- package/node_modules/pi-lens/dist/clients/tool-definition.js +41 -1
- package/node_modules/pi-lens/dist/clients/tool-policy.js +92 -22
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +40 -40
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +87 -100
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +81 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +51 -46
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +28 -0
- package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -4
- package/node_modules/pi-lens/dist/clients/typos-config.js +5 -0
- package/node_modules/pi-lens/dist/clients/user-notify.js +6 -6
- package/node_modules/pi-lens/dist/clients/widget-state.js +191 -26
- package/node_modules/pi-lens/dist/clients/word-index-logger.js +2 -9
- package/node_modules/pi-lens/dist/clients/word-index-store.js +23 -12
- package/node_modules/pi-lens/dist/clients/word-index.js +252 -47
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +8 -1
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +4 -1
- package/node_modules/pi-lens/dist/index.js +65899 -55340
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -1
- package/node_modules/pi-lens/dist/mcp/build-staleness.js +1 -1
- package/node_modules/pi-lens/dist/mcp/server.js +127 -5
- package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +583 -0
- package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +129 -0
- package/node_modules/pi-lens/dist/tools/effective-config.js +89 -0
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +96 -25
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +104 -86
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +37 -16
- package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +3 -3
- package/node_modules/pi-lens/dist/tools/render-compact.js +1 -1
- package/node_modules/pi-lens/dist/tools/shared.js +0 -1
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -2
- package/node_modules/pi-lens/docs/agent-guide.md +64 -1
- package/node_modules/pi-lens/docs/configuration.md +222 -0
- package/node_modules/pi-lens/docs/dependencies.md +3 -3
- package/node_modules/pi-lens/docs/features.md +44 -5
- package/node_modules/pi-lens/docs/globalconfig.md +20 -0
- package/node_modules/pi-lens/docs/language-coverage.md +2 -2
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +9 -1
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +25 -0
- package/node_modules/pi-lens/docs/pi-lens-investigator.md +25 -0
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +33 -0
- package/node_modules/pi-lens/docs/pi-lens-subagent.md +49 -0
- package/node_modules/pi-lens/docs/pi-lens-warden.md +55 -0
- package/node_modules/pi-lens/docs/public-api-stability.md +359 -0
- package/node_modules/pi-lens/docs/release-qa-baseline.md +190 -0
- package/node_modules/pi-lens/docs/servercapabilities.md +7 -3
- package/node_modules/pi-lens/docs/subagent-compat.md +110 -30
- package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +2 -2
- package/node_modules/pi-lens/docs/word-index.md +1 -1
- package/node_modules/pi-lens/package.json +18 -35
- package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
- package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
- package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
- package/node_modules/pi-lens/rules/typos/_typos.toml +4 -2
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +192 -1
- package/node_modules/pi-lens/scripts/install-selftest.mjs +58 -2
- package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +129 -0
- package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +1 -1
- package/node_modules/pi-lens/scripts/rpc-load-check.mjs +3 -1
- package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +4 -2
- package/node_modules/pi-web-access/CHANGELOG.md +28 -0
- package/node_modules/pi-web-access/README.md +62 -15
- package/node_modules/pi-web-access/curator-page.ts +3 -1
- package/node_modules/pi-web-access/curator-server.ts +5 -1
- package/node_modules/pi-web-access/gemini-search.ts +8 -4
- package/node_modules/pi-web-access/github-extract.ts +242 -1
- package/node_modules/pi-web-access/index.ts +118 -64
- package/node_modules/pi-web-access/mistral-search.ts +281 -0
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/perplexity.ts +14 -1
- package/node_modules/pi-web-access/utils.ts +23 -6
- package/node_modules/pi-web-access/xai-search.ts +96 -33
- package/package.json +6 -6
|
@@ -4,8 +4,16 @@
|
|
|
4
4
|
* Two known subagent-spawning ecosystems set different env vocabularies in
|
|
5
5
|
* every spawned child's environment:
|
|
6
6
|
*
|
|
7
|
-
* - nicobailon/pi-subagents sets `PI_SUBAGENT_CHILD=1` unconditionally
|
|
8
|
-
*
|
|
7
|
+
* - nicobailon/pi-subagents sets `PI_SUBAGENT_CHILD=1` unconditionally on
|
|
8
|
+
* every process that hosts a child session — the only var light mode keys
|
|
9
|
+
* on for this vocabulary. It ALSO used to set `PI_SUBAGENT_RUN_ID` /
|
|
10
|
+
* `PI_SUBAGENT_CHILD_AGENT` alongside it for best-effort identity, but
|
|
11
|
+
* removed both entirely in `pi-subagents@0.65.0`'s native-AgentSession
|
|
12
|
+
* rewrite (grep-verified absent from the whole 0.66.0 source tree, #2581/
|
|
13
|
+
* #2680) — child identity now travels through an in-process object, not
|
|
14
|
+
* env vars. `getSubagentIdentity()` below already treats identity as
|
|
15
|
+
* optional best-effort metadata, so this permanent absence for the
|
|
16
|
+
* nicobailon vocabulary needed no behavior change, only the doc update.
|
|
9
17
|
* - avtc-pi-subagent (the spawn engine under avtc-pi-feature-flow) is the same
|
|
10
18
|
* execution model — real child-process `pi --mode rpc` / `--mode json -p`
|
|
11
19
|
* spawns, full env inheritance — but never sets `PI_SUBAGENT_CHILD`. It sets
|
|
@@ -63,8 +71,13 @@ export function isSubagentSession() {
|
|
|
63
71
|
/**
|
|
64
72
|
* Best-effort identity of the current subagent, read from the env vars the
|
|
65
73
|
* detected extension sets alongside its subagent marker. Returns `undefined`
|
|
66
|
-
* when neither identity var is present
|
|
67
|
-
*
|
|
74
|
+
* when neither identity var is present — not a subagent session, an
|
|
75
|
+
* extension that never set them, OR nicobailon/pi-subagents@0.65.0+, which
|
|
76
|
+
* removed both identity vars upstream while still setting `PI_SUBAGENT_CHILD`
|
|
77
|
+
* (#2581/#2680): light mode still engages via `isSubagentSession()` alone,
|
|
78
|
+
* `marker` is still populated, only `runId`/`agentName` degrade to
|
|
79
|
+
* `undefined`. Identity is metadata for observability, never a light-mode
|
|
80
|
+
* gate — callers must not treat its absence as "not a subagent".
|
|
68
81
|
*/
|
|
69
82
|
export function getSubagentIdentity() {
|
|
70
83
|
const runId = process.env.PI_SUBAGENT_RUN_ID || undefined;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { createHash } from "node:crypto";
|
|
14
14
|
import * as fs from "node:fs";
|
|
15
15
|
import * as path from "node:path";
|
|
16
|
+
import { BoundedFifoMap } from "./bounded-cache.js";
|
|
16
17
|
import { emitBounded } from "./bounded-telemetry.js";
|
|
17
18
|
import { LEDGER_FIELD_MAX } from "./degradation-ledger.js";
|
|
18
19
|
import { minimatch } from "./deps/minimatch.js";
|
|
@@ -20,9 +21,46 @@ import { createSubsystemLogger } from "./extension-log.js";
|
|
|
20
21
|
import { detectFileRole } from "./file-role.js";
|
|
21
22
|
import { findGlobalBinary } from "./package-manager.js";
|
|
22
23
|
import { PathKeyedMap } from "./path-keyed-map.js";
|
|
23
|
-
import {
|
|
24
|
+
import { augmentPythonEnvironment, detectPythonEnvironment, } from "./python-environment.js";
|
|
25
|
+
import { normalizeEphemeralMapKey, normalizeMapKey, toPosix, } from "./path-utils.js";
|
|
24
26
|
import { isMeasuredDuration, toMeasuredDurationMs } from "./run-duration.js";
|
|
25
27
|
import { safeSpawn, safeSpawnAsync } from "./safe-spawn.js";
|
|
28
|
+
import { stripAnsi } from "./sanitize.js";
|
|
29
|
+
/**
|
|
30
|
+
* #2532: the ONE classification seam for "this `TestResult` carries NO
|
|
31
|
+
* counted failure the agent needs to fix, even though the runner also
|
|
32
|
+
* reported an `error`" — i.e. `failed === 0 && !!error`. This is NOT "the
|
|
33
|
+
* suite never started": a runner can report BOTH counted failures and an
|
|
34
|
+
* error (pytest exit 2 "Interrupted" after `2 failed, 1 passed` already
|
|
35
|
+
* printed — `parsePytestOutput` sets `error` from the exit code
|
|
36
|
+
* independently of the parsed counts, review round 1 S2). The rule is
|
|
37
|
+
* exactly `failed === 0`, nothing about `error`'s presence or cause: a
|
|
38
|
+
* result with counted failures stays blocking even when `error` is also
|
|
39
|
+
* set (`testResultToProjectDiagnostics`/`formatResult` still mention the
|
|
40
|
+
* error in that case, they just don't let it downgrade the verdict).
|
|
41
|
+
*
|
|
42
|
+
* `hasRealFailure`/`runnerErrorOnly` (`runtime-turn.ts`, #2522) key off the
|
|
43
|
+
* exact same fact for the turn-end delivery framing — a batch made
|
|
44
|
+
* entirely of `isRunnerErrorResult` results is advisory, one containing
|
|
45
|
+
* even one counted failure keeps "fix before continuing". Three more
|
|
46
|
+
* surfaces route through this SAME predicate instead of re-deriving it
|
|
47
|
+
* (review round 1 S1's class sweep), so the identical `TestResult` cannot
|
|
48
|
+
* classify differently across them: `testResultToProjectDiagnostics`
|
|
49
|
+
* (`lens_diagnostics mode=full`), the `--lens-guard` merge call in
|
|
50
|
+
* `handleTurnEnd`, and `TestRunnerClient.formatResult`/the turn-end dbg
|
|
51
|
+
* summary (both in this file / `runtime-turn.ts`).
|
|
52
|
+
*
|
|
53
|
+
* Two call sites intentionally test the COMPLEMENT (a truly clean result,
|
|
54
|
+
* no counted failure AND no error) rather than this predicate, and are not
|
|
55
|
+
* a missed fourth spelling: `runtime-turn.ts`'s `cleanFiles` filter
|
|
56
|
+
* (`--lens-guard`'s clear-blocker list) and `testResultToProjectDiagnostics`'s
|
|
57
|
+
* own early "nothing to report" return. Both need "genuinely clean", which
|
|
58
|
+
* `!isRunnerErrorResult(result)` cannot express (it is also true for a
|
|
59
|
+
* counted failure).
|
|
60
|
+
*/
|
|
61
|
+
export function isRunnerErrorResult(result) {
|
|
62
|
+
return result.failed === 0 && !!result.error;
|
|
63
|
+
}
|
|
26
64
|
// Source file → test file patterns (reverse lookup)
|
|
27
65
|
const SOURCE_TO_TEST_PATTERNS = [
|
|
28
66
|
{
|
|
@@ -70,6 +108,57 @@ const MAX_NODE_MODULES_WALK_UP = 5;
|
|
|
70
108
|
// test type rather than mirroring source layout) — capped depth, never an
|
|
71
109
|
// unbounded walk of the whole tests tree.
|
|
72
110
|
const MAX_PYTEST_RECURSE_DEPTH = 3;
|
|
111
|
+
/**
|
|
112
|
+
* #2522: built-in exclusion list for the turn-end auto-fired test selection.
|
|
113
|
+
*
|
|
114
|
+
* `getTestRunTarget`'s three strategies (failed-first / related / self) will
|
|
115
|
+
* happily resolve to ANY test file on disk, including integration/e2e suites
|
|
116
|
+
* that spawn external processes. A plegma dogfooding turn resolved
|
|
117
|
+
* `tests/integration/opencode-delegate.test.ts` this way — it spawns
|
|
118
|
+
* `opencode` and needs a configured provider, so on a box without one it took
|
|
119
|
+
* 17s to fail and was reported to the agent as "3/3 failed, fix before
|
|
120
|
+
* proceeding" on an unrelated model-switch turn (#2522, refs #2504/#2509).
|
|
121
|
+
*
|
|
122
|
+
* One hard-coded list, no per-project config knob (maintainer decision
|
|
123
|
+
* 2026-09-03) — this is a safety bound on what turn_end may auto-fire, not a
|
|
124
|
+
* project preference. Documented in AGENTS.md and docs/. Matched against the
|
|
125
|
+
* resolved test file's project-relative, POSIX-folded path so it applies
|
|
126
|
+
* uniformly to whichever strategy produced the target.
|
|
127
|
+
*/
|
|
128
|
+
export const TURN_END_EXCLUDED_TEST_GLOBS = [
|
|
129
|
+
"**/integration/**",
|
|
130
|
+
"**/e2e/**",
|
|
131
|
+
"**/*.integration.*",
|
|
132
|
+
"**/*.e2e.*",
|
|
133
|
+
];
|
|
134
|
+
/**
|
|
135
|
+
* Whether a resolved test target falls under the built-in turn-end
|
|
136
|
+
* exclusion list (#2522). `testFilePath` may be absolute or relative, and
|
|
137
|
+
* may use either path-separator form — folded through `toPosix` after being
|
|
138
|
+
* made cwd-relative so `\`- and `/`-separated inputs match identically
|
|
139
|
+
* (AGENTS.md cross-form-path screen).
|
|
140
|
+
*/
|
|
141
|
+
export function isExcludedTestTarget(testFilePath, cwd) {
|
|
142
|
+
const rel = toPosix(path.relative(cwd, path.resolve(cwd, testFilePath)));
|
|
143
|
+
// #2522 review round 2, F6: a target that resolves OUTSIDE the project root
|
|
144
|
+
// yields a `..`-leading relative path (or, across Windows drives, a still
|
|
145
|
+
// absolute one), which matches none of the globs — so the bare `some()`
|
|
146
|
+
// below reported it as "not excluded" and turn_end would have auto-spawned a
|
|
147
|
+
// runner against a file outside the project the turn is running in. An
|
|
148
|
+
// out-of-tree target, and the project root itself, fail CLOSED.
|
|
149
|
+
if (rel === "" ||
|
|
150
|
+
rel === ".." ||
|
|
151
|
+
rel.startsWith("../") ||
|
|
152
|
+
path.isAbsolute(rel))
|
|
153
|
+
return true;
|
|
154
|
+
// #2522 review round 2, F5: case-INSENSITIVE. `tests/Integration/`,
|
|
155
|
+
// `tests/E2E/` and `foo.E2E.test.ts` are the same hazard as their lowercase
|
|
156
|
+
// spellings, and on the case-insensitive filesystems Windows and macOS ship
|
|
157
|
+
// by default they are literally the same files — so a case-sensitive match
|
|
158
|
+
// made the SAME repo excluded on one box and unbounded on another. A safety
|
|
159
|
+
// bound a capital letter defeats is not a bound.
|
|
160
|
+
return TURN_END_EXCLUDED_TEST_GLOBS.some((glob) => minimatch(rel, glob, { dot: true, nocase: true }));
|
|
161
|
+
}
|
|
73
162
|
// --- Runner Detection ---
|
|
74
163
|
export const RUNNERS = {
|
|
75
164
|
vitest: {
|
|
@@ -223,7 +312,7 @@ function canonicalProjectRoot(cwd) {
|
|
|
223
312
|
return { key: normalizeEphemeralMapKey(absolute), resolved: false };
|
|
224
313
|
}
|
|
225
314
|
}
|
|
226
|
-
const MAX_CANONICAL_ROOT_MEMO_ENTRIES = 512;
|
|
315
|
+
export const MAX_CANONICAL_ROOT_MEMO_ENTRIES = 512;
|
|
227
316
|
function filesystemErrorCode(error) {
|
|
228
317
|
if (error !== null &&
|
|
229
318
|
typeof error === "object" &&
|
|
@@ -233,6 +322,50 @@ function filesystemErrorCode(error) {
|
|
|
233
322
|
}
|
|
234
323
|
return undefined;
|
|
235
324
|
}
|
|
325
|
+
const PYTEST_SUMMARY_OUTCOME = "(?:passed|failed|skipped|reruns?|errors?|warnings?|deselected|xfailed|xpassed)";
|
|
326
|
+
const PYTEST_SUMMARY_LINE = new RegExp(`^\\s*(?:=+\\s*)?\\d+\\s+${PYTEST_SUMMARY_OUTCOME}` +
|
|
327
|
+
`(?:\\s*,\\s*\\d+\\s+${PYTEST_SUMMARY_OUTCOME})*` +
|
|
328
|
+
`\\s+in\\s+[\\d.]+s(?:\\s*=+)?\\s*$`, "i");
|
|
329
|
+
/** Extract aggregate values from pytest's final outcome line only. */
|
|
330
|
+
function parsePytestSummary(output) {
|
|
331
|
+
// Pytest may color the whole summary or individual tokens. Strip ANSI before
|
|
332
|
+
// selecting and extracting so formatting cannot turn the first count into 0.
|
|
333
|
+
const normalizedOutput = stripAnsi(output);
|
|
334
|
+
const summaryLine = normalizedOutput
|
|
335
|
+
.split(/\r?\n/)
|
|
336
|
+
.reverse()
|
|
337
|
+
.find((line) => PYTEST_SUMMARY_LINE.test(line));
|
|
338
|
+
let passed = 0;
|
|
339
|
+
let failed = 0;
|
|
340
|
+
let skipped = 0;
|
|
341
|
+
if (!summaryLine)
|
|
342
|
+
return { passed, failed, skipped };
|
|
343
|
+
const summaryBody = summaryLine.replace(/^=+\s*|\s*=+\s*$/g, "");
|
|
344
|
+
for (const field of summaryBody.split(",")) {
|
|
345
|
+
const [countText, outcome] = field.trim().split(/\s+/, 2);
|
|
346
|
+
const count = Number.parseInt(countText, 10);
|
|
347
|
+
if (!Number.isFinite(count))
|
|
348
|
+
continue;
|
|
349
|
+
switch (outcome) {
|
|
350
|
+
case "passed":
|
|
351
|
+
passed = count;
|
|
352
|
+
break;
|
|
353
|
+
case "failed":
|
|
354
|
+
failed = count;
|
|
355
|
+
break;
|
|
356
|
+
case "skipped":
|
|
357
|
+
skipped = count;
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const durationMatch = /in\s+([\d.]+)s/.exec(summaryLine);
|
|
362
|
+
// Rounded, like `jsonRunDurationMs` and PHPUnit's legacy path:
|
|
363
|
+
// `in 2.01s` is 2009.9999999999998 in binary floating point.
|
|
364
|
+
const duration = durationMatch
|
|
365
|
+
? Math.round(Number.parseFloat(durationMatch[1]) * 1000)
|
|
366
|
+
: undefined;
|
|
367
|
+
return { passed, failed, skipped, duration };
|
|
368
|
+
}
|
|
236
369
|
export class TestRunnerClient {
|
|
237
370
|
log;
|
|
238
371
|
// This is an instance-lifetime memo of RESOLVED spellings only, which leaves
|
|
@@ -247,7 +380,7 @@ export class TestRunnerClient {
|
|
|
247
380
|
// resolve — a state where `detectRunner` already walks node_modules on every
|
|
248
381
|
// call. Keep the memo bounded so pathological spelling churn cannot grow it
|
|
249
382
|
// without limit.
|
|
250
|
-
canonicalRootMemo = new
|
|
383
|
+
canonicalRootMemo = new BoundedFifoMap(MAX_CANONICAL_ROOT_MEMO_ENTRIES);
|
|
251
384
|
availableRunners = new PathKeyedMap(normalizeEphemeralMapKey);
|
|
252
385
|
failedTestsByRunner = new Map();
|
|
253
386
|
failedTargetSequence = 0;
|
|
@@ -271,14 +404,13 @@ export class TestRunnerClient {
|
|
|
271
404
|
const { key, resolved } = canonicalProjectRoot(cwd);
|
|
272
405
|
if (!resolved)
|
|
273
406
|
return key;
|
|
274
|
-
if (this.canonicalRootMemo.size >= MAX_CANONICAL_ROOT_MEMO_ENTRIES) {
|
|
275
|
-
const oldest = this.canonicalRootMemo.keys().next().value;
|
|
276
|
-
if (oldest !== undefined)
|
|
277
|
-
this.canonicalRootMemo.delete(oldest);
|
|
278
|
-
}
|
|
279
407
|
this.canonicalRootMemo.set(cwd, key);
|
|
280
408
|
return key;
|
|
281
409
|
}
|
|
410
|
+
/** #2442 test-only: exercise canonicalRootMemo's bounded eviction directly. */
|
|
411
|
+
_getCanonicalProjectRootForTests(cwd) {
|
|
412
|
+
return this.getCanonicalProjectRoot(cwd);
|
|
413
|
+
}
|
|
282
414
|
getRunnerAvailability(byRunner, runner) {
|
|
283
415
|
const cached = byRunner?.get(runner);
|
|
284
416
|
if (!cached)
|
|
@@ -904,16 +1036,20 @@ export class TestRunnerClient {
|
|
|
904
1036
|
else {
|
|
905
1037
|
request = runnerOrRequest;
|
|
906
1038
|
}
|
|
907
|
-
const { runner, config, turnIndex } = request;
|
|
1039
|
+
const { runner, config, turnIndex, signal } = request;
|
|
908
1040
|
if (!fs.existsSync(absoluteTestFile)) {
|
|
909
1041
|
return this.emptyResult(absoluteTestFile, "", runner, "Test file not found");
|
|
910
1042
|
}
|
|
911
1043
|
try {
|
|
912
|
-
const { command, args } = await this.resolveExec(runner, config, absoluteTestFile, cwd);
|
|
1044
|
+
const { command, args, env } = await this.resolveExec(runner, config, absoluteTestFile, cwd);
|
|
913
1045
|
this.log(`Running (async): ${command} ${args.join(" ")}`);
|
|
914
1046
|
const result = await safeSpawnAsync(command, args, {
|
|
915
1047
|
cwd,
|
|
916
1048
|
timeout: 60000,
|
|
1049
|
+
env,
|
|
1050
|
+
// #2522 R2 F1. `safeSpawnAsync` resolves `options.signal ?? ambient`,
|
|
1051
|
+
// so an absent batch signal keeps the pre-#2522 ambient behaviour.
|
|
1052
|
+
signal,
|
|
917
1053
|
});
|
|
918
1054
|
const stdout = result.stdout || "";
|
|
919
1055
|
const stderr = result.stderr || "";
|
|
@@ -1273,31 +1409,9 @@ export class TestRunnerClient {
|
|
|
1273
1409
|
parsePytestOutput(stdout, stderr, exitCode, testFile, _cwd, runner) {
|
|
1274
1410
|
const failures = [];
|
|
1275
1411
|
const output = `${stdout}\n${stderr}`;
|
|
1276
|
-
//
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
let passed = 0;
|
|
1280
|
-
let failed = 0;
|
|
1281
|
-
let skipped = 0;
|
|
1282
|
-
// #1479: undefined until pytest's own `in N.NNs` is read. `in 0.00s` is
|
|
1283
|
-
// a real pytest summary, so 0 has to stay available as a measurement.
|
|
1284
|
-
let duration;
|
|
1285
|
-
if (summaryMatch) {
|
|
1286
|
-
// Extract numbers from various patterns
|
|
1287
|
-
const passedMatch = output.match(/(\d+)\s+passed/);
|
|
1288
|
-
const failedMatch = output.match(/(\d+)\s+failed/);
|
|
1289
|
-
const skippedMatch = output.match(/(\d+)\s+skipped/);
|
|
1290
|
-
const durationMatch = output.match(/in\s+([\d.]+)s/);
|
|
1291
|
-
passed = passedMatch ? parseInt(passedMatch[1], 10) : 0;
|
|
1292
|
-
failed = failedMatch ? parseInt(failedMatch[1], 10) : 0;
|
|
1293
|
-
skipped = skippedMatch ? parseInt(skippedMatch[1], 10) : 0;
|
|
1294
|
-
// Rounded, like `jsonRunDurationMs` and PHPUnit's legacy path:
|
|
1295
|
-
// `in 2.01s` is 2009.9999999999998 in binary floating point, and
|
|
1296
|
-
// the turn-end log prints the number as it stands.
|
|
1297
|
-
// (Routing this through `toMeasuredDurationMs` is #1484, not this.)
|
|
1298
|
-
if (durationMatch)
|
|
1299
|
-
duration = Math.round(parseFloat(durationMatch[1]) * 1000);
|
|
1300
|
-
}
|
|
1412
|
+
// #1479: `duration` stays undefined unless pytest emits its own `in N.NNs`
|
|
1413
|
+
// summary. A measured `in 0.00s` remains a real zero.
|
|
1414
|
+
const { passed, failed, skipped, duration } = parsePytestSummary(output);
|
|
1301
1415
|
// Parse individual failures: "FAILED tests/test_foo.py::test_something - AssertionError: ..."
|
|
1302
1416
|
const failureRegex = /FAILED\s+(\S+::\S+)\s*-\s*(.+?)(?:\n|$)/g;
|
|
1303
1417
|
let match;
|
|
@@ -1325,7 +1439,11 @@ export class TestRunnerClient {
|
|
|
1325
1439
|
skipped,
|
|
1326
1440
|
failures,
|
|
1327
1441
|
duration,
|
|
1328
|
-
error: exitCode ===
|
|
1442
|
+
error: exitCode === 4
|
|
1443
|
+
? "Pytest configuration error"
|
|
1444
|
+
: exitCode === 2
|
|
1445
|
+
? "Pytest interrupted"
|
|
1446
|
+
: undefined,
|
|
1329
1447
|
};
|
|
1330
1448
|
}
|
|
1331
1449
|
// --- PHPUnit Parser (text-based, default CLI output) ---
|
|
@@ -1961,9 +2079,15 @@ export class TestRunnerClient {
|
|
|
1961
2079
|
* Format test result for LLM consumption
|
|
1962
2080
|
*/
|
|
1963
2081
|
formatResult(result) {
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
2082
|
+
// #2532 review S1: folded onto `isRunnerErrorResult` instead of the old
|
|
2083
|
+
// local `error && passed === 0 && failed === 0` spelling — that missed a
|
|
2084
|
+
// runner error reported alongside partial passes (pytest `Interrupted`
|
|
2085
|
+
// after some tests already ran clean), which fell through to the normal
|
|
2086
|
+
// "N/N passed" branch below and silently dropped the interruption.
|
|
2087
|
+
if (isRunnerErrorResult(result)) {
|
|
2088
|
+
return result.passed > 0
|
|
2089
|
+
? `[Tests] ⚠ Could not complete tests: ${result.error} (${result.passed} passed before)`
|
|
2090
|
+
: `[Tests] ⚠ Could not run tests: ${result.error}`;
|
|
1967
2091
|
}
|
|
1968
2092
|
const total = result.passed + result.failed + result.skipped;
|
|
1969
2093
|
if (total === 0) {
|
|
@@ -2094,6 +2218,19 @@ export class TestRunnerClient {
|
|
|
2094
2218
|
* runners like vitest/jest/pytest).
|
|
2095
2219
|
*/
|
|
2096
2220
|
async resolveExec(runner, config, testFile, cwd) {
|
|
2221
|
+
// Run pytest through the project interpreter itself, not a generic `python`
|
|
2222
|
+
// resolved from the host PATH. The child-only environment also keeps tools
|
|
2223
|
+
// spawned by tests inside the same project environment.
|
|
2224
|
+
if (runner === "pytest") {
|
|
2225
|
+
const pythonEnvironment = await detectPythonEnvironment(cwd);
|
|
2226
|
+
if (pythonEnvironment) {
|
|
2227
|
+
return {
|
|
2228
|
+
command: pythonEnvironment.pythonPath,
|
|
2229
|
+
args: config.args(testFile, cwd),
|
|
2230
|
+
env: augmentPythonEnvironment(process.env, pythonEnvironment),
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2097
2234
|
// PHPUnit has no npx-style automatic local-binary resolution — Composer's
|
|
2098
2235
|
// standard local-install location is vendor/bin/phpunit, so check that
|
|
2099
2236
|
// explicitly before falling back to a global `phpunit` on PATH.
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automatic test-runner delivery for the post-agent idle window (#2366).
|
|
3
|
+
*
|
|
4
|
+
* Test results remain in `test-runner-findings` for pull diagnostics and the
|
|
5
|
+
* commit guard. This module stages an owner-qualified pointer and marks it
|
|
6
|
+
* eligible for the next context build after the host proves that the agent is
|
|
7
|
+
* idle. Provenance validation stays in `peekTestFindings`, the one shared
|
|
8
|
+
* delivery gate for this cache.
|
|
9
|
+
*/
|
|
10
|
+
import { emitBounded } from "./bounded-telemetry.js";
|
|
11
|
+
import { consumeTestFindings, peekTestFindings } from "./runtime-context.js";
|
|
12
|
+
const MAX_PENDING_DELIVERIES = 32;
|
|
13
|
+
const pending = new Map();
|
|
14
|
+
function key(cwd, sessionId, ownerId) {
|
|
15
|
+
// `cwd` comes from the same host session field at turn_end and settle. Keep
|
|
16
|
+
// that canonical value unchanged so cache reads use the identical workspace
|
|
17
|
+
// identity; dispatch contexts already normalize their own path fields.
|
|
18
|
+
return `${ownerId ?? "direct"}\u0000${cwd}\u0000${sessionId}`;
|
|
19
|
+
}
|
|
20
|
+
function record(identity, outcome, delivery, metadata = {}) {
|
|
21
|
+
emitBounded("test_runner_delivery", identity, {
|
|
22
|
+
filePath: delivery.cwd,
|
|
23
|
+
durationMs: 0,
|
|
24
|
+
metadata: {
|
|
25
|
+
outcome,
|
|
26
|
+
sessionId: delivery.sessionId,
|
|
27
|
+
generation: delivery.generation,
|
|
28
|
+
targetCount: delivery.targetCount,
|
|
29
|
+
deliveryBoundary: "agent_settled",
|
|
30
|
+
ownership: "pi",
|
|
31
|
+
droppedDetailCount: 0,
|
|
32
|
+
...metadata,
|
|
33
|
+
},
|
|
34
|
+
}, {
|
|
35
|
+
capPerTurn: { limit: 8, turnIndex: delivery.generation },
|
|
36
|
+
ledgerKind: outcome === "delivery-failed" ? "test-runner-delivery" : undefined,
|
|
37
|
+
reason: `test runner delivery ${outcome}`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function prunePending() {
|
|
41
|
+
while (pending.size > MAX_PENDING_DELIVERIES) {
|
|
42
|
+
const oldest = pending.keys().next();
|
|
43
|
+
if (oldest.done)
|
|
44
|
+
return;
|
|
45
|
+
pending.delete(oldest.value);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Stage the newest result for this session. Clean results supersede failures. */
|
|
49
|
+
export function stageTestRunnerDelivery(args) {
|
|
50
|
+
const deliveryKey = key(args.cwd, args.sessionId, args.owner?.ownerId);
|
|
51
|
+
const prior = pending.get(deliveryKey);
|
|
52
|
+
if (prior && prior.generation > args.generation) {
|
|
53
|
+
const superseded = {
|
|
54
|
+
cwd: args.cwd,
|
|
55
|
+
sessionId: args.sessionId,
|
|
56
|
+
ownerId: args.owner?.ownerId,
|
|
57
|
+
generation: args.generation,
|
|
58
|
+
targetCount: args.targetCount,
|
|
59
|
+
createdAt: Date.now(),
|
|
60
|
+
eligible: false,
|
|
61
|
+
owner: args.owner,
|
|
62
|
+
};
|
|
63
|
+
record(deliveryKey, "superseded", superseded, {
|
|
64
|
+
currentGeneration: prior.generation,
|
|
65
|
+
});
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!args.hasFindings) {
|
|
69
|
+
if (prior) {
|
|
70
|
+
pending.delete(deliveryKey);
|
|
71
|
+
record(deliveryKey, "superseded", {
|
|
72
|
+
...prior,
|
|
73
|
+
targetCount: args.targetCount,
|
|
74
|
+
generation: args.generation,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const delivery = {
|
|
80
|
+
cwd: args.cwd,
|
|
81
|
+
sessionId: args.sessionId,
|
|
82
|
+
ownerId: args.owner?.ownerId,
|
|
83
|
+
generation: args.generation,
|
|
84
|
+
targetCount: args.targetCount,
|
|
85
|
+
createdAt: Date.now(),
|
|
86
|
+
eligible: false,
|
|
87
|
+
owner: args.owner,
|
|
88
|
+
};
|
|
89
|
+
pending.set(deliveryKey, delivery);
|
|
90
|
+
prunePending();
|
|
91
|
+
record(deliveryKey, "staged", delivery);
|
|
92
|
+
}
|
|
93
|
+
/** Mark the latest staged result eligible during a host-confirmed idle window. */
|
|
94
|
+
export function deliverTestRunnerFindings(args) {
|
|
95
|
+
const cwd = args.ctx.cwd ?? process.cwd();
|
|
96
|
+
const deliveryKey = key(cwd, args.sessionId, args.ownerId);
|
|
97
|
+
const delivery = pending.get(deliveryKey);
|
|
98
|
+
if (!delivery)
|
|
99
|
+
return;
|
|
100
|
+
const currentGeneration = args.cacheManager.readCache("test-runner-findings", cwd)?.data?.testRunGeneration;
|
|
101
|
+
if (currentGeneration !== undefined &&
|
|
102
|
+
currentGeneration > delivery.generation) {
|
|
103
|
+
pending.delete(deliveryKey);
|
|
104
|
+
record(deliveryKey, "superseded", delivery, { currentGeneration });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (typeof args.ctx.isIdle !== "function") {
|
|
108
|
+
pending.delete(deliveryKey);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
if (!args.ctx.isIdle()) {
|
|
113
|
+
record(deliveryKey, "carried", delivery);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
record(deliveryKey, "delivery-failed", delivery, {
|
|
119
|
+
error: String(error).slice(0, 500),
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (!peekTestFindings(args.cacheManager, cwd, args.runtime, true)) {
|
|
124
|
+
pending.delete(deliveryKey);
|
|
125
|
+
record(deliveryKey, "superseded", delivery);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// Recheck immediately before marking eligible. The host may accept a prompt
|
|
129
|
+
// between the first check and this synchronous call.
|
|
130
|
+
try {
|
|
131
|
+
if (!args.ctx.isIdle()) {
|
|
132
|
+
record(deliveryKey, "carried", delivery);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
record(deliveryKey, "delivery-failed", delivery, {
|
|
138
|
+
error: String(error).slice(0, 500),
|
|
139
|
+
});
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
delivery.eligible = true;
|
|
143
|
+
const current = args.cacheManager.readCache("test-runner-findings", cwd)?.data;
|
|
144
|
+
if (!current?.content || current.testRunGeneration !== delivery.generation) {
|
|
145
|
+
pending.delete(deliveryKey);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
args.cacheManager.writeCache("test-runner-findings", {
|
|
149
|
+
...current,
|
|
150
|
+
deliveryEligible: {
|
|
151
|
+
sessionId: delivery.sessionId,
|
|
152
|
+
generation: delivery.generation,
|
|
153
|
+
eligibleAt: Date.now(),
|
|
154
|
+
},
|
|
155
|
+
}, cwd);
|
|
156
|
+
record(deliveryKey, "eligible", delivery, {
|
|
157
|
+
ageMs: Math.max(0, Date.now() - delivery.createdAt),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/** Deliver only the result staged by this activation and settled session. */
|
|
161
|
+
export function deliverStagedTestRunnerFindings(args) {
|
|
162
|
+
if (!args?.cwd || !args.ownerId)
|
|
163
|
+
return;
|
|
164
|
+
const delivery = args.sessionId
|
|
165
|
+
? pending.get(key(args.cwd, args.sessionId, args.ownerId))
|
|
166
|
+
: [...pending.values()]
|
|
167
|
+
.reverse()
|
|
168
|
+
.find((candidate) => candidate.cwd === args.cwd && candidate.ownerId === args.ownerId);
|
|
169
|
+
if (!delivery?.owner)
|
|
170
|
+
return;
|
|
171
|
+
deliverTestRunnerFindings({
|
|
172
|
+
ctx: delivery.owner.getCtx(),
|
|
173
|
+
cacheManager: delivery.owner.cacheManager,
|
|
174
|
+
runtime: delivery.owner.runtime,
|
|
175
|
+
sessionId: delivery.sessionId,
|
|
176
|
+
ownerId: delivery.ownerId,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/** Consume the result made eligible by the settled idle boundary. */
|
|
180
|
+
export function consumeStagedTestRunnerFindings(args) {
|
|
181
|
+
const deliveryKey = key(args.cwd, args.sessionId, args.ownerId);
|
|
182
|
+
let delivery = pending.get(deliveryKey);
|
|
183
|
+
const persisted = args.cacheManager.readCache("test-runner-findings", args.cwd)?.data;
|
|
184
|
+
const eligible = persisted?.deliveryEligible;
|
|
185
|
+
if (!delivery && persisted?.content && eligible) {
|
|
186
|
+
const sameSession = eligible.sessionId === args.sessionId;
|
|
187
|
+
if (!sameSession) {
|
|
188
|
+
const foreignDelivery = {
|
|
189
|
+
cwd: args.cwd,
|
|
190
|
+
sessionId: eligible.sessionId,
|
|
191
|
+
generation: eligible.generation,
|
|
192
|
+
targetCount: 0,
|
|
193
|
+
createdAt: eligible.eligibleAt,
|
|
194
|
+
eligible: true,
|
|
195
|
+
rehydrated: true,
|
|
196
|
+
};
|
|
197
|
+
record(deliveryKey, "foreign-session", foreignDelivery, {
|
|
198
|
+
currentSessionId: args.sessionId,
|
|
199
|
+
currentOwnerId: args.ownerId,
|
|
200
|
+
reason: "session-mismatch",
|
|
201
|
+
});
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
delivery = {
|
|
205
|
+
cwd: args.cwd,
|
|
206
|
+
sessionId: args.sessionId,
|
|
207
|
+
ownerId: args.ownerId,
|
|
208
|
+
generation: eligible.generation,
|
|
209
|
+
targetCount: 0,
|
|
210
|
+
createdAt: eligible.eligibleAt,
|
|
211
|
+
eligible: true,
|
|
212
|
+
rehydrated: true,
|
|
213
|
+
};
|
|
214
|
+
pending.set(deliveryKey, delivery);
|
|
215
|
+
}
|
|
216
|
+
if (!delivery?.eligible)
|
|
217
|
+
return undefined;
|
|
218
|
+
const currentGeneration = persisted?.testRunGeneration;
|
|
219
|
+
if (currentGeneration !== delivery.generation) {
|
|
220
|
+
pending.delete(deliveryKey);
|
|
221
|
+
record(deliveryKey, "superseded", delivery, { currentGeneration });
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
const findings = consumeTestFindings(args.cacheManager, args.cwd, args.runtime);
|
|
225
|
+
if (!findings) {
|
|
226
|
+
pending.delete(deliveryKey);
|
|
227
|
+
record(deliveryKey, "superseded", delivery);
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
pending.delete(deliveryKey);
|
|
231
|
+
record(deliveryKey, "delivered", delivery, {
|
|
232
|
+
ageMs: Math.max(0, Date.now() - delivery.createdAt),
|
|
233
|
+
rehydrated: delivery.rehydrated === true,
|
|
234
|
+
});
|
|
235
|
+
return findings;
|
|
236
|
+
}
|
|
237
|
+
/** Test-only reset; session ownership prevents cross-session consumption. */
|
|
238
|
+
export function _resetTestRunnerDeliveryForTests() {
|
|
239
|
+
pending.clear();
|
|
240
|
+
}
|
|
241
|
+
/** Clear staged results when a primary session replaces the owning runtime. */
|
|
242
|
+
export function resetTestRunnerDelivery() {
|
|
243
|
+
pending.clear();
|
|
244
|
+
}
|
|
@@ -1,7 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE final registration boundary for a host-facing tool: everything that
|
|
3
|
+
* must be true of a tool definition the instant before `pi.registerTool`
|
|
4
|
+
* sees it. `index.ts` runs every tool it registers — always-active,
|
|
5
|
+
* situational, and the compact-line-wrapped variants — through this one
|
|
6
|
+
* function, so a property added here reaches every tool without a second
|
|
7
|
+
* call site to keep in sync.
|
|
8
|
+
*
|
|
9
|
+
* Two jobs today:
|
|
10
|
+
* 1. metadata the host requires is present (a non-empty description);
|
|
11
|
+
* 2. an in-flight tool call HOLDS the event loop (#2507). pi-lens unrefs the
|
|
12
|
+
* LSP child, its stdio pipes and several waiting timers so an idle
|
|
13
|
+
* one-shot process can exit; in a headless child with no other referenced
|
|
14
|
+
* handle that let Node exit 0 in the middle of `lsp_diagnostics`. Every
|
|
15
|
+
* tool call therefore takes a counted hold for its own duration — see
|
|
16
|
+
* `clients/event-loop-hold.ts` for why the hold lives there and not in
|
|
17
|
+
* the unref itself.
|
|
18
|
+
*/
|
|
19
|
+
import { acquireEventLoopHold } from "./event-loop-hold.js";
|
|
1
20
|
export function normalizeToolDefinition(tool) {
|
|
2
21
|
const name = typeof tool.name === "string" ? tool.name.trim() : "tool";
|
|
3
22
|
const description = typeof tool.description === "string" && tool.description.trim().length > 0
|
|
4
23
|
? tool.description
|
|
5
24
|
: `Use the ${name} tool.`;
|
|
6
|
-
|
|
25
|
+
const execute = tool.execute;
|
|
26
|
+
if (typeof execute !== "function") {
|
|
27
|
+
return { ...tool, description };
|
|
28
|
+
}
|
|
29
|
+
const inner = execute;
|
|
30
|
+
return {
|
|
31
|
+
...tool,
|
|
32
|
+
description,
|
|
33
|
+
// A plain function, not an arrow: `this` stays whatever the host called
|
|
34
|
+
// the tool with, so a definition whose `execute` reads `this` keeps
|
|
35
|
+
// working. The hold is released in `finally`, so a rejected or aborted
|
|
36
|
+
// call releases exactly like a resolved one.
|
|
37
|
+
execute: async function (...args) {
|
|
38
|
+
const release = acquireEventLoopHold(name);
|
|
39
|
+
try {
|
|
40
|
+
return await inner.apply(this, args);
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
release();
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
};
|
|
7
47
|
}
|