@herbertgao/pi-extensions 2026.9.2 → 2026.9.4
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 +4 -2
- package/THIRD_PARTY_NOTICES.md +49 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +3 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +16 -11
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +20 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +12 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +98 -0
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/sol-pi/LICENSE +19 -0
- package/node_modules/@herbertgao/sol-pi/README.md +159 -0
- package/node_modules/@herbertgao/sol-pi/SECURITY.md +26 -0
- package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +19 -0
- package/node_modules/@herbertgao/sol-pi/agents-install.md +150 -0
- package/node_modules/@herbertgao/sol-pi/assets/sol-pi-hero.png +0 -0
- package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +69 -0
- package/node_modules/@herbertgao/sol-pi/docs/configuration.md +75 -0
- package/node_modules/@herbertgao/sol-pi/package.json +76 -0
- package/node_modules/@herbertgao/sol-pi/scripts/check-pi-compat.mjs +32 -0
- package/node_modules/@herbertgao/sol-pi/scripts/check-sol-pi-config.mjs +120 -0
- package/node_modules/@herbertgao/sol-pi/sol-pi.example.json +10 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/config.ts +135 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/file-queue.ts +74 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +185 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/then-run.ts +128 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/archive.ts +53 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/candidate.ts +101 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/config.ts +71 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/index.ts +220 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/journal.ts +25 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/provider.ts +164 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/receipt.ts +177 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/index.ts +227 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/ledger.ts +20 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/observation.ts +252 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts +237 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +455 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/index.ts +49 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/plan.ts +79 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +208 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/tools.ts +100 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/index.ts +42 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/runtime-paths.ts +17 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/tui.ts +71 -0
- package/node_modules/@narumitw/pi-btw/dist/index.ts +12 -2
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +2 -2
- package/node_modules/@narumitw/pi-btw/package.json +1 -1
- package/node_modules/@narumitw/pi-btw/src/btw.ts +13 -2
- package/node_modules/pi-antigravity/LICENSE +21 -0
- package/node_modules/pi-antigravity/README.md +194 -0
- package/node_modules/pi-antigravity/package.json +67 -0
- package/node_modules/pi-antigravity/src/auth/index.ts +14 -0
- package/node_modules/pi-antigravity/src/auth/oauth.ts +442 -0
- package/node_modules/pi-antigravity/src/client/client.ts +561 -0
- package/node_modules/pi-antigravity/src/client/index.ts +1 -0
- package/node_modules/pi-antigravity/src/diagnostics/diagnostics.ts +96 -0
- package/node_modules/pi-antigravity/src/diagnostics/index.ts +1 -0
- package/node_modules/pi-antigravity/src/image/image.ts +336 -0
- package/node_modules/pi-antigravity/src/image/index.ts +1 -0
- package/node_modules/pi-antigravity/src/index.ts +280 -0
- package/node_modules/pi-antigravity/src/models/discovery.ts +154 -0
- package/node_modules/pi-antigravity/src/models/grouping.ts +424 -0
- package/node_modules/pi-antigravity/src/models/index.ts +3 -0
- package/node_modules/pi-antigravity/src/models/models.ts +500 -0
- package/node_modules/pi-antigravity/src/stream/index.ts +1 -0
- package/node_modules/pi-antigravity/src/stream/stream.ts +1460 -0
- package/node_modules/pi-antigravity/src/types/enums.ts +42 -0
- package/node_modules/pi-antigravity/src/types/index.ts +2 -0
- package/node_modules/pi-antigravity/src/types/types.ts +292 -0
- package/node_modules/pi-antigravity/src/usage/index.ts +1 -0
- package/node_modules/pi-antigravity/src/usage/usage.ts +371 -0
- package/node_modules/pi-antigravity/src/utils/http.ts +91 -0
- package/node_modules/pi-antigravity/src/utils/index.ts +3 -0
- package/node_modules/pi-antigravity/src/utils/security.ts +73 -0
- package/node_modules/pi-antigravity/src/utils/util.ts +132 -0
- package/node_modules/pi-antigravity/tsconfig.json +21 -0
- package/node_modules/pi-lens/CHANGELOG.md +176 -0
- package/node_modules/pi-lens/README.md +13 -8
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -1
- package/node_modules/pi-lens/dist/clients/analysed-root.js +1 -0
- package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +1 -1
- package/node_modules/pi-lens/dist/clients/biome-client.js +13 -2
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +2 -0
- package/node_modules/pi-lens/dist/clients/cache-observability.js +122 -1
- package/node_modules/pi-lens/dist/clients/complexity-client.js +5 -0
- package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +2 -0
- package/node_modules/pi-lens/dist/clients/config-resolve.js +6 -3
- package/node_modules/pi-lens/dist/clients/config-schema.js +17 -0
- package/node_modules/pi-lens/dist/clients/config-warn.js +2 -2
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +8 -1
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +38 -15
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +4 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +19 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +12 -12
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +2 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +12 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +2 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +5 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +2 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +8 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +4 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +2 -1
- package/node_modules/pi-lens/dist/clients/effective-config.js +12 -2
- package/node_modules/pi-lens/dist/clients/extension-log.js +2 -0
- package/node_modules/pi-lens/dist/clients/file-role.js +18 -1
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +4 -0
- package/node_modules/pi-lens/dist/clients/formatters.js +54 -188
- package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -0
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +42 -6
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +48 -7
- package/node_modules/pi-lens/dist/clients/installer/index.js +196 -52
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +9 -1
- package/node_modules/pi-lens/dist/clients/knip-client.js +9 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +12 -2
- package/node_modules/pi-lens/dist/clients/latency-logger.js +2 -0
- package/node_modules/pi-lens/dist/clients/lens-config.js +4 -0
- package/node_modules/pi-lens/dist/clients/lsp/config.js +36 -7
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +24 -1
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +78 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +281 -45
- package/node_modules/pi-lens/dist/clients/lsp/server.js +62 -15
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +37 -4
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +3 -0
- package/node_modules/pi-lens/dist/clients/mcp/session.js +2 -0
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +2 -0
- package/node_modules/pi-lens/dist/clients/package-manager.js +2 -1
- package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +8 -3
- package/node_modules/pi-lens/dist/clients/pipeline.js +3 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +38 -10
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +10 -9
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +2 -0
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +2 -0
- package/node_modules/pi-lens/dist/clients/ruff-client.js +18 -9
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +9 -0
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +5 -0
- package/node_modules/pi-lens/dist/clients/runtime-session.js +13 -4
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +4 -1
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +48 -1
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -2
- package/node_modules/pi-lens/dist/clients/session-event-guard.js +69 -1
- package/node_modules/pi-lens/dist/clients/sg-runner.js +2 -1
- package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +108 -0
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +291 -76
- package/node_modules/pi-lens/dist/clients/tool-config.js +199 -0
- package/node_modules/pi-lens/dist/clients/tool-cwd.js +299 -0
- package/node_modules/pi-lens/dist/clients/tool-probe.js +53 -0
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +42 -0
- package/node_modules/pi-lens/dist/clients/trivy-client.js +9 -1
- package/node_modules/pi-lens/dist/clients/turn-context.js +52 -0
- package/node_modules/pi-lens/dist/clients/widget-state.js +4 -3
- package/node_modules/pi-lens/dist/index.js +21969 -20005
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -2
- package/node_modules/pi-lens/dist/mcp/server.js +135 -159
- package/node_modules/pi-lens/dist/tools/activate-tools.js +10 -7
- package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +2 -13
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +8 -13
- package/node_modules/pi-lens/dist/tools/ast-grep-search.js +83 -39
- package/node_modules/pi-lens/dist/tools/effective-config.js +4 -3
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +1 -10
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +353 -85
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +21 -110
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +20 -44
- package/node_modules/pi-lens/dist/tools/module-report.js +5 -9
- package/node_modules/pi-lens/dist/tools/project-report.js +9 -17
- package/node_modules/pi-lens/dist/tools/render-compact.js +280 -1
- package/node_modules/pi-lens/dist/tools/symbol-search.js +2 -2
- package/node_modules/pi-lens/docs/agent-guide.md +3 -4
- package/node_modules/pi-lens/docs/agent-tools.md +46 -22
- package/node_modules/pi-lens/docs/configuration.md +9 -0
- package/node_modules/pi-lens/docs/environment-variables.md +0 -5
- package/node_modules/pi-lens/docs/features.md +5 -5
- package/node_modules/pi-lens/docs/globalconfig.md +11 -1
- package/node_modules/pi-lens/docs/mcp.md +13 -1
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +71 -0
- package/node_modules/pi-lens/docs/pi-lens-investigator.md +15 -0
- package/node_modules/pi-lens/docs/pi-lens-monitor.md +88 -0
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +35 -0
- package/node_modules/pi-lens/docs/pi-lens-subagent.md +12 -4
- package/node_modules/pi-lens/docs/public-api-stability.md +1 -0
- package/node_modules/pi-lens/docs/real-harness.md +46 -0
- package/node_modules/pi-lens/docs/release-qa-baseline.md +5 -1
- package/node_modules/pi-lens/docs/servercapabilities.md +1 -6
- package/node_modules/pi-lens/docs/settings.md +37 -2
- package/node_modules/pi-lens/docs/tools_improvement2.md +4 -4
- package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
- package/node_modules/pi-lens/docs/usage.md +13 -3
- package/node_modules/pi-lens/package.json +10 -1
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +7 -3
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +8 -6
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +19 -9
- package/node_modules/pi-web-access/CHANGELOG.md +29 -0
- package/node_modules/pi-web-access/README.md +33 -22
- package/node_modules/pi-web-access/abortable.ts +17 -0
- package/node_modules/pi-web-access/crawl4ai.ts +204 -0
- package/node_modules/pi-web-access/credential-source.ts +1 -0
- package/node_modules/pi-web-access/curator-page.ts +20 -2
- package/node_modules/pi-web-access/curator-run.ts +44 -0
- package/node_modules/pi-web-access/curator-server.ts +3 -1
- package/node_modules/pi-web-access/duckduckgo.ts +1 -1
- package/node_modules/pi-web-access/extract.ts +57 -12
- package/node_modules/pi-web-access/fetch-params.ts +1 -1
- package/node_modules/pi-web-access/gemini-search.ts +9 -5
- package/node_modules/pi-web-access/index.ts +25 -18
- package/node_modules/pi-web-access/openai-search.ts +35 -13
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/page-query.ts +5 -2
- package/node_modules/pi-web-access/query-rewrite.ts +5 -2
- package/node_modules/pi-web-access/serpapi.ts +220 -0
- package/node_modules/pi-web-access/ssrf-protection.ts +5 -1
- package/node_modules/pi-web-access/storage.ts +10 -0
- package/node_modules/pi-web-access/summary-review.ts +34 -21
- package/node_modules/pi-web-access/utils.ts +6 -4
- package/package.json +11 -5
- package/node_modules/pi-lens/dist/tools/ast-dump.js +0 -103
|
@@ -18,14 +18,18 @@ import { emitBounded } from "./bounded-telemetry.js";
|
|
|
18
18
|
import { LEDGER_FIELD_MAX } from "./degradation-ledger.js";
|
|
19
19
|
import { minimatch } from "./deps/minimatch.js";
|
|
20
20
|
import { createSubsystemLogger } from "./extension-log.js";
|
|
21
|
+
import { detectFileKind } from "./file-kinds.js";
|
|
21
22
|
import { detectFileRole } from "./file-role.js";
|
|
23
|
+
import { resolveLanguageRootForFile } from "./language-profile.js";
|
|
22
24
|
import { findGlobalBinary } from "./package-manager.js";
|
|
23
25
|
import { PathKeyedMap } from "./path-keyed-map.js";
|
|
24
26
|
import { augmentPythonEnvironment, detectPythonEnvironment, } from "./python-environment.js";
|
|
25
|
-
import { normalizeEphemeralMapKey, normalizeMapKey, toPosix, } from "./path-utils.js";
|
|
27
|
+
import { isUnderDir, normalizeEphemeralMapKey, normalizeMapKey, toPosix, } from "./path-utils.js";
|
|
28
|
+
import { findNearestDirWithAnyBasename } from "./workspace-topology.js";
|
|
26
29
|
import { isMeasuredDuration, toMeasuredDurationMs } from "./run-duration.js";
|
|
27
30
|
import { safeSpawn, safeSpawnAsync } from "./safe-spawn.js";
|
|
28
31
|
import { stripAnsi } from "./sanitize.js";
|
|
32
|
+
import { resolveToolCwd } from "./tool-cwd.js";
|
|
29
33
|
/**
|
|
30
34
|
* #2532: the ONE classification seam for "this `TestResult` carries NO
|
|
31
35
|
* counted failure the agent needs to fix, even though the runner also
|
|
@@ -99,10 +103,26 @@ const SOURCE_TO_TEST_PATTERNS = [
|
|
|
99
103
|
// basename (e.g. lib/accounts/user.ex -> test/accounts/user_test.exs).
|
|
100
104
|
{ ext: ".ex", testExts: ["_test.exs"], dirs: ["test"] },
|
|
101
105
|
];
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Nearest `node_modules/<packageName>` at or above `cwd` — monorepo workspace
|
|
108
|
+
* hoisting (npm/yarn/pnpm), where a workspace package's own `node_modules` may
|
|
109
|
+
* not exist at all and the runner lives in the workspace root's.
|
|
110
|
+
*
|
|
111
|
+
* #2870 net-count: this was a private bounded climb loop of its own (the
|
|
112
|
+
* fourth walker in this file). It is now the shared workspace-topology
|
|
113
|
+
* walker every other per-directory marker walk in the codebase goes through
|
|
114
|
+
* — same `walkUpDirs` primitive, the `$HOME` ceiling the private loop
|
|
115
|
+
* lacked, the `MAX_WALK_DEPTH` cap with its cap-trip latency record, and the
|
|
116
|
+
* per-directory marker cache, so a chain already walked for a language root
|
|
117
|
+
* is not re-`readdir`'d here. Returns the directory CONTAINING
|
|
118
|
+
* `node_modules` (the private loop returned the `node_modules` path itself;
|
|
119
|
+
* only the log line reads it).
|
|
120
|
+
*/
|
|
121
|
+
function findHoistedNodeModulesPackage(cwd, packageName) {
|
|
122
|
+
return findNearestDirWithAnyBasename(path.resolve(cwd), [
|
|
123
|
+
`node_modules/${packageName}`,
|
|
124
|
+
]);
|
|
125
|
+
}
|
|
106
126
|
// Bound for recursive descent into a Python test directory when the exact
|
|
107
127
|
// same-relative-subdir mirror doesn't match (e.g. tests/unit/ grouping by
|
|
108
128
|
// test type rather than mirroring source layout) — capped depth, never an
|
|
@@ -162,6 +182,7 @@ export function isExcludedTestTarget(testFilePath, cwd) {
|
|
|
162
182
|
// --- Runner Detection ---
|
|
163
183
|
export const RUNNERS = {
|
|
164
184
|
vitest: {
|
|
185
|
+
kinds: ["jsts"],
|
|
165
186
|
configFiles: ["vitest.config.ts", "vitest.config.js", "vitest.config.mjs"],
|
|
166
187
|
command: "npx",
|
|
167
188
|
binName: "vitest",
|
|
@@ -175,6 +196,7 @@ export const RUNNERS = {
|
|
|
175
196
|
parseJson: true,
|
|
176
197
|
},
|
|
177
198
|
jest: {
|
|
199
|
+
kinds: ["jsts"],
|
|
178
200
|
configFiles: [
|
|
179
201
|
"jest.config.ts",
|
|
180
202
|
"jest.config.js",
|
|
@@ -193,12 +215,26 @@ export const RUNNERS = {
|
|
|
193
215
|
parseJson: true,
|
|
194
216
|
},
|
|
195
217
|
pytest: {
|
|
218
|
+
kinds: ["python"],
|
|
196
219
|
configFiles: ["pytest.ini", "pyproject.toml", "setup.cfg", "tox.ini"],
|
|
220
|
+
// #2879 review round 3, F7: `pyproject.toml` is CONTENT-conditional in
|
|
221
|
+
// detection — the Priority-1 loop accepts it only when it carries
|
|
222
|
+
// `[tool.pytest.ini_options]` — but the cwd seam walks basenames, so
|
|
223
|
+
// handing `configFiles` over verbatim anchored the child on the very
|
|
224
|
+
// file detection had refused. Omitting it here makes the spawn walk
|
|
225
|
+
// stop only on evidence the detector itself would accept; a project
|
|
226
|
+
// whose ONLY pytest config is a real `[tool.pytest.ini_options]`
|
|
227
|
+
// section therefore falls back to the dispatch root, which is exactly
|
|
228
|
+
// where master ran it. (The proper fix is to carry detection's
|
|
229
|
+
// accepted evidence path into the spawn resolution instead of
|
|
230
|
+
// re-deriving it from a basename table — filed as a follow-up.)
|
|
231
|
+
spawnCwdMarkers: ["pytest.ini", "tox.ini", "setup.cfg"],
|
|
197
232
|
command: "python",
|
|
198
233
|
args: (testFile, _cwd) => ["-m", "pytest", testFile, "--tb=short", "-q"],
|
|
199
234
|
parseJson: false, // pytest JSON requires plugin, use text parsing
|
|
200
235
|
},
|
|
201
236
|
go: {
|
|
237
|
+
kinds: ["go"],
|
|
202
238
|
configFiles: ["go.mod"],
|
|
203
239
|
command: "go",
|
|
204
240
|
args: (testFile, cwd) => {
|
|
@@ -210,30 +246,47 @@ export const RUNNERS = {
|
|
|
210
246
|
parseJson: false, // Go test output is text-based
|
|
211
247
|
},
|
|
212
248
|
cargo: {
|
|
249
|
+
kinds: ["rust"],
|
|
213
250
|
configFiles: ["Cargo.toml"],
|
|
214
251
|
command: "cargo",
|
|
215
252
|
args: (_testFile, _cwd) => ["test", "--no-fail-fast"],
|
|
216
253
|
parseJson: false, // cargo test output is text-based
|
|
217
254
|
},
|
|
218
255
|
dotnet: {
|
|
256
|
+
kinds: ["csharp", "fsharp"],
|
|
219
257
|
configFiles: ["*.csproj", "*.sln"],
|
|
220
258
|
command: "dotnet",
|
|
221
259
|
args: (_testFile, _cwd) => ["test", "--no-build"],
|
|
222
260
|
parseJson: false,
|
|
223
261
|
},
|
|
224
262
|
gradle: {
|
|
225
|
-
|
|
263
|
+
kinds: ["java", "kotlin"],
|
|
264
|
+
// #2870: `settings.gradle.kts` alongside its Groovy sibling — a
|
|
265
|
+
// Kotlin-DSL build whose root carries only the settings script was
|
|
266
|
+
// invisible to detection, the same one-spelling gap the java root
|
|
267
|
+
// markers had.
|
|
268
|
+
configFiles: [
|
|
269
|
+
"build.gradle",
|
|
270
|
+
"build.gradle.kts",
|
|
271
|
+
"settings.gradle",
|
|
272
|
+
"settings.gradle.kts",
|
|
273
|
+
],
|
|
226
274
|
command: process.platform === "win32" ? "gradlew.bat" : "./gradlew",
|
|
275
|
+
// The child's cwd must be a directory the wrapper actually lives in.
|
|
276
|
+
spawnCwdMarkers: ["gradlew", "gradlew.bat"],
|
|
227
277
|
args: (_testFile, _cwd) => ["test", "--no-daemon"],
|
|
228
278
|
parseJson: false,
|
|
229
279
|
},
|
|
230
280
|
maven: {
|
|
281
|
+
kinds: ["java", "kotlin"],
|
|
231
282
|
configFiles: ["pom.xml"],
|
|
232
283
|
command: "mvn",
|
|
284
|
+
spawnCwdMarkers: ["mvnw", "mvnw.cmd"],
|
|
233
285
|
args: (_testFile, _cwd) => ["test", "-q"],
|
|
234
286
|
parseJson: false,
|
|
235
287
|
},
|
|
236
288
|
rspec: {
|
|
289
|
+
kinds: ["ruby"],
|
|
237
290
|
configFiles: [".rspec", "spec/spec_helper.rb"],
|
|
238
291
|
command: "bundle",
|
|
239
292
|
// The real binary is "bundle" (the command runs `bundle exec rspec
|
|
@@ -245,22 +298,33 @@ export const RUNNERS = {
|
|
|
245
298
|
parseJson: false,
|
|
246
299
|
},
|
|
247
300
|
minitest: {
|
|
301
|
+
kinds: ["ruby"],
|
|
248
302
|
configFiles: ["Gemfile"],
|
|
249
303
|
command: "ruby",
|
|
250
304
|
args: (testFile, _cwd) => ["-Itest", testFile],
|
|
251
305
|
parseJson: false,
|
|
252
306
|
},
|
|
253
307
|
phpunit: {
|
|
308
|
+
kinds: ["php"],
|
|
254
309
|
// phpunit.xml(.dist) is the strong signal; composer.json is checked for
|
|
255
310
|
// a require-dev dependency on phpunit/phpunit (see the special case in
|
|
256
311
|
// detectRunner's Priority-1 loop, mirroring the pytest/pyproject.toml
|
|
257
312
|
// handling above).
|
|
258
313
|
configFiles: ["phpunit.xml", "phpunit.xml.dist", "composer.json"],
|
|
314
|
+
// #2879 review round 3, F7: same shape as pytest's `pyproject.toml`,
|
|
315
|
+
// and this one is load-bearing — phpunit reads `phpunit.xml` from its
|
|
316
|
+
// CWD only, so a child launched in a directory whose `composer.json`
|
|
317
|
+
// carries no `phpunit/phpunit` dependency runs with no bootstrap and
|
|
318
|
+
// no autoloader, and the resulting fatal error reaches the agent as a
|
|
319
|
+
// test failure (measured end to end with a fake phpunit recording its
|
|
320
|
+
// own cwd). `composer.json` is an anchor, never phpunit evidence.
|
|
321
|
+
spawnCwdMarkers: ["phpunit.xml", "phpunit.xml.dist"],
|
|
259
322
|
command: "phpunit",
|
|
260
323
|
args: (testFile, _cwd) => [testFile],
|
|
261
324
|
parseJson: false, // PHPUnit's default CLI output is text-based
|
|
262
325
|
},
|
|
263
326
|
mix: {
|
|
327
|
+
kinds: ["elixir"],
|
|
264
328
|
configFiles: ["mix.exs"],
|
|
265
329
|
command: "mix",
|
|
266
330
|
args: (testFile, _cwd) => ["test", testFile],
|
|
@@ -441,14 +505,127 @@ export class TestRunnerClient {
|
|
|
441
505
|
return;
|
|
442
506
|
byRunner.set(runner, { available, evidencePath });
|
|
443
507
|
}
|
|
508
|
+
/**
|
|
509
|
+
* Which runners may be handed `sourceFilePath`, and the directory their
|
|
510
|
+
* config files are probed in (#2870).
|
|
511
|
+
*
|
|
512
|
+
* `eligible === null` means "no file was named" — the session-level
|
|
513
|
+
* caller (`runtime-session.ts`'s summary) asks "what runner does this
|
|
514
|
+
* project have" and has no file to scope to, so every runner stays a
|
|
515
|
+
* candidate and the probe root is the dispatch root, exactly as before.
|
|
516
|
+
*
|
|
517
|
+
* A named file resolves TWO independent facts, and the order matters:
|
|
518
|
+
* - its KIND decides which runners may claim it at all (the gate), and
|
|
519
|
+
* - `resolveLanguageRootForFile` decides WHERE that kind's config files
|
|
520
|
+
* are looked for (the anchor) — the same per-kind, cached,
|
|
521
|
+
* workspace-clamped walk the dispatch runners already resolve their
|
|
522
|
+
* roots with, not a private walker.
|
|
523
|
+
*
|
|
524
|
+
* Returning `null` means no runner can own this file (a kind no entry
|
|
525
|
+
* claims: markdown, yaml, an unknown extension), so no directory is
|
|
526
|
+
* probed at all.
|
|
527
|
+
*/
|
|
528
|
+
resolveDetectionScope(cwd, sourceFilePath) {
|
|
529
|
+
if (sourceFilePath === undefined)
|
|
530
|
+
return { root: cwd, eligible: null };
|
|
531
|
+
const absoluteFile = path.resolve(sourceFilePath);
|
|
532
|
+
// #2522 (pinned by "fails closed through the REAL getTestRunTarget when
|
|
533
|
+
// the resolved target is out of tree"): a file OUTSIDE the dispatch root
|
|
534
|
+
// keeps the pre-#2870 resolution — every runner, probed at the dispatch
|
|
535
|
+
// root — so `getTestRunTarget` still hands the exclusion layer a target
|
|
536
|
+
// to fail closed on instead of quietly answering "no runner" for a file
|
|
537
|
+
// the turn should refuse loudly. Per-file anchoring has nothing to
|
|
538
|
+
// anchor to out there: the language-root walk is clamped to the
|
|
539
|
+
// workspace, so every out-of-tree file resolves to the dispatch root
|
|
540
|
+
// anyway.
|
|
541
|
+
if (!isUnderDir(absoluteFile, path.resolve(cwd))) {
|
|
542
|
+
return { root: cwd, eligible: null };
|
|
543
|
+
}
|
|
544
|
+
const kind = detectFileKind(absoluteFile);
|
|
545
|
+
if (!kind)
|
|
546
|
+
return null;
|
|
547
|
+
const eligible = new Set(Object.entries(RUNNERS)
|
|
548
|
+
.filter(([, config]) => config.kinds.includes(kind))
|
|
549
|
+
.map(([name]) => name));
|
|
550
|
+
if (eligible.size === 0)
|
|
551
|
+
return null;
|
|
552
|
+
return {
|
|
553
|
+
root: resolveLanguageRootForFile(absoluteFile, cwd),
|
|
554
|
+
eligible,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
444
557
|
/**
|
|
445
558
|
* Check if a test runner is available in the project
|
|
446
559
|
* Detection order:
|
|
447
560
|
* 1. Config files (vitest.config.ts, jest.config.js, etc.)
|
|
448
561
|
* 2. package.json dependencies
|
|
449
562
|
* 3. node_modules presence
|
|
563
|
+
*
|
|
564
|
+
* #2870: when `sourceFilePath` is given, the answer is scoped to that
|
|
565
|
+
* file's kind and resolved language root — see `resolveDetectionScope`.
|
|
450
566
|
*/
|
|
451
|
-
detectRunner(
|
|
567
|
+
detectRunner(dispatchRoot, sourceFilePath) {
|
|
568
|
+
const scope = this.resolveDetectionScope(dispatchRoot, sourceFilePath);
|
|
569
|
+
if (!scope)
|
|
570
|
+
return null;
|
|
571
|
+
const { root, eligible } = scope;
|
|
572
|
+
const anchored = this.probeRunnersAt(root, eligible);
|
|
573
|
+
if (anchored)
|
|
574
|
+
return anchored;
|
|
575
|
+
// #2879 review round 2, F1: the anchor is a LANGUAGE root, and
|
|
576
|
+
// `ROOT_MARKERS_BY_KIND` is deliberately BROADER than any runner's
|
|
577
|
+
// `configFiles` — `requirements.txt`, `setup.py`, `Pipfile`,
|
|
578
|
+
// `Rakefile`, `composer.lock`, `.classpath` all anchor a language
|
|
579
|
+
// without configuring a test runner. Probing the anchored directory
|
|
580
|
+
// ALONE therefore let any such intermediate marker shadow the
|
|
581
|
+
// project root's real runner config, and pytest/rspec/minitest/
|
|
582
|
+
// phpunit have no later priority to rescue them: four single-language
|
|
583
|
+
// repos went from a working runner to no target at all (measured, the
|
|
584
|
+
// review's `anchor.mjs` probe), which is exactly what #2870's amended
|
|
585
|
+
// criterion 4 protects.
|
|
586
|
+
//
|
|
587
|
+
// So the anchored probe is a PREFERENCE, not a restriction: on a miss,
|
|
588
|
+
// re-probe the dispatch root under the SAME kind gate. The gate is
|
|
589
|
+
// what keeps #2870 fixed — a `.java` file re-probing a polyglot root
|
|
590
|
+
// still cannot reach `go`, because `go` never claims the `java` kind.
|
|
591
|
+
const dispatch = path.resolve(dispatchRoot);
|
|
592
|
+
if (path.resolve(root) !== dispatch) {
|
|
593
|
+
const atDispatch = this.probeRunnersAt(dispatch, eligible);
|
|
594
|
+
if (atDispatch)
|
|
595
|
+
return atDispatch;
|
|
596
|
+
}
|
|
597
|
+
// Priority 5: Check if pytest is available globally (Python files only)
|
|
598
|
+
const isPythonSource = typeof sourceFilePath === "string" && sourceFilePath.endsWith(".py");
|
|
599
|
+
if (!isPythonSource)
|
|
600
|
+
return null;
|
|
601
|
+
try {
|
|
602
|
+
const whichCmd = process.platform === "win32" ? "where" : "which";
|
|
603
|
+
const result = safeSpawn(whichCmd, ["pytest"], {
|
|
604
|
+
timeout: 2000,
|
|
605
|
+
});
|
|
606
|
+
if (result.status === 0) {
|
|
607
|
+
this.log("Detected pytest globally");
|
|
608
|
+
return { runner: "pytest", config: RUNNERS.pytest };
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
catch (err) {
|
|
612
|
+
void err;
|
|
613
|
+
}
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Priorities 1-4 of the detection ladder, against ONE directory: config
|
|
618
|
+
* files, `package.json` dependencies, a hoisted `node_modules`, and the
|
|
619
|
+
* glob-capable whole-project runners. `eligible` is the kind gate
|
|
620
|
+
* (#2870); `null` means no file was named and every runner is a
|
|
621
|
+
* candidate. Returns `null` when this directory configures no eligible
|
|
622
|
+
* runner, which is what lets `detectRunner` try the dispatch root next.
|
|
623
|
+
*/
|
|
624
|
+
probeRunnersAt(cwd, eligible) {
|
|
625
|
+
// Keyed on the probed directory, not the dispatch root: two modules of
|
|
626
|
+
// one polyglot repo have different runners available, and a memo keyed
|
|
627
|
+
// on the shared dispatch root would serve the first module's verdict to
|
|
628
|
+
// every other module (#2870).
|
|
452
629
|
const rootKey = this.getCanonicalProjectRoot(cwd);
|
|
453
630
|
let byRunner = this.availableRunners.get(rootKey);
|
|
454
631
|
if (!byRunner) {
|
|
@@ -457,6 +634,8 @@ export class TestRunnerClient {
|
|
|
457
634
|
}
|
|
458
635
|
// Priority 1: Config files
|
|
459
636
|
for (const [name, config] of Object.entries(RUNNERS)) {
|
|
637
|
+
if (eligible && !eligible.has(name))
|
|
638
|
+
continue;
|
|
460
639
|
const cached = this.getRunnerAvailability(byRunner, name);
|
|
461
640
|
if (cached !== undefined) {
|
|
462
641
|
if (cached) {
|
|
@@ -520,17 +699,18 @@ export class TestRunnerClient {
|
|
|
520
699
|
...pkg.devDependencies,
|
|
521
700
|
};
|
|
522
701
|
// Check for vitest first (more specific than jest)
|
|
523
|
-
if (allDeps.vitest) {
|
|
702
|
+
if (allDeps.vitest && (!eligible || eligible.has("vitest"))) {
|
|
524
703
|
this.log("Detected vitest in package.json");
|
|
525
704
|
this.setRunnerAvailability(byRunner, "vitest", true, packageJsonPath);
|
|
526
705
|
return { runner: "vitest", config: RUNNERS.vitest };
|
|
527
706
|
}
|
|
528
|
-
if (allDeps.jest) {
|
|
707
|
+
if (allDeps.jest && (!eligible || eligible.has("jest"))) {
|
|
529
708
|
this.log("Detected jest in package.json");
|
|
530
709
|
this.setRunnerAvailability(byRunner, "jest", true, packageJsonPath);
|
|
531
710
|
return { runner: "jest", config: RUNNERS.jest };
|
|
532
711
|
}
|
|
533
|
-
if (allDeps.pytest || allDeps["pytest-cov"])
|
|
712
|
+
if ((allDeps.pytest || allDeps["pytest-cov"]) &&
|
|
713
|
+
(!eligible || eligible.has("pytest"))) {
|
|
534
714
|
this.log("Detected pytest in package.json (unusual)");
|
|
535
715
|
this.setRunnerAvailability(byRunner, "pytest", true, packageJsonPath);
|
|
536
716
|
return { runner: "pytest", config: RUNNERS.pytest };
|
|
@@ -543,21 +723,19 @@ export class TestRunnerClient {
|
|
|
543
723
|
// Priority 3: Check node_modules for installed packages, including a
|
|
544
724
|
// hoisted monorepo layout where cwd is a workspace package (e.g.
|
|
545
725
|
// packages/foo) but the runner only lives in node_modules at the
|
|
546
|
-
// workspace root (npm/yarn/pnpm workspace hoisting).
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
const hoistedJest = this.findHoistedNodeModulesPackage(cwd, "jest");
|
|
556
|
-
if (hoistedJest) {
|
|
557
|
-
this.log(`Detected jest in node_modules (${hoistedJest})`);
|
|
558
|
-
return { runner: "jest", config: RUNNERS.jest };
|
|
726
|
+
// workspace root (npm/yarn/pnpm workspace hoisting).
|
|
727
|
+
for (const name of ["vitest", "jest"]) {
|
|
728
|
+
if (eligible && !eligible.has(name))
|
|
729
|
+
continue;
|
|
730
|
+
const hoisted = findHoistedNodeModulesPackage(cwd, name);
|
|
731
|
+
if (hoisted) {
|
|
732
|
+
this.log(`Detected ${name} in node_modules (${hoisted})`);
|
|
733
|
+
return { runner: name, config: RUNNERS[name] };
|
|
734
|
+
}
|
|
559
735
|
}
|
|
560
736
|
for (const name of ["go", "cargo", "dotnet", "gradle", "maven"]) {
|
|
737
|
+
if (eligible && !eligible.has(name))
|
|
738
|
+
continue;
|
|
561
739
|
const config = RUNNERS[name];
|
|
562
740
|
const found = config.configFiles.some((cf) => {
|
|
563
741
|
// Handle glob patterns like *.csproj
|
|
@@ -577,47 +755,6 @@ export class TestRunnerClient {
|
|
|
577
755
|
return { runner: name, config };
|
|
578
756
|
}
|
|
579
757
|
}
|
|
580
|
-
// Priority 5: Check if pytest is available globally (Python files only)
|
|
581
|
-
const isPythonSource = typeof sourceFilePath === "string" && sourceFilePath.endsWith(".py");
|
|
582
|
-
if (!isPythonSource)
|
|
583
|
-
return null;
|
|
584
|
-
try {
|
|
585
|
-
const whichCmd = process.platform === "win32" ? "where" : "which";
|
|
586
|
-
const result = safeSpawn(whichCmd, ["pytest"], {
|
|
587
|
-
timeout: 2000,
|
|
588
|
-
});
|
|
589
|
-
if (result.status === 0) {
|
|
590
|
-
this.log("Detected pytest globally");
|
|
591
|
-
return { runner: "pytest", config: RUNNERS.pytest };
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
catch (err) {
|
|
595
|
-
void err;
|
|
596
|
-
}
|
|
597
|
-
return null;
|
|
598
|
-
}
|
|
599
|
-
/**
|
|
600
|
-
* Walk up from `cwd` through parent directories looking for a
|
|
601
|
-
* `node_modules/<packageName>` — handles monorepo workspace hoisting
|
|
602
|
-
* (npm/yarn/pnpm), where a workspace package's own `node_modules` may
|
|
603
|
-
* not exist at all, with dependencies hoisted to the workspace root
|
|
604
|
-
* several directories up. Bounded by `MAX_NODE_MODULES_WALK_UP` levels
|
|
605
|
-
* and stops at the filesystem root — never an unbounded walk.
|
|
606
|
-
* Returns the `node_modules` directory where the package was found, or
|
|
607
|
-
* null if not found within the bound.
|
|
608
|
-
*/
|
|
609
|
-
findHoistedNodeModulesPackage(cwd, packageName) {
|
|
610
|
-
let dir = path.resolve(cwd);
|
|
611
|
-
for (let level = 0; level <= MAX_NODE_MODULES_WALK_UP; level++) {
|
|
612
|
-
const nodeModulesPath = path.join(dir, "node_modules");
|
|
613
|
-
if (fs.existsSync(path.join(nodeModulesPath, packageName))) {
|
|
614
|
-
return nodeModulesPath;
|
|
615
|
-
}
|
|
616
|
-
const parent = path.dirname(dir);
|
|
617
|
-
if (parent === dir)
|
|
618
|
-
break; // reached filesystem root
|
|
619
|
-
dir = parent;
|
|
620
|
-
}
|
|
621
758
|
return null;
|
|
622
759
|
}
|
|
623
760
|
/**
|
|
@@ -790,7 +927,8 @@ export class TestRunnerClient {
|
|
|
790
927
|
* file whose *related* test file needs to be discovered).
|
|
791
928
|
*
|
|
792
929
|
* Primary signal: `detectFileRole` (naming convention: `.test.`/`.spec.`
|
|
793
|
-
* basenames, `
|
|
930
|
+
* basenames, `_test.`/`_spec.` suffix infixes, `*Test(s).<ext>` CamelCase
|
|
931
|
+
* suffixes, `test_`/`spec_` prefixes, `__tests__/`/`tests/`/`spec/`
|
|
794
932
|
* directories — shared with the rest of the codebase, not a second
|
|
795
933
|
* parallel detector).
|
|
796
934
|
*
|
|
@@ -1024,6 +1162,43 @@ export class TestRunnerClient {
|
|
|
1024
1162
|
strategy: "related",
|
|
1025
1163
|
};
|
|
1026
1164
|
}
|
|
1165
|
+
/**
|
|
1166
|
+
* The working directory the test-runner CHILD is spawned in (#2871).
|
|
1167
|
+
*
|
|
1168
|
+
* AGENTS.md defect shape 40: `resolveToolCwd` is the one seam for a child
|
|
1169
|
+
* process's cwd, and this file resolved its own — it handed
|
|
1170
|
+
* `safeSpawnAsync` the dispatch root, so `RUNNERS.go.args` built
|
|
1171
|
+
* `./tools/tapctl/internal/lightning` relative to a root module that does
|
|
1172
|
+
* not own that package. Marker discovery, the `.git` fallback, the
|
|
1173
|
+
* dispatch-root fallback, the `$HOME` ceiling, the once-per-key `tool-cwd`
|
|
1174
|
+
* log line and the bounded `tool-cwd-resolution` degradation now all come
|
|
1175
|
+
* from the seam, with the runner's own table as the markers.
|
|
1176
|
+
*
|
|
1177
|
+
* This is a SECOND value, never a reassignment of `cwd`: the failed-target
|
|
1178
|
+
* ledger (`recordResult` → `getFailedTargets`, read back by
|
|
1179
|
+
* `getTestRunTarget`) is keyed by the DISPATCH root, and re-keying it per
|
|
1180
|
+
* module would make a recorded failure unfindable from the turn that
|
|
1181
|
+
* selects targets.
|
|
1182
|
+
*
|
|
1183
|
+
* Review round 2, F6: there is deliberately NO clamp on a resolution that
|
|
1184
|
+
* lands outside the dispatch root. The seam only answers outside it when
|
|
1185
|
+
* the FILE is outside it (`tool-cwd.ts`: every in-tree branch is
|
|
1186
|
+
* `isUnderDir`-checked), and the one production caller —
|
|
1187
|
+
* `runtime-turn.ts`'s turn-end batch, on both the fresh and the deferred
|
|
1188
|
+
* path — filters every target through `isExcludedTestTarget`, which fails
|
|
1189
|
+
* CLOSED out of tree (#2522). Round 1 shipped that clamp plus a
|
|
1190
|
+
* `tool-cwd-resolution` ledger row for it; neither could fire in a live
|
|
1191
|
+
* session, so the row was a record nothing could observe and the guard was
|
|
1192
|
+
* defence against a caller that does not exist. Both are gone. If a second
|
|
1193
|
+
* caller is ever added that can pass an out-of-tree file, it needs this
|
|
1194
|
+
* decision made where that caller is, with a test that reaches it.
|
|
1195
|
+
*/
|
|
1196
|
+
resolveSpawnCwd(runner, config, testFile, dispatchRoot) {
|
|
1197
|
+
return resolveToolCwd("runner", runner, testFile, {
|
|
1198
|
+
cwd: path.resolve(dispatchRoot),
|
|
1199
|
+
rootMarkers: config.spawnCwdMarkers ?? config.configFiles,
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1027
1202
|
async runTestFileAsync(testFile, cwd, runnerOrRequest, legacyConfig) {
|
|
1028
1203
|
const absoluteTestFile = path.resolve(testFile);
|
|
1029
1204
|
let request;
|
|
@@ -1041,10 +1216,11 @@ export class TestRunnerClient {
|
|
|
1041
1216
|
return this.emptyResult(absoluteTestFile, "", runner, "Test file not found");
|
|
1042
1217
|
}
|
|
1043
1218
|
try {
|
|
1044
|
-
const
|
|
1045
|
-
|
|
1219
|
+
const spawnCwd = this.resolveSpawnCwd(runner, config, absoluteTestFile, cwd);
|
|
1220
|
+
const { command, args, env } = await this.resolveExec(runner, config, absoluteTestFile, cwd, spawnCwd);
|
|
1221
|
+
this.log(`Running (async): ${command} ${args.join(" ")} (cwd ${spawnCwd})`);
|
|
1046
1222
|
const result = await safeSpawnAsync(command, args, {
|
|
1047
|
-
cwd,
|
|
1223
|
+
cwd: spawnCwd,
|
|
1048
1224
|
timeout: 60000,
|
|
1049
1225
|
env,
|
|
1050
1226
|
// #2522 R2 F1. `safeSpawnAsync` resolves `options.signal ?? ambient`,
|
|
@@ -1942,7 +2118,26 @@ export class TestRunnerClient {
|
|
|
1942
2118
|
const slash = name.indexOf("/");
|
|
1943
2119
|
return slash === -1 || !goFailNameSet.has(name.slice(0, slash));
|
|
1944
2120
|
}).length;
|
|
2121
|
+
// #2870: go's own `[setup failed]` verdict line — "there was no package
|
|
2122
|
+
// to test" — the positive twin of the `(?:build|setup) failed`
|
|
2123
|
+
// lookahead below. #1524-r4 stopped it from being counted as a test
|
|
2124
|
+
// failure but left it with no classification of its own, so whether the
|
|
2125
|
+
// agent saw "could not run tests" or a fabricated `✗ 1/1 failed ✗ go
|
|
2126
|
+
// failure` came down to whether go's output happened to contain the
|
|
2127
|
+
// substring "error" (the runner-error condition below): `FAIL <pkg>
|
|
2128
|
+
// [setup failed]` + `error: no packages to test` was advisory, the same
|
|
2129
|
+
// line on its own was a blocking failure the agent could not tell from
|
|
2130
|
+
// a real one (#2870, measured).
|
|
2131
|
+
//
|
|
2132
|
+
// `[build failed]` is deliberately NOT included (review round 2, F5).
|
|
2133
|
+
// The maintainer's amendment on #2870 names `[setup failed]`; a go
|
|
2134
|
+
// COMPILE error is usually one the agent just introduced, and
|
|
2135
|
+
// downgrading it to advisory is a signal change on master's behaviour
|
|
2136
|
+
// that belongs on the issue, not in this fix. Its classification is
|
|
2137
|
+
// still the coin flip described above — see the PR body's follow-ups.
|
|
2138
|
+
let goInfraVerdict = false;
|
|
1945
2139
|
if (runner === "go") {
|
|
2140
|
+
goInfraVerdict = /^FAIL[^\S\n]+\S+[^\S\n]+\[setup failed\]/m.test(output);
|
|
1946
2141
|
// #1524-r4: `(?![^\n]*\[(?:build|setup) failed\])` rejects go's
|
|
1947
2142
|
// INFRASTRUCTURE verdict lines — `FAIL <pkg> [build failed]` (a
|
|
1948
2143
|
// compile error) and `FAIL <pkg> [setup failed]` (no packages to
|
|
@@ -2018,10 +2213,19 @@ export class TestRunnerClient {
|
|
|
2018
2213
|
// runner error they actually are). They still label a name onto
|
|
2019
2214
|
// `failures` above when `matched` or `goFailNames` already settled
|
|
2020
2215
|
// the question some other way, but they no longer settle it alone.
|
|
2216
|
+
//
|
|
2217
|
+
// #2870: `goInfraVerdict` is a SECOND sufficient condition, not a
|
|
2218
|
+
// widening of the first. go's `[build failed]`/`[setup failed]` line is
|
|
2219
|
+
// the runner itself saying no test ran, which is exactly what this
|
|
2220
|
+
// branch reports — so it no longer has to also print the word "error"
|
|
2221
|
+
// to be classified as one. The `!matched`/`goFailNames` vetoes still
|
|
2222
|
+
// apply: a run that produced real go counts or a real `--- FAIL:` is a
|
|
2223
|
+
// verdict, never a runner error, even if a later package failed to
|
|
2224
|
+
// build.
|
|
2021
2225
|
const runnerError = exitCode !== 0 &&
|
|
2022
2226
|
!matched &&
|
|
2023
2227
|
goFailNames.length === 0 &&
|
|
2024
|
-
lower.includes("error")
|
|
2228
|
+
(lower.includes("error") || goInfraVerdict)
|
|
2025
2229
|
? `Runner ${runner} exited with ${exitCode}`
|
|
2026
2230
|
: undefined;
|
|
2027
2231
|
// #1524-r4 (tidy): a runner-error result reports through
|
|
@@ -2217,7 +2421,18 @@ export class TestRunnerClient {
|
|
|
2217
2421
|
* with an npx-style runner-name arg, which only holds for wrapper-style
|
|
2218
2422
|
* runners like vitest/jest/pytest).
|
|
2219
2423
|
*/
|
|
2220
|
-
async resolveExec(runner, config, testFile, cwd
|
|
2424
|
+
async resolveExec(runner, config, testFile, cwd,
|
|
2425
|
+
/**
|
|
2426
|
+
* #2871: the directory the child will RUN in, which is the only cwd
|
|
2427
|
+
* `args()` may be built against — go's package path is relative to it.
|
|
2428
|
+
* Where the binary and the Python environment are INSTALLED is a
|
|
2429
|
+
* different question with a different answer: they stay resolved from
|
|
2430
|
+
* the dispatch root, exactly as before, so a workspace package whose
|
|
2431
|
+
* dependencies are hoisted to the repo root still finds
|
|
2432
|
+
* `node_modules/.bin/<runner>` instead of falling through to `npx`.
|
|
2433
|
+
* Defaults to `cwd`, which is every call where the two are the same.
|
|
2434
|
+
*/
|
|
2435
|
+
spawnCwd = cwd) {
|
|
2221
2436
|
// Run pytest through the project interpreter itself, not a generic `python`
|
|
2222
2437
|
// resolved from the host PATH. The child-only environment also keeps tools
|
|
2223
2438
|
// spawned by tests inside the same project environment.
|
|
@@ -2226,7 +2441,7 @@ export class TestRunnerClient {
|
|
|
2226
2441
|
if (pythonEnvironment) {
|
|
2227
2442
|
return {
|
|
2228
2443
|
command: pythonEnvironment.pythonPath,
|
|
2229
|
-
args: config.args(testFile,
|
|
2444
|
+
args: config.args(testFile, spawnCwd),
|
|
2230
2445
|
env: augmentPythonEnvironment(process.env, pythonEnvironment),
|
|
2231
2446
|
};
|
|
2232
2447
|
}
|
|
@@ -2238,9 +2453,9 @@ export class TestRunnerClient {
|
|
|
2238
2453
|
const suffix = process.platform === "win32" ? ".bat" : "";
|
|
2239
2454
|
const vendorBin = path.join(cwd, "vendor", "bin", `phpunit${suffix}`);
|
|
2240
2455
|
if (fs.existsSync(vendorBin)) {
|
|
2241
|
-
return { command: vendorBin, args: config.args(testFile,
|
|
2456
|
+
return { command: vendorBin, args: config.args(testFile, spawnCwd) };
|
|
2242
2457
|
}
|
|
2243
|
-
return { command: "phpunit", args: config.args(testFile,
|
|
2458
|
+
return { command: "phpunit", args: config.args(testFile, spawnCwd) };
|
|
2244
2459
|
}
|
|
2245
2460
|
const binName = config.binName ?? runner;
|
|
2246
2461
|
const suffix = process.platform === "win32" ? ".cmd" : "";
|
|
@@ -2251,7 +2466,7 @@ export class TestRunnerClient {
|
|
|
2251
2466
|
if (fs.existsSync(localBin)) {
|
|
2252
2467
|
return {
|
|
2253
2468
|
command: localBin,
|
|
2254
|
-
args: stripWrapperArgs(binName, config.args(testFile,
|
|
2469
|
+
args: stripWrapperArgs(binName, config.args(testFile, spawnCwd)),
|
|
2255
2470
|
};
|
|
2256
2471
|
}
|
|
2257
2472
|
// Any package manager's global bin dir (npm/pnpm/yarn/bun) before npx (#375).
|
|
@@ -2259,10 +2474,10 @@ export class TestRunnerClient {
|
|
|
2259
2474
|
if (globalBin) {
|
|
2260
2475
|
return {
|
|
2261
2476
|
command: globalBin,
|
|
2262
|
-
args: stripWrapperArgs(binName, config.args(testFile,
|
|
2477
|
+
args: stripWrapperArgs(binName, config.args(testFile, spawnCwd)),
|
|
2263
2478
|
};
|
|
2264
2479
|
}
|
|
2265
|
-
return { command: config.command, args: config.args(testFile,
|
|
2480
|
+
return { command: config.command, args: config.args(testFile, spawnCwd) };
|
|
2266
2481
|
}
|
|
2267
2482
|
emptyResult(testFile, sourceFile, runner, error) {
|
|
2268
2483
|
return {
|