@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
|
@@ -15,7 +15,8 @@ import * as os from "node:os";
|
|
|
15
15
|
import * as path from "node:path";
|
|
16
16
|
import { BoundedLruCache } from "./bounded-cache.js";
|
|
17
17
|
import { createGenerationSource } from "./generation-guard.js";
|
|
18
|
-
import {
|
|
18
|
+
import { normalizeMapKey } from "./path-utils.js";
|
|
19
|
+
import { resolveToolCwd } from "./tool-cwd.js";
|
|
19
20
|
import { resolveCargoPackageEdition } from "./cargo-manifest.js";
|
|
20
21
|
import { resolveKtfmtGradleStyle } from "./gradle-ktfmt-style.js";
|
|
21
22
|
import { resolvePhpCsFixerConfig } from "./php-cs-fixer-config.js";
|
|
@@ -27,8 +28,10 @@ import { compareOrdinal } from "./string-utils.js";
|
|
|
27
28
|
import { classifyProbeFailure, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
|
|
28
29
|
import { findGlobalBinary, findLocalBinUpwards, VENDOR_BIN_DIRS, VENV_BIN_DIRS, } from "./package-manager.js";
|
|
29
30
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
31
|
+
import { probeToolAsync } from "./tool-probe.js";
|
|
30
32
|
import { assertInstallAllowed } from "./project-trust.js";
|
|
31
33
|
import { tryLazyInstallForFormatter } from "./dispatch/runners/utils/lazy-installer.js";
|
|
34
|
+
import { getToolPath } from "./installer/index.js";
|
|
32
35
|
import { findPSScriptAnalyzerConfigPath, getAutoInstallToolIdForFormatter, getFormatterPolicyForFile, getSmartDefaultFormatterName, hasBiomeConfig, hasBlackConfig, hasClangFormatConfig, hasCljfmtConfig, hasCmakeFormatConfig, hasCsharpierConfig, hasFantomasConfig, hasGoogleJavaFormatConfig, hasKtfmtConfig, hasKtlintConfig, hasMixFormatConfig, hasNearestPackageJsonDependency, hasNearestPackageJsonField, hasOcamlformatConfig, hasOrmoluConfig, hasOxfmtConfig, hasOxfmtSvelteConfig, hasPhpCsFixerConfig, hasPrettierConfig, hasPSScriptAnalyzerConfig, hasRubocopConfig, hasRuffConfig, hasSqlfluffConfig, hasStandardrbConfig, hasStyluaConfig, hasSwiftformatConfig, hasTaploConfig, hasTerraformConfig, hasVitePlusConfig, OXFMT_SUPPORTED_EXTENSIONS, } from "./tool-policy.js";
|
|
33
36
|
/**
|
|
34
37
|
* Lazy-install a formatter's tool, through the shared seam (#1537).
|
|
@@ -380,7 +383,7 @@ async function resolveGoFmtBinary() {
|
|
|
380
383
|
const inPath = await which("gofmt");
|
|
381
384
|
if (inPath)
|
|
382
385
|
return inPath;
|
|
383
|
-
const goCheck = await
|
|
386
|
+
const goCheck = await probeToolAsync("go", ["env", "GOROOT"], {
|
|
384
387
|
timeout: 5000,
|
|
385
388
|
});
|
|
386
389
|
if (goCheck.error || goCheck.status !== 0)
|
|
@@ -468,6 +471,22 @@ async function resolveManagedSmartDefaultCommand(formatterName, filePath, args)
|
|
|
468
471
|
return null;
|
|
469
472
|
return [installed, ...args, filePath];
|
|
470
473
|
}
|
|
474
|
+
/** Resolve an explicitly selected formatter from the shared managed-tool seam. */
|
|
475
|
+
async function resolveManagedFormatterCommand(toolId, filePath, args, findLocal, cwd) {
|
|
476
|
+
const local = findLocal && cwd ? await findLocal(toolId, cwd) : null;
|
|
477
|
+
const installed = local ?? (await which(toolId)) ?? (await getToolPath(toolId));
|
|
478
|
+
return installed ? [installed, ...args, filePath] : FORMATTER_UNAVAILABLE;
|
|
479
|
+
}
|
|
480
|
+
function managedFormatterResolver(toolId, args, findLocal) {
|
|
481
|
+
return (filePath, cwd) => resolveManagedFormatterCommand(toolId, filePath, args, findLocal, cwd);
|
|
482
|
+
}
|
|
483
|
+
function managedToolDetect(toolId, configDetect, nativeDetect) {
|
|
484
|
+
return async (cwd) => {
|
|
485
|
+
const available = (nativeDetect && (await nativeDetect(cwd))) ||
|
|
486
|
+
Boolean(await getToolPath(toolId));
|
|
487
|
+
return available && (configDetect ? await configDetect(cwd) : true);
|
|
488
|
+
};
|
|
489
|
+
}
|
|
471
490
|
/**
|
|
472
491
|
* One entry per formatter that can be selected via explicit project config
|
|
473
492
|
* (the `formatterPolicy` "explicit-config" branch of `getFormattersForFile`).
|
|
@@ -603,109 +622,19 @@ async function indentationArgs(filePath, tool, cwd) {
|
|
|
603
622
|
String(indentation.width),
|
|
604
623
|
];
|
|
605
624
|
}
|
|
606
|
-
const FORMATTER_MARKERS_BY_NAME = new Map([
|
|
607
|
-
["biome", ["biome.json", "biome.jsonc", "package.json"]],
|
|
608
|
-
[
|
|
609
|
-
"prettier",
|
|
610
|
-
[
|
|
611
|
-
".prettierrc",
|
|
612
|
-
".prettierrc.json",
|
|
613
|
-
".prettierrc.yaml",
|
|
614
|
-
".prettierrc.yml",
|
|
615
|
-
".prettierrc.js",
|
|
616
|
-
".prettierrc.cjs",
|
|
617
|
-
".prettierrc.mjs",
|
|
618
|
-
"prettier.config.js",
|
|
619
|
-
"prettier.config.cjs",
|
|
620
|
-
"prettier.config.mjs",
|
|
621
|
-
".prettierignore",
|
|
622
|
-
"package.json",
|
|
623
|
-
],
|
|
624
|
-
],
|
|
625
|
-
[
|
|
626
|
-
"oxfmt",
|
|
627
|
-
["oxfmt.toml", ".oxfmtrc.json", "vite-plus.json", "package.json"],
|
|
628
|
-
],
|
|
629
|
-
["ruff", ["pyproject.toml", "ruff.toml", ".ruff.toml"]],
|
|
630
|
-
["black", ["pyproject.toml", "black.toml", ".black"]],
|
|
631
|
-
["sqlfluff", [".sqlfluff", "pyproject.toml", "setup.cfg"]],
|
|
632
|
-
["rustfmt", ["rustfmt.toml", ".rustfmt.toml", "Cargo.toml"]],
|
|
633
|
-
["rubocop", [".rubocop.yml", ".rubocop.yaml"]],
|
|
634
|
-
["standardrb", [".standard.yml", ".standard.yaml"]],
|
|
635
|
-
["clang-format", [".clang-format", "_clang-format"]],
|
|
636
|
-
["php-cs-fixer", [".php-cs-fixer.php", ".php-cs-fixer.dist.php"]],
|
|
637
|
-
["stylua", ["stylua.toml", ".stylua.toml"]],
|
|
638
|
-
["ocamlformat", [".ocamlformat"]],
|
|
639
|
-
["google-java-format", [".google-java-format", ".editorconfig"]],
|
|
640
|
-
["cljfmt", [".cljfmt.edn", "cljfmt.edn", ".cljfmt"]],
|
|
641
|
-
[
|
|
642
|
-
"cmake-format",
|
|
643
|
-
[
|
|
644
|
-
".cmake-format",
|
|
645
|
-
".cmake-format.yaml",
|
|
646
|
-
".cmake-format.yml",
|
|
647
|
-
".cmake-format.json",
|
|
648
|
-
".cmake-format.py",
|
|
649
|
-
"cmake-format.yaml",
|
|
650
|
-
"cmake-format.yml",
|
|
651
|
-
".editorconfig",
|
|
652
|
-
],
|
|
653
|
-
],
|
|
654
|
-
[
|
|
655
|
-
"psscriptanalyzer-format",
|
|
656
|
-
["PSScriptAnalyzerSettings.psd1", "ScriptAnalyzerSettings.psd1"],
|
|
657
|
-
],
|
|
658
|
-
[
|
|
659
|
-
"csharpier",
|
|
660
|
-
[
|
|
661
|
-
".csharpierrc",
|
|
662
|
-
".csharpierrc.json",
|
|
663
|
-
".csharpierrc.yaml",
|
|
664
|
-
".csharpierrc.yml",
|
|
665
|
-
],
|
|
666
|
-
],
|
|
667
|
-
["ormolu", [".ormolu"]],
|
|
668
|
-
["taplo", ["taplo.toml", ".taplo.toml"]],
|
|
669
|
-
["terraform", [".terraform.lock.hcl"]],
|
|
670
|
-
["swiftformat", [".swiftformat"]],
|
|
671
|
-
["fantomas", [".fantomasignore", ".editorconfig"]],
|
|
672
|
-
["mix", [".formatter.exs"]],
|
|
673
|
-
["shfmt", [".editorconfig"]],
|
|
674
|
-
["ktlint", [".editorconfig"]],
|
|
675
|
-
[
|
|
676
|
-
"ktfmt",
|
|
677
|
-
[
|
|
678
|
-
".editorconfig",
|
|
679
|
-
".ktfmt",
|
|
680
|
-
".ktfmt.kts",
|
|
681
|
-
"build.gradle",
|
|
682
|
-
"build.gradle.kts",
|
|
683
|
-
"settings.gradle",
|
|
684
|
-
"settings.gradle.kts",
|
|
685
|
-
],
|
|
686
|
-
],
|
|
687
|
-
]);
|
|
688
625
|
/**
|
|
689
626
|
* Resolve the cwd for the formatter child, capped at the user's home
|
|
690
627
|
* directory. Files outside a project retain the historical file-directory
|
|
691
628
|
* cwd. The optional homeDir is only for the ceiling regression test.
|
|
692
629
|
*/
|
|
693
630
|
export function resolveFormatterCwd(absolutePath, formatterName, homeDir = os.homedir()) {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
:
|
|
699
|
-
|
|
700
|
-
homeDir: effectiveHome,
|
|
701
|
-
});
|
|
702
|
-
if (root)
|
|
703
|
-
return root;
|
|
704
|
-
const gitRoot = findNearestMarkerRoot(fileDir, [".git"], {
|
|
705
|
-
homeDir: effectiveHome,
|
|
706
|
-
markerPredicate: isRealGitMarker,
|
|
631
|
+
return resolveToolCwd("formatter", formatterName ?? "unknown", absolutePath, {
|
|
632
|
+
// Formatter discovery historically walks above the file directory. The
|
|
633
|
+
// filesystem root is the neutral dispatch boundary; the seam's `$HOME`
|
|
634
|
+
// ceiling prevents it from escaping the user's workspace.
|
|
635
|
+
cwd: path.parse(path.resolve(absolutePath)).root,
|
|
636
|
+
homeDir,
|
|
707
637
|
});
|
|
708
|
-
return gitRoot ?? fileDir;
|
|
709
638
|
}
|
|
710
639
|
/**
|
|
711
640
|
* Every biome invocation must carry this. Biome exits 1 with "No files were
|
|
@@ -888,11 +817,7 @@ export const oxfmtFormatter = {
|
|
|
888
817
|
const found = await which("oxfmt");
|
|
889
818
|
if (found)
|
|
890
819
|
return [found, OXFMT_NO_ERROR_ON_UNMATCHED, filePath];
|
|
891
|
-
|
|
892
|
-
// config-only (it never probes the binary), so selection can reach here
|
|
893
|
-
// with nothing installed. The static command is bare `oxfmt` — spawning it
|
|
894
|
-
// only reproduces the reported `spawn oxfmt ENOENT`. Prove it unavailable.
|
|
895
|
-
return FORMATTER_UNAVAILABLE;
|
|
820
|
+
return ((await resolveManagedFormatterCommand("oxfmt", filePath, [OXFMT_NO_ERROR_ON_UNMATCHED], findInNodeModules, cwd)) ?? FORMATTER_UNAVAILABLE);
|
|
896
821
|
},
|
|
897
822
|
// Single source of truth: OXFMT_SUPPORTED_EXTENSIONS in tool-policy.ts.
|
|
898
823
|
// Do not hand-maintain a second copy of this list (#1134 — previously two
|
|
@@ -941,21 +866,14 @@ export const ruffFormatter = {
|
|
|
941
866
|
return true;
|
|
942
867
|
// No-config fallback: if Ruff is already available, allow formatter usage.
|
|
943
868
|
// This keeps Python default behavior consistent with startup defaults.
|
|
944
|
-
|
|
945
|
-
const installed = await getToolPath("ruff");
|
|
946
|
-
return Boolean(installed);
|
|
869
|
+
return Boolean(await getToolPath("ruff"));
|
|
947
870
|
},
|
|
948
871
|
};
|
|
949
872
|
export const blackFormatter = {
|
|
950
873
|
name: "black",
|
|
951
874
|
command: ["black", "$FILE"],
|
|
952
875
|
extensions: [".py", ".pyi"],
|
|
953
|
-
|
|
954
|
-
const venv = await findInVenv("black", cwd);
|
|
955
|
-
if (venv)
|
|
956
|
-
return [venv, filePath];
|
|
957
|
-
return null;
|
|
958
|
-
},
|
|
876
|
+
resolveCommand: managedFormatterResolver("black", [], findInVenv),
|
|
959
877
|
async detect(cwd) {
|
|
960
878
|
return hasBlackConfig(cwd);
|
|
961
879
|
},
|
|
@@ -1054,12 +972,7 @@ export const shfmtFormatter = {
|
|
|
1054
972
|
...styleArgs,
|
|
1055
973
|
]);
|
|
1056
974
|
},
|
|
1057
|
-
async
|
|
1058
|
-
if ((await which("shfmt")) !== null)
|
|
1059
|
-
return true;
|
|
1060
|
-
const { getToolPath } = await import("./installer/index.js");
|
|
1061
|
-
return Boolean(await getToolPath("shfmt"));
|
|
1062
|
-
},
|
|
975
|
+
detect: managedToolDetect("shfmt", undefined, async () => (await which("shfmt")) !== null),
|
|
1063
976
|
};
|
|
1064
977
|
export const nixfmtFormatter = {
|
|
1065
978
|
name: "nixfmt",
|
|
@@ -1121,12 +1034,7 @@ export const ktlintFormatter = {
|
|
|
1121
1034
|
return [inPath, "-F", filePath];
|
|
1122
1035
|
return resolveManagedSmartDefaultCommand("ktlint", filePath, ["-F"]);
|
|
1123
1036
|
},
|
|
1124
|
-
async
|
|
1125
|
-
if ((await which("ktlint")) !== null)
|
|
1126
|
-
return true;
|
|
1127
|
-
const { getToolPath } = await import("./installer/index.js");
|
|
1128
|
-
return Boolean(await getToolPath("ktlint"));
|
|
1129
|
-
},
|
|
1037
|
+
detect: managedToolDetect("ktlint", undefined, async () => (await which("ktlint")) !== null),
|
|
1130
1038
|
};
|
|
1131
1039
|
export const ktfmtFormatter = {
|
|
1132
1040
|
name: "ktfmt",
|
|
@@ -1256,36 +1164,25 @@ export const phpCsFixerFormatter = {
|
|
|
1256
1164
|
const configPath = resolvePhpCsFixerConfig(filePath);
|
|
1257
1165
|
const binary = (await findInVendorBin("php-cs-fixer", cwd)) ??
|
|
1258
1166
|
(await which("php-cs-fixer"));
|
|
1167
|
+
const managed = await resolveManagedFormatterCommand("php-cs-fixer", filePath, [], findInVendorBin, cwd);
|
|
1168
|
+
if (managed === FORMATTER_UNAVAILABLE)
|
|
1169
|
+
return FORMATTER_UNAVAILABLE;
|
|
1170
|
+
const resolved = binary ?? managed[0];
|
|
1259
1171
|
// #2413/#2472 review F4: both probes (vendor/bin, then PATH) have
|
|
1260
1172
|
// PROVEN the binary is absent — returning `null` here would fall back
|
|
1261
1173
|
// to the static `command` above, which is the SAME bare
|
|
1262
1174
|
// `php-cs-fixer` this just failed to find, spawning it only to
|
|
1263
1175
|
// re-observe the ENOENT already known. Report the proven-missing
|
|
1264
1176
|
// state instead so `formatFile` skips the wasted spawn.
|
|
1265
|
-
if (!
|
|
1177
|
+
if (!resolved)
|
|
1266
1178
|
return FORMATTER_UNAVAILABLE;
|
|
1267
1179
|
return configPath
|
|
1268
|
-
? [
|
|
1269
|
-
: [
|
|
1270
|
-
},
|
|
1271
|
-
async detect(cwd) {
|
|
1272
|
-
const vendorBin = await findInVendorBin("php-cs-fixer", cwd);
|
|
1273
|
-
const globalBin = await which("php-cs-fixer");
|
|
1274
|
-
if (!vendorBin && !globalBin)
|
|
1275
|
-
return false;
|
|
1276
|
-
// Only run if project has explicit config. This is a presence-only
|
|
1277
|
-
// climb from the project `cwd` (not necessarily the formatted file's
|
|
1278
|
-
// own directory) via this file's own `findUp` — deliberately NOT
|
|
1279
|
-
// merged with `resolvePhpCsFixerConfig` above (#2472 AC4): that
|
|
1280
|
-
// resolver climbs from the FILE's directory and needs the exact
|
|
1281
|
-
// winning path for `--config`, while this only needs a yes/no answer
|
|
1282
|
-
// from whatever `cwd` the caller passed. `rustfmtFormatter.detect`
|
|
1283
|
-
// keeps the same non-merged shape against `resolveCargoPackageEdition`
|
|
1284
|
-
// for the identical reason.
|
|
1285
|
-
const configs = [".php-cs-fixer.php", ".php-cs-fixer.dist.php"];
|
|
1286
|
-
const found = await findUp(configs, cwd);
|
|
1287
|
-
return found.length > 0;
|
|
1180
|
+
? [resolved, "fix", "--config", configPath, filePath]
|
|
1181
|
+
: [resolved, "fix", filePath];
|
|
1288
1182
|
},
|
|
1183
|
+
detect: managedToolDetect("php-cs-fixer", async (cwd) => (await findUp([".php-cs-fixer.php", ".php-cs-fixer.dist.php"], cwd))
|
|
1184
|
+
.length > 0, async (cwd) => Boolean((await findInVendorBin("php-cs-fixer", cwd)) ||
|
|
1185
|
+
(await which("php-cs-fixer")))),
|
|
1289
1186
|
};
|
|
1290
1187
|
export const csharpierFormatter = {
|
|
1291
1188
|
name: "csharpier",
|
|
@@ -1301,9 +1198,7 @@ export const csharpierFormatter = {
|
|
|
1301
1198
|
}
|
|
1302
1199
|
// CSharpier 0.x: invoked through the dotnet driver.
|
|
1303
1200
|
if ((await which("dotnet")) !== null) {
|
|
1304
|
-
const legacy = await
|
|
1305
|
-
timeout: 5000,
|
|
1306
|
-
});
|
|
1201
|
+
const legacy = await probeToolAsync("dotnet", ["csharpier", "--version"], { timeout: 5000 });
|
|
1307
1202
|
if (!legacy.error && legacy.status === 0) {
|
|
1308
1203
|
return ["dotnet", "csharpier", filePath];
|
|
1309
1204
|
}
|
|
@@ -1317,7 +1212,7 @@ export const csharpierFormatter = {
|
|
|
1317
1212
|
// … or the legacy dotnet-driver form (CSharpier 0.x).
|
|
1318
1213
|
if ((await which("dotnet")) === null)
|
|
1319
1214
|
return false;
|
|
1320
|
-
const result = await
|
|
1215
|
+
const result = await probeToolAsync("dotnet", ["csharpier", "--version"], {
|
|
1321
1216
|
timeout: 5000,
|
|
1322
1217
|
});
|
|
1323
1218
|
return !result.error && result.status === 0;
|
|
@@ -1343,23 +1238,8 @@ export const styluaFormatter = {
|
|
|
1343
1238
|
name: "stylua",
|
|
1344
1239
|
command: ["stylua", "$FILE"],
|
|
1345
1240
|
extensions: [".lua"],
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
// managed install, so before this the ONLY resolution was a bare
|
|
1349
|
-
// `stylua` PATH lookup — a project-local install via npm
|
|
1350
|
-
// `@johnnymorganz/stylua` (`node_modules/.bin/stylua`) was invisible.
|
|
1351
|
-
const local = findLocalBinUpwards("stylua", cwd);
|
|
1352
|
-
return local ? [local, filePath] : null;
|
|
1353
|
-
},
|
|
1354
|
-
async detect(cwd) {
|
|
1355
|
-
const local = findLocalBinUpwards("stylua", cwd);
|
|
1356
|
-
if (!local && (await which("stylua")) === null)
|
|
1357
|
-
return false;
|
|
1358
|
-
// Prefer explicit config but also run if binary is present in a Lua project
|
|
1359
|
-
const configs = ["stylua.toml", ".stylua.toml"];
|
|
1360
|
-
const found = await findUp(configs, cwd);
|
|
1361
|
-
return found.length > 0;
|
|
1362
|
-
},
|
|
1241
|
+
resolveCommand: managedFormatterResolver("stylua", [], findLocalBinUpwards),
|
|
1242
|
+
detect: managedToolDetect("stylua", async (cwd) => (await findUp(["stylua.toml", ".stylua.toml"], cwd)).length > 0, async (cwd) => Boolean(findLocalBinUpwards("stylua", cwd) || (await which("stylua")))),
|
|
1363
1243
|
};
|
|
1364
1244
|
export const ormoluFormatter = {
|
|
1365
1245
|
name: "ormolu",
|
|
@@ -1379,42 +1259,28 @@ export const taploFormatter = {
|
|
|
1379
1259
|
return [inPath, "fmt", filePath];
|
|
1380
1260
|
return resolveManagedSmartDefaultCommand("taplo", filePath, ["fmt"]);
|
|
1381
1261
|
},
|
|
1382
|
-
async
|
|
1383
|
-
if ((await which("taplo")) !== null)
|
|
1384
|
-
return true;
|
|
1385
|
-
const { getToolPath } = await import("./installer/index.js");
|
|
1386
|
-
return Boolean(await getToolPath("taplo"));
|
|
1387
|
-
},
|
|
1262
|
+
detect: managedToolDetect("taplo", undefined, async () => (await which("taplo")) !== null),
|
|
1388
1263
|
};
|
|
1389
1264
|
export const googleJavaFormatFormatter = {
|
|
1390
1265
|
name: "google-java-format",
|
|
1391
1266
|
command: ["google-java-format", "--replace", "$FILE"],
|
|
1392
1267
|
extensions: [".java"],
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
return false;
|
|
1396
|
-
return hasGoogleJavaFormatConfig(cwd);
|
|
1397
|
-
},
|
|
1268
|
+
resolveCommand: managedFormatterResolver("google-java-format", ["--replace"]),
|
|
1269
|
+
detect: managedToolDetect("google-java-format", hasGoogleJavaFormatConfig, async () => (await which("google-java-format")) !== null),
|
|
1398
1270
|
};
|
|
1399
1271
|
export const cljfmtFormatter = {
|
|
1400
1272
|
name: "cljfmt",
|
|
1401
1273
|
command: ["cljfmt", "fix", "$FILE"],
|
|
1402
1274
|
extensions: [".clj", ".cljc", ".cljs"],
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
return false;
|
|
1406
|
-
return hasCljfmtConfig(cwd);
|
|
1407
|
-
},
|
|
1275
|
+
resolveCommand: managedFormatterResolver("cljfmt", ["fix"]),
|
|
1276
|
+
detect: managedToolDetect("cljfmt", hasCljfmtConfig, async () => (await which("cljfmt")) !== null),
|
|
1408
1277
|
};
|
|
1409
1278
|
export const cmakeFormatFormatter = {
|
|
1410
1279
|
name: "cmake-format",
|
|
1411
1280
|
command: ["cmake-format", "-i", "$FILE"],
|
|
1412
1281
|
extensions: [".cmake"],
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
return false;
|
|
1416
|
-
return hasCmakeFormatConfig(cwd);
|
|
1417
|
-
},
|
|
1282
|
+
resolveCommand: managedFormatterResolver("cmake-format", ["-i"]),
|
|
1283
|
+
detect: managedToolDetect("cmake-format", hasCmakeFormatConfig, async () => (await which("cmake-format")) !== null),
|
|
1418
1284
|
};
|
|
1419
1285
|
export const cueFormatter = {
|
|
1420
1286
|
name: "cue",
|
|
@@ -1487,7 +1353,7 @@ export const psscriptanalyzerFormatFormatter = {
|
|
|
1487
1353
|
if (!pwsh)
|
|
1488
1354
|
return false;
|
|
1489
1355
|
// Check PSScriptAnalyzer module is available
|
|
1490
|
-
const result = await
|
|
1356
|
+
const result = await probeToolAsync(pwsh, [
|
|
1491
1357
|
"-NoProfile",
|
|
1492
1358
|
"-Command",
|
|
1493
1359
|
"Get-Module -ListAvailable PSScriptAnalyzer | Select-Object -First 1 -ExpandProperty Name",
|
|
@@ -45,11 +45,12 @@
|
|
|
45
45
|
* module doc for why blanket gitignore-respect is wrong for a secrets scanner
|
|
46
46
|
* (an untracked `.env` with a real credential is exactly the case gitleaks
|
|
47
47
|
* exists to catch, and `.env` is commonly gitignored). `classifyAndFilterFindings`
|
|
48
|
-
* backstops the config with a TS-side re-classification
|
|
49
|
-
* secrets-lane scratch tier
|
|
50
|
-
* `
|
|
51
|
-
*
|
|
52
|
-
*
|
|
48
|
+
* backstops the config with a TS-side re-classification. Findings under the
|
|
49
|
+
* secrets-lane scratch tier, in gitignored files, or inside nested repositories
|
|
50
|
+
* are DEMOTED (not deleted) to `severity: "info"` / `semantic: "none"` by the
|
|
51
|
+
* project-diagnostics adapter. They remain visible for an explicit audit but
|
|
52
|
+
* can never surface as blocking "leaked secret" alarms or automatic turn
|
|
53
|
+
* context. Tracked and ordinary untracked files remain blocking.
|
|
53
54
|
*
|
|
54
55
|
* Refs: #130, #1562
|
|
55
56
|
*/
|
|
@@ -333,8 +334,10 @@ export class GitleaksClient extends SecurityScanClient {
|
|
|
333
334
|
}
|
|
334
335
|
const rawFindings = parseGitleaksReport(fs.readFileSync(reportPath, "utf-8"));
|
|
335
336
|
const findings = await classifyAndFilterFindings(rawFindings, cwd);
|
|
337
|
+
// #2154: the one gitleaks site that parsed a scan of this root.
|
|
336
338
|
return {
|
|
337
339
|
success: true,
|
|
340
|
+
analyzed: true,
|
|
338
341
|
findings,
|
|
339
342
|
scannedAt,
|
|
340
343
|
};
|
|
@@ -375,7 +378,10 @@ export class GitleaksClient extends SecurityScanClient {
|
|
|
375
378
|
* assert the config we hand it, so this backstop guarantees the demotion
|
|
376
379
|
* even if the generated regex ever mismatches gitleaks's own path
|
|
377
380
|
* normalization.
|
|
378
|
-
* 2.
|
|
381
|
+
* 2. A finding below a nested git boundary is labelled
|
|
382
|
+
* `nested-repository`. Parent scans must not claim ownership of a
|
|
383
|
+
* submodule's or nested repository's files.
|
|
384
|
+
* 3. Every OTHER finding gets `pathStatus` set from git's tracked/ignored
|
|
379
385
|
* view, so a future false-positive triage is a log read.
|
|
380
386
|
*
|
|
381
387
|
* `collectTrackedFiles`/`collectUntrackedIgnoredIds` degrade to `undefined`
|
|
@@ -383,6 +389,32 @@ export class GitleaksClient extends SecurityScanClient {
|
|
|
383
389
|
* keep `pathStatus: undefined` rather than a guessed value (fail-open,
|
|
384
390
|
* matching `git-tracked-ignore.ts`'s own contract). Exported for unit tests.
|
|
385
391
|
*/
|
|
392
|
+
function isInsideNestedGitRepository(file, cwd) {
|
|
393
|
+
const root = path.resolve(cwd);
|
|
394
|
+
const absoluteFile = path.isAbsolute(file)
|
|
395
|
+
? path.resolve(file)
|
|
396
|
+
: path.resolve(root, file);
|
|
397
|
+
const relative = path.relative(root, absoluteFile);
|
|
398
|
+
if (relative === ".." ||
|
|
399
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
400
|
+
path.isAbsolute(relative))
|
|
401
|
+
return false;
|
|
402
|
+
let directory = path.dirname(absoluteFile);
|
|
403
|
+
while (directory !== root) {
|
|
404
|
+
try {
|
|
405
|
+
if (fs.existsSync(path.join(directory, ".git")))
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
const parent = path.dirname(directory);
|
|
412
|
+
if (parent === directory)
|
|
413
|
+
return false;
|
|
414
|
+
directory = parent;
|
|
415
|
+
}
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
386
418
|
export async function classifyAndFilterFindings(findings, cwd) {
|
|
387
419
|
if (findings.length === 0)
|
|
388
420
|
return findings;
|
|
@@ -399,6 +431,10 @@ export async function classifyAndFilterFindings(findings, cwd) {
|
|
|
399
431
|
const absPath = path.isAbsolute(finding.file)
|
|
400
432
|
? finding.file
|
|
401
433
|
: path.resolve(cwd, finding.file);
|
|
434
|
+
if (isInsideNestedGitRepository(absPath, cwd)) {
|
|
435
|
+
classified.push({ ...finding, pathStatus: "nested-repository" });
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
402
438
|
let pathStatus;
|
|
403
439
|
if (trackedIds?.has(normalizeEphemeralMapKey(absPath))) {
|
|
404
440
|
pathStatus = "tracked";
|
|
@@ -20,6 +20,8 @@ import * as fs from "node:fs";
|
|
|
20
20
|
import * as os from "node:os";
|
|
21
21
|
import * as path from "node:path";
|
|
22
22
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
23
|
+
import { probeToolAsync } from "./tool-probe.js";
|
|
24
|
+
import { firstOutputLine, spawnFailedWithNoOutput, } from "./dispatch/runners/utils/spawn-outcome.js";
|
|
23
25
|
import { assertInstallAllowed } from "./project-trust.js";
|
|
24
26
|
import { SecurityScanClient } from "./security-scan-client.js";
|
|
25
27
|
import { classifyProbeFailure, describeProbeEvidence, INSTALL_TRANSIENT_MAX_ATTEMPTS, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
|
|
@@ -86,7 +88,7 @@ export class GovulncheckClient extends SecurityScanClient {
|
|
|
86
88
|
let goOnPath;
|
|
87
89
|
let goHostStallMs;
|
|
88
90
|
try {
|
|
89
|
-
goOnPath = await
|
|
91
|
+
goOnPath = await probeToolAsync("go", ["version"], {
|
|
90
92
|
timeout: 5000,
|
|
91
93
|
});
|
|
92
94
|
}
|
|
@@ -219,7 +221,7 @@ export class GovulncheckClient extends SecurityScanClient {
|
|
|
219
221
|
let reprobe;
|
|
220
222
|
let reprobeHostStallMs;
|
|
221
223
|
try {
|
|
222
|
-
reprobe = await
|
|
224
|
+
reprobe = await probeToolAsync("govulncheck", ["-version"], {
|
|
223
225
|
timeout: 5000,
|
|
224
226
|
});
|
|
225
227
|
}
|
|
@@ -366,21 +368,60 @@ export class GovulncheckClient extends SecurityScanClient {
|
|
|
366
368
|
const bin = this.binaryPath ?? "govulncheck";
|
|
367
369
|
try {
|
|
368
370
|
const result = await safeSpawnAsync(bin, ["-mode=source", "-format=json", "./..."], { cwd, timeout: SCAN_TIMEOUT_MS });
|
|
371
|
+
// #2154 v4 F1: this was the one runner client with no `result.error`
|
|
372
|
+
// check before its parse site. `safeSpawnAsync` reports a timeout or
|
|
373
|
+
// an ambient abort as `{status: null, error, stdout: <partial>}`, and
|
|
374
|
+
// the old guard (`!stdout.trim() && status !== 0 && status !== 3`)
|
|
375
|
+
// let that partial stream through as an authoritative scan. Same
|
|
376
|
+
// order as the seven sibling clients: the process first, its output
|
|
377
|
+
// second.
|
|
378
|
+
if (result.error) {
|
|
379
|
+
this.log(`Scan error: ${result.error.message}`);
|
|
380
|
+
return {
|
|
381
|
+
...EMPTY_RESULT,
|
|
382
|
+
scannedAt,
|
|
383
|
+
summary: result.error.message.slice(0, 200),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
369
386
|
// govulncheck exits non-zero (status 3) when vulnerabilities are
|
|
370
|
-
// found — that's success from our perspective.
|
|
371
|
-
//
|
|
387
|
+
// found — that's success from our perspective. Any other exit code
|
|
388
|
+
// means the scan was not analysed, even if its config preamble or
|
|
389
|
+
// partial stream reached stdout before the failure.
|
|
372
390
|
const rawStdout = result.stdout ?? "";
|
|
373
|
-
if (
|
|
374
|
-
|
|
391
|
+
if (result.status !== 0 && result.status !== 3) {
|
|
392
|
+
return {
|
|
393
|
+
...EMPTY_RESULT,
|
|
394
|
+
scannedAt,
|
|
395
|
+
summary: firstOutputLine(result.stderr) || "scan failed",
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
if (!rawStdout.trim()) {
|
|
399
|
+
// Nothing to parse. `govulncheck -format=json` always writes a
|
|
400
|
+
// stream (a `config` record at minimum), so an empty one is never
|
|
401
|
+
// evidence of "no vulnerabilities" — it must not retire a retained
|
|
402
|
+
// CVE finding, whatever the exit code. `spawnFailedWithNoOutput`
|
|
403
|
+
// is the same shared discriminator knip and dead-code use, in
|
|
404
|
+
// place of this file's hand-rolled status test.
|
|
405
|
+
if (spawnFailedWithNoOutput(result, rawStdout)) {
|
|
406
|
+
this.log(`Scan failed: ${(result.stderr ?? "").slice(0, 200)}`);
|
|
407
|
+
return {
|
|
408
|
+
...EMPTY_RESULT,
|
|
409
|
+
scannedAt,
|
|
410
|
+
summary: firstOutputLine(result.stderr) || "scan failed",
|
|
411
|
+
};
|
|
412
|
+
}
|
|
375
413
|
return {
|
|
376
414
|
...EMPTY_RESULT,
|
|
415
|
+
success: true,
|
|
377
416
|
scannedAt,
|
|
378
|
-
summary:
|
|
417
|
+
summary: "govulncheck produced no output; nothing parsed",
|
|
379
418
|
};
|
|
380
419
|
}
|
|
381
420
|
const findings = parseGovulncheckJson(rawStdout);
|
|
421
|
+
// #2154: the one govulncheck site that parsed a scan of this root.
|
|
382
422
|
return {
|
|
383
423
|
success: true,
|
|
424
|
+
analyzed: true,
|
|
384
425
|
findings,
|
|
385
426
|
scannedAt,
|
|
386
427
|
};
|