@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
|
@@ -26,6 +26,8 @@ import { logSessionStart } from "../sessionstart-logger.js";
|
|
|
26
26
|
import { findLocalSgconfig, resolveBaselineSgconfig } from "../sgconfig.js";
|
|
27
27
|
import { findLocalTyposConfig } from "../typos-config.js";
|
|
28
28
|
import { resolvePackagePath } from "../package-root.js";
|
|
29
|
+
import { resolveToolCwd } from "../tool-cwd.js";
|
|
30
|
+
import { recordDegradationOnce } from "../degradation-ledger.js";
|
|
29
31
|
import { hasCargoWorkspaceTable, readCargoWorkspaceExclude, readCargoWorkspaceMembers, readTextFileOrUndefined, } from "../cargo-manifest.js";
|
|
30
32
|
import { resolveAstGrepNativeExe } from "./wait-policy/index.js";
|
|
31
33
|
import { hasSpawnFailureKind, isCommandAvailableAsync, safeSpawnAsync, } from "../safe-spawn.js";
|
|
@@ -36,6 +38,48 @@ import { normalizeMapKey } from "./path-utils.js";
|
|
|
36
38
|
import { getRubyVersionDirNamesSync } from "./ruby-drive-dirs.js";
|
|
37
39
|
import { getProcessSingleton } from "../process-singletons.js";
|
|
38
40
|
import { createGenerationSource, } from "../generation-guard.js";
|
|
41
|
+
function withRootMarkers(root, markers) {
|
|
42
|
+
root.rootMarkers = markers;
|
|
43
|
+
return root;
|
|
44
|
+
}
|
|
45
|
+
/** Resolve a server identity cwd through the shared tool-cwd seam. */
|
|
46
|
+
export async function resolveLspServerCwd(server, filePath, sessionCwd, onRootFailure) {
|
|
47
|
+
const rootMarkers = server.rootMarkers ?? server.root.rootMarkers;
|
|
48
|
+
let serverRoot;
|
|
49
|
+
let rootFailed = false;
|
|
50
|
+
try {
|
|
51
|
+
serverRoot = await server.root(filePath);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
55
|
+
rootFailed = true;
|
|
56
|
+
onRootFailure?.(reason);
|
|
57
|
+
}
|
|
58
|
+
const isFileDirFallback = serverRoot !== undefined &&
|
|
59
|
+
rootMarkers?.length &&
|
|
60
|
+
path.resolve(serverRoot) === path.resolve(path.dirname(filePath));
|
|
61
|
+
if (!serverRoot || isFileDirFallback) {
|
|
62
|
+
if (!serverRoot) {
|
|
63
|
+
recordDegradationOnce({
|
|
64
|
+
kind: "tool-cwd-resolution",
|
|
65
|
+
subject: server.id,
|
|
66
|
+
reason: `lsp:server-root-${rootFailed ? "failed" : "fallback"}:${filePath}`,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (!rootMarkers?.length)
|
|
70
|
+
return undefined;
|
|
71
|
+
return resolveToolCwd("lsp", server.id, filePath, {
|
|
72
|
+
cwd: path.dirname(path.resolve(filePath)),
|
|
73
|
+
rootMarkers,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const boundedServerRoot = enforceLspRootCeiling(serverRoot, sessionCwd, filePath);
|
|
77
|
+
return resolveToolCwd("lsp", server.id, filePath, {
|
|
78
|
+
cwd: sessionCwd,
|
|
79
|
+
rootMarkers,
|
|
80
|
+
serverRoot: boundedServerRoot,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
39
83
|
const FIXTURE_ROOT_SEGMENTS = new Set(["__fixtures__", "testdata"]);
|
|
40
84
|
const FALLBACK_PROJECT_MARKERS = [
|
|
41
85
|
".git",
|
|
@@ -845,12 +889,12 @@ function normalizeRootKey(root) {
|
|
|
845
889
|
}
|
|
846
890
|
function IgnoreHomeRoot(primary) {
|
|
847
891
|
const homeKey = normalizeRootKey(os.homedir());
|
|
848
|
-
return async (file) => {
|
|
892
|
+
return withRootMarkers(async (file) => {
|
|
849
893
|
const root = await primary(file);
|
|
850
894
|
if (!root)
|
|
851
895
|
return undefined;
|
|
852
896
|
return normalizeRootKey(root) === homeKey ? undefined : root;
|
|
853
|
-
};
|
|
897
|
+
}, primary.rootMarkers ?? []);
|
|
854
898
|
}
|
|
855
899
|
function rubyBinCandidates(baseName) {
|
|
856
900
|
const candidates = [];
|
|
@@ -879,6 +923,7 @@ function createInteractiveServer(spec) {
|
|
|
879
923
|
name: spec.name,
|
|
880
924
|
extensions: spec.extensions,
|
|
881
925
|
root: spec.root,
|
|
926
|
+
rootMarkers: spec.root.rootMarkers,
|
|
882
927
|
fallbackFor: spec.fallbackFor,
|
|
883
928
|
availabilityKey: typeof spec.command === "string" && isSimpleCommand(spec.command)
|
|
884
929
|
? spec.command
|
|
@@ -917,29 +962,29 @@ function createInteractiveServer(spec) {
|
|
|
917
962
|
}
|
|
918
963
|
export function PriorityRoot(markerGroups, excludePatterns, stopDir) {
|
|
919
964
|
const resolvers = markerGroups.map((markers) => NearestRoot(markers, excludePatterns, stopDir));
|
|
920
|
-
return async (file) => {
|
|
965
|
+
return withRootMarkers(async (file) => {
|
|
921
966
|
for (const resolve of resolvers) {
|
|
922
967
|
const root = await resolve(file);
|
|
923
968
|
if (root)
|
|
924
969
|
return root;
|
|
925
970
|
}
|
|
926
971
|
return undefined;
|
|
927
|
-
};
|
|
972
|
+
}, markerGroups.flat());
|
|
928
973
|
}
|
|
929
974
|
export const FileDirRoot = async (file) => {
|
|
930
975
|
const candidate = path.resolve(path.dirname(file));
|
|
931
976
|
return nearestNonExcludedFallbackRoot(candidate);
|
|
932
977
|
};
|
|
933
978
|
export function RootWithFallback(primary, fallback = FileDirRoot) {
|
|
934
|
-
return async (file) => {
|
|
979
|
+
return withRootMarkers(async (file) => {
|
|
935
980
|
const primaryRoot = await primary(file);
|
|
936
981
|
if (primaryRoot)
|
|
937
982
|
return primaryRoot;
|
|
938
983
|
return fallback(file);
|
|
939
|
-
};
|
|
984
|
+
}, primary.rootMarkers ?? []);
|
|
940
985
|
}
|
|
941
986
|
export function WorkspacePriorityRoot(markerGroups, excludePatterns) {
|
|
942
|
-
return async (file) => PriorityRoot(markerGroups, excludePatterns, process.cwd())(file);
|
|
987
|
+
return withRootMarkers(async (file) => PriorityRoot(markerGroups, excludePatterns, process.cwd())(file), markerGroups.flat());
|
|
943
988
|
}
|
|
944
989
|
function isPermissionFsError(err) {
|
|
945
990
|
const code = err?.code;
|
|
@@ -1010,7 +1055,7 @@ export function NearestRoot(includePatterns, excludePatterns, stopDir) {
|
|
|
1010
1055
|
// re-walk cost for configless repos is a known trade-off; bounding the
|
|
1011
1056
|
// walk with stopDir for in-cwd files is the tracked optimization (#1412).
|
|
1012
1057
|
const inFlight = new Map();
|
|
1013
|
-
return async (file) => {
|
|
1058
|
+
return withRootMarkers(async (file) => {
|
|
1014
1059
|
// Cache key is the resolved directory — all files in the same dir share a root.
|
|
1015
1060
|
const startDir = path.resolve(path.dirname(file));
|
|
1016
1061
|
const dirKey = normalizeMapKey(startDir);
|
|
@@ -1052,6 +1097,7 @@ export function NearestRoot(includePatterns, excludePatterns, stopDir) {
|
|
|
1052
1097
|
// glob markers like `*.csproj` match real project filenames (#201).
|
|
1053
1098
|
for (const pattern of includePatterns) {
|
|
1054
1099
|
if ((await markerExists(currentDir, pattern)) &&
|
|
1100
|
+
(pattern !== ".git" || currentDir !== os.tmpdir()) &&
|
|
1055
1101
|
!(await isExcludedLspRoot(currentDir))) {
|
|
1056
1102
|
return enforceLspRootCeiling(currentDir, process.cwd(), file);
|
|
1057
1103
|
}
|
|
@@ -1073,7 +1119,7 @@ export function NearestRoot(includePatterns, excludePatterns, stopDir) {
|
|
|
1073
1119
|
finally {
|
|
1074
1120
|
inFlight.delete(dirKey);
|
|
1075
1121
|
}
|
|
1076
|
-
};
|
|
1122
|
+
}, includePatterns);
|
|
1077
1123
|
}
|
|
1078
1124
|
/** Alias kept for backward compatibility */
|
|
1079
1125
|
export const createRootDetector = NearestRoot;
|
|
@@ -1579,7 +1625,7 @@ async function hasAgentLevelProjectMarker(extensionRootKey) {
|
|
|
1579
1625
|
}
|
|
1580
1626
|
return false;
|
|
1581
1627
|
}
|
|
1582
|
-
const TypeScriptRoot = DenoExcludeRoot(async (file) => {
|
|
1628
|
+
const TypeScriptRoot = withRootMarkers(DenoExcludeRoot(async (file) => {
|
|
1583
1629
|
const extensionRootKey = piAgentExtensionsRootKey(file);
|
|
1584
1630
|
if (extensionRootKey) {
|
|
1585
1631
|
// Bounded walk so we never adopt a parent (e.g. ~/.pi/agent/) as the
|
|
@@ -1603,13 +1649,14 @@ const TypeScriptRoot = DenoExcludeRoot(async (file) => {
|
|
|
1603
1649
|
if (projectRoot)
|
|
1604
1650
|
return projectRoot;
|
|
1605
1651
|
return FileDirRoot(file);
|
|
1606
|
-
});
|
|
1652
|
+
}), [...TS_CONFIG_MARKERS, ...TS_TOOLING_MARKERS]);
|
|
1607
1653
|
export const TypeScriptServer = {
|
|
1608
1654
|
id: "typescript",
|
|
1609
1655
|
name: "TypeScript Language Server",
|
|
1610
1656
|
extensions: JS_TS_LSP_EXTENSIONS,
|
|
1611
1657
|
autoPropagateDiagnostics: true,
|
|
1612
1658
|
root: TypeScriptRoot,
|
|
1659
|
+
rootMarkers: TypeScriptRoot.rootMarkers,
|
|
1613
1660
|
async spawn(root, options) {
|
|
1614
1661
|
const fs = await import("node:fs/promises");
|
|
1615
1662
|
const nativeLsp = await findNativeTypeScriptLsp(root);
|
|
@@ -1919,7 +1966,7 @@ function sessionHoistCeiling(sessionCwd, file) {
|
|
|
1919
1966
|
}
|
|
1920
1967
|
function RustWorkspaceRoot() {
|
|
1921
1968
|
const crateRoot = createRootDetector(["Cargo.toml", "Cargo.lock"]);
|
|
1922
|
-
return async (file) => {
|
|
1969
|
+
return withRootMarkers(async (file) => {
|
|
1923
1970
|
const root = await crateRoot(file);
|
|
1924
1971
|
if (!root)
|
|
1925
1972
|
return undefined;
|
|
@@ -1958,7 +2005,7 @@ function RustWorkspaceRoot() {
|
|
|
1958
2005
|
current = parent;
|
|
1959
2006
|
}
|
|
1960
2007
|
return root;
|
|
1961
|
-
};
|
|
2008
|
+
}, crateRoot.rootMarkers ?? []);
|
|
1962
2009
|
}
|
|
1963
2010
|
/**
|
|
1964
2011
|
* Resolve whether `parentPomPath`'s <modules> block declares `childDir` as one
|
|
@@ -2011,7 +2058,7 @@ function JavaWorkspaceRoot() {
|
|
|
2011
2058
|
"build.gradle",
|
|
2012
2059
|
".classpath",
|
|
2013
2060
|
]);
|
|
2014
|
-
return async (file) => {
|
|
2061
|
+
return withRootMarkers(async (file) => {
|
|
2015
2062
|
const root = await moduleRoot(file);
|
|
2016
2063
|
if (!root)
|
|
2017
2064
|
return undefined;
|
|
@@ -2051,7 +2098,7 @@ function JavaWorkspaceRoot() {
|
|
|
2051
2098
|
current = parent;
|
|
2052
2099
|
}
|
|
2053
2100
|
return enforceLspRootCeiling(lastHop, sessionCwd, file);
|
|
2054
|
-
};
|
|
2101
|
+
}, moduleRoot.rootMarkers ?? []);
|
|
2055
2102
|
}
|
|
2056
2103
|
export const RustServer = {
|
|
2057
2104
|
id: "rust",
|
|
@@ -8,6 +8,7 @@ import { normalizeMapKey } from "../path-utils.js";
|
|
|
8
8
|
import { loadReverseDependencyIndexFromSnapshot } from "../reverse-deps.js";
|
|
9
9
|
import { freshnessFromMtime } from "../freshness.js";
|
|
10
10
|
import { logLatency } from "../latency-logger.js";
|
|
11
|
+
import { recordDegradationOnce } from "../degradation-ledger.js";
|
|
11
12
|
import { compareOrdinal } from "../string-utils.js";
|
|
12
13
|
import { workspaceDiagnosticsCacheSessionStart } from "./workspace-diagnostics-session.js";
|
|
13
14
|
import { createDiskBindingCache, } from "./diagnostic-binding.js";
|
|
@@ -31,12 +32,17 @@ import { createDiskBindingCache, } from "./diagnostic-binding.js";
|
|
|
31
32
|
* `LSPWorkspaceDiagnosticResult.timedOut` in `./index.ts`) into
|
|
32
33
|
* `WorkspaceDiagnosticsCacheEntry`.
|
|
33
34
|
*/
|
|
35
|
+
// v3 (#2776): cached diagnostics must carry per-diagnostic `serverId`
|
|
36
|
+
// provenance before replay, because primary/auxiliary partitioning is
|
|
37
|
+
// server-owned rather than source-owned. v2 entries predate that contract and
|
|
38
|
+
// are rejected so the owning server can re-collect them instead of silently
|
|
39
|
+
// demoting old primary findings to auxiliary.
|
|
34
40
|
// v2 (#1095): entries may carry an optional `contentHash` fingerprint of the
|
|
35
41
|
// file bytes the cached diagnostics were computed against, so a `lookup` can
|
|
36
42
|
// surface a content `binding` (boundToCurrentDisk) beyond the mtime proxy.
|
|
37
43
|
// Legacy v1 entries are rejected by the version guard and re-scanned — a
|
|
38
44
|
// deliberately clean break so no entry lacking the field is ever served.
|
|
39
|
-
export const WORKSPACE_DIAGNOSTICS_CACHE_VERSION =
|
|
45
|
+
export const WORKSPACE_DIAGNOSTICS_CACHE_VERSION = 3;
|
|
40
46
|
const CACHE_FILE = "lsp-workspace-diagnostics.json";
|
|
41
47
|
function cachePath(cwd) {
|
|
42
48
|
return path.join(getProjectDataDir(cwd), "cache", CACHE_FILE);
|
|
@@ -161,11 +167,34 @@ export function loadWorkspaceDiagnosticsCache(cwd) {
|
|
|
161
167
|
if (!parsed || typeof parsed !== "object")
|
|
162
168
|
return undefined;
|
|
163
169
|
const cache = parsed;
|
|
164
|
-
if (cache.version !== WORKSPACE_DIAGNOSTICS_CACHE_VERSION)
|
|
170
|
+
if (cache.version !== WORKSPACE_DIAGNOSTICS_CACHE_VERSION) {
|
|
171
|
+
if (cache.version === 1 || cache.version === 2) {
|
|
172
|
+
recordDegradationOnce({
|
|
173
|
+
kind: "lsp-workspace-cache-migration",
|
|
174
|
+
subject: path.resolve(cwd),
|
|
175
|
+
reason: `rejected v${cache.version} cache with ${cache.entries && typeof cache.entries === "object"
|
|
176
|
+
? Object.keys(cache.entries).length
|
|
177
|
+
: 0} entries during provenance migration`,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
165
180
|
return undefined;
|
|
181
|
+
}
|
|
166
182
|
if (!cache.entries || typeof cache.entries !== "object")
|
|
167
183
|
return undefined;
|
|
168
|
-
|
|
184
|
+
const validEntries = {};
|
|
185
|
+
for (const [key, entry] of Object.entries(cache.entries)) {
|
|
186
|
+
if (!entry || typeof entry !== "object")
|
|
187
|
+
continue;
|
|
188
|
+
if (!Array.isArray(entry.diagnostics) ||
|
|
189
|
+
entry.diagnostics.some((diagnostic) => !diagnostic ||
|
|
190
|
+
typeof diagnostic !== "object" ||
|
|
191
|
+
typeof diagnostic.serverId !== "string" ||
|
|
192
|
+
diagnostic.serverId.length === 0)) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
validEntries[key] = entry;
|
|
196
|
+
}
|
|
197
|
+
return { ...cache, entries: validEntries };
|
|
169
198
|
});
|
|
170
199
|
}
|
|
171
200
|
export function saveWorkspaceDiagnosticsCache(cwd, cache) {
|
|
@@ -470,6 +499,9 @@ export function createWorkspaceDiagnosticsCacheContext(cwd) {
|
|
|
470
499
|
depIndexColdRefusalCount = 0;
|
|
471
500
|
};
|
|
472
501
|
return {
|
|
502
|
+
importsFor(filePath) {
|
|
503
|
+
return getImports(filePath);
|
|
504
|
+
},
|
|
473
505
|
lookup(filePath, scopeKey) {
|
|
474
506
|
// #1669 review N4: the epoch guarded `persist()` but not `lookup()` —
|
|
475
507
|
// an in-flight sweep kept REPLAYING disowned entries into its OWN
|
|
@@ -552,11 +584,12 @@ export function createWorkspaceDiagnosticsCacheContext(cwd) {
|
|
|
552
584
|
};
|
|
553
585
|
},
|
|
554
586
|
record(filePath, scopeKey, diagnostics, mtimeMs, contentHash, sizeBytes) {
|
|
587
|
+
const scanGeneration = Date.now();
|
|
555
588
|
entries[cacheKeyFor(filePath)] = {
|
|
556
589
|
diagnostics,
|
|
557
590
|
count: diagnostics.length,
|
|
558
591
|
mtimeMs,
|
|
559
|
-
scannedAt:
|
|
592
|
+
scannedAt: scanGeneration,
|
|
560
593
|
scopeKey,
|
|
561
594
|
// #1793: stamp whether THIS FILE actually had dependency
|
|
562
595
|
// knowledge this sweep (not just whether SOME index was
|
|
@@ -367,6 +367,9 @@ export async function analyzeFile(filePath, cwd, options = {}) {
|
|
|
367
367
|
const lspRunner = latencyReport?.runners.find((runner) => runner.runnerId === "lsp");
|
|
368
368
|
const lsp = lspRunner
|
|
369
369
|
? {
|
|
370
|
+
// `deferred` means the runner entered the LSP path but its auxiliary
|
|
371
|
+
// coverage is delivered later, so it still counts as ran here. The
|
|
372
|
+
// status remains available to surfaces that need the coverage detail.
|
|
370
373
|
ran: lspRunner.status !== "skipped" &&
|
|
371
374
|
lspRunner.status !== "when_skipped" &&
|
|
372
375
|
lspRunner.status !== "test_file_skipped",
|
|
@@ -30,6 +30,7 @@ import { RuntimeCoordinator } from "../runtime-coordinator.js";
|
|
|
30
30
|
import { handleSessionStart } from "../runtime-session.js";
|
|
31
31
|
import { handleTurnEnd } from "../runtime-turn.js";
|
|
32
32
|
import { createMcpHost } from "./host-shim.js";
|
|
33
|
+
import { startSituationalToolTelemetrySession } from "../situational-tool-telemetry.js";
|
|
33
34
|
let contextPromise;
|
|
34
35
|
let resolvedRuntime;
|
|
35
36
|
/**
|
|
@@ -79,6 +80,7 @@ function joinMessages(consumed) {
|
|
|
79
80
|
* `pilens_health` afterwards for the scan results as they land.
|
|
80
81
|
*/
|
|
81
82
|
export async function runSessionStart(cwd) {
|
|
83
|
+
startSituationalToolTelemetrySession("mcp");
|
|
82
84
|
const ctx = await getMcpSessionContext();
|
|
83
85
|
const host = createMcpHost(undefined, cwd);
|
|
84
86
|
await handleSessionStart({
|
|
@@ -148,8 +148,10 @@ export class OpengrepClient extends SecurityScanClient {
|
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
150
|
const findings = parseOpengrepReport(fs.readFileSync(reportPath, "utf-8"));
|
|
151
|
+
// #2154: the one opengrep site that parsed a scan of this root.
|
|
151
152
|
return {
|
|
152
153
|
success: true,
|
|
154
|
+
analyzed: true,
|
|
153
155
|
findings,
|
|
154
156
|
scannedAt,
|
|
155
157
|
};
|
|
@@ -18,6 +18,7 @@ import * as os from "node:os";
|
|
|
18
18
|
import * as path from "node:path";
|
|
19
19
|
import { classifyProbeFailure, createAvailabilityLatch, isTransientDecision, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
|
|
20
20
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
21
|
+
import { probeToolAsync } from "./tool-probe.js";
|
|
21
22
|
import { createAvailabilityProbeFlight } from "./availability-probe-flight.js";
|
|
22
23
|
import { createGenerationSource, } from "./generation-guard.js";
|
|
23
24
|
import { isAtOrAboveHomeDir, walkUpDirs } from "./path-utils.js";
|
|
@@ -114,7 +115,7 @@ async function probeAvailability(pm) {
|
|
|
114
115
|
let result;
|
|
115
116
|
let hostStallMs;
|
|
116
117
|
try {
|
|
117
|
-
result = await
|
|
118
|
+
result = await probeToolAsync(finder, [pm], { timeout: PROBE_TIMEOUT_MS });
|
|
118
119
|
}
|
|
119
120
|
finally {
|
|
120
121
|
hostStallMs = sampler.stop();
|
|
@@ -91,8 +91,9 @@
|
|
|
91
91
|
* gate says "config exists" while the carriage says "not found", silently
|
|
92
92
|
* dropping the `--config` argv this module exists to carry.
|
|
93
93
|
*/
|
|
94
|
+
import { existsSync } from "node:fs";
|
|
94
95
|
import * as path from "node:path";
|
|
95
|
-
import {
|
|
96
|
+
import { resolveToolCwd } from "./tool-cwd.js";
|
|
96
97
|
/**
|
|
97
98
|
* In `computeConfigFiles()`'s own precedence order — `.php-cs-fixer.php`
|
|
98
99
|
* before `.php-cs-fixer.dist.php` — so a same-directory precedence check can
|
|
@@ -109,6 +110,10 @@ const PHP_CS_FIXER_CONFIG_NAMES = [
|
|
|
109
110
|
* agreeing with this tool's own (unceilinged) detection gates.
|
|
110
111
|
*/
|
|
111
112
|
export function resolvePhpCsFixerConfig(filePath) {
|
|
112
|
-
const
|
|
113
|
-
|
|
113
|
+
const absolute = path.resolve(filePath);
|
|
114
|
+
const startDir = resolveToolCwd("formatter", "php-cs-fixer", absolute, {
|
|
115
|
+
cwd: path.parse(absolute).root,
|
|
116
|
+
allowHomeMarker: true,
|
|
117
|
+
});
|
|
118
|
+
return PHP_CS_FIXER_CONFIG_NAMES.map((name) => path.join(startDir, name)).find(existsSync);
|
|
114
119
|
}
|
|
@@ -34,6 +34,7 @@ import { clearGraphCache } from "./review-graph/builder.js";
|
|
|
34
34
|
import { BoundedLruCache } from "./bounded-cache.js";
|
|
35
35
|
import { RUNTIME_CONFIG } from "./runtime-config.js";
|
|
36
36
|
import { getAmbientAbortSignal, safeSpawnAsync } from "./safe-spawn.js";
|
|
37
|
+
import { probeToolAsync } from "./tool-probe.js";
|
|
37
38
|
import { bounded } from "./deadline-utils.js";
|
|
38
39
|
import { enabledAuxiliaryLspServerIds } from "./dispatch/auxiliary-lsp.js";
|
|
39
40
|
import { recordDegradationOnce } from "./degradation-ledger.js";
|
|
@@ -356,7 +357,7 @@ async function tryOxlintFix(filePath, cwd) {
|
|
|
356
357
|
return detectFileChangedAfterCommand(filePath, cmd, ["--fix", filePath], cwd, [1]);
|
|
357
358
|
}
|
|
358
359
|
async function tryRustClippyFix(filePath) {
|
|
359
|
-
const check = await
|
|
360
|
+
const check = await probeToolAsync("cargo", ["--version"], { timeout: 5000 });
|
|
360
361
|
if (check.error || check.status !== 0)
|
|
361
362
|
return [];
|
|
362
363
|
const cargoDir = findNearestContaining(path.dirname(path.resolve(filePath)), [
|
|
@@ -371,7 +372,7 @@ async function tryRustClippyFix(filePath) {
|
|
|
371
372
|
return diffProjectSnapshot(cargoDir, before);
|
|
372
373
|
}
|
|
373
374
|
async function tryDartFix(filePath) {
|
|
374
|
-
const check = await
|
|
375
|
+
const check = await probeToolAsync("dart", ["--version"], { timeout: 5000 });
|
|
375
376
|
if (check.error || check.status !== 0)
|
|
376
377
|
return [];
|
|
377
378
|
const pubspecDir = findNearestContaining(path.dirname(path.resolve(filePath)), ["pubspec.yaml"]);
|
|
@@ -149,6 +149,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
|
|
|
149
149
|
return {
|
|
150
150
|
diagnostics: [],
|
|
151
151
|
runners: [],
|
|
152
|
+
analyzed: [],
|
|
152
153
|
cold: [...ANALYZER_IDS],
|
|
153
154
|
coldReasons: Object.fromEntries(ANALYZER_IDS.map((id) => [id, unsafeRootReason])),
|
|
154
155
|
failed: [],
|
|
@@ -158,6 +159,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
|
|
|
158
159
|
}
|
|
159
160
|
const diagnostics = [];
|
|
160
161
|
const runners = [];
|
|
162
|
+
const analyzed = [];
|
|
161
163
|
const cold = [];
|
|
162
164
|
// #1623: the specific reason each `cold` id was skipped, captured at the
|
|
163
165
|
// gate that decided it — see FreshProjectDiagnosticsResult.coldReasons.
|
|
@@ -188,7 +190,17 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
|
|
|
188
190
|
pushUnique(cold, id);
|
|
189
191
|
coldReasons[id] = reason;
|
|
190
192
|
}
|
|
191
|
-
|
|
193
|
+
/**
|
|
194
|
+
* `analysedRoot` is the client's own opt-in `AnalysedRootSignal` (#2154),
|
|
195
|
+
* never a property of reaching this function: `success: true` is also what
|
|
196
|
+
* a skipped, crashed-before-reporting or memoised run returns, and only the
|
|
197
|
+
* client knows which of those it is. Findings are recorded either way — an
|
|
198
|
+
* id that did not analyse the root simply carries no authority to retire a
|
|
199
|
+
* retained finding.
|
|
200
|
+
*/
|
|
201
|
+
function record(id, adapted, elapsedMs, analysedRoot) {
|
|
202
|
+
if (analysedRoot)
|
|
203
|
+
pushUnique(analyzed, id);
|
|
192
204
|
timings[id] = (timings[id] ?? 0) + elapsedMs;
|
|
193
205
|
const kept = applyDispositionsMultiFile(adapted, analysisRoot, (d) => d.filePath);
|
|
194
206
|
const suppressedHere = adapted.length - kept.length;
|
|
@@ -223,10 +235,17 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
|
|
|
223
235
|
recordFailed("knip", result);
|
|
224
236
|
return;
|
|
225
237
|
}
|
|
238
|
+
// knip reports `success: true` for a run that never happened (no
|
|
239
|
+
// project root, a memo hit, an unparseable exit-0). Those belong in
|
|
240
|
+
// the honest `cold` channel, not in "fetched fresh this call".
|
|
241
|
+
if (result.analyzed !== true) {
|
|
242
|
+
markCold("knip", result.summary ?? "knip did not analyse this root");
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
226
245
|
cacheManager.writeCache("knip", result, analysisRoot, {
|
|
227
246
|
scanDurationMs: Date.now() - startMs,
|
|
228
247
|
});
|
|
229
|
-
record("knip", knipIssuesToProjectDiagnostics(analysisRoot, result.issues ?? []), Date.now() - startMs);
|
|
248
|
+
record("knip", knipIssuesToProjectDiagnostics(analysisRoot, result.issues ?? []), Date.now() - startMs, true);
|
|
230
249
|
}),
|
|
231
250
|
// jscpd — duplicate code detection. Cache key varies with TS-project
|
|
232
251
|
// detection, exactly mirroring session_start's own logic.
|
|
@@ -243,10 +262,14 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
|
|
|
243
262
|
recordFailed("jscpd", result);
|
|
244
263
|
return;
|
|
245
264
|
}
|
|
265
|
+
if (result.analyzed !== true) {
|
|
266
|
+
markCold("jscpd", "jscpd did not analyse this root (no source files, or the scan produced no report)");
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
246
269
|
cacheManager.writeCache(scannerKey, result, analysisRoot, {
|
|
247
270
|
scanDurationMs: Date.now() - startMs,
|
|
248
271
|
});
|
|
249
|
-
record("jscpd", jscpdResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs);
|
|
272
|
+
record("jscpd", jscpdResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs, true);
|
|
250
273
|
}),
|
|
251
274
|
// madge — circular-dependency detection.
|
|
252
275
|
task("madge", async () => {
|
|
@@ -259,7 +282,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
|
|
|
259
282
|
cacheManager.writeCache("madge", result, analysisRoot, {
|
|
260
283
|
scanDurationMs: Date.now() - startMs,
|
|
261
284
|
});
|
|
262
|
-
record("madge", circularDepsToProjectDiagnostics(analysisRoot, result.circular ?? []), Date.now() - startMs);
|
|
285
|
+
record("madge", circularDepsToProjectDiagnostics(analysisRoot, result.circular ?? []), Date.now() - startMs, result.analyzed === true);
|
|
263
286
|
}),
|
|
264
287
|
// gitleaks — committed-secrets detection. session_start/per-edit stay
|
|
265
288
|
// config-gated per #130's strict default (GitleaksClient.hasGitleaksSignal),
|
|
@@ -290,7 +313,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
|
|
|
290
313
|
cacheManager.writeCache("gitleaks", result, analysisRoot, {
|
|
291
314
|
scanDurationMs: Date.now() - startMs,
|
|
292
315
|
});
|
|
293
|
-
record("gitleaks", gitleaksResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs);
|
|
316
|
+
record("gitleaks", gitleaksResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs, result.analyzed === true);
|
|
294
317
|
}),
|
|
295
318
|
// govulncheck — Go module CVE detection. Go-module-gated per #132.
|
|
296
319
|
task("govulncheck", async () => {
|
|
@@ -322,7 +345,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
|
|
|
322
345
|
cacheManager.writeCache("govulncheck", result, analysisRoot, {
|
|
323
346
|
scanDurationMs: Date.now() - startMs,
|
|
324
347
|
});
|
|
325
|
-
record("govulncheck", govulncheckResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs);
|
|
348
|
+
record("govulncheck", govulncheckResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs, result.analyzed === true);
|
|
326
349
|
}),
|
|
327
350
|
// opengrep — full-workspace semgrep-grade security/quality findings via a
|
|
328
351
|
// single project-wide CLI scan (#584). Structurally always-on: mirrors
|
|
@@ -352,7 +375,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
|
|
|
352
375
|
cacheManager.writeCache("opengrep", result, analysisRoot, {
|
|
353
376
|
scanDurationMs: Date.now() - startMs,
|
|
354
377
|
});
|
|
355
|
-
record("opengrep", opengrepResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs);
|
|
378
|
+
record("opengrep", opengrepResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs, result.analyzed === true);
|
|
356
379
|
}),
|
|
357
380
|
// trivy — dependency CVE detection. Explicit opt-in per #131.
|
|
358
381
|
task("trivy", async () => {
|
|
@@ -373,7 +396,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
|
|
|
373
396
|
cacheManager.writeCache("trivy", result, analysisRoot, {
|
|
374
397
|
scanDurationMs: Date.now() - startMs,
|
|
375
398
|
});
|
|
376
|
-
record("trivy", trivyResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs);
|
|
399
|
+
record("trivy", trivyResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs, result.analyzed === true);
|
|
377
400
|
}),
|
|
378
401
|
// dead-code — cross-file dead-code for non-JS/TS languages (#127).
|
|
379
402
|
// Each client self-gates via detect(); only matching-language projects
|
|
@@ -395,7 +418,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
|
|
|
395
418
|
cacheManager.writeCache(cacheKey, result, analysisRoot, {
|
|
396
419
|
scanDurationMs: Date.now() - startMs,
|
|
397
420
|
});
|
|
398
|
-
record("dead-code", deadCodeResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs);
|
|
421
|
+
record("dead-code", deadCodeResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs, result.analyzed === true);
|
|
399
422
|
}));
|
|
400
423
|
}),
|
|
401
424
|
// test-runner — CACHE-READ only, unlike every task above (#1004). Its
|
|
@@ -440,7 +463,10 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
|
|
|
440
463
|
// looking indistinguishable from a lane that just executed.
|
|
441
464
|
cachedAgeMs["test-runner"] =
|
|
442
465
|
Date.now() - new Date(cached.meta.timestamp).getTime();
|
|
443
|
-
record("test-runner", testRunnerFindingsToProjectDiagnostics(cached.data, analysisRoot, options.runtime), Date.now() - startMs
|
|
466
|
+
record("test-runner", testRunnerFindingsToProjectDiagnostics(cached.data, analysisRoot, options.runtime), Date.now() - startMs,
|
|
467
|
+
// Never authoritative: this lane reads the cache turn_end wrote,
|
|
468
|
+
// it never runs a suite over the root this call (#2154).
|
|
469
|
+
false);
|
|
444
470
|
}),
|
|
445
471
|
];
|
|
446
472
|
// Swallow any later rejection so an aborted-and-abandoned task can never
|
|
@@ -470,6 +496,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
|
|
|
470
496
|
return {
|
|
471
497
|
diagnostics,
|
|
472
498
|
runners,
|
|
499
|
+
analyzed,
|
|
473
500
|
cold,
|
|
474
501
|
coldReasons,
|
|
475
502
|
failed,
|
|
@@ -484,6 +511,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
|
|
|
484
511
|
return {
|
|
485
512
|
diagnostics,
|
|
486
513
|
runners,
|
|
514
|
+
analyzed,
|
|
487
515
|
cold,
|
|
488
516
|
coldReasons,
|
|
489
517
|
failed,
|
|
@@ -3,12 +3,11 @@ import * as path from "node:path";
|
|
|
3
3
|
* A gitleaks finding is a leaked secret at a concrete `file:startLine`. Secrets
|
|
4
4
|
* are treated as **blocking** — a committed credential is not a style nit.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* "leaked secret" alarm, while remaining visible for an audit read.
|
|
6
|
+
* Findings in scratch trees, gitignored files, and nested repositories are
|
|
7
|
+
* retained for explicit audit but demoted to `severity: "info"` / `semantic:
|
|
8
|
+
* "none"`. They are outside the current repository's shippable source, so they
|
|
9
|
+
* must never read as blocking leaked-secret alarms. Tracked files and ordinary
|
|
10
|
+
* untracked files remain blocking.
|
|
12
11
|
*/
|
|
13
12
|
export function gitleaksFindingToProjectDiagnostic(cwd, finding) {
|
|
14
13
|
// #1562 observability criterion: surface the git status right in the
|
|
@@ -17,14 +16,16 @@ export function gitleaksFindingToProjectDiagnostic(cwd, finding) {
|
|
|
17
16
|
const statusSuffix = finding.pathStatus
|
|
18
17
|
? ` [git: ${finding.pathStatus}]`
|
|
19
18
|
: "";
|
|
20
|
-
const
|
|
19
|
+
const isBlocking = finding.pathStatus !== "scratch" &&
|
|
20
|
+
finding.pathStatus !== "ignored" &&
|
|
21
|
+
finding.pathStatus !== "nested-repository";
|
|
21
22
|
return {
|
|
22
23
|
filePath: path.isAbsolute(finding.file)
|
|
23
24
|
? finding.file
|
|
24
25
|
: path.resolve(cwd, finding.file),
|
|
25
26
|
line: finding.startLine,
|
|
26
|
-
severity:
|
|
27
|
-
semantic:
|
|
27
|
+
severity: isBlocking ? "error" : "info",
|
|
28
|
+
semantic: isBlocking ? "blocking" : "none",
|
|
28
29
|
tool: "gitleaks",
|
|
29
30
|
runner: "gitleaks",
|
|
30
31
|
rule: `gitleaks:${finding.ruleId}`,
|
|
@@ -69,6 +69,7 @@ import { deprecationRecords, ignoredRecordCollector, projectLocationFor, readCon
|
|
|
69
69
|
import { finalizeRecords, } from "./config-core/records.js";
|
|
70
70
|
import { homeRelativePath, isAtOrAboveHomeDir, walkUpDirs, } from "./path-utils.js";
|
|
71
71
|
import { findPiLensConfigMarkerInDir } from "./workspace-topology.js";
|
|
72
|
+
import { readToolConfig } from "./tool-config.js";
|
|
72
73
|
/**
|
|
73
74
|
* Project config basenames, in DESCENDING precedence (first match wins), for
|
|
74
75
|
* this loader's own discovery.
|
|
@@ -563,6 +564,7 @@ function parseConfigFile(configPath) {
|
|
|
563
564
|
for (const spec of PROJECT_SCOPED_LENS_FLAGS) {
|
|
564
565
|
assignFlagConfigSection(obj, mutations, spec.configKey, note);
|
|
565
566
|
}
|
|
567
|
+
const toolConfig = readToolConfig(obj, note);
|
|
566
568
|
const rules = {};
|
|
567
569
|
if (obj.rules && typeof obj.rules === "object" && !Array.isArray(obj.rules)) {
|
|
568
570
|
const rawRules = obj.rules;
|
|
@@ -738,6 +740,7 @@ function parseConfigFile(configPath) {
|
|
|
738
740
|
}
|
|
739
741
|
return {
|
|
740
742
|
config: {
|
|
743
|
+
...(toolConfig === undefined ? {} : { tools: toolConfig }),
|
|
741
744
|
...(startup === undefined ? {} : { startup }),
|
|
742
745
|
ignore,
|
|
743
746
|
rules,
|
|
@@ -3,6 +3,7 @@ import { isTestMode } from "./env-utils.js";
|
|
|
3
3
|
import { getGlobalPiLensLogDir } from "./probe-home-state.js";
|
|
4
4
|
import { createNdjsonLogger } from "./ndjson-logger.js";
|
|
5
5
|
import { normalizeFilePath } from "./path-utils.js";
|
|
6
|
+
import { getTurnId } from "./turn-context.js";
|
|
6
7
|
const READ_GUARD_LOG_DIR = getGlobalPiLensLogDir();
|
|
7
8
|
const READ_GUARD_LOG_FILE = path.join(READ_GUARD_LOG_DIR, "read-guard.log");
|
|
8
9
|
const READ_GUARD_LOG_BACKUP_FILE = path.join(READ_GUARD_LOG_DIR, "read-guard.log.1");
|
|
@@ -236,6 +237,7 @@ export function logReadGuardEvent(entry) {
|
|
|
236
237
|
writer.log({
|
|
237
238
|
ts: new Date().toISOString(),
|
|
238
239
|
...logEntry,
|
|
240
|
+
turnId: getTurnId(),
|
|
239
241
|
filePath: normalizeFilePath(logEntry.filePath),
|
|
240
242
|
metadata,
|
|
241
243
|
});
|
|
@@ -4,6 +4,7 @@ import { getGlobalPiLensLogDir } from "./probe-home-state.js";
|
|
|
4
4
|
import { getMaxLogSizeMB } from "./log-cleanup.js";
|
|
5
5
|
import { createNdjsonLogger } from "./ndjson-logger.js";
|
|
6
6
|
import { normalizeFilePath } from "./path-utils.js";
|
|
7
|
+
import { getTurnId } from "./turn-context.js";
|
|
7
8
|
const REVIEW_GRAPH_LOG_DIR = getGlobalPiLensLogDir();
|
|
8
9
|
const REVIEW_GRAPH_LOG_FILE = path.join(REVIEW_GRAPH_LOG_DIR, "review-graph.log");
|
|
9
10
|
// Bounded like every other global sink (#2505) — see sessionstart-logger.
|
|
@@ -64,6 +65,7 @@ export function logReviewGraph(entry) {
|
|
|
64
65
|
...entry,
|
|
65
66
|
cwd: normalizeFilePath(entry.cwd),
|
|
66
67
|
pid: process.pid,
|
|
68
|
+
turnId: getTurnId(),
|
|
67
69
|
});
|
|
68
70
|
}
|
|
69
71
|
export function getReviewGraphLogPath() {
|