@herbertgao/pi-extensions 2026.9.2 → 2026.9.3
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 +3 -2
- package/THIRD_PARTY_NOTICES.md +24 -0
- 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-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 +7 -4
- package/node_modules/pi-lens/dist/tools/ast-dump.js +0 -103
|
@@ -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
|
};
|
|
@@ -66,6 +66,7 @@ import { commitDurableStoreAsync } from "../durable-store.js";
|
|
|
66
66
|
import { getGlobalPiLensDir } from "../file-utils.js";
|
|
67
67
|
import { allAvailableGlobalBinDirs, installArgs, pmBinary, resolveNodePackageManager, } from "../package-manager.js";
|
|
68
68
|
import { resetSafeSpawnWindowsCommandCache, safeSpawnAsync, } from "../safe-spawn.js";
|
|
69
|
+
import { probeToolAsync } from "../tool-probe.js";
|
|
69
70
|
import { logSessionStart } from "../sessionstart-logger.js";
|
|
70
71
|
// Global installation directory for pi-lens tools
|
|
71
72
|
const TOOLS_DIR = path.join(getGlobalPiLensDir(), "tools");
|
|
@@ -227,6 +228,114 @@ const OS_ARCH_ZIP_ASSETS = {
|
|
|
227
228
|
darwin: { x64: "darwin_amd64.zip", arm64: "darwin_arm64.zip" },
|
|
228
229
|
win32: { x64: "windows_amd64.zip", arm64: "windows_arm64.zip" },
|
|
229
230
|
};
|
|
231
|
+
function managedPackageFormatterTool(spec) {
|
|
232
|
+
return {
|
|
233
|
+
id: spec.id,
|
|
234
|
+
name: spec.name,
|
|
235
|
+
checkCommand: spec.id,
|
|
236
|
+
checkArgs: ["--version"],
|
|
237
|
+
installStrategy: spec.installStrategy,
|
|
238
|
+
packageName: spec.packageName,
|
|
239
|
+
binaryName: spec.id,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const MANAGED_PACKAGE_FORMATTERS = [
|
|
243
|
+
{ id: "black", name: "Black", installStrategy: "pip", packageName: "black" },
|
|
244
|
+
{
|
|
245
|
+
id: "cmake-format",
|
|
246
|
+
name: "cmake-format",
|
|
247
|
+
installStrategy: "pip",
|
|
248
|
+
packageName: "cmakelang",
|
|
249
|
+
},
|
|
250
|
+
{ id: "oxfmt", name: "oxfmt", installStrategy: "npm", packageName: "oxfmt" },
|
|
251
|
+
];
|
|
252
|
+
function managedGitHubFormatterTool(spec) {
|
|
253
|
+
return {
|
|
254
|
+
id: spec.id,
|
|
255
|
+
name: spec.name,
|
|
256
|
+
checkCommand: spec.id,
|
|
257
|
+
checkArgs: ["--version"],
|
|
258
|
+
installStrategy: "github",
|
|
259
|
+
binaryName: spec.id,
|
|
260
|
+
github: {
|
|
261
|
+
repo: `${spec.owner}/${spec.repo}`,
|
|
262
|
+
assetMatch: spec.assetPattern,
|
|
263
|
+
...(spec.binaryInArchive && { binaryInArchive: spec.binaryInArchive }),
|
|
264
|
+
...(spec.kind === "phar" && { launcher: "php" }),
|
|
265
|
+
...(spec.kind === "jar" && { launcher: "java" }),
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
const MANAGED_GITHUB_FORMATTERS = [
|
|
270
|
+
{
|
|
271
|
+
id: "stylua",
|
|
272
|
+
name: "StyLua",
|
|
273
|
+
owner: "JohnnyMorganz",
|
|
274
|
+
repo: "StyLua",
|
|
275
|
+
assetPattern: archAssetMatch({
|
|
276
|
+
linux: { x64: "linux-x86_64.zip", arm64: "linux-aarch64.zip" },
|
|
277
|
+
darwin: { x64: "macos-x86_64.zip", arm64: "macos-aarch64.zip" },
|
|
278
|
+
win32: { x64: "windows-x86_64.zip" },
|
|
279
|
+
}),
|
|
280
|
+
kind: "binary",
|
|
281
|
+
binaryInArchive: "stylua",
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
id: "php-cs-fixer",
|
|
285
|
+
name: "PHP CS Fixer",
|
|
286
|
+
owner: "PHP-CS-Fixer",
|
|
287
|
+
repo: "PHP-CS-Fixer",
|
|
288
|
+
assetPattern: (platform) => platform === "linux" || platform === "darwin" || platform === "win32"
|
|
289
|
+
? "php-cs-fixer.phar"
|
|
290
|
+
: undefined,
|
|
291
|
+
kind: "phar",
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: "cljfmt",
|
|
295
|
+
name: "cljfmt",
|
|
296
|
+
owner: "weavejester",
|
|
297
|
+
repo: "cljfmt",
|
|
298
|
+
assetPattern: (platform, arch) => {
|
|
299
|
+
if (platform === "linux")
|
|
300
|
+
return arch === "arm64"
|
|
301
|
+
? "standalone.jar"
|
|
302
|
+
: "linux-amd64-static.tar.gz";
|
|
303
|
+
if (platform === "darwin")
|
|
304
|
+
return "standalone.jar";
|
|
305
|
+
if (platform === "win32")
|
|
306
|
+
return "win-amd64.zip";
|
|
307
|
+
return undefined;
|
|
308
|
+
},
|
|
309
|
+
kind: "jar",
|
|
310
|
+
binaryInArchive: "cljfmt",
|
|
311
|
+
},
|
|
312
|
+
];
|
|
313
|
+
const MANAGED_MAVEN_FORMATTERS = [
|
|
314
|
+
{
|
|
315
|
+
id: "google-java-format",
|
|
316
|
+
name: "google-java-format",
|
|
317
|
+
groupId: "com.google.googlejavaformat",
|
|
318
|
+
artifactId: "google-java-format",
|
|
319
|
+
version: "1.27.0",
|
|
320
|
+
classifier: "all-deps",
|
|
321
|
+
},
|
|
322
|
+
];
|
|
323
|
+
function managedMavenFormatterTool(spec) {
|
|
324
|
+
return {
|
|
325
|
+
id: spec.id,
|
|
326
|
+
name: spec.name,
|
|
327
|
+
checkCommand: spec.id,
|
|
328
|
+
checkArgs: ["--version"],
|
|
329
|
+
installStrategy: "maven",
|
|
330
|
+
binaryName: spec.id,
|
|
331
|
+
maven: {
|
|
332
|
+
groupId: spec.groupId,
|
|
333
|
+
artifactId: spec.artifactId,
|
|
334
|
+
version: spec.version,
|
|
335
|
+
classifier: spec.classifier,
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
}
|
|
230
339
|
export const TOOLS = [
|
|
231
340
|
// Core LSP servers
|
|
232
341
|
{
|
|
@@ -275,6 +384,7 @@ export const TOOLS = [
|
|
|
275
384
|
packageName: "prettier",
|
|
276
385
|
binaryName: "prettier",
|
|
277
386
|
},
|
|
387
|
+
...MANAGED_PACKAGE_FORMATTERS.map(managedPackageFormatterTool),
|
|
278
388
|
{
|
|
279
389
|
id: "ruff",
|
|
280
390
|
name: "Ruff",
|
|
@@ -707,6 +817,8 @@ export const TOOLS = [
|
|
|
707
817
|
// bare binary, no archive
|
|
708
818
|
},
|
|
709
819
|
},
|
|
820
|
+
...MANAGED_GITHUB_FORMATTERS.map(managedGitHubFormatterTool),
|
|
821
|
+
...MANAGED_MAVEN_FORMATTERS.map(managedMavenFormatterTool),
|
|
710
822
|
{
|
|
711
823
|
id: "rust-analyzer",
|
|
712
824
|
name: "rust-analyzer",
|
|
@@ -1633,29 +1745,16 @@ function isAstGrepVersionOutput(output) {
|
|
|
1633
1745
|
return /\bast[- ]grep\b/i.test(output);
|
|
1634
1746
|
}
|
|
1635
1747
|
async function verifyAstGrepProbePath(binPath) {
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
timeout: 5000,
|
|
1643
|
-
});
|
|
1644
|
-
}
|
|
1645
|
-
catch {
|
|
1646
|
-
// SYNCHRONOUS spawn throw (Windows `spawn UNKNOWN`/EINVAL, the pidusage
|
|
1647
|
-
// bug class, #533) — best-effort probe, resolve rather than reject.
|
|
1648
|
-
resolve(false);
|
|
1649
|
-
return;
|
|
1650
|
-
}
|
|
1651
|
-
let output = "";
|
|
1652
|
-
proc.stdout?.on("data", (data) => (output += data));
|
|
1653
|
-
proc.stderr?.on("data", (data) => (output += data));
|
|
1654
|
-
proc.on("exit", (code) => {
|
|
1655
|
-
resolve(code === 0 && isAstGrepVersionOutput(output));
|
|
1656
|
-
});
|
|
1657
|
-
proc.on("error", () => resolve(false));
|
|
1748
|
+
// #2894: through the probe seam rather than a hand-rolled `spawn` promise.
|
|
1749
|
+
// `probeToolAsync` already owns the synchronous-throw case (#533), the
|
|
1750
|
+
// Windows `.cmd`/`.bat` resolution this used `shell: true` for, and the
|
|
1751
|
+
// timeout tree-kill a bare `spawn({ timeout })` does not do.
|
|
1752
|
+
const result = await probeToolAsync(binPath, ["--version"], {
|
|
1753
|
+
timeout: 5000,
|
|
1658
1754
|
});
|
|
1755
|
+
return (!result.error &&
|
|
1756
|
+
result.status === 0 &&
|
|
1757
|
+
isAstGrepVersionOutput(`${result.stdout}${result.stderr}`));
|
|
1659
1758
|
}
|
|
1660
1759
|
// Exported for testing only.
|
|
1661
1760
|
export async function checkProbeCache(toolId) {
|
|
@@ -2151,29 +2250,15 @@ export async function getAllToolStatuses() {
|
|
|
2151
2250
|
status.installed = true;
|
|
2152
2251
|
status.source = "global-path";
|
|
2153
2252
|
status.path = tool.checkCommand;
|
|
2154
|
-
// Try to get version
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
2160
|
-
shell: process.platform === "win32",
|
|
2161
|
-
timeout: 5000,
|
|
2162
|
-
});
|
|
2163
|
-
}
|
|
2164
|
-
catch {
|
|
2165
|
-
// SYNCHRONOUS spawn throw (Windows `spawn UNKNOWN`/EINVAL, the
|
|
2166
|
-
// pidusage bug class, #533) — best-effort, resolve empty version.
|
|
2167
|
-
resolve("");
|
|
2168
|
-
return;
|
|
2169
|
-
}
|
|
2170
|
-
let out = "";
|
|
2171
|
-
proc.stdout?.on("data", (d) => (out += d));
|
|
2172
|
-
proc.stderr?.on("data", (d) => (out += d));
|
|
2173
|
-
proc.on("exit", () => resolve(out.trim().split("\n")[0]?.slice(0, 30) || ""));
|
|
2174
|
-
proc.on("error", () => resolve(""));
|
|
2253
|
+
// Try to get version — through the probe seam (#2894), which owns the
|
|
2254
|
+
// synchronous-throw case (#533) and the timeout tree-kill that a bare
|
|
2255
|
+
// `spawn({ timeout })` promise did not do.
|
|
2256
|
+
const probe = await probeToolAsync(tool.checkCommand, ["--version"], {
|
|
2257
|
+
timeout: 5000,
|
|
2175
2258
|
});
|
|
2176
|
-
status.version =
|
|
2259
|
+
status.version =
|
|
2260
|
+
`${probe.stdout}${probe.stderr}`.trim().split("\n")[0]?.slice(0, 30) ||
|
|
2261
|
+
undefined;
|
|
2177
2262
|
statuses.push(status);
|
|
2178
2263
|
continue;
|
|
2179
2264
|
}
|
|
@@ -2515,6 +2600,13 @@ function getGitHubInstalledBinaryName(binaryName, platform, assetName) {
|
|
|
2515
2600
|
return `${binaryName}.cmd`;
|
|
2516
2601
|
return `${binaryName}.exe`;
|
|
2517
2602
|
}
|
|
2603
|
+
function launcherForGitHubAsset(spec, assetName) {
|
|
2604
|
+
if (spec.launcher === "php" && assetName.endsWith(".phar"))
|
|
2605
|
+
return "php";
|
|
2606
|
+
if (spec.launcher === "java" && assetName.endsWith(".jar"))
|
|
2607
|
+
return "java";
|
|
2608
|
+
return undefined;
|
|
2609
|
+
}
|
|
2518
2610
|
function getArchiveBinaryCandidates(binaryName, platform, assetName) {
|
|
2519
2611
|
if (platform !== "win32")
|
|
2520
2612
|
return [binaryName];
|
|
@@ -2867,10 +2959,35 @@ prefetchedRelease) {
|
|
|
2867
2959
|
const binaryName = tool.binaryName ?? tool.id;
|
|
2868
2960
|
const isWindows = platform === "win32";
|
|
2869
2961
|
const finalBinaryName = getGitHubInstalledBinaryName(binaryName, platform, asset.name);
|
|
2870
|
-
|
|
2962
|
+
let destPath = path.join(GITHUB_BIN_DIR, finalBinaryName);
|
|
2871
2963
|
const assetName = asset.name;
|
|
2964
|
+
const launcherRuntime = launcherForGitHubAsset(spec, assetName);
|
|
2965
|
+
if (launcherRuntime &&
|
|
2966
|
+
!(await isCommandAvailable(launcherRuntime, ["--version"]))) {
|
|
2967
|
+
logSessionStart(`github-install ${tool.id}: ${launcherRuntime} not found — asset requires a runtime launcher`);
|
|
2968
|
+
return undefined;
|
|
2969
|
+
}
|
|
2872
2970
|
try {
|
|
2873
|
-
if (
|
|
2971
|
+
if (launcherRuntime) {
|
|
2972
|
+
const assetPath = path.join(GITHUB_BIN_DIR, `${tool.id}${assetName.endsWith(".phar") ? ".phar" : ".jar"}`);
|
|
2973
|
+
await writeFileAtomicAsync(assetPath, assetBuffer, {
|
|
2974
|
+
bestEffort: false,
|
|
2975
|
+
mode: 0o750,
|
|
2976
|
+
});
|
|
2977
|
+
const launcherName = isWindows ? `${binaryName}.bat` : binaryName;
|
|
2978
|
+
destPath = path.join(GITHUB_BIN_DIR, launcherName);
|
|
2979
|
+
const runtimeTarget = assetName.endsWith(".phar")
|
|
2980
|
+
? `${tool.id}.phar`
|
|
2981
|
+
: `${tool.id}.jar`;
|
|
2982
|
+
const command = isWindows
|
|
2983
|
+
? `@echo off\r\n${launcherRuntime} "%~dp0${runtimeTarget}" %*\r\n`
|
|
2984
|
+
: `#!/bin/sh\nexec ${launcherRuntime} "$(dirname "$0")/${runtimeTarget}" "$@"\n`;
|
|
2985
|
+
await writeFileAtomicAsync(destPath, command, {
|
|
2986
|
+
bestEffort: false,
|
|
2987
|
+
mode: isWindows ? undefined : 0o750,
|
|
2988
|
+
});
|
|
2989
|
+
}
|
|
2990
|
+
else if (assetName.endsWith(".gz") && !assetName.endsWith(".tar.gz")) {
|
|
2874
2991
|
// Bare gzip (e.g. rust-analyzer-x86_64-unknown-linux-gnu.gz) — decompress directly
|
|
2875
2992
|
const decompressed = await new Promise((resolve, reject) => {
|
|
2876
2993
|
const gunzip = createGunzip();
|
|
@@ -3220,7 +3337,7 @@ async function probeManagedToolVersion(tool) {
|
|
|
3220
3337
|
if (!cached?.path || !existsSync(cached.path))
|
|
3221
3338
|
return undefined;
|
|
3222
3339
|
try {
|
|
3223
|
-
const result = await
|
|
3340
|
+
const result = await probeToolAsync(cached.path, tool.checkArgs, {
|
|
3224
3341
|
timeout: getToolVerificationTimeout(tool),
|
|
3225
3342
|
input: "",
|
|
3226
3343
|
ignoreAmbientSignal: true,
|
|
@@ -3926,11 +4043,17 @@ async function installArchiveTool(tool) {
|
|
|
3926
4043
|
* generic fallback `finishInstallAttempt` uses when nothing set it.
|
|
3927
4044
|
*/
|
|
3928
4045
|
function recordPackageManagerInstallException(toolId, strategyLabel, packageName, err) {
|
|
3929
|
-
const message = err.message;
|
|
4046
|
+
const message = boundInstallError(err.message);
|
|
3930
4047
|
logSessionStart(`auto-install ${strategyLabel} ${packageName}: exception: ${message}`);
|
|
3931
4048
|
installFailureReasons.set(toolId, message);
|
|
3932
4049
|
return undefined;
|
|
3933
4050
|
}
|
|
4051
|
+
const INSTALL_ERROR_LINE_LIMIT = 1000;
|
|
4052
|
+
const INSTALL_CANDIDATE_ERROR_LIMIT = 200;
|
|
4053
|
+
function boundInstallError(value, limit = INSTALL_ERROR_LINE_LIMIT) {
|
|
4054
|
+
const line = value.replace(/[\r\n]+/g, " ").trim();
|
|
4055
|
+
return line.length > limit ? `${line.slice(0, limit - 3)}...` : line;
|
|
4056
|
+
}
|
|
3934
4057
|
async function installNpmTool(toolId, packageName, binaryName, verificationArgs = ["--version"], verificationTimeoutMs = 10_000,
|
|
3935
4058
|
/** See {@link packageEntryVerification} — spawn-free verification (#2722). */
|
|
3936
4059
|
packageEntryOf) {
|
|
@@ -4136,7 +4259,7 @@ options = {}) {
|
|
|
4136
4259
|
? [...verb, packageName]
|
|
4137
4260
|
: ["-m", "pip", ...verb, packageName],
|
|
4138
4261
|
}));
|
|
4139
|
-
|
|
4262
|
+
const errors = [];
|
|
4140
4263
|
for (const candidate of pipCandidates) {
|
|
4141
4264
|
const pipResult = await safeSpawnAsync(candidate.command, candidate.args, {
|
|
4142
4265
|
timeout: 120_000,
|
|
@@ -4226,10 +4349,11 @@ options = {}) {
|
|
|
4226
4349
|
}
|
|
4227
4350
|
return packageName;
|
|
4228
4351
|
}
|
|
4229
|
-
|
|
4230
|
-
|
|
4352
|
+
const candidateError = `${candidate.command} ${candidate.args.join(" ")}: ${boundInstallError(outcome.error, INSTALL_CANDIDATE_ERROR_LIMIT)}`;
|
|
4353
|
+
errors.push(candidateError);
|
|
4354
|
+
debugLog(`[pip-fallback] ${candidateError}`);
|
|
4231
4355
|
}
|
|
4232
|
-
throw new Error(`Failed to install ${packageName}: no usable pip command found (${
|
|
4356
|
+
throw new Error(`Failed to install ${packageName}: no usable pip command found (${errors.join(" | ") || "unknown error"})`);
|
|
4233
4357
|
}
|
|
4234
4358
|
catch (err) {
|
|
4235
4359
|
return recordPackageManagerInstallException(toolId, "pip", packageName, err);
|
|
@@ -4381,6 +4505,12 @@ export async function installTool(toolId) {
|
|
|
4381
4505
|
case "github": {
|
|
4382
4506
|
if (!tool.github)
|
|
4383
4507
|
return false;
|
|
4508
|
+
if (!tool.github.assetMatch(process.platform, process.arch)) {
|
|
4509
|
+
const reason = `unsupported platform=${process.platform} arch=${process.arch}`;
|
|
4510
|
+
noteInstallAttempt(tool.id, "unavailable", reason);
|
|
4511
|
+
logSessionStart(`auto-install ${tool.id}: ${reason}`);
|
|
4512
|
+
return false;
|
|
4513
|
+
}
|
|
4384
4514
|
const ghPath = await installGitHubTool(tool);
|
|
4385
4515
|
return finishInstallAttempt(tool.id, ghPath !== undefined, startedAt);
|
|
4386
4516
|
}
|
|
@@ -4393,6 +4523,12 @@ export async function installTool(toolId) {
|
|
|
4393
4523
|
case "archive": {
|
|
4394
4524
|
if (!tool.archive)
|
|
4395
4525
|
return false;
|
|
4526
|
+
if (!resolveArchiveUrl(tool.archive)) {
|
|
4527
|
+
const reason = `unsupported platform=${process.platform} arch=${process.arch}`;
|
|
4528
|
+
noteInstallAttempt(tool.id, "unavailable", reason);
|
|
4529
|
+
logSessionStart(`auto-install ${tool.id}: ${reason}`);
|
|
4530
|
+
return false;
|
|
4531
|
+
}
|
|
4396
4532
|
const archivePath = await installArchiveTool(tool);
|
|
4397
4533
|
return finishInstallAttempt(tool.id, archivePath !== undefined, startedAt);
|
|
4398
4534
|
}
|
|
@@ -4711,6 +4847,8 @@ export function getToolInstallStrategy(toolId) {
|
|
|
4711
4847
|
* "at least one platform" guard instead.
|
|
4712
4848
|
*/
|
|
4713
4849
|
export const GITHUB_TOOLS = [
|
|
4850
|
+
"cljfmt",
|
|
4851
|
+
"php-cs-fixer",
|
|
4714
4852
|
"shellcheck",
|
|
4715
4853
|
"shfmt",
|
|
4716
4854
|
"rust-analyzer",
|
|
@@ -4745,6 +4883,12 @@ export function resolveGitHubAsset(toolId, platform, arch) {
|
|
|
4745
4883
|
const tool = TOOLS.find((t) => t.id === toolId);
|
|
4746
4884
|
return tool?.github?.assetMatch(platform, arch);
|
|
4747
4885
|
}
|
|
4886
|
+
export function resolveGitHubAssetLauncher(toolId, _platform, assetName) {
|
|
4887
|
+
const tool = TOOLS.find((t) => t.id === toolId);
|
|
4888
|
+
return tool?.github
|
|
4889
|
+
? launcherForGitHubAsset(tool.github, assetName)
|
|
4890
|
+
: undefined;
|
|
4891
|
+
}
|
|
4748
4892
|
export function resolveGitHubInstalledBinaryName(toolId, platform, assetName) {
|
|
4749
4893
|
const tool = TOOLS.find((t) => t.id === toolId);
|
|
4750
4894
|
if (!tool)
|
|
@@ -346,7 +346,15 @@ export class JscpdClient {
|
|
|
346
346
|
lines: c.lines ?? 0,
|
|
347
347
|
tokens: c.tokens ?? 0,
|
|
348
348
|
}));
|
|
349
|
-
|
|
349
|
+
// #2154: the one jscpd site that parsed a scan of this root.
|
|
350
|
+
return {
|
|
351
|
+
success: true,
|
|
352
|
+
analyzed: true,
|
|
353
|
+
clones,
|
|
354
|
+
duplicatedLines,
|
|
355
|
+
totalLines,
|
|
356
|
+
percentage,
|
|
357
|
+
};
|
|
350
358
|
}
|
|
351
359
|
catch (err) {
|
|
352
360
|
void err;
|
|
@@ -323,7 +323,10 @@ export class KnipClient {
|
|
|
323
323
|
completed?.projectSeq === options.projectSeq &&
|
|
324
324
|
this.matchesMemoSignal(targetDir, completed.signal)) {
|
|
325
325
|
this.log(`Analysis cache hit for ${key} at projectSeq ${options.projectSeq}`);
|
|
326
|
-
|
|
326
|
+
// #2154: a memo hit did not scan the root during THIS call, so it
|
|
327
|
+
// carries no authority to retire a retained finding — the flag is
|
|
328
|
+
// cleared even though the memoised result set it when it ran.
|
|
329
|
+
return { ...completed.result, execution: "cache", analyzed: false };
|
|
327
330
|
}
|
|
328
331
|
// A project that ships its own knip needs no managed install and no
|
|
329
332
|
// managed probe — the shim is already on disk, and gating on
|
|
@@ -510,7 +513,9 @@ export class KnipClient {
|
|
|
510
513
|
// status === 0 with empty stdout has never been observed against a
|
|
511
514
|
// real knip binary (a genuine clean run still prints
|
|
512
515
|
// `{"issues":[]}`), but a defensive fallback is cheap insurance
|
|
513
|
-
// against a future knip release that changes this.
|
|
516
|
+
// against a future knip release that changes this. Deliberately NOT
|
|
517
|
+
// `analyzed` (#2154): nothing was parsed, so this shape must not
|
|
518
|
+
// retire a retained knip finding — it is reported cold instead.
|
|
514
519
|
return {
|
|
515
520
|
...EMPTY_RESULT,
|
|
516
521
|
success: true,
|
|
@@ -711,6 +716,8 @@ export class KnipClient {
|
|
|
711
716
|
}
|
|
712
717
|
return {
|
|
713
718
|
success: true,
|
|
719
|
+
// #2154: the one knip site that parsed a scan of this root.
|
|
720
|
+
analyzed: true,
|
|
714
721
|
issues,
|
|
715
722
|
unusedExports,
|
|
716
723
|
unusedFiles,
|
|
@@ -30,7 +30,13 @@ const PROJECT_MARKERS_BY_KIND = {
|
|
|
30
30
|
sql: [".sqlfluff", "pyproject.toml"],
|
|
31
31
|
php: ["composer.json", "composer.lock"],
|
|
32
32
|
prisma: ["schema.prisma", "prisma/schema.prisma"],
|
|
33
|
-
|
|
33
|
+
// #2870: `build.gradle.kts` is the Kotlin-DSL spelling of `build.gradle`
|
|
34
|
+
// and marks a Java module just as well — its absence here (and in
|
|
35
|
+
// ROOT_MARKERS_BY_KIND below) is what left a `.java` file in a
|
|
36
|
+
// Kotlin-DSL Gradle module resolving to the workspace root, where a
|
|
37
|
+
// polyglot repo's go.mod then claimed it. `kotlin` already lists both
|
|
38
|
+
// spellings; java listed only the Groovy one.
|
|
39
|
+
java: ["pom.xml", "build.gradle", "build.gradle.kts", ".classpath"],
|
|
34
40
|
kotlin: ["build.gradle.kts", "build.gradle", "pom.xml"],
|
|
35
41
|
swift: ["Package.swift"],
|
|
36
42
|
dart: ["pubspec.yaml"],
|
|
@@ -76,7 +82,11 @@ const ROOT_MARKERS_BY_KIND = {
|
|
|
76
82
|
sql: [".sqlfluff", "pyproject.toml", "setup.cfg", "tox.ini"],
|
|
77
83
|
php: ["composer.json", "composer.lock"],
|
|
78
84
|
prisma: ["prisma/schema.prisma", "schema.prisma"],
|
|
79
|
-
|
|
85
|
+
// #2870: see PROJECT_MARKERS_BY_KIND.java above — this is the table
|
|
86
|
+
// `resolveLanguageRootForFile` walks, so the missing Kotlin-DSL spelling
|
|
87
|
+
// is what anchored a nested Gradle module's `.java` file at the
|
|
88
|
+
// workspace root.
|
|
89
|
+
java: ["pom.xml", "build.gradle", "build.gradle.kts", ".classpath"],
|
|
80
90
|
kotlin: ["build.gradle.kts", "build.gradle", "pom.xml"],
|
|
81
91
|
swift: ["Package.swift"],
|
|
82
92
|
dart: ["pubspec.yaml"],
|
|
@@ -4,6 +4,7 @@ import { getGlobalPiLensLogDir } from "./probe-home-state.js";
|
|
|
4
4
|
import { createNdjsonLogger } from "./ndjson-logger.js";
|
|
5
5
|
import { getMaxLogSizeMB } from "./log-cleanup.js";
|
|
6
6
|
import { normalizeLoggedPath } from "./path-utils.js";
|
|
7
|
+
import { getTurnId } from "./turn-context.js";
|
|
7
8
|
const LATENCY_LOG_DIR = getGlobalPiLensLogDir();
|
|
8
9
|
const LATENCY_LOG_FILE = path.join(LATENCY_LOG_DIR, "latency.log");
|
|
9
10
|
const writer = createNdjsonLogger({
|
|
@@ -697,6 +698,7 @@ export function logLatency(entry) {
|
|
|
697
698
|
...entry,
|
|
698
699
|
ts,
|
|
699
700
|
pid: process.pid,
|
|
701
|
+
turnId: entry.turnId ?? getTurnId(),
|
|
700
702
|
filePath: normalizeLoggedPath(entry.filePath),
|
|
701
703
|
});
|
|
702
704
|
}
|
|
@@ -9,6 +9,7 @@ import { CANONICAL_GLOBAL_CONFIG_FILE, getPiLensGlobalConfigPath, GLOBAL_CONFIG_
|
|
|
9
9
|
export { getPiLensGlobalConfigPath } from "./config-locations.js";
|
|
10
10
|
import { ignoredRecordCollector, readConfigDocument, reportConfigReadFailure, reportPiLensConfigRecords, resolveOnePiLensConfigDocument, } from "./config-resolve.js";
|
|
11
11
|
import { findNestedProjectMutationValue, } from "./project-lens-config.js";
|
|
12
|
+
import { readToolConfig } from "./tool-config.js";
|
|
12
13
|
/**
|
|
13
14
|
* The canonical global location, looked up rather than constructed, so a change
|
|
14
15
|
* to the shared table cannot leave this loader describing a file that is no
|
|
@@ -123,6 +124,9 @@ export function loadPiLensGlobalConfig(configPath = getPiLensGlobalConfigPath())
|
|
|
123
124
|
const raw = resolved.value;
|
|
124
125
|
const warnInvalid = note;
|
|
125
126
|
const config = {};
|
|
127
|
+
const toolConfig = readToolConfig(raw, warnInvalid);
|
|
128
|
+
if (toolConfig)
|
|
129
|
+
config.tools = toolConfig;
|
|
126
130
|
for (const spec of LENS_FLAGS) {
|
|
127
131
|
if (spec.readGlobal)
|
|
128
132
|
continue;
|
|
@@ -63,10 +63,11 @@ import { getGlobalPiLensDir } from "../file-utils.js";
|
|
|
63
63
|
import { claimPhaseOncePerSession, currentSessionRecordId, logLatency, releaseOncePerSessionPhase, releasePhaseClaim, } from "../latency-logger.js";
|
|
64
64
|
import { getPiLensGlobalConfigPath } from "../lens-config.js";
|
|
65
65
|
import { normalizeFilePath } from "../path-utils.js";
|
|
66
|
+
import { resolveToolCwd } from "../tool-cwd.js";
|
|
66
67
|
import { logSessionStart } from "../sessionstart-logger.js";
|
|
67
68
|
import { launchLSP } from "./launch.js";
|
|
68
69
|
import { registerSessionRoot, resetSessionRootsForTests, sessionRootConfigEntries, setSessionRootConfig, } from "./session-roots.js";
|
|
69
|
-
import {
|
|
70
|
+
import { LSP_SERVERS, resetLSPCaseSensitivityState, resolveLspServerCwd, } from "./server.js";
|
|
70
71
|
// --- Config Loading ---
|
|
71
72
|
/**
|
|
72
73
|
* For tests that need to force the warn-once cache to reset between cases —
|
|
@@ -338,9 +339,13 @@ export function createCustomServer(config, id) {
|
|
|
338
339
|
name: config.name,
|
|
339
340
|
custom: true,
|
|
340
341
|
extensions: config.extensions,
|
|
342
|
+
...(config.rootMarkers ? { rootMarkers: config.rootMarkers } : {}),
|
|
341
343
|
root: config.rootMarkers
|
|
342
|
-
?
|
|
343
|
-
|
|
344
|
+
? async (file) => resolveToolCwd("lsp", id, file, {
|
|
345
|
+
cwd: process.cwd(),
|
|
346
|
+
...(config.rootMarkers ? { rootMarkers: config.rootMarkers } : {}),
|
|
347
|
+
})
|
|
348
|
+
: async (file) => resolveToolCwd("lsp", id, file, { cwd: process.cwd() }),
|
|
344
349
|
async spawn(root) {
|
|
345
350
|
const proc = await launchLSP(config.command, config.args ?? ["--stdio"], {
|
|
346
351
|
cwd: root,
|
|
@@ -600,12 +605,21 @@ export function getServersForFileWithConfig(filePath) {
|
|
|
600
605
|
const base = path.basename(filePath).toLowerCase();
|
|
601
606
|
return registeredServers(config).filter((server) => selectionReason(server, config, filePath, ext, base) === "selected");
|
|
602
607
|
}
|
|
608
|
+
/**
|
|
609
|
+
* The primary language server ENTRY for a file — the one "first non-auxiliary
|
|
610
|
+
* server" predicate, shared by {@link primaryServerId} and
|
|
611
|
+
* {@link resolveLspCwdForFile} so the id-level and entry-level consumers
|
|
612
|
+
* cannot drift. `role` is only ever set to "auxiliary" on cross-cutting
|
|
613
|
+
* scanner entries (ast-grep, opengrep, zizmor, typos, marksman, ...) — see
|
|
614
|
+
* clients/lsp/server.ts; undefined here means a real language server.
|
|
615
|
+
*/
|
|
616
|
+
function primaryServerEntry(filePath) {
|
|
617
|
+
return getServersForFileWithConfig(filePath).find((s) => s.role !== "auxiliary");
|
|
618
|
+
}
|
|
603
619
|
/**
|
|
604
620
|
* The primary language server for a file (e.g. "typescript"), as opposed to a
|
|
605
621
|
* cross-cutting auxiliary scanner attached via clientScope "all"/
|
|
606
|
-
* "with-auxiliary" (ast-grep, opengrep, zizmor, typos, marksman, ...).
|
|
607
|
-
* is only ever set to "auxiliary" on those auxiliary entries (see
|
|
608
|
-
* clients/lsp/server.ts) — undefined means a real language server. Used to
|
|
622
|
+
* "with-auxiliary" (ast-grep, opengrep, zizmor, typos, marksman, ...). Used to
|
|
609
623
|
* split a file's diagnostics into "primary confirmation" vs "auxiliary
|
|
610
624
|
* findings" so a page of ast-grep/opengrep/marksman noise never buries
|
|
611
625
|
* whether the actual type checker/compiler confirmed the file clean.
|
|
@@ -616,7 +630,22 @@ export function getServersForFileWithConfig(filePath) {
|
|
|
616
630
|
* now report the same primary-vs-auxiliary split for the same file.
|
|
617
631
|
*/
|
|
618
632
|
export function primaryServerId(filePath) {
|
|
619
|
-
return
|
|
633
|
+
return primaryServerEntry(filePath)?.id;
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* #2777 O1: the one seam a tool uses to answer "which cwd did this file's
|
|
637
|
+
* primary LSP server resolve to". Folds the three-step lookup callers used to
|
|
638
|
+
* hand-roll (`primaryServerId` + `getServersForFileWithConfig` +
|
|
639
|
+
* `resolveLspServerCwd`) into one call that returns `undefined` for a file
|
|
640
|
+
* with no primary LSP server, so the caller must handle the absent case
|
|
641
|
+
* instead of rendering it (the N1 `cwd=undefined` row becomes structurally
|
|
642
|
+
* impossible).
|
|
643
|
+
*/
|
|
644
|
+
export async function resolveLspCwdForFile(filePath, sessionCwd) {
|
|
645
|
+
const primary = primaryServerEntry(filePath);
|
|
646
|
+
if (!primary)
|
|
647
|
+
return undefined;
|
|
648
|
+
return resolveLspServerCwd(primary, filePath, sessionCwd);
|
|
620
649
|
}
|
|
621
650
|
/**
|
|
622
651
|
* Look up an initializationOptions override for a built-in server.
|