@herbertgao/pi-extensions 2026.8.5 → 2026.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +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 +272 -22
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +48 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +14 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +16 -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 +22 -15
- 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 +34 -10
- package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +29 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +3 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +46 -10
- package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +8 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +30 -7
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +30 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +26 -5
- package/node_modules/@narumitw/pi-btw/README.md +119 -103
- 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 +13 -9
- package/node_modules/@narumitw/pi-btw/src/btw.ts +40 -42
- 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 +78 -15
- package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
- package/node_modules/pi-lens/CHANGELOG.md +1004 -0
- package/node_modules/pi-lens/README.md +11 -3
- package/node_modules/pi-lens/config/ruff/core.toml +42 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +427 -0
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +79 -16
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
- package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +285 -8
- package/node_modules/pi-lens/dist/clients/biome-client.js +176 -37
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
- package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +119 -0
- package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +192 -0
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
- package/node_modules/pi-lens/dist/clients/bus-publish.js +11 -3
- package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
- package/node_modules/pi-lens/dist/clients/cascade-format.js +76 -2
- 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/console-guard-install.js +16 -4
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +249 -52
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
- package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +62 -13
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +43 -9
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +153 -37
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +165 -11
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +325 -72
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +81 -41
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +23 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +20 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +10 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +18 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +488 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +84 -0
- 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 +943 -93
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +72 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +106 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +9 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
- package/node_modules/pi-lens/dist/clients/extension-log.js +317 -3
- package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +294 -0
- package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
- package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +39 -8
- 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 +49 -21
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
- package/node_modules/pi-lens/dist/clients/go-client.js +21 -39
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +219 -10
- package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
- package/node_modules/pi-lens/dist/clients/host-ports.js +9 -1
- package/node_modules/pi-lens/dist/clients/installer/index.js +1119 -101
- 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 +86 -15
- package/node_modules/pi-lens/dist/clients/knip-client.js +361 -24
- package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +2 -0
- package/node_modules/pi-lens/dist/clients/latency-logger.js +487 -9
- 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-events.js +56 -25
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +45 -2
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +160 -20
- package/node_modules/pi-lens/dist/clients/lsp/client.js +1666 -148
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +111 -0
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2496 -171
- 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 +517 -69
- 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 +172 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +61 -3
- 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/analyze.js +4 -0
- package/node_modules/pi-lens/dist/clients/mcp/session.js +39 -18
- package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
- package/node_modules/pi-lens/dist/clients/module-report.js +8 -0
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
- package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
- package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
- package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +178 -30
- 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 +85 -15
- 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/runner-findings.js +23 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -0
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
- package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
- package/node_modules/pi-lens/dist/clients/read-guard.js +184 -10
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
- package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +76 -4
- package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +248 -13
- package/node_modules/pi-lens/dist/clients/runtime-context.js +112 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +472 -61
- package/node_modules/pi-lens/dist/clients/runtime-session.js +359 -13
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +269 -22
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +948 -87
- package/node_modules/pi-lens/dist/clients/rust-client.js +21 -37
- 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 +224 -8
- package/node_modules/pi-lens/dist/clients/sg-runner.js +249 -37
- package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +18 -11
- package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
- package/node_modules/pi-lens/dist/clients/startup-timing.js +26 -2
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +574 -32
- package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
- package/node_modules/pi-lens/dist/clients/tool-policy.js +127 -20
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
- package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
- package/node_modules/pi-lens/dist/clients/warm-attach.js +17 -0
- package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
- package/node_modules/pi-lens/dist/clients/word-index.js +305 -33
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
- package/node_modules/pi-lens/dist/index.js +64677 -49484
- package/node_modules/pi-lens/dist/mcp/server.js +8 -2
- package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +469 -56
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +190 -46
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
- package/node_modules/pi-lens/docs/agent-guide.md +44 -14
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +518 -501
- package/node_modules/pi-lens/docs/dispositions.md +21 -0
- package/node_modules/pi-lens/docs/features.md +33 -6
- package/node_modules/pi-lens/docs/globalconfig.md +14 -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 +25 -1
- package/node_modules/pi-lens/docs/usage.md +24 -5
- package/node_modules/pi-lens/docs/word-index.md +35 -0
- package/node_modules/pi-lens/package.json +10 -6
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +22 -0
- 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-conditional-empty-object-spread-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
- 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 +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +9 -0
- 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-laundering-test.yml +11 -0
- 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 +66 -0
- 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-conditional-empty-object-spread-js.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
- 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 +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +42 -0
- 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 +32 -0
- 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 +132 -1
- package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
- package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +12 -11
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +87 -116
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +170 -0
- package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
- package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +49 -0
- package/node_modules/pi-mcp-adapter/README.md +51 -6
- package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
- package/node_modules/pi-mcp-adapter/cli.js +188 -5
- package/node_modules/pi-mcp-adapter/commands.ts +128 -22
- package/node_modules/pi-mcp-adapter/config.ts +28 -10
- package/node_modules/pi-mcp-adapter/direct-tools.ts +23 -19
- 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/mcp-setup-panel.ts +2 -1
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +46 -25
- package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
- package/node_modules/pi-mcp-adapter/package.json +4 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +330 -0
- package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
- package/node_modules/pi-mcp-adapter/server-manager.ts +145 -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-metadata.ts +38 -18
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
- package/node_modules/pi-mcp-adapter/types.ts +95 -10
- 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 +38 -14
- package/node_modules/pi-web-access/anysearch.ts +4 -2
- package/node_modules/pi-web-access/auth-fetch.ts +148 -0
- package/node_modules/pi-web-access/brave.ts +16 -4
- package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
- package/node_modules/pi-web-access/curator-page.ts +8 -3
- package/node_modules/pi-web-access/curator-server.ts +5 -1
- package/node_modules/pi-web-access/exa.ts +18 -7
- package/node_modules/pi-web-access/extract.ts +134 -37
- package/node_modules/pi-web-access/fetch-params.ts +17 -3
- package/node_modules/pi-web-access/firecrawl.ts +172 -12
- package/node_modules/pi-web-access/gemini-search.ts +33 -7
- package/node_modules/pi-web-access/index.ts +160 -104
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/page-query.ts +4 -2
- package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
- package/node_modules/pi-web-access/parallel.ts +2 -2
- package/node_modules/pi-web-access/query-rewrite.ts +51 -0
- package/node_modules/pi-web-access/serper.ts +211 -0
- package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
- package/node_modules/pi-web-access/summary-review.ts +51 -23
- 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/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +13 -13
|
@@ -16,6 +16,7 @@ import * as path from "node:path";
|
|
|
16
16
|
import { minimatch } from "./deps/minimatch.js";
|
|
17
17
|
import { detectFileRole } from "./file-role.js";
|
|
18
18
|
import { findGlobalBinary } from "./package-manager.js";
|
|
19
|
+
import { isMeasuredDuration, toMeasuredDurationMs } from "./run-duration.js";
|
|
19
20
|
import { safeSpawn, safeSpawnAsync } from "./safe-spawn.js";
|
|
20
21
|
// Source file → test file patterns (reverse lookup)
|
|
21
22
|
const SOURCE_TO_TEST_PATTERNS = [
|
|
@@ -870,9 +871,17 @@ export class TestRunnerClient {
|
|
|
870
871
|
runner,
|
|
871
872
|
passed: json.numPassedTests || 0,
|
|
872
873
|
failed: json.numFailedTests || 0,
|
|
873
|
-
|
|
874
|
+
// #1452: `numSkippedTests` is absent from both reporters' JSON, so
|
|
875
|
+
// this read was always 0. `numPendingTests` is where a `test.skip`
|
|
876
|
+
// actually lands; `numTodoTests` is counted with it because the
|
|
877
|
+
// text parsers (pytest `N skipped`, mix `N excluded` + `N skipped`)
|
|
878
|
+
// also fold every not-run test into one `skipped` figure.
|
|
879
|
+
// `??` would accept a present 0, so a reporter that emits
|
|
880
|
+
// `numSkippedTests: 0` beside a real `numPendingTests` would
|
|
881
|
+
// reproduce the very defect this removes. Take the larger reading.
|
|
882
|
+
skipped: Math.max(json.numSkippedTests ?? 0, (json.numPendingTests || 0) + (json.numTodoTests || 0)),
|
|
874
883
|
failures,
|
|
875
|
-
duration:
|
|
884
|
+
duration: this.jsonRunDurationMs(json.testResults),
|
|
876
885
|
};
|
|
877
886
|
}
|
|
878
887
|
catch (err) {
|
|
@@ -881,6 +890,74 @@ export class TestRunnerClient {
|
|
|
881
890
|
return this.emptyResult(testFile, "", runner, failed ? "Tests failed (could not parse output)" : undefined);
|
|
882
891
|
}
|
|
883
892
|
}
|
|
893
|
+
/**
|
|
894
|
+
* #1452: real run duration in ms from a vitest/jest `--json` payload.
|
|
895
|
+
*
|
|
896
|
+
* NOT `testResults[].perfStats`. That field exists on jest's INTERNAL
|
|
897
|
+
* `TestResult`, but the JSON reporter's `formatTestResults` projects it to
|
|
898
|
+
* per-suite `startTime`/`endTime` and drops it — measured absent from both
|
|
899
|
+
* vitest 4.1.10 and jest 30.4.2 output, so reading it would have left this
|
|
900
|
+
* at 0. The per-suite epoch pair is what both reporters actually emit.
|
|
901
|
+
*
|
|
902
|
+
* Wall-clock SPAN across suites (max end - min start), not a sum: suites in
|
|
903
|
+
* one payload may have run in parallel workers, and summing would report
|
|
904
|
+
* more elapsed time than the run took. With the single suite pi-lens
|
|
905
|
+
* actually produces (one test file per invocation) the two agree.
|
|
906
|
+
*
|
|
907
|
+
* The span excludes the runner's own startup: the top-level `startTime` is
|
|
908
|
+
* ~330ms earlier than the first suite's on this repo. What the per-suite
|
|
909
|
+
* pair then measures is NOT the same quantity across runners. On vitest it
|
|
910
|
+
* tracks test time closely (135ms span against 134ms of summed assertions),
|
|
911
|
+
* but jest stamps a suite's `startTime` before transform and module load,
|
|
912
|
+
* so the same fields give 5595ms against 128ms of assertions. Both are
|
|
913
|
+
* honest suite wall clock; neither is comparable to the other, and only the
|
|
914
|
+
* vitest figure is close to what pytest's `in 0.05s` or ExUnit's
|
|
915
|
+
* `Finished in 0.05 seconds` report.
|
|
916
|
+
*
|
|
917
|
+
* Falls back to the summed per-assertion `duration` when a reporter omits
|
|
918
|
+
* the suite pair. Never returns a negative or non-finite value — a garbled
|
|
919
|
+
* payload must degrade to "unmeasured", not to a wrong number.
|
|
920
|
+
*
|
|
921
|
+
* #1479: that degradation is now literal. This used to return 0 for a
|
|
922
|
+
* payload it could not read, which is the figure a sub-millisecond suite
|
|
923
|
+
* also produces, so the caller could not tell them apart. It returns
|
|
924
|
+
* `undefined` instead. A readable pair whose span is 0 still returns 0,
|
|
925
|
+
* because that is a measurement.
|
|
926
|
+
*/
|
|
927
|
+
jsonRunDurationMs(suites) {
|
|
928
|
+
let minStart = Number.POSITIVE_INFINITY;
|
|
929
|
+
let maxEnd = Number.NEGATIVE_INFINITY;
|
|
930
|
+
let assertionTotal = 0;
|
|
931
|
+
for (const suite of suites || []) {
|
|
932
|
+
if (typeof suite.startTime === "number" &&
|
|
933
|
+
Number.isFinite(suite.startTime) &&
|
|
934
|
+
typeof suite.endTime === "number" &&
|
|
935
|
+
Number.isFinite(suite.endTime)) {
|
|
936
|
+
minStart = Math.min(minStart, suite.startTime);
|
|
937
|
+
maxEnd = Math.max(maxEnd, suite.endTime);
|
|
938
|
+
}
|
|
939
|
+
for (const assertion of suite.assertionResults || []) {
|
|
940
|
+
if (typeof assertion.duration === "number" &&
|
|
941
|
+
Number.isFinite(assertion.duration) &&
|
|
942
|
+
assertion.duration > 0) {
|
|
943
|
+
assertionTotal += assertion.duration;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
const span = maxEnd - minStart;
|
|
948
|
+
if (Number.isFinite(span) && span > 0)
|
|
949
|
+
return Math.round(span);
|
|
950
|
+
if (assertionTotal > 0)
|
|
951
|
+
return Math.round(assertionTotal);
|
|
952
|
+
// Ordering above is unchanged from #1452 on purpose: a positive span
|
|
953
|
+
// still beats the assertion sum, and the sum still beats a suite pair
|
|
954
|
+
// that read as zero. Only the terminal case moved. A pair we could
|
|
955
|
+
// read whose span is 0 is a run that took under a millisecond — report
|
|
956
|
+
// it. Everything else was never measured.
|
|
957
|
+
if (Number.isFinite(span) && span === 0)
|
|
958
|
+
return 0;
|
|
959
|
+
return undefined;
|
|
960
|
+
}
|
|
884
961
|
// --- Vitest Parser ---
|
|
885
962
|
parseVitestOutput(stdout, stderr, testFile, cwd, runner) {
|
|
886
963
|
return this.parseJsonTestOutput(stdout, stderr, testFile, cwd, runner);
|
|
@@ -899,7 +976,9 @@ export class TestRunnerClient {
|
|
|
899
976
|
let passed = 0;
|
|
900
977
|
let failed = 0;
|
|
901
978
|
let skipped = 0;
|
|
902
|
-
|
|
979
|
+
// #1479: undefined until pytest's own `in N.NNs` is read. `in 0.00s` is
|
|
980
|
+
// a real pytest summary, so 0 has to stay available as a measurement.
|
|
981
|
+
let duration;
|
|
903
982
|
if (summaryMatch) {
|
|
904
983
|
// Extract numbers from various patterns
|
|
905
984
|
const passedMatch = output.match(/(\d+)\s+passed/);
|
|
@@ -909,7 +988,12 @@ export class TestRunnerClient {
|
|
|
909
988
|
passed = passedMatch ? parseInt(passedMatch[1], 10) : 0;
|
|
910
989
|
failed = failedMatch ? parseInt(failedMatch[1], 10) : 0;
|
|
911
990
|
skipped = skippedMatch ? parseInt(skippedMatch[1], 10) : 0;
|
|
912
|
-
|
|
991
|
+
// Rounded, like `jsonRunDurationMs` and PHPUnit's legacy path:
|
|
992
|
+
// `in 2.01s` is 2009.9999999999998 in binary floating point, and
|
|
993
|
+
// the turn-end log prints the number as it stands.
|
|
994
|
+
// (Routing this through `toMeasuredDurationMs` is #1484, not this.)
|
|
995
|
+
if (durationMatch)
|
|
996
|
+
duration = Math.round(parseFloat(durationMatch[1]) * 1000);
|
|
913
997
|
}
|
|
914
998
|
// Parse individual failures: "FAILED tests/test_foo.py::test_something - AssertionError: ..."
|
|
915
999
|
const failureRegex = /FAILED\s+(\S+::\S+)\s*-\s*(.+?)(?:\n|$)/g;
|
|
@@ -972,6 +1056,46 @@ export class TestRunnerClient {
|
|
|
972
1056
|
while ((match = failureRegex.exec(output)) !== null) {
|
|
973
1057
|
failures.push({ name: match[1], message: match[1] });
|
|
974
1058
|
}
|
|
1059
|
+
// #1452: PHPUnit prints its own elapsed time and this parser dropped it,
|
|
1060
|
+
// so every PHPUnit run reported 0ms. Two shapes are accepted because the
|
|
1061
|
+
// summary changed across supported majors:
|
|
1062
|
+
// PHPUnit >= 9.3 "Time: 00:00.123, Memory: 8.00 MB" (HH:)MM:SS.mmm
|
|
1063
|
+
// PHPUnit <= 9.2 "Time: 1.23 seconds, Memory: 10.00MB" | "Time: 123 ms"
|
|
1064
|
+
// NOT VERIFIED AGAINST A LIVE PHPUnit — there is no PHP toolchain on the
|
|
1065
|
+
// box this was written on. Both shapes are covered by unit tests against
|
|
1066
|
+
// literal summary lines taken from the PHPUnit printers, and the parser
|
|
1067
|
+
// leaves duration UNMEASURED when neither matches (#1479 — it used to
|
|
1068
|
+
// leave 0, which the turn-end log printed as a measurement), so an
|
|
1069
|
+
// unrecognised summary degrades to "we do not know" rather than to a
|
|
1070
|
+
// wrong figure.
|
|
1071
|
+
let duration;
|
|
1072
|
+
const clockMatch = output.match(/^Time:\s*(?:(\d+):)?(\d{1,2}):(\d{2})(?:\.(\d{1,3}))?/im);
|
|
1073
|
+
if (clockMatch) {
|
|
1074
|
+
const hours = clockMatch[1] ? Number.parseInt(clockMatch[1], 10) : 0;
|
|
1075
|
+
const minutes = Number.parseInt(clockMatch[2], 10);
|
|
1076
|
+
const seconds = Number.parseInt(clockMatch[3], 10);
|
|
1077
|
+
// ".1" is a tenth, ".12" hundredths — pad rather than parseInt, or
|
|
1078
|
+
// "Time: 00:00.1" would read as 1ms instead of 100ms.
|
|
1079
|
+
const millis = clockMatch[4]
|
|
1080
|
+
? Number.parseInt(clockMatch[4].padEnd(3, "0"), 10)
|
|
1081
|
+
: 0;
|
|
1082
|
+
duration = ((hours * 60 + minutes) * 60 + seconds) * 1000 + millis;
|
|
1083
|
+
}
|
|
1084
|
+
else {
|
|
1085
|
+
const legacyMatch = output.match(/^Time:\s*([\d.]+)\s*(seconds?|s|ms|milliseconds?|minutes?)\b/im);
|
|
1086
|
+
if (legacyMatch) {
|
|
1087
|
+
const value = Number.parseFloat(legacyMatch[1]);
|
|
1088
|
+
const unit = legacyMatch[2].toLowerCase();
|
|
1089
|
+
const scale = unit.startsWith("ms") || unit.startsWith("milli")
|
|
1090
|
+
? 1
|
|
1091
|
+
: unit.startsWith("min")
|
|
1092
|
+
? 60_000
|
|
1093
|
+
: 1000;
|
|
1094
|
+
if (Number.isFinite(value) && value > 0) {
|
|
1095
|
+
duration = Math.round(value * scale);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
975
1099
|
return {
|
|
976
1100
|
file: testFile,
|
|
977
1101
|
sourceFile: "",
|
|
@@ -980,7 +1104,7 @@ export class TestRunnerClient {
|
|
|
980
1104
|
failed,
|
|
981
1105
|
skipped,
|
|
982
1106
|
failures,
|
|
983
|
-
duration
|
|
1107
|
+
duration,
|
|
984
1108
|
error: exitCode !== 0 && passed === 0 && failed === 0
|
|
985
1109
|
? "PHPUnit runner error"
|
|
986
1110
|
: undefined,
|
|
@@ -992,7 +1116,8 @@ export class TestRunnerClient {
|
|
|
992
1116
|
let passed = 0;
|
|
993
1117
|
let failed = 0;
|
|
994
1118
|
let skipped = 0;
|
|
995
|
-
|
|
1119
|
+
// #1479: undefined until ExUnit's own `Finished in N seconds` is read.
|
|
1120
|
+
let duration;
|
|
996
1121
|
// Summary: "3 tests, 1 failure" (optionally ", N excluded" / ", N skipped")
|
|
997
1122
|
const summaryMatch = output.match(/(\d+)\s+tests?,\s*(\d+)\s+failures?(?:,\s*(\d+)\s+excluded)?(?:,\s*(\d+)\s+skipped)?/i);
|
|
998
1123
|
if (summaryMatch) {
|
|
@@ -1007,7 +1132,10 @@ export class TestRunnerClient {
|
|
|
1007
1132
|
}
|
|
1008
1133
|
const durationMatch = output.match(/Finished in\s+([\d.]+)\s+seconds?/i);
|
|
1009
1134
|
if (durationMatch) {
|
|
1010
|
-
|
|
1135
|
+
// Rounded for the same reason pytest's is: `2.01` seconds is
|
|
1136
|
+
// 2009.9999999999998 ms unrounded, and that reaches the log.
|
|
1137
|
+
// (Routing this through `toMeasuredDurationMs` is #1484.)
|
|
1138
|
+
duration = Math.round(Number.parseFloat(durationMatch[1]) * 1000);
|
|
1011
1139
|
}
|
|
1012
1140
|
// Individual failures: " 1) test some behavior (MyModuleTest)"
|
|
1013
1141
|
const failures = [];
|
|
@@ -1035,43 +1163,306 @@ export class TestRunnerClient {
|
|
|
1035
1163
|
};
|
|
1036
1164
|
}
|
|
1037
1165
|
// --- Generic text parser for non-JSON runners ---
|
|
1166
|
+
/**
|
|
1167
|
+
* #1480: elapsed time for the runners `parseGenericRunnerOutput` handles.
|
|
1168
|
+
*
|
|
1169
|
+
* Before this, only go's `ok pkg 0.25s` was read and every other runner
|
|
1170
|
+
* reported a hardcoded 0. #1479 made the log tell "measured" from
|
|
1171
|
+
* "unmeasured", but this parser is the `default:` arm behind cargo, dotnet,
|
|
1172
|
+
* maven, gradle, rspec, minitest and every unrecognised runner, so all of
|
|
1173
|
+
* them still reported a number nobody measured. Each runner below prints
|
|
1174
|
+
* its elapsed time in the same summary block this parser already regexes
|
|
1175
|
+
* for pass/fail counts.
|
|
1176
|
+
*
|
|
1177
|
+
* Absent, not 0, is the answer when nothing is found — see
|
|
1178
|
+
* `TestResult.duration` and `run-duration.ts`. A probe that returned 0 here
|
|
1179
|
+
* would be claiming a measurement.
|
|
1180
|
+
*
|
|
1181
|
+
* One parser serves all runners, so the probe is selected BY RUNNER NAME.
|
|
1182
|
+
* Running every probe over every runner's output was the original shape of
|
|
1183
|
+
* this code, and it let gradle borrow a number: `BUILD SUCCESSFUL in 3s`
|
|
1184
|
+
* plus a preceding `... ok` line satisfied go's `ok <pkg> <n>s` probe, so
|
|
1185
|
+
* the whole-build wall clock got reported as test time — the exact wrong
|
|
1186
|
+
* number this function refuses to print. Gating on the runner makes that
|
|
1187
|
+
* structurally impossible rather than merely unlikely, and it matters most
|
|
1188
|
+
* for the `default:` arm of the switch, which is where an unrecognised or
|
|
1189
|
+
* custom runner's arbitrary output lands.
|
|
1190
|
+
*
|
|
1191
|
+
* Within a runner the patterns are still anchored where an anchor helps,
|
|
1192
|
+
* for the same reason #1452's PHPUnit `Time:` pattern is anchored: an
|
|
1193
|
+
* unanchored /m match takes the FIRST hit over stdout+stderr, and a failure
|
|
1194
|
+
* diff quoting "Finished in ..." would beat the real summary. Note what the
|
|
1195
|
+
* `^` in `^Finished in` does and does not buy. It rejects a decoy that is
|
|
1196
|
+
* INDENTED, which is what a quoted expectation or an assertion diff is; it
|
|
1197
|
+
* does NOT rank two column-0 matches, so an unindented decoy printed by the
|
|
1198
|
+
* suite itself would still win. It is a cheap filter for the common shape,
|
|
1199
|
+
* not a proof of uniqueness. And it is not an anchor to the counts line for
|
|
1200
|
+
* rspec or minitest: both print their elapsed time on a `Finished in ...`
|
|
1201
|
+
* line and their counts (`3 examples, 0 failures`, `1 runs, 1 assertions,
|
|
1202
|
+
* ...`) on a different line.
|
|
1203
|
+
*
|
|
1204
|
+
* KNOWN LIMIT — first summary only. cargo across multiple crates, `dotnet
|
|
1205
|
+
* test` across multiple assemblies, and `go test ./...` across multiple
|
|
1206
|
+
* packages each print one summary per unit, and these probes take the
|
|
1207
|
+
* first. A multi-unit run therefore UNDER-REPORTS its duration. That is
|
|
1208
|
+
* left as-is deliberately: the count parsers below have the same first-match
|
|
1209
|
+
* shape for those runners, so duration and counts describe the same scope.
|
|
1210
|
+
* Fixing one without the other would trade an under-report for an
|
|
1211
|
+
* inconsistency. Pinned by test so it stays a known limit, not an accident.
|
|
1212
|
+
*
|
|
1213
|
+
* Formats and how each was verified:
|
|
1214
|
+
*
|
|
1215
|
+
* - go — `ok example.com/pkg 0.253s`. Pre-existing pattern, unchanged
|
|
1216
|
+
* apart from the shared finite/non-negative guard.
|
|
1217
|
+
*
|
|
1218
|
+
* - cargo — `test result: ok. 3 passed; 0 failed; 1 ignored; 0 measured;
|
|
1219
|
+
* 0 filtered out; finished in 0.253s`. NOT VERIFIED AGAINST A LIVE CARGO
|
|
1220
|
+
* RUN — this box has no MSVC linker, so `cargo test` cannot link. Format
|
|
1221
|
+
* read out of the libtest printer shipped with the local rustc 1.94.1:
|
|
1222
|
+
* `library/test/src/formatters/pretty.rs` builds `"; finished in
|
|
1223
|
+
* {exec_time}"` and `library/test/src/time.rs` renders `TestSuiteExecTime`
|
|
1224
|
+
* as `{:.2}s`. Older rustc omits the suffix entirely; that degrades to
|
|
1225
|
+
* unmeasured.
|
|
1226
|
+
*
|
|
1227
|
+
* - dotnet/vstest — `Failed: 1, Passed: 2, Skipped: 0, Total: 3, Duration:
|
|
1228
|
+
* 1 m 30 s - t.dll (net8.0)`. NOT VERIFIED AGAINST A LIVE `dotnet test` —
|
|
1229
|
+
* NuGet restore has no network here. Format read out of the
|
|
1230
|
+
* vstest.console.dll shipped with the local .NET SDK 8.0.423, which holds
|
|
1231
|
+
* the literal `{0} - Failed: {1}, Passed: {2}, Skipped: {3}, Total: {4},
|
|
1232
|
+
* Duration: {5}` next to the unit literals `" h"`, `" m"`, `" s"`,
|
|
1233
|
+
* `" ms"`, `"< 1 ms"`. The duration is a space-joined token list, so it
|
|
1234
|
+
* is summed rather than read as one number.
|
|
1235
|
+
*
|
|
1236
|
+
* - maven/surefire — `Tests run: 4, Failures: 0, Errors: 0, Skipped: 0,
|
|
1237
|
+
* Time elapsed: 0.05 s -- in com.example.AppTest`. NOT VERIFIED AGAINST A
|
|
1238
|
+
* LIVE MAVEN — no mvn on this box. Summed across the per-class lines,
|
|
1239
|
+
* because surefire prints `Time elapsed` per test class and its final
|
|
1240
|
+
* `Results:` total carries no time. `[INFO] Total time: 3.4 s` is
|
|
1241
|
+
* deliberately NOT used: that is whole-build wall clock including compile,
|
|
1242
|
+
* which would report a wrong number rather than none. Surefire 2.x wrote
|
|
1243
|
+
* `sec` where 3.x writes `s`; both are accepted.
|
|
1244
|
+
*
|
|
1245
|
+
* EXPECT THIS TO BE ABSENT IN PRACTICE. pi-lens invokes `mvn test -q`
|
|
1246
|
+
* (see RUNNERS.maven above), and surefire logs its per-class `Tests run:
|
|
1247
|
+
* ..., Time elapsed: ...` lines at INFO, which `-q` suppresses. Only the
|
|
1248
|
+
* ERROR-level lines of a FAILING class survive, so a green maven run
|
|
1249
|
+
* typically reports unmeasured and a red one reports the failing classes'
|
|
1250
|
+
* time alone. REASONED, NOT RUN — there is no mvn on this box to confirm
|
|
1251
|
+
* it. Left in rather than dropped: it costs nothing, it is correct when
|
|
1252
|
+
* the output does carry the lines (a repo that sets `-Dsurefire.useFile`
|
|
1253
|
+
* or drops `-q` via `.mvn/maven.config`), and `unmeasured` is an honest
|
|
1254
|
+
* report of the quiet case.
|
|
1255
|
+
*
|
|
1256
|
+
* - rspec — `Finished in 0.32394 seconds (files took 0.49427 seconds to
|
|
1257
|
+
* load)`. VERIFIED against a live rspec-core 3.13.6 run on ruby 3.4.10.
|
|
1258
|
+
* The minutes form (`Finished in 2 minutes 15.14 seconds`) comes from
|
|
1259
|
+
* `RSpec::Core::Formatters::Helpers.format_duration` in the same
|
|
1260
|
+
* installed gem; rspec never prints hours. Load time trails the run time
|
|
1261
|
+
* on the same line and must not be read instead of it.
|
|
1262
|
+
*
|
|
1263
|
+
* - minitest — `Finished in 0.254594s, 7.8557 runs/s, 7.8557 assertions/s.`
|
|
1264
|
+
* VERIFIED against a live minitest 5.25.4 run on ruby 3.4.10. The format
|
|
1265
|
+
* string is `"Finished in %.6fs, ..."` in minitest.rb, always seconds.
|
|
1266
|
+
*
|
|
1267
|
+
* - gradle — deliberately left unmeasured, and now UNREACHABLE by any other
|
|
1268
|
+
* runner's probe rather than merely unmatched by it. Gradle's console
|
|
1269
|
+
* summary (`4 tests completed, 1 failed`) carries no elapsed time, and
|
|
1270
|
+
* `BUILD SUCCESSFUL in 3s` is whole-build wall clock including compile
|
|
1271
|
+
* and dependency resolution. Reporting that as test time would be a wrong
|
|
1272
|
+
* number; #1479 makes the absence legible in the log instead.
|
|
1273
|
+
*/
|
|
1274
|
+
parseGenericRunnerDuration(output, runner) {
|
|
1275
|
+
switch (runner) {
|
|
1276
|
+
case "go":
|
|
1277
|
+
return this.parseGoDuration(output);
|
|
1278
|
+
case "cargo":
|
|
1279
|
+
return this.parseCargoDuration(output);
|
|
1280
|
+
case "dotnet":
|
|
1281
|
+
return this.parseDotnetDuration(output);
|
|
1282
|
+
case "maven":
|
|
1283
|
+
return this.parseMavenDuration(output);
|
|
1284
|
+
case "rspec":
|
|
1285
|
+
return this.parseRspecDuration(output);
|
|
1286
|
+
case "minitest":
|
|
1287
|
+
return this.parseMinitestDuration(output);
|
|
1288
|
+
default:
|
|
1289
|
+
// gradle and anything unrecognised: unmeasured, never
|
|
1290
|
+
// zero-as-measurement and never another runner's number.
|
|
1291
|
+
return undefined;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
/** go: `ok example.com/pkg 0.253s`. First package summary only. */
|
|
1295
|
+
parseGoDuration(output) {
|
|
1296
|
+
const goSummary = output.match(/ok\s+\S+\s+([\d.]+)s/m);
|
|
1297
|
+
if (!goSummary)
|
|
1298
|
+
return undefined;
|
|
1299
|
+
return toMeasuredDurationMs(Number.parseFloat(goSummary[1]) * 1000);
|
|
1300
|
+
}
|
|
1301
|
+
/** cargo: `...; 0 filtered out; finished in 0.25s`. First crate only. */
|
|
1302
|
+
parseCargoDuration(output) {
|
|
1303
|
+
const cargoTime = output.match(/^test result:.*?;\s*finished in\s+([\d.]+)\s*s\b/im);
|
|
1304
|
+
if (!cargoTime)
|
|
1305
|
+
return undefined;
|
|
1306
|
+
return toMeasuredDurationMs(Number.parseFloat(cargoTime[1]) * 1000);
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* dotnet/vstest: `..., Total: 3, Duration: 1 m 30 s - t.dll (net8.0)`.
|
|
1310
|
+
*
|
|
1311
|
+
* Anchored to the counts line, and the tail stops at the ` - <dll>`
|
|
1312
|
+
* separator: without that stop an assembly name is scanned for unit tokens,
|
|
1313
|
+
* and a name like `Timeouts.30s.Tests.dll` adds 30 seconds of nothing.
|
|
1314
|
+
* First assembly only.
|
|
1315
|
+
*/
|
|
1316
|
+
parseDotnetDuration(output) {
|
|
1317
|
+
const dotnetTime = output.match(/Failed:\s*\d+,\s*Passed:\s*\d+,\s*Skipped:\s*\d+,\s*Total:\s*\d+,\s*Duration:\s*([^\r\n-]+)/i);
|
|
1318
|
+
if (!dotnetTime)
|
|
1319
|
+
return undefined;
|
|
1320
|
+
// `< 1 ms` is vstest's "too fast to name a number", and under the
|
|
1321
|
+
// optional-duration contract 0 is exactly the right thing to say: the
|
|
1322
|
+
// run WAS measured and it rounds to 0 ms. The token scan below would
|
|
1323
|
+
// reach the same 0 by finding no tokens, but only by accident, and the
|
|
1324
|
+
// accident is indistinguishable from an unparseable tail — so the case
|
|
1325
|
+
// is spelled out.
|
|
1326
|
+
if (/^\s*</.test(dotnetTime[1]))
|
|
1327
|
+
return 0;
|
|
1328
|
+
let total = 0;
|
|
1329
|
+
let tokens = 0;
|
|
1330
|
+
// "ms" before "m", or "250 ms" scores as 250 minutes.
|
|
1331
|
+
const units = {
|
|
1332
|
+
ms: 1,
|
|
1333
|
+
s: 1000,
|
|
1334
|
+
m: 60_000,
|
|
1335
|
+
h: 3_600_000,
|
|
1336
|
+
};
|
|
1337
|
+
for (const token of dotnetTime[1].matchAll(/([\d.]+)\s*(ms|h|m|s)\b/gi)) {
|
|
1338
|
+
total += Number.parseFloat(token[1]) * units[token[2].toLowerCase()];
|
|
1339
|
+
tokens++;
|
|
1340
|
+
}
|
|
1341
|
+
// A tail we matched but could not read a single token out of is not a
|
|
1342
|
+
// zero-length run, it is an unrecognised format.
|
|
1343
|
+
if (tokens === 0)
|
|
1344
|
+
return undefined;
|
|
1345
|
+
return toMeasuredDurationMs(total);
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* maven/surefire: summed across per-class `Time elapsed` lines.
|
|
1349
|
+
*
|
|
1350
|
+
* The guard is "did any line match", NOT "is the sum positive". Surefire
|
|
1351
|
+
* prints `Time elapsed: 0.00 s` for a trivial test class, and that is a
|
|
1352
|
+
* measurement of zero, not a failure to measure.
|
|
1353
|
+
*/
|
|
1354
|
+
parseMavenDuration(output) {
|
|
1355
|
+
let surefireTotal = 0;
|
|
1356
|
+
let matched = false;
|
|
1357
|
+
for (const line of output.matchAll(/^.*Tests run:\s*\d+,.*?Time elapsed:\s*([\d.]+)\s*(?:s|sec|secs|seconds)\b.*$/gim)) {
|
|
1358
|
+
const seconds = Number.parseFloat(line[1]);
|
|
1359
|
+
if (!Number.isFinite(seconds) || seconds < 0)
|
|
1360
|
+
continue;
|
|
1361
|
+
surefireTotal += seconds;
|
|
1362
|
+
matched = true;
|
|
1363
|
+
}
|
|
1364
|
+
if (!matched)
|
|
1365
|
+
return undefined;
|
|
1366
|
+
return toMeasuredDurationMs(surefireTotal * 1000);
|
|
1367
|
+
}
|
|
1368
|
+
/** rspec: `Finished in 2 minutes 15.14 seconds (files took 0.5 ...)`. */
|
|
1369
|
+
parseRspecDuration(output) {
|
|
1370
|
+
const rspecTime = output.match(/^Finished in\s+(?:([\d.]+)\s+minutes?\s+)?([\d.]+)\s+seconds?/im);
|
|
1371
|
+
if (!rspecTime)
|
|
1372
|
+
return undefined;
|
|
1373
|
+
const minutes = rspecTime[1] ? Number.parseFloat(rspecTime[1]) : 0;
|
|
1374
|
+
return toMeasuredDurationMs(minutes * 60_000 + Number.parseFloat(rspecTime[2]) * 1000);
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* minitest: `Finished in 0.254594s, 7.8557 runs/s, ...`.
|
|
1378
|
+
*
|
|
1379
|
+
* The trailing `,` is load-bearing, not decoration: it is what separates
|
|
1380
|
+
* minitest's own line from a bare `Finished in 99s` the suite under test
|
|
1381
|
+
* printed at column 0, which the `^` alone does not rank.
|
|
1382
|
+
*/
|
|
1383
|
+
parseMinitestDuration(output) {
|
|
1384
|
+
const minitestTime = output.match(/^Finished in\s+([\d.]+)s\s*,/im);
|
|
1385
|
+
if (!minitestTime)
|
|
1386
|
+
return undefined;
|
|
1387
|
+
return toMeasuredDurationMs(Number.parseFloat(minitestTime[1]) * 1000);
|
|
1388
|
+
}
|
|
1038
1389
|
parseGenericRunnerOutput(stdout, stderr, exitCode, testFile, runner) {
|
|
1039
1390
|
const output = `${stdout}\n${stderr}`;
|
|
1040
1391
|
const lower = output.toLowerCase();
|
|
1041
1392
|
let passed = 0;
|
|
1042
|
-
|
|
1393
|
+
// #1487: NOT `exitCode === 0 ? 0 : 1`. Pre-seeding `failed` to 1 on a
|
|
1394
|
+
// non-zero exit made the runner-error branch below unreachable — it
|
|
1395
|
+
// requires `failed === 0`, which a spawn/config/load failure (no
|
|
1396
|
+
// counts to parse) could never reach once this had already claimed
|
|
1397
|
+
// the slot. `matched` tracks whether a count parser actually found
|
|
1398
|
+
// real counts; the exit-code-distrust fallback further down uses it
|
|
1399
|
+
// to tell "a runner that never ran" from "a runner whose summary
|
|
1400
|
+
// legitimately parsed to zero failures".
|
|
1401
|
+
let failed = 0;
|
|
1043
1402
|
let skipped = 0;
|
|
1044
|
-
let
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1403
|
+
let matched = false;
|
|
1404
|
+
// #1480: `number | undefined`, and sourced per runner. This used to be
|
|
1405
|
+
// `let duration = 0` with only go's probe able to move it, so every
|
|
1406
|
+
// other runner reported a zero it never measured.
|
|
1407
|
+
const duration = this.parseGenericRunnerDuration(output, runner);
|
|
1049
1408
|
const cargoSummary = output.match(/test result:\s+\w+\.\s+(\d+)\s+passed;\s+(\d+)\s+failed;\s+(\d+)\s+ignored;/i);
|
|
1050
1409
|
if (cargoSummary) {
|
|
1051
1410
|
passed = Number.parseInt(cargoSummary[1], 10);
|
|
1052
1411
|
failed = Number.parseInt(cargoSummary[2], 10);
|
|
1053
1412
|
skipped = Number.parseInt(cargoSummary[3], 10);
|
|
1413
|
+
matched = true;
|
|
1054
1414
|
}
|
|
1055
1415
|
const dotnetSummary = output.match(/Failed:\s*(\d+),\s*Passed:\s*(\d+),\s*Skipped:\s*(\d+)/i);
|
|
1056
1416
|
if (dotnetSummary) {
|
|
1057
1417
|
failed = Number.parseInt(dotnetSummary[1], 10);
|
|
1058
1418
|
passed = Number.parseInt(dotnetSummary[2], 10);
|
|
1059
1419
|
skipped = Number.parseInt(dotnetSummary[3], 10);
|
|
1420
|
+
matched = true;
|
|
1060
1421
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1422
|
+
// #1480 (adjacent, duration-independent): surefire prints one
|
|
1423
|
+
// `Tests run:` line PER TEST CLASS (those carry `Time elapsed:`) and
|
|
1424
|
+
// then a per-MODULE aggregate under `Results:` (which does not). Taking
|
|
1425
|
+
// the FIRST match scored a run by its first class alone — a two-class
|
|
1426
|
+
// run with a failure in the second class reported 0 failures.
|
|
1427
|
+
//
|
|
1428
|
+
// Taking the LAST match is just as wrong, in a worse direction. A
|
|
1429
|
+
// multi-module reactor run prints one `Results:` aggregate per module,
|
|
1430
|
+
// and the last is the last module: a `--fail-at-end` build whose first
|
|
1431
|
+
// module had 3 failures and whose second module was green would report
|
|
1432
|
+
// 0 failures, turning a red build into `PASS` in the turn-end log. That
|
|
1433
|
+
// is reachable without pi-lens passing the flag, because maven also
|
|
1434
|
+
// reads `.mvn/maven.config` and `MAVEN_ARGS`.
|
|
1435
|
+
//
|
|
1436
|
+
// So: SUM the aggregates. That makes counts reactor-wide, the same
|
|
1437
|
+
// scope `parseMavenDuration` sums its per-class times over. When no
|
|
1438
|
+
// aggregate is present (output truncated, or `Results:` suppressed) the
|
|
1439
|
+
// per-class lines sum to the same totals, so they are the fallback.
|
|
1440
|
+
const mavenLines = [
|
|
1441
|
+
...output.matchAll(/^.*?Tests run:\s*(\d+),\s*Failures:\s*(\d+),\s*Errors:\s*(\d+),\s*Skipped:\s*(\d+).*$/gim),
|
|
1442
|
+
];
|
|
1443
|
+
const mavenAggregates = mavenLines.filter((line) => !/Time elapsed:/i.test(line[0]));
|
|
1444
|
+
const mavenScored = mavenAggregates.length > 0 ? mavenAggregates : mavenLines;
|
|
1445
|
+
if (mavenScored.length > 0) {
|
|
1446
|
+
let total = 0;
|
|
1447
|
+
let mavenFailed = 0;
|
|
1448
|
+
let mavenSkipped = 0;
|
|
1449
|
+
for (const line of mavenScored) {
|
|
1450
|
+
total += Number.parseInt(line[1], 10);
|
|
1451
|
+
mavenFailed +=
|
|
1452
|
+
Number.parseInt(line[2], 10) + Number.parseInt(line[3], 10);
|
|
1453
|
+
mavenSkipped += Number.parseInt(line[4], 10);
|
|
1454
|
+
}
|
|
1455
|
+
failed = mavenFailed;
|
|
1456
|
+
skipped = mavenSkipped;
|
|
1068
1457
|
passed = Math.max(0, total - failed - skipped);
|
|
1458
|
+
matched = true;
|
|
1069
1459
|
}
|
|
1070
1460
|
const rspecSummary = output.match(/(\d+)\s+examples?,\s+(\d+)\s+failures?/i);
|
|
1071
1461
|
if (rspecSummary) {
|
|
1072
1462
|
const total = Number.parseInt(rspecSummary[1], 10);
|
|
1073
1463
|
failed = Number.parseInt(rspecSummary[2], 10);
|
|
1074
1464
|
passed = Math.max(0, total - failed);
|
|
1465
|
+
matched = true;
|
|
1075
1466
|
}
|
|
1076
1467
|
const minitestSummary = output.match(/(\d+)\s+runs?,\s+\d+\s+assertions?,\s+(\d+)\s+failures?,\s+(\d+)\s+errors?/i);
|
|
1077
1468
|
if (minitestSummary) {
|
|
@@ -1080,25 +1471,160 @@ export class TestRunnerClient {
|
|
|
1080
1471
|
const errors = Number.parseInt(minitestSummary[3], 10);
|
|
1081
1472
|
failed = failures + errors;
|
|
1082
1473
|
passed = Math.max(0, total - failed);
|
|
1474
|
+
matched = true;
|
|
1083
1475
|
}
|
|
1084
1476
|
const gradleSummary = output.match(/(\d+)\s+tests? completed,\s+(\d+)\s+failed/i);
|
|
1085
1477
|
if (gradleSummary) {
|
|
1086
1478
|
const total = Number.parseInt(gradleSummary[1], 10);
|
|
1087
1479
|
failed = Number.parseInt(gradleSummary[2], 10);
|
|
1088
1480
|
passed = Math.max(0, total - failed);
|
|
1481
|
+
matched = true;
|
|
1089
1482
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1483
|
+
// #1487/#1524/#1524-r3: `--- FAIL: TestName` is extracted first and
|
|
1484
|
+
// separately from the other two name patterns, because it is the
|
|
1485
|
+
// ONLY one of the three that is unambiguous evidence a real go test
|
|
1486
|
+
// ran — see the `runnerError` comment below for why the other two
|
|
1487
|
+
// may no longer veto on their own. go otherwise has no count parser
|
|
1488
|
+
// at all (only a duration probe), so without this go's `matched`
|
|
1489
|
+
// stayed false even on a real failure.
|
|
1490
|
+
//
|
|
1491
|
+
// #1524-r3: go ALSO gets a real count parser here, not just a name
|
|
1492
|
+
// extractor. A panic inside `TestMain`/package `init` (exit 2) never
|
|
1493
|
+
// prints a `--- FAIL:` line — there is no test to name, the process
|
|
1494
|
+
// died before any test ran — so `goFailNames` alone left `matched`
|
|
1495
|
+
// false and such a panic hit the same runner-error branch as a
|
|
1496
|
+
// spawn failure, even though `go test` DID run and DID fail. `FAIL
|
|
1497
|
+
// \t<pkg>` is go's own per-package verdict line and is printed for
|
|
1498
|
+
// exactly this case; `ok <pkg> <n>s` is its pass counterpart.
|
|
1499
|
+
// Package-line counts, like the go duration probe above, take the
|
|
1500
|
+
// FIRST package summary only — the same known limit already
|
|
1501
|
+
// documented on `parseGenericRunnerDuration`.
|
|
1502
|
+
const goFailNames = [
|
|
1503
|
+
...output.matchAll(/--- FAIL:[^\S\n]+([^\s(]+)/g),
|
|
1504
|
+
];
|
|
1505
|
+
// #1524-r4: only COUNT unparented failures. go prints a `--- FAIL:`
|
|
1506
|
+
// line for every level of a failing subtest tree — `TestA` AND
|
|
1507
|
+
// `TestA/sub` each get their own line for what is really one
|
|
1508
|
+
// underlying failure — so counting every line inflated `failed`
|
|
1509
|
+
// (two lines here read as two failures for one broken test). A name
|
|
1510
|
+
// containing "/" whose prefix up to that "/" is itself in the list
|
|
1511
|
+
// is a subtest of an already-counted parent; only names that are
|
|
1512
|
+
// NOT such a subtest count. `goFailNames` itself (all lines) is
|
|
1513
|
+
// still what decides `matched`/the runner-error veto below and
|
|
1514
|
+
// what's listed in `failures` — subtest names are still useful
|
|
1515
|
+
// detail there, just not double-counted.
|
|
1516
|
+
const goFailNameSet = new Set(goFailNames.map((m) => m[1].trim()));
|
|
1517
|
+
const goTopLevelFailCount = goFailNames.filter((m) => {
|
|
1518
|
+
const name = m[1].trim();
|
|
1519
|
+
const slash = name.indexOf("/");
|
|
1520
|
+
return slash === -1 || !goFailNameSet.has(name.slice(0, slash));
|
|
1521
|
+
}).length;
|
|
1522
|
+
if (runner === "go") {
|
|
1523
|
+
// #1524-r4: `(?![^\n]*\[(?:build|setup) failed\])` rejects go's
|
|
1524
|
+
// INFRASTRUCTURE verdict lines — `FAIL <pkg> [build failed]` (a
|
|
1525
|
+
// compile error) and `FAIL <pkg> [setup failed]` (no packages to
|
|
1526
|
+
// test) — which matched the same shape as a real `FAIL <pkg>
|
|
1527
|
+
// <duration>` test verdict line. Neither ran a single test; both
|
|
1528
|
+
// were rendering as `✗ 1/1 failed ✗ go failure` instead of the
|
|
1529
|
+
// runner error they are.
|
|
1530
|
+
const goFailPackage = /^FAIL(?![^\n]*\[(?:build|setup) failed\])[^\S\n]+\S+/m.test(output);
|
|
1531
|
+
const goOkPackages = [
|
|
1532
|
+
...output.matchAll(/^ok[^\S\n]+\S+[^\S\n]+[\d.]+s/gm),
|
|
1533
|
+
];
|
|
1534
|
+
if (goFailNames.length > 0 || goFailPackage) {
|
|
1535
|
+
failed = Math.max(failed, goTopLevelFailCount || 1);
|
|
1536
|
+
matched = true;
|
|
1537
|
+
}
|
|
1538
|
+
// #1524-r4: unconditional, not `else if`. A multi-package run
|
|
1539
|
+
// can have BOTH a real failure and packages that passed clean —
|
|
1540
|
+
// `ok a` / `--- FAIL: TestB` / `ok c` is 2 passed AND 1 failed,
|
|
1541
|
+
// not "1 failed, 0 passed" with the green packages silently
|
|
1542
|
+
// dropped because the fail branch above already ran.
|
|
1543
|
+
if (goOkPackages.length > 0) {
|
|
1544
|
+
passed = Math.max(passed, goOkPackages.length);
|
|
1545
|
+
matched = true;
|
|
1546
|
+
}
|
|
1092
1547
|
}
|
|
1548
|
+
// #1524-r3: anchored to line START (`^`), and `[^\S\n]` (horizontal
|
|
1549
|
+
// whitespace only) in place of `\s` between the keyword and the
|
|
1550
|
+
// name. The prior `\bFAILED\s+([^\n]+)` and `Failure:\s+([^\n]+)`
|
|
1551
|
+
// let their OWN `\s+` gap cross the newline: a bare `FAILED` at the
|
|
1552
|
+
// end of a line has nothing after it on that line, so `\s+` ate the
|
|
1553
|
+
// newline itself and `([^\n]+)` captured the FIRST TOKEN OF THE
|
|
1554
|
+
// NEXT LINE as the "test name" — proven on a gradle compile failure
|
|
1555
|
+
// (`> Task :compileJava FAILED` followed by
|
|
1556
|
+
// `FAILURE: Build failed with an exception.`, which then rendered
|
|
1557
|
+
// as the invented failure name) and the newline-spanning shape in
|
|
1558
|
+
// general (`FAILED\n\nsome trailing note` → name "some trailing
|
|
1559
|
+
// note"). Requiring `(\S.*)$` on the SAME line makes a keyword with
|
|
1560
|
+
// nothing following it on that line simply not match, rather than
|
|
1561
|
+
// reaching across for content that was never the name.
|
|
1093
1562
|
const failures = [];
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1563
|
+
for (const m of goFailNames.slice(0, 5)) {
|
|
1564
|
+
failures.push({ name: m[1].trim(), message: m[1].trim() });
|
|
1565
|
+
}
|
|
1566
|
+
const otherNames = [
|
|
1567
|
+
...output.matchAll(/^[^\S\n]*FAILED[^\S\n]+(\S.*)$/gm),
|
|
1568
|
+
...output.matchAll(/^[^\S\n]*Failure:[^\S\n]+(\S.*)$/gm),
|
|
1098
1569
|
];
|
|
1099
|
-
for (const m of
|
|
1570
|
+
for (const m of otherNames) {
|
|
1571
|
+
if (failures.length >= 5)
|
|
1572
|
+
break;
|
|
1100
1573
|
failures.push({ name: m[1].trim(), message: m[1].trim() });
|
|
1101
1574
|
}
|
|
1575
|
+
// #1487: gated on `!matched`, not on `failed === 0`. A non-zero exit
|
|
1576
|
+
// with NO count parser match — a spawn/config/load failure, nothing
|
|
1577
|
+
// ran — is infrastructure, not a test verdict: report it as a runner
|
|
1578
|
+
// error. A non-zero exit a count parser DID match (even to a
|
|
1579
|
+
// legitimate `failed === 0`, e.g. a green last module of a red
|
|
1580
|
+
// reactor) is a real run that produced real counts, so it is never a
|
|
1581
|
+
// runner error, and the exit-code-distrust guard below still forces
|
|
1582
|
+
// at least one failure so it can't render as PASS.
|
|
1583
|
+
//
|
|
1584
|
+
// #1524-r3: the veto is `goFailNames.length === 0`, NOT
|
|
1585
|
+
// `failures.length === 0`. Only `--- FAIL:` is a marker a test
|
|
1586
|
+
// runner emits SPECIFICALLY for a failed test; `FAILED`/`Failure:`
|
|
1587
|
+
// are generic words a build tool prints for reasons that have
|
|
1588
|
+
// nothing to do with a test — a gradle task failing to compile, a
|
|
1589
|
+
// maven goal failing before surefire ever runs, an rspec file that
|
|
1590
|
+
// never loaded. Letting those two veto the runner-error branch on
|
|
1591
|
+
// their own reintroduced #1487's exact symptom for the commonest
|
|
1592
|
+
// gradle/maven/rspec failure shapes (proven: `[ERROR] Failed to
|
|
1593
|
+
// execute goal ... FAILED` and rspec's `Failure: cannot load such
|
|
1594
|
+
// file` both rendered as a fabricated test failure instead of the
|
|
1595
|
+
// runner error they actually are). They still label a name onto
|
|
1596
|
+
// `failures` above when `matched` or `goFailNames` already settled
|
|
1597
|
+
// the question some other way, but they no longer settle it alone.
|
|
1598
|
+
const runnerError = exitCode !== 0 && !matched && goFailNames.length === 0 && lower.includes("error")
|
|
1599
|
+
? `Runner ${runner} exited with ${exitCode}`
|
|
1600
|
+
: undefined;
|
|
1601
|
+
// #1524-r4 (tidy): a runner-error result reports through
|
|
1602
|
+
// `formatResult`'s runner-error branch, not the failed-tests
|
|
1603
|
+
// branch, so any name `otherNames` picked up before this was
|
|
1604
|
+
// decided (e.g. a same-line `Failure: cannot load such file`)
|
|
1605
|
+
// would sit in `failures` unused but visible to anything reading
|
|
1606
|
+
// the raw `TestResult` — an error result with a non-empty
|
|
1607
|
+
// `failures` list is a self-contradiction. Clear it here so the
|
|
1608
|
+
// result is one or the other, never both.
|
|
1609
|
+
if (runnerError) {
|
|
1610
|
+
failures.length = 0;
|
|
1611
|
+
}
|
|
1612
|
+
// #1480 (adjacent): a non-zero exit is the runner saying the run
|
|
1613
|
+
// failed. Every count parser above can legitimately arrive at
|
|
1614
|
+
// `failed === 0` — a summary that only covers part of the run, a green
|
|
1615
|
+
// module of a red reactor build, a failure outside any test — and the
|
|
1616
|
+
// turn-end log would then print PASS over a build the runner rejected.
|
|
1617
|
+
// Trust the exit code: no parse of the text may talk it out of at least
|
|
1618
|
+
// one failure. Skipped when `runnerError` is set — that case already
|
|
1619
|
+
// reports through `formatResult`'s runner-error branch, which requires
|
|
1620
|
+
// `passed === 0 && failed === 0` to stay reachable, and forcing
|
|
1621
|
+
// `failed` to 1 here would make it unreachable again (#1487).
|
|
1622
|
+
if (exitCode !== 0 && failed === 0 && !runnerError) {
|
|
1623
|
+
failed = 1;
|
|
1624
|
+
}
|
|
1625
|
+
if (passed === 0 && failed === 0 && skipped === 0 && exitCode === 0) {
|
|
1626
|
+
passed = 1;
|
|
1627
|
+
}
|
|
1102
1628
|
if (failures.length === 0 && failed > 0) {
|
|
1103
1629
|
const firstLine = output
|
|
1104
1630
|
.split("\n")
|
|
@@ -1107,6 +1633,9 @@ export class TestRunnerClient {
|
|
|
1107
1633
|
.slice(0, 300) || `Tests failed for runner ${runner}`;
|
|
1108
1634
|
failures.push({ name: `${runner} failure`, message: firstLine });
|
|
1109
1635
|
}
|
|
1636
|
+
this.log(runnerError
|
|
1637
|
+
? `Generic runner ${runner}: never started (${runnerError})`
|
|
1638
|
+
: `Generic runner ${runner}: ran (matched=${matched}, passed=${passed}, failed=${failed}, failures=${failures.length})`);
|
|
1110
1639
|
return {
|
|
1111
1640
|
file: testFile,
|
|
1112
1641
|
sourceFile: "",
|
|
@@ -1116,9 +1645,7 @@ export class TestRunnerClient {
|
|
|
1116
1645
|
skipped,
|
|
1117
1646
|
failures,
|
|
1118
1647
|
duration,
|
|
1119
|
-
error:
|
|
1120
|
-
? `Runner ${runner} exited with ${exitCode}`
|
|
1121
|
-
: undefined,
|
|
1648
|
+
error: runnerError,
|
|
1122
1649
|
};
|
|
1123
1650
|
}
|
|
1124
1651
|
// --- Formatting ---
|
|
@@ -1134,7 +1661,21 @@ export class TestRunnerClient {
|
|
|
1134
1661
|
if (total === 0) {
|
|
1135
1662
|
return ""; // No tests to report
|
|
1136
1663
|
}
|
|
1137
|
-
|
|
1664
|
+
// #1479 deliberately does NOT change this surface. The agent-facing
|
|
1665
|
+
// string already suppressed the suffix for a 0, so an unmeasured run
|
|
1666
|
+
// and a zero-length one look the same here and always did. The issue
|
|
1667
|
+
// scopes the unmeasured/zero distinction to the turn-end log line;
|
|
1668
|
+
// widening it to the LLM prompt is a separate call about prompt noise.
|
|
1669
|
+
// #1480: the "is this a measurement at all" half of the test comes from
|
|
1670
|
+
// `run-duration.ts` so this surface cannot drift from the log's answer.
|
|
1671
|
+
// The `> 0` half is the scope decision above and stays local to it — it
|
|
1672
|
+
// is what suppresses the suffix for a measured zero, which is a choice
|
|
1673
|
+
// about prompt noise rather than about the duration contract. Routing
|
|
1674
|
+
// the first half through the shared predicate also stops a non-finite
|
|
1675
|
+
// duration rendering as ` (Infinitys)`.
|
|
1676
|
+
const durationStr = isMeasuredDuration(result.duration) && result.duration > 0
|
|
1677
|
+
? ` (${(result.duration / 1000).toFixed(2)}s)`
|
|
1678
|
+
: "";
|
|
1138
1679
|
if (result.failed === 0) {
|
|
1139
1680
|
return `[Tests] ✓ ${result.passed}/${total} passed${durationStr} — ${result.runner}`;
|
|
1140
1681
|
}
|
|
@@ -1283,7 +1824,8 @@ export class TestRunnerClient {
|
|
|
1283
1824
|
failed: 0,
|
|
1284
1825
|
skipped: 0,
|
|
1285
1826
|
failures: [],
|
|
1286
|
-
|
|
1827
|
+
// #1479: no duration key at all. Nothing ran, so there is nothing
|
|
1828
|
+
// to report — this used to say 0, which reads as "ran, instantly".
|
|
1287
1829
|
error,
|
|
1288
1830
|
};
|
|
1289
1831
|
}
|