pi-lens 3.8.73 → 4.0.0
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/CHANGELOG.md +2614 -12
- package/README.md +50 -21
- package/dist/clients/actionable-warnings.js +73 -18
- package/dist/clients/agent-behavior-client.js +21 -4
- package/dist/clients/ast-grep-client.js +68 -16
- package/dist/clients/ast-grep-types.js +0 -1
- package/dist/clients/ast-grep-yaml-synth.js +37 -10
- package/dist/clients/atomic-write-staging.js +63 -0
- package/dist/clients/atomic-write.js +116 -16
- package/dist/clients/bash-file-access.js +193 -23
- package/dist/clients/biome-client.js +29 -25
- package/dist/clients/bootstrap.js +11 -2
- package/dist/clients/bounded-cache.js +34 -0
- package/dist/clients/bounded-pid-file-lock.js +192 -0
- package/dist/clients/bus-events-logger.js +5 -1
- package/dist/clients/bus-publish.js +10 -3
- package/dist/clients/cache/rule-cache.js +89 -6
- package/dist/clients/cache-manager.js +97 -9
- package/dist/clients/cache-observability.js +498 -0
- package/dist/clients/call-graph.js +460 -108
- package/dist/clients/cascade-logger.js +5 -1
- package/dist/clients/child-unref.js +85 -0
- package/dist/clients/codebase-model.js +52 -23
- package/dist/clients/collateral-test-role.js +40 -0
- package/dist/clients/complexity-client.js +6 -8
- package/dist/clients/console-guard-install.js +14 -0
- package/dist/clients/cooperative-budget.js +42 -0
- package/dist/clients/dead-code-client.js +2 -1
- package/dist/clients/debug-handles.js +206 -0
- package/dist/clients/debug-heap.js +167 -0
- package/dist/clients/degradation-ledger.js +153 -0
- package/dist/clients/dependency-checker.js +466 -63
- package/dist/clients/diagnostic-dispositions.js +83 -13
- package/dist/clients/diagnostics-publish.js +10 -3
- package/dist/clients/dispatch/auxiliary-lsp.js +34 -0
- package/dist/clients/dispatch/dispatcher.js +71 -13
- package/dist/clients/dispatch/fact-store.js +11 -0
- package/dist/clients/dispatch/facts/function-facts.js +37 -7
- package/dist/clients/dispatch/facts/import-facts.js +108 -119
- package/dist/clients/dispatch/facts/tree-sitter-facts.js +12 -12
- package/dist/clients/dispatch/facts/try-catch-facts.js +13 -0
- package/dist/clients/dispatch/indent-detect.js +35 -0
- package/dist/clients/dispatch/inline-suppressions.js +17 -8
- package/dist/clients/dispatch/integration.js +1094 -611
- package/dist/clients/dispatch/lazy.js +14 -0
- package/dist/clients/dispatch/plan.js +11 -1
- package/dist/clients/dispatch/rule-id-normalize.js +50 -0
- package/dist/clients/dispatch/rule-policy.js +135 -0
- package/dist/clients/dispatch/rules/async-noise.js +1 -0
- package/dist/clients/dispatch/rules/unsafe-boundary.js +7 -0
- package/dist/clients/dispatch/runners/ast-grep-napi.js +47 -6
- package/dist/clients/dispatch/runners/biome-check.js +5 -9
- package/dist/clients/dispatch/runners/detekt.js +3 -2
- package/dist/clients/dispatch/runners/hadolint.js +2 -1
- package/dist/clients/dispatch/runners/helm-lint.js +154 -0
- package/dist/clients/dispatch/runners/htmlhint.js +3 -2
- package/dist/clients/dispatch/runners/index.js +5 -1
- package/dist/clients/dispatch/runners/lsp.js +14 -6
- package/dist/clients/dispatch/runners/markdownlint.js +4 -9
- package/dist/clients/dispatch/runners/psscriptanalyzer.js +10 -1
- package/dist/clients/dispatch/runners/pyright.js +9 -5
- package/dist/clients/dispatch/runners/ruff.js +4 -8
- package/dist/clients/dispatch/runners/shellcheck.js +2 -3
- package/dist/clients/dispatch/runners/shfmt.js +2 -3
- package/dist/clients/dispatch/runners/spotbugs.js +2 -1
- package/dist/clients/dispatch/runners/taplo.js +2 -1
- package/dist/clients/dispatch/runners/terragrunt.js +163 -0
- package/dist/clients/dispatch/runners/tflint.js +37 -5
- package/dist/clients/dispatch/runners/tree-sitter.js +155 -166
- package/dist/clients/dispatch/runners/trivy-config.js +13 -7
- package/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
- package/dist/clients/dispatch/runners/utils/runner-helpers.js +284 -34
- package/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
- package/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
- package/dist/clients/disposition-publish.js +10 -3
- package/dist/clients/durable-store.js +97 -0
- package/dist/clients/event-loop-monitor.js +94 -3
- package/dist/clients/extension-log.js +139 -0
- package/dist/clients/extension-mode.js +73 -0
- package/dist/clients/feature-hints.js +53 -7
- package/dist/clients/file-kinds.js +143 -31
- package/dist/clients/file-role.js +18 -3
- package/dist/clients/file-utils.js +56 -34
- package/dist/clients/fix-worklog.js +14 -2
- package/dist/clients/format-events-publish.js +16 -3
- package/dist/clients/format-service.js +24 -8
- package/dist/clients/formatters-lazy.js +8 -0
- package/dist/clients/formatters.js +358 -61
- package/dist/clients/generated-artifacts.js +169 -15
- package/dist/clients/git-guard.js +681 -11
- package/dist/clients/git-tracked-ignore.js +4 -0
- package/dist/clients/go-client.js +2 -1
- package/dist/clients/govulncheck-client.js +8 -0
- package/dist/clients/grammar-source.js +12 -4
- package/dist/clients/gzip-stage-write.js +102 -0
- package/dist/clients/host-ports.js +31 -0
- package/dist/clients/installer/index.js +733 -213
- package/dist/clients/instance-reaper.js +142 -172
- package/dist/clients/jscpd-client.js +31 -54
- package/dist/clients/knip-client.js +78 -36
- package/dist/clients/language-policy.js +14 -2
- package/dist/clients/language-profile.js +72 -77
- package/dist/clients/latency-logger.js +28 -16
- package/dist/clients/lens-config.js +140 -166
- package/dist/clients/lens-engine.js +124 -21
- package/dist/clients/lens-events.js +27 -4
- package/dist/clients/lens-flag-registry.js +293 -0
- package/dist/clients/lens-map.js +96 -26
- package/dist/clients/live-bus-emitter.js +30 -0
- package/dist/clients/log-cleanup.js +8 -18
- package/dist/clients/lsp/cascade-tier.js +23 -3
- package/dist/clients/lsp/client.js +566 -57
- package/dist/clients/lsp/config.js +74 -10
- package/dist/clients/lsp/diagnostic-binding.js +86 -0
- package/dist/clients/lsp/edits.js +1012 -121
- package/dist/clients/lsp/index.js +1840 -803
- package/dist/clients/lsp/language.js +4 -1
- package/dist/clients/lsp/launch.js +40 -32
- package/dist/clients/lsp/path-utils.js +1 -1
- package/dist/clients/lsp/ruby-drive-dirs.js +83 -0
- package/dist/clients/lsp/server.js +279 -60
- package/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
- package/dist/clients/lsp-document-symbols.js +112 -0
- package/dist/clients/lsp-lazy.js +8 -0
- package/dist/clients/lsp-mutation.js +248 -0
- package/dist/clients/mcp/analyze.js +128 -36
- package/dist/clients/mcp/ipc.js +157 -24
- package/dist/clients/mcp/review.js +36 -3
- package/dist/clients/mcp/session.js +216 -8
- package/dist/clients/memory-sampler.js +115 -0
- package/dist/clients/metrics-client.js +2 -1
- package/dist/clients/metrics-history.js +2 -1
- package/dist/clients/module-report-lsp.js +5 -0
- package/dist/clients/module-report.js +267 -82
- package/dist/clients/ndjson-logger.js +334 -103
- package/dist/clients/opengrep-client.js +1 -1
- package/dist/clients/partial-edit-apply.js +120 -5
- package/dist/clients/path-keyed-map.js +76 -0
- package/dist/clients/path-utils.js +141 -5
- package/dist/clients/performance-report.js +327 -0
- package/dist/clients/pipeline.js +54 -13
- package/dist/clients/print-mode.js +21 -0
- package/dist/clients/project-changes.js +117 -4
- package/dist/clients/project-diagnostics/cache.js +3 -2
- package/dist/clients/project-diagnostics/extractors.js +26 -115
- package/dist/clients/project-diagnostics/fresh-fetch.js +149 -19
- package/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +117 -0
- package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +37 -5
- package/dist/clients/project-diagnostics/scanner.js +268 -128
- package/dist/clients/project-lens-config.js +138 -34
- package/dist/clients/project-report.js +64 -25
- package/dist/clients/project-scale.js +8 -10
- package/dist/clients/project-snapshot-persist-worker.js +6 -0
- package/dist/clients/project-snapshot.js +767 -8
- package/dist/clients/project-trust.js +181 -0
- package/dist/clients/quiet-window.js +27 -5
- package/dist/clients/read-bridge.js +147 -0
- package/dist/clients/read-expansion.js +22 -12
- package/dist/clients/read-guard-logger.js +159 -1
- package/dist/clients/read-guard-tool-lines.js +150 -29
- package/dist/clients/read-guard.js +262 -3
- package/dist/clients/redact/secrets.js +449 -0
- package/dist/clients/resource-sampler.js +144 -39
- package/dist/clients/reverse-deps.js +48 -0
- package/dist/clients/review-graph/builder.js +2570 -326
- package/dist/clients/review-graph/git-identity.js +2 -1
- package/dist/clients/review-graph/import-resolvers.js +21 -10
- package/dist/clients/review-graph/persist-worker.js +10 -0
- package/dist/clients/review-graph/query.js +5 -0
- package/dist/clients/review-graph/shared-extraction-ir.js +90 -0
- package/dist/clients/review-graph/symbol-id.js +46 -0
- package/dist/clients/review-graph/tsconfig-paths.js +57 -6
- package/dist/clients/review-graph/workspace-modules.js +51 -3
- package/dist/clients/review-graph-logger.js +53 -0
- package/dist/clients/ruff-client.js +27 -44
- package/dist/clients/runtime-agent-end.js +111 -75
- package/dist/clients/runtime-context.js +36 -12
- package/dist/clients/runtime-coordinator.js +76 -8
- package/dist/clients/runtime-session.js +799 -137
- package/dist/clients/runtime-tool-call.js +117 -62
- package/dist/clients/runtime-tool-result.js +218 -23
- package/dist/clients/runtime-turn.js +284 -40
- package/dist/clients/rust-client.js +2 -1
- package/dist/clients/safe-spawn.js +873 -71
- package/dist/clients/security-scan-client.js +2 -1
- package/dist/clients/session-start-observability.js +15 -0
- package/dist/clients/session-state-store.js +5 -3
- package/dist/clients/sessionstart-logger.js +18 -0
- package/dist/clients/sg-runner.js +284 -159
- package/dist/clients/slow-fs.js +2 -1
- package/dist/clients/smells-rollup.js +224 -0
- package/dist/clients/source-filter.js +231 -43
- package/dist/clients/source-walker.js +66 -10
- package/dist/clients/startup-scan.js +15 -16
- package/dist/clients/startup-timing.js +9 -0
- package/dist/clients/test-runner-client.js +38 -8
- package/dist/clients/todo-scanner.js +30 -3
- package/dist/clients/tool-event.js +35 -0
- package/dist/clients/tool-policy.js +388 -39
- package/dist/clients/tool-render.js +155 -0
- package/dist/clients/tree-sitter-cache.js +153 -140
- package/dist/clients/tree-sitter-client.js +1722 -80
- package/dist/clients/tree-sitter-logger.js +64 -1
- package/dist/clients/tree-sitter-query-loader.js +117 -12
- package/dist/clients/tree-sitter-shared.js +48 -19
- package/dist/clients/tree-sitter-symbol-extractor.js +115 -11
- package/dist/clients/user-notify.js +51 -0
- package/dist/clients/vanished-instance-marker.js +57 -0
- package/dist/clients/warm-attach.js +33 -13
- package/dist/clients/widget-state.js +342 -49
- package/dist/clients/word-index-logger.js +44 -0
- package/dist/clients/word-index.js +612 -68
- package/dist/clients/workspace-topology.js +100 -7
- package/dist/index.js +65923 -47161
- package/dist/mcp/analyze-cli.js +185 -29
- package/dist/mcp/cli.js +110 -0
- package/dist/mcp/server.js +272 -50
- package/dist/tools/ast-grep-search.js +93 -27
- package/dist/tools/lens-diagnostics.js +247 -41
- package/dist/tools/lsp-diagnostics.js +153 -40
- package/dist/tools/lsp-navigation.js +51 -31
- package/dist/tools/module-report.js +9 -0
- package/dist/tools/symbol-search.js +21 -2
- package/docs/agent-guide.md +247 -0
- package/docs/agent-tools.md +21 -10
- package/docs/analysisall.md +192 -0
- package/docs/api-ports-inventory.md +165 -0
- package/docs/ast-grep_rules_catalog.md +5 -2
- package/docs/astplayground.md +3 -4
- package/docs/audit1.md +5 -5
- package/docs/durable-store-audit-1202.md +32 -0
- package/docs/environment-variables.md +197 -11
- package/docs/fable.md +27 -0
- package/docs/features.md +35 -5
- package/docs/globalconfig.md +101 -5
- package/docs/language-coverage.md +6 -5
- package/docs/lsp-capability-matrix.md +2 -2
- package/docs/mcp.md +54 -20
- package/docs/servercapabilities.md +7 -4
- package/docs/settings.md +192 -0
- package/docs/tools.md +18 -1
- package/docs/tree-sitter_rules_catalog.md +11 -31
- package/docs/usage.md +31 -0
- package/package.json +139 -138
- package/rules/ast-grep-rules/rule-tests/hardcoded-url-js-test.yml +1 -0
- package/rules/ast-grep-rules/rule-tests/hardcoded-url-test.yml +2 -0
- package/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
- package/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
- package/rules/ast-grep-rules/rule-tests/no-case-declarations-js-test.yml +10 -0
- package/rules/ast-grep-rules/rule-tests/no-case-declarations-test.yml +10 -0
- package/rules/ast-grep-rules/rule-tests/no-javascript-url-js-test.yml +10 -0
- package/rules/ast-grep-rules/rule-tests/no-javascript-url-test.yml +10 -0
- package/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
- package/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
- package/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
- package/rules/ast-grep-rules/rule-tests/strict-equality-js-test.yml +3 -0
- package/rules/ast-grep-rules/rule-tests/strict-equality-test.yml +3 -0
- package/rules/ast-grep-rules/rule-tests/strict-inequality-js-test.yml +3 -0
- package/rules/ast-grep-rules/rule-tests/strict-inequality-test.yml +4 -0
- package/rules/ast-grep-rules/rules/hardcoded-url-js.yml +22 -4
- package/rules/ast-grep-rules/rules/hardcoded-url.yml +22 -4
- package/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
- package/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
- package/rules/ast-grep-rules/rules/no-case-declarations-js.yml +4 -3
- package/rules/ast-grep-rules/rules/no-case-declarations.yml +4 -3
- package/rules/ast-grep-rules/rules/no-console-except-error-js.yml +10 -0
- package/rules/ast-grep-rules/rules/no-console-except-error.yml +10 -0
- package/rules/ast-grep-rules/rules/no-javascript-url-js.yml +42 -6
- package/rules/ast-grep-rules/rules/no-javascript-url.yml +42 -6
- package/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
- package/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
- package/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
- package/rules/ast-grep-rules/rules/strict-equality-js.yml +14 -1
- package/rules/ast-grep-rules/rules/strict-equality.yml +14 -1
- package/rules/ast-grep-rules/rules/strict-inequality-js.yml +14 -1
- package/rules/ast-grep-rules/rules/strict-inequality.yml +14 -1
- package/rules/tree-sitter-queries/cpp/no-memset-sensitive-data.yml +9 -3
- package/rules/tree-sitter-queries/cpp/no-scoped-lock-without-args.yml +6 -6
- package/rules/tree-sitter-queries/css/calc-spacing.yml +8 -6
- package/rules/tree-sitter-queries/go/go-mutex-copy.yml +21 -18
- package/rules/tree-sitter-queries/go/go-shared-map-write-goroutine.yml +3 -3
- package/rules/tree-sitter-queries/java/infinite-loop-java.yml +3 -3
- package/rules/tree-sitter-queries/java/junit-call-super.yml +6 -4
- package/rules/tree-sitter-queries/java/no-double-checked-locking.yml +10 -8
- package/rules/tree-sitter-queries/java/no-field-shadowing.yml +1 -1
- package/rules/tree-sitter-queries/java/prepared-statement-valid-indices.yml +2 -3
- package/rules/tree-sitter-queries/java/resources-closed.yml +2 -2
- package/rules/tree-sitter-queries/java/springboot-default-package.yml +6 -3
- package/rules/tree-sitter-queries/java/switch-fall-through.yml +6 -4
- package/rules/tree-sitter-queries/java/switch-non-case-labels.yml +4 -3
- package/rules/tree-sitter-queries/java/tests-include-assertions.yml +6 -5
- package/rules/tree-sitter-queries/javascript/switch-case-termination-js.yml +5 -8
- package/rules/tree-sitter-queries/javascript/switch-non-case-labels.yml +3 -2
- package/rules/tree-sitter-queries/kotlin/prepared-statement-indices.yml +9 -5
- package/rules/tree-sitter-queries/php/this-in-static-context.yml +5 -5
- package/rules/tree-sitter-queries/python/exit-signature-check.yml +2 -9
- package/rules/tree-sitter-queries/python/in-operator-unsupported.yml +3 -4
- package/rules/tree-sitter-queries/python/no-super-torchscript.yml +4 -8
- package/rules/tree-sitter-queries/python/notimplemented-boolean-context.yml +1 -5
- package/rules/tree-sitter-queries/python/python-empty-except.yml +1 -1
- package/rules/tree-sitter-queries/python/return-in-init.yml +0 -2
- package/rules/tree-sitter-queries/python/yield-return-outside-function.yml +0 -3
- package/rules/tree-sitter-queries/ruby/ruby-command-injection.yml +1 -12
- package/rules/tree-sitter-queries/ruby/ruby-eval.yml +1 -1
- package/rules/tree-sitter-queries/ruby/ruby-insecure-deserialization.yml +4 -4
- package/rules/tree-sitter-queries/ruby/ruby-insecure-random.yml +4 -11
- package/rules/tree-sitter-queries/ruby/ruby-open-struct.yml +1 -1
- package/rules/tree-sitter-queries/ruby/ruby-string-eval.yml +2 -3
- package/rules/tree-sitter-queries/ruby/ruby-weak-hash.yml +4 -4
- package/rules/tree-sitter-queries/rust/rust-lock-held-across-await.yml +4 -3
- package/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
- package/rules/tree-sitter-queries/typescript/console-statement.yml +11 -0
- package/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +4 -9
- package/rules/tree-sitter-queries/typescript/empty-switch-case.yml +13 -9
- package/rules/tree-sitter-queries/typescript/infinite-loop.yml +2 -2
- package/rules/tree-sitter-queries/typescript/mixed-async-styles.yml +10 -1
- package/rules/tree-sitter-queries/typescript/switch-case-termination.yml +5 -8
- package/rules/tree-sitter-queries/typescript/ts-insecure-random.yml +7 -10
- package/rules/tree-sitter-queries/typescript/unsafe-regex.yml +4 -2
- package/rules/typos/_typos.toml +17 -0
- package/scripts/analyze-pi-lens-logs.mjs +55 -2
- package/skills/pi-lens-write-tree-sitter-rule/SKILL.md +5 -0
- package/dist/clients/ast-grep-parser.js +0 -86
- package/dist/clients/config-enabled-shape.js +0 -39
- package/dist/clients/lsp/interactive-install.js +0 -367
- package/dist/clients/lsp/lsp-index.js +0 -10
- package/dist/clients/lsp/server-strategies.js +0 -5
- package/dist/clients/project-metadata.js +0 -690
- package/dist/clients/source-groups.js +0 -96
- package/dist/clients/types.js +0 -11
- package/docs/env_variables.md +0 -10
- package/rules/tree-sitter-queries/abap-disabled/delete-where.yml +0 -46
- package/rules/tree-sitter-queries/cpp/no-confused-move-forward.yml +0 -59
- package/rules/tree-sitter-queries/cpp/noexcept-functions.yml +0 -58
- package/rules/tree-sitter-queries/java/mockito-initialized.yml +0 -66
- package/rules/tree-sitter-queries/java/no-wait-notify-on-thread.yml +0 -58
- package/rules/tree-sitter-queries/java/short-circuit-logic.yml +0 -57
- package/rules/tree-sitter-queries/java/spring-session-attributes-setcomplete.yml +0 -75
- package/rules/tree-sitter-queries/plsql-disabled/delete-update-where.yml +0 -56
- package/rules/tree-sitter-queries/plsql-disabled/end-loop-semicolon.yml +0 -51
- package/rules/tree-sitter-queries/plsql-disabled/fetch-bulk-collect-limit.yml +0 -47
- package/rules/tree-sitter-queries/plsql-disabled/forallsave-exceptions.yml +0 -51
- package/rules/tree-sitter-queries/plsql-disabled/nchar-nvarchar2-bytes.yml +0 -54
- package/rules/tree-sitter-queries/plsql-disabled/not-null-initialization.yml +0 -59
- package/rules/tree-sitter-queries/plsql-disabled/raise-application-error-codes.yml +0 -50
- package/rules/tree-sitter-queries/typescript-disabled/ts-path-traversal.yml +0 -71
package/dist/clients/pipeline.js
CHANGED
|
@@ -19,7 +19,7 @@ import { recordFromCodeQualityDiagnostic, } from "./code-quality-warnings.js";
|
|
|
19
19
|
import { recordDiagnostics } from "./widget-state.js";
|
|
20
20
|
import { getDiagnosticLogger } from "./diagnostic-logger.js";
|
|
21
21
|
import { getDiagnosticTracker } from "./diagnostic-tracker.js";
|
|
22
|
-
import {
|
|
22
|
+
import { loadDispatchIntegration } from "./dispatch/lazy.js";
|
|
23
23
|
import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
|
|
24
24
|
import { createAvailabilityChecker, resolveAvailableOrInstall, resolveCommandArgsWithInstallFallback, resolveToolCommand, resolveToolCommandWithInstallFallback, } from "./dispatch/runners/utils/runner-helpers.js";
|
|
25
25
|
import { findDetektConfig } from "./dispatch/runners/detekt.js";
|
|
@@ -29,12 +29,14 @@ import { logLatency } from "./latency-logger.js";
|
|
|
29
29
|
import { emitLensAnalysisComplete } from "./lens-events.js";
|
|
30
30
|
import { publishFilesTouched } from "./bus-publish.js";
|
|
31
31
|
import { publishDiagnostics, wasPreviouslyReportedDirty, } from "./diagnostics-publish.js";
|
|
32
|
-
import {
|
|
32
|
+
import { loadLspService } from "./lsp-lazy.js";
|
|
33
33
|
import { clearGraphCache } from "./review-graph/builder.js";
|
|
34
|
+
import { BoundedLruCache } from "./bounded-cache.js";
|
|
34
35
|
import { RUNTIME_CONFIG } from "./runtime-config.js";
|
|
35
36
|
import { getAmbientAbortSignal, safeSpawnAsync } from "./safe-spawn.js";
|
|
36
37
|
import { combineAbortSignals } from "./deadline-utils.js";
|
|
37
|
-
import {
|
|
38
|
+
import { recordDegradationOnce } from "./degradation-ledger.js";
|
|
39
|
+
import { getAutofixPolicyForFile, getPreferredAutofixTools, getRubocopCommand, hasBiomeConfig, hasDetektConfig, hasEslintConfig, hasGolangciConfig, hasKtfmtConfig, hasKtlintConfig, hasOxlintConfig, hasRubocopConfig, hasSqlfluffConfig, hasStylelintConfig, markdownlintConfigArgs, } from "./tool-policy.js";
|
|
38
40
|
const LSP_MAX_FILE_BYTES = RUNTIME_CONFIG.pipeline.lspMaxFileBytes;
|
|
39
41
|
const LSP_MAX_FILE_LINES = RUNTIME_CONFIG.pipeline.lspMaxFileLines;
|
|
40
42
|
const LSP_SPAWN_BUDGET_MS = RUNTIME_CONFIG.pipeline.lspSpawnBudgetMs;
|
|
@@ -66,7 +68,10 @@ const SNAPSHOT_YIELD_EVERY = 500;
|
|
|
66
68
|
async function snapshotDirInto(dir, ignoreMatcher, stack, snapshot, counter) {
|
|
67
69
|
let entries;
|
|
68
70
|
try {
|
|
69
|
-
|
|
71
|
+
// Async (#1137): a synchronous per-directory read blocks the loop for the
|
|
72
|
+
// whole stall on a slow cloud-backed (OneDrive) directory; fs.promises
|
|
73
|
+
// keeps this tool_result-path walk off the event loop.
|
|
74
|
+
entries = await nodeFs.promises.readdir(dir, { withFileTypes: true });
|
|
70
75
|
}
|
|
71
76
|
catch {
|
|
72
77
|
return;
|
|
@@ -85,7 +90,7 @@ async function snapshotDirInto(dir, ignoreMatcher, stack, snapshot, counter) {
|
|
|
85
90
|
if (ignoreMatcher.isIgnored(fullPath, false))
|
|
86
91
|
continue;
|
|
87
92
|
try {
|
|
88
|
-
const stat = nodeFs.
|
|
93
|
+
const stat = await nodeFs.promises.stat(fullPath);
|
|
89
94
|
snapshot.set(path.resolve(fullPath), {
|
|
90
95
|
mtimeMs: stat.mtimeMs,
|
|
91
96
|
size: stat.size,
|
|
@@ -173,7 +178,7 @@ function createPhaseTracker(toolName, filePath) {
|
|
|
173
178
|
}
|
|
174
179
|
// --- ESLint autofix helpers ---
|
|
175
180
|
export { hasEslintConfig, hasRubocopConfig, hasSqlfluffConfig, hasStylelintConfig, };
|
|
176
|
-
const _eslintCache = new
|
|
181
|
+
const _eslintCache = new BoundedLruCache(32);
|
|
177
182
|
/**
|
|
178
183
|
* Run eslint --fix on a file. Runs a single spawn and diffs the file before/after,
|
|
179
184
|
* same idiom as the other autofix helpers below. Exit code 1 (unfixable problems
|
|
@@ -186,7 +191,9 @@ async function tryEslintFix(filePath, cwd) {
|
|
|
186
191
|
const userHasConfig = hasEslintConfig(cwd);
|
|
187
192
|
if (!userHasConfig)
|
|
188
193
|
return 0;
|
|
189
|
-
|
|
194
|
+
// PATH is part of command resolution; include it so an install or PATH
|
|
195
|
+
// refresh cannot leave a negative eslint result live for the session.
|
|
196
|
+
const cacheKey = `${path.resolve(cwd)}|${process.env.PATH ?? ""}`;
|
|
190
197
|
let cached = _eslintCache.get(cacheKey);
|
|
191
198
|
if (!cached) {
|
|
192
199
|
const candidate = resolveToolCommand(cwd, "eslint") ?? "eslint";
|
|
@@ -274,8 +281,12 @@ async function tryMarkdownlintFix(filePath, cwd) {
|
|
|
274
281
|
const cmd = await resolveToolCommandWithInstallFallback(cwd, "markdownlint");
|
|
275
282
|
if (!cmd)
|
|
276
283
|
return 0;
|
|
284
|
+
// Shared config-args seam (#1247): the lint runner consumes the same
|
|
285
|
+
// builder, so the bare --fix here can never fall back to markdownlint's
|
|
286
|
+
// default all-rules-on config again (the whole-file CHANGELOG/AGENTS
|
|
287
|
+
// reformat + `#946` → `# 946` corruption).
|
|
277
288
|
// markdownlint-cli2 --fix exits non-zero when unfixable violations remain.
|
|
278
|
-
return detectFileChangedAfterCommand(filePath, cmd, ["--fix", filePath], cwd, [1]);
|
|
289
|
+
return detectFileChangedAfterCommand(filePath, cmd, [...markdownlintConfigArgs(cwd), "--fix", filePath], cwd, [1]);
|
|
279
290
|
}
|
|
280
291
|
async function tryOxlintFix(filePath, cwd) {
|
|
281
292
|
const cmd = await resolveToolCommandWithInstallFallback(cwd, "oxlint");
|
|
@@ -356,6 +367,7 @@ export async function runAutofix(filePath, cwd, getFlag, dbg, deps, getFlagSourc
|
|
|
356
367
|
hasGolangciConfig: hasGolangciConfig(cwd),
|
|
357
368
|
hasDetektConfig: hasDetektConfig(cwd),
|
|
358
369
|
hasKtfmtConfig: hasKtfmtConfig(cwd),
|
|
370
|
+
hasKtlintConfig: hasKtlintConfig(cwd),
|
|
359
371
|
hasOxlintConfig: hasOxlintConfig(cwd),
|
|
360
372
|
};
|
|
361
373
|
const autofixPolicy = getAutofixPolicyForFile(filePath, autofixContext);
|
|
@@ -396,7 +408,7 @@ export async function runAutofix(filePath, cwd, getFlag, dbg, deps, getFlagSourc
|
|
|
396
408
|
dbg(`autofix: ruff unavailable for ${filePath}`);
|
|
397
409
|
continue;
|
|
398
410
|
}
|
|
399
|
-
const result = await ruffClient.fixFileAsync(filePath);
|
|
411
|
+
const result = await ruffClient.fixFileAsync(filePath, cwd);
|
|
400
412
|
if (result.success && result.fixed > 0) {
|
|
401
413
|
fixedCount += result.fixed;
|
|
402
414
|
autofixTools.push(`ruff:${result.fixed}`);
|
|
@@ -415,7 +427,7 @@ export async function runAutofix(filePath, cwd, getFlag, dbg, deps, getFlagSourc
|
|
|
415
427
|
dbg(`autofix: biome unavailable or unsupported for ${filePath}`);
|
|
416
428
|
continue;
|
|
417
429
|
}
|
|
418
|
-
const result = await biomeClient.fixFileAsync(filePath);
|
|
430
|
+
const result = await biomeClient.fixFileAsync(filePath, cwd);
|
|
419
431
|
if (result.success && result.fixed > 0) {
|
|
420
432
|
fixedCount += result.fixed;
|
|
421
433
|
autofixTools.push(`biome:${result.fixed}`);
|
|
@@ -570,7 +582,6 @@ export async function runAutofix(filePath, cwd, getFlag, dbg, deps, getFlagSourc
|
|
|
570
582
|
dbg(`autofix: oxlint --fix fixed ${filePath}`);
|
|
571
583
|
needsContentRefresh = true;
|
|
572
584
|
}
|
|
573
|
-
continue;
|
|
574
585
|
}
|
|
575
586
|
}
|
|
576
587
|
if (attemptedTools.length > 0 && autofixTools.length === 0) {
|
|
@@ -593,7 +604,7 @@ export async function resyncLspFile(filePath, fileContent, needsContentRefresh,
|
|
|
593
604
|
if (limitCheck.tooLarge)
|
|
594
605
|
return;
|
|
595
606
|
try {
|
|
596
|
-
const lspService = getLSPService();
|
|
607
|
+
const lspService = (await loadLspService()).getLSPService();
|
|
597
608
|
if (lspService.supportsLSP(filePath)) {
|
|
598
609
|
// Push the final post-format/post-fix content through touchFile (not the
|
|
599
610
|
// bare openFile) so it registers in the touch-debounce map via
|
|
@@ -704,6 +715,13 @@ export async function runFormatPhase(filePath, getFormatService, dbg) {
|
|
|
704
715
|
}
|
|
705
716
|
if (!result.allSucceeded) {
|
|
706
717
|
const failures = result.formatters.filter((f) => !f.success);
|
|
718
|
+
for (const failure of failures) {
|
|
719
|
+
recordDegradationOnce({
|
|
720
|
+
kind: "formatter-failure",
|
|
721
|
+
subject: `${failure.name}:${path.basename(filePath)}`,
|
|
722
|
+
reason: failure.error ?? "unknown error",
|
|
723
|
+
});
|
|
724
|
+
}
|
|
707
725
|
formatFailures.push(...failures.map((f) => `${f.name}: ${f.error ?? "unknown error"}`));
|
|
708
726
|
dbg("autoformat: " +
|
|
709
727
|
failures
|
|
@@ -713,6 +731,11 @@ export async function runFormatPhase(filePath, getFormatService, dbg) {
|
|
|
713
731
|
}
|
|
714
732
|
catch (err) {
|
|
715
733
|
const message = err instanceof Error ? err.message : String(err);
|
|
734
|
+
recordDegradationOnce({
|
|
735
|
+
kind: "formatter-failure",
|
|
736
|
+
subject: `format-service:${path.basename(filePath)}`,
|
|
737
|
+
reason: message,
|
|
738
|
+
});
|
|
716
739
|
formatFailures.push(message);
|
|
717
740
|
dbg(`autoformat error: ${err}`);
|
|
718
741
|
}
|
|
@@ -871,11 +894,15 @@ export async function runPipeline(ctx, deps) {
|
|
|
871
894
|
const piApi = {
|
|
872
895
|
getFlag: getFlag,
|
|
873
896
|
};
|
|
897
|
+
const { dispatchLintWithResult, computeCascadeForFile } = await loadDispatchIntegration();
|
|
874
898
|
const dispatchResult = await dispatchLintWithResult(filePath, cwd, piApi, ctx.modifiedRanges, {
|
|
875
899
|
model: ctx.telemetry?.model ?? "unknown",
|
|
876
900
|
sessionId: ctx.telemetry?.sessionId ?? "unknown",
|
|
877
901
|
turnIndex: ctx.telemetry?.turnIndex ?? 0,
|
|
878
902
|
writeIndex: ctx.telemetry?.writeIndex ?? 0,
|
|
903
|
+
}, {
|
|
904
|
+
projectRoot: ctx.projectRoot,
|
|
905
|
+
writeIndex: ctx.telemetry?.writeIndex,
|
|
879
906
|
});
|
|
880
907
|
recordDiagnostics(filePath, dispatchResult.diagnostics, ctx.telemetry?.writeIndex);
|
|
881
908
|
// #502: emit the write batch's FINAL diagnostic state immediately after
|
|
@@ -984,6 +1011,11 @@ export async function runPipeline(ctx, deps) {
|
|
|
984
1011
|
// result is never shown inline — settled (bounded) and surfaced at turn_end.
|
|
985
1012
|
// The stored promise must never reject: an unhandled rejection is fatal, so a
|
|
986
1013
|
// failing compute resolves to an "error" skip-run instead.
|
|
1014
|
+
const cascadeOrigin = {
|
|
1015
|
+
turnSeq: ctx.telemetry?.turnIndex,
|
|
1016
|
+
writeSeq: ctx.telemetry?.writeIndex,
|
|
1017
|
+
projectSeq: ctx.seqState?.projectSeq(),
|
|
1018
|
+
};
|
|
987
1019
|
const cascadePromise = getFlag("no-lsp")
|
|
988
1020
|
? undefined
|
|
989
1021
|
: computeCascadeForFile(filePath, cwd, {
|
|
@@ -995,14 +1027,23 @@ export async function runPipeline(ctx, deps) {
|
|
|
995
1027
|
fileContent,
|
|
996
1028
|
wordIndex: ctx.wordIndex,
|
|
997
1029
|
onWordIndexUpdated: ctx.onWordIndexUpdated,
|
|
998
|
-
}).
|
|
1030
|
+
}).then((run) => ({ ...run, origin: cascadeOrigin }))
|
|
1031
|
+
.catch((err) => {
|
|
999
1032
|
dbg(`cascade compute failed for ${filePath}: ${err}`);
|
|
1000
1033
|
return {
|
|
1001
1034
|
filePath,
|
|
1035
|
+
origin: cascadeOrigin,
|
|
1002
1036
|
result: undefined,
|
|
1003
1037
|
neighborCount: 0,
|
|
1004
1038
|
diagnosticCount: 0,
|
|
1005
1039
|
skipReason: "error",
|
|
1040
|
+
// #1023: a thrown compute is ALSO "couldn't compute downstream
|
|
1041
|
+
// impact" — carry an indeterminate marker so turn_end surfaces an
|
|
1042
|
+
// honest advisory instead of the error skip being logs-only.
|
|
1043
|
+
indeterminate: {
|
|
1044
|
+
reason: "error",
|
|
1045
|
+
detail: "cascade computation failed",
|
|
1046
|
+
},
|
|
1006
1047
|
};
|
|
1007
1048
|
});
|
|
1008
1049
|
// --- Final timing + all-clear ---
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-shot invocation detection, shared across the session_start-scheduled
|
|
3
|
+
* background work that must NOT run — or retain the process — in print mode
|
|
4
|
+
* (#1154 quick-mode warmup, #1153 orphan-reaper).
|
|
5
|
+
*
|
|
6
|
+
* `pi -p` / `pi --print` is a one-shot: the host computes a single turn and
|
|
7
|
+
* exits. Work scheduled to warm caches / clean up state for a *future* session
|
|
8
|
+
* in the same process is pure waste in this mode AND — when it arms a
|
|
9
|
+
* referenced libuv handle (a non-`unref`'d timer, an LSP child, an fs walk)
|
|
10
|
+
* with no session_shutdown teardown — keeps the already-settled one-shot
|
|
11
|
+
* process alive. That is the referenced-handle retention class of
|
|
12
|
+
* #1097/#1110/#1148/#1149 and the located #1122 hypothesis-A concern.
|
|
13
|
+
*
|
|
14
|
+
* argv is read live (not captured at module load) so tests can drive it; the
|
|
15
|
+
* flag never changes within a real process, and there is no hot-path caller,
|
|
16
|
+
* so recomputing is free. This mirrors the print detection in
|
|
17
|
+
* `runtime-session.ts`'s `resolveStartupMode` — keep them a single source.
|
|
18
|
+
*/
|
|
19
|
+
export function isPrintMode(argv = process.argv) {
|
|
20
|
+
return argv.includes("--print") || argv.includes("-p");
|
|
21
|
+
}
|
|
@@ -44,6 +44,31 @@ export function readProjectChanges(cwd) {
|
|
|
44
44
|
return [];
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Async twin of `readProjectChanges` (#1162). `fs.promises.readFile` — unlike
|
|
49
|
+
* the sync `readFileSync` above — YIELDS to the event loop, which is what lets
|
|
50
|
+
* a caller bound it with a `Promise.race` timeout: a `setTimeout` can never
|
|
51
|
+
* preempt a synchronous blocking read (the thread doesn't return to the event
|
|
52
|
+
* loop until the OS does), so a bounded read on the session_start hot path
|
|
53
|
+
* requires the async form. Same parse/error-swallow semantics as the sync
|
|
54
|
+
* version — a missing/corrupt log is the normal cold-start case, not a
|
|
55
|
+
* caller-visible error.
|
|
56
|
+
*/
|
|
57
|
+
export async function readProjectChangesAsync(cwd) {
|
|
58
|
+
const logPath = getProjectChangeLogPath(cwd);
|
|
59
|
+
try {
|
|
60
|
+
const content = await fs.promises.readFile(logPath, "utf-8");
|
|
61
|
+
return content
|
|
62
|
+
.split(/\r?\n/)
|
|
63
|
+
.map((line) => line.trim())
|
|
64
|
+
.filter(Boolean)
|
|
65
|
+
.map(parseChangeLine)
|
|
66
|
+
.filter((entry) => Boolean(entry));
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
47
72
|
export function readChangesSince(cwd, seq, maxEntries = 200) {
|
|
48
73
|
const limit = Math.max(1, maxEntries);
|
|
49
74
|
return readProjectChanges(cwd)
|
|
@@ -51,16 +76,104 @@ export function readChangesSince(cwd, seq, maxEntries = 200) {
|
|
|
51
76
|
.sort((a, b) => a.seq - b.seq)
|
|
52
77
|
.slice(-limit);
|
|
53
78
|
}
|
|
54
|
-
|
|
79
|
+
// Test-observable count of the EXPENSIVE per-entry folds (each one does a
|
|
80
|
+
// `path.resolve` + `normalizeMapKey`/`realpathSync.native` syscall). Lets the
|
|
81
|
+
// #1019 equivalence tests prove the partial path folds strictly FEWER entries
|
|
82
|
+
// than the full replay for the same log — i.e. that the bound actually holds —
|
|
83
|
+
// without a brittle spy on the path helpers.
|
|
84
|
+
let _sequenceFoldCountForTests = 0;
|
|
85
|
+
export function getSequenceFoldCountForTests() {
|
|
86
|
+
return _sequenceFoldCountForTests;
|
|
87
|
+
}
|
|
88
|
+
export function resetSequenceFoldCountForTests() {
|
|
89
|
+
_sequenceFoldCountForTests = 0;
|
|
90
|
+
}
|
|
91
|
+
function foldEntry(entry, fileSeqByPath) {
|
|
92
|
+
_sequenceFoldCountForTests++;
|
|
93
|
+
const key = normalizeMapKey(path.resolve(entry.filePath));
|
|
94
|
+
fileSeqByPath.set(key, Math.max(fileSeqByPath.get(key) ?? 0, entry.fileSeq));
|
|
95
|
+
}
|
|
96
|
+
function fullReplay(entries) {
|
|
55
97
|
let projectSeq = 0;
|
|
56
98
|
const fileSeqByPath = new Map();
|
|
57
|
-
for (const entry of
|
|
99
|
+
for (const entry of entries) {
|
|
100
|
+
projectSeq = Math.max(projectSeq, entry.seq);
|
|
101
|
+
foldEntry(entry, fileSeqByPath);
|
|
102
|
+
}
|
|
103
|
+
return { projectSeq, fileSeqByPath };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Attempt the bounded partial replay: hydrate `base` (pre-normalized keys, no
|
|
107
|
+
* `realpath` cost) and fold ONLY entries with `seq > base.sinceSeq`. Returns
|
|
108
|
+
* `null` — signalling the caller to fall back to a full replay — whenever the
|
|
109
|
+
* base cannot be trusted to be byte-identical to a full replay:
|
|
110
|
+
*
|
|
111
|
+
* - `base.sinceSeq` is AHEAD of the log's max seq (the log was truncated /
|
|
112
|
+
* rotated below the snapshot, or the snapshot seq is simply ahead). Serving
|
|
113
|
+
* the base would over-report the seq; the full replay reflects the real log.
|
|
114
|
+
*
|
|
115
|
+
* The result is provably equal to a full replay under the append-only,
|
|
116
|
+
* single-writer invariant (`base` == fold of the log up to `sinceSeq`): every
|
|
117
|
+
* historical file's seq is already in `base`, and folding the strictly-newer
|
|
118
|
+
* entries with the SAME `Math.max` the full replay uses reproduces the max over
|
|
119
|
+
* the whole log for both `projectSeq` and each file. `Math.max` makes the fold
|
|
120
|
+
* order-independent, so gaps / out-of-order entries are handled exactly as the
|
|
121
|
+
* full replay handles them.
|
|
122
|
+
*/
|
|
123
|
+
function partialReplay(entries, base) {
|
|
124
|
+
let logMaxSeq = 0;
|
|
125
|
+
for (const entry of entries) {
|
|
126
|
+
if (entry.seq > logMaxSeq)
|
|
127
|
+
logMaxSeq = entry.seq;
|
|
128
|
+
}
|
|
129
|
+
// Snapshot ahead of / newer than the log (truncation, rotation, or a stale
|
|
130
|
+
// log): the base describes a state the log no longer backs — never serve it.
|
|
131
|
+
if (base.sinceSeq > logMaxSeq)
|
|
132
|
+
return null;
|
|
133
|
+
const fileSeqByPath = new Map(base.fileSeqByPath);
|
|
134
|
+
let projectSeq = Math.max(0, base.projectSeq);
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
if (entry.seq <= base.sinceSeq)
|
|
137
|
+
continue; // covered by the base
|
|
58
138
|
projectSeq = Math.max(projectSeq, entry.seq);
|
|
59
|
-
|
|
60
|
-
fileSeqByPath.set(key, Math.max(fileSeqByPath.get(key) ?? 0, entry.fileSeq));
|
|
139
|
+
foldEntry(entry, fileSeqByPath);
|
|
61
140
|
}
|
|
62
141
|
return { projectSeq, fileSeqByPath };
|
|
63
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Compute the current `{ projectSeq, fileSeqByPath }` from the append-only
|
|
145
|
+
* change log. With no `base`, this is a full replay of the entire log. With a
|
|
146
|
+
* snapshot-embedded `base` (#1019), it folds only entries newer than
|
|
147
|
+
* `base.sinceSeq` on top of the base — O(changes since snapshot) instead of
|
|
148
|
+
* O(entire log) — and transparently falls back to a full replay when the base
|
|
149
|
+
* cannot be trusted (see `partialReplay`). A legacy/stale/missing snapshot
|
|
150
|
+
* simply passes no `base` (or one the guard rejects), so correctness never
|
|
151
|
+
* depends on the optimization being applicable.
|
|
152
|
+
*/
|
|
153
|
+
export function readLatestProjectSequence(cwd, base) {
|
|
154
|
+
const entries = readProjectChanges(cwd);
|
|
155
|
+
if (base) {
|
|
156
|
+
const partial = partialReplay(entries, base);
|
|
157
|
+
if (partial)
|
|
158
|
+
return partial;
|
|
159
|
+
}
|
|
160
|
+
return fullReplay(entries);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Async twin of `readLatestProjectSequence` (#1162) — same fold logic, but
|
|
164
|
+
* reads the log via `readProjectChangesAsync` so a caller can bound the wait
|
|
165
|
+
* with a `Promise.race` timeout (see that function's doc comment for why the
|
|
166
|
+
* sync form can't be bounded at all).
|
|
167
|
+
*/
|
|
168
|
+
export async function readLatestProjectSequenceAsync(cwd, base) {
|
|
169
|
+
const entries = await readProjectChangesAsync(cwd);
|
|
170
|
+
if (base) {
|
|
171
|
+
const partial = partialReplay(entries, base);
|
|
172
|
+
if (partial)
|
|
173
|
+
return partial;
|
|
174
|
+
}
|
|
175
|
+
return fullReplay(entries);
|
|
176
|
+
}
|
|
64
177
|
export function appendProjectChange(cwd, entry) {
|
|
65
178
|
const logPath = getProjectChangeLogPath(cwd);
|
|
66
179
|
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { getProjectDataDir } from "../file-utils.js";
|
|
4
|
+
import { writeFileAtomic } from "../atomic-write.js";
|
|
4
5
|
import { readJsonCache } from "../json-cache-read.js";
|
|
5
6
|
// v2: cheap-tier scan now also runs ast-grep-napi (#308); invalidate older
|
|
6
7
|
// snapshots so a pre-ast-grep cache isn't served as complete via refreshRunners=cached.
|
|
@@ -25,7 +26,7 @@ export function loadProjectDiagnosticsSnapshot(cwd) {
|
|
|
25
26
|
export function saveProjectDiagnosticsSnapshot(cwd, snapshot) {
|
|
26
27
|
const filePath = cachePath(cwd, SNAPSHOT_CACHE_FILE);
|
|
27
28
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
28
|
-
|
|
29
|
+
writeFileAtomic(filePath, JSON.stringify(snapshot, null, 2));
|
|
29
30
|
}
|
|
30
31
|
export function loadProjectDiagnosticsDeltaReport(cwd) {
|
|
31
32
|
return readJsonCache(cachePath(cwd, DELTA_CACHE_FILE), (parsed) => {
|
|
@@ -42,7 +43,7 @@ export function loadProjectDiagnosticsDeltaReport(cwd) {
|
|
|
42
43
|
export function writeProjectDiagnosticsDeltaReport(cwd, report) {
|
|
43
44
|
const filePath = cachePath(cwd, DELTA_CACHE_FILE);
|
|
44
45
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
45
|
-
|
|
46
|
+
writeFileAtomic(filePath, JSON.stringify(report, null, 2));
|
|
46
47
|
}
|
|
47
48
|
/**
|
|
48
49
|
* Drop diagnostics whose underlying file changed on disk after the snapshot was
|
|
@@ -1,83 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* mode=full`.
|
|
2
|
+
* Provenance metadata for the heavyweight project analyzers surfaced in
|
|
3
|
+
* `lens_diagnostics mode=full`.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* NOTE (#585 regression cleanup): this module USED to also own
|
|
6
|
+
* `extractCachedProjectDiagnostics` — a CACHE-ONLY reader that adapted each
|
|
7
|
+
* analyzer's cached result to `ProjectDiagnostic[]` via a parallel `EXTRACTORS`
|
|
8
|
+
* registry. #585 replaced that reader in production with
|
|
9
|
+
* `fetchFreshProjectDiagnostics` (./fresh-fetch.ts), which triggers-or-joins a
|
|
10
|
+
* fresh run instead of settling for a possibly-hours-stale session_start
|
|
11
|
+
* snapshot. The old reader lingered here wired only to a test — a SECOND,
|
|
12
|
+
* dead surfacing path whose registry could (and did) silently diverge from
|
|
13
|
+
* fresh-fetch's `ANALYZER_IDS`: opengrep was registered here but missing there,
|
|
14
|
+
* so opengrep scanned+cached yet never reached the agent (the exact #585-class
|
|
15
|
+
* regression this cleanup removes). The parallel registry is gone; fresh-fetch
|
|
16
|
+
* is the single source of truth (#883).
|
|
17
|
+
*
|
|
18
|
+
* What survives is the honesty metadata (#533): given an analyzer id that
|
|
19
|
+
* fresh-fetch reported `cold` (not applicable / unavailable this run),
|
|
20
|
+
* `warmTriggerFor` names what WOULD warm it, so the rendered note is actionable
|
|
21
|
+
* rather than a bare "not run".
|
|
11
22
|
*/
|
|
12
|
-
import { deadCodeResultToProjectDiagnostics } from "./runner-adapters/dead-code.js";
|
|
13
|
-
import { gitleaksResultToProjectDiagnostics } from "./runner-adapters/gitleaks.js";
|
|
14
|
-
import { govulncheckResultToProjectDiagnostics } from "./runner-adapters/govulncheck.js";
|
|
15
|
-
import { jscpdResultToProjectDiagnostics } from "./runner-adapters/jscpd.js";
|
|
16
|
-
import { knipIssuesToProjectDiagnostics } from "./runner-adapters/knip.js";
|
|
17
|
-
import { circularDepsToProjectDiagnostics } from "./runner-adapters/madge.js";
|
|
18
|
-
import { opengrepResultToProjectDiagnostics } from "./runner-adapters/opengrep.js";
|
|
19
|
-
import { testRunnerFindingsToProjectDiagnostics } from "./runner-adapters/runner-findings.js";
|
|
20
|
-
import { trivyResultToProjectDiagnostics } from "./runner-adapters/trivy.js";
|
|
21
|
-
// biome-ignore lint/suspicious/noExplicitAny: heterogeneous result types per row
|
|
22
|
-
const EXTRACTORS = [
|
|
23
|
-
{
|
|
24
|
-
id: "knip",
|
|
25
|
-
cacheKeys: ["knip"],
|
|
26
|
-
adapt: (cwd, r) => knipIssuesToProjectDiagnostics(cwd, r.issues ?? []),
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
id: "jscpd",
|
|
30
|
-
cacheKeys: ["jscpd-ts", "jscpd"],
|
|
31
|
-
adapt: (cwd, r) => jscpdResultToProjectDiagnostics(cwd, r),
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
id: "madge",
|
|
35
|
-
cacheKeys: ["madge"],
|
|
36
|
-
adapt: (cwd, r) => circularDepsToProjectDiagnostics(cwd, r.circular ?? []),
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
id: "gitleaks",
|
|
40
|
-
cacheKeys: ["gitleaks"],
|
|
41
|
-
adapt: (cwd, r) => gitleaksResultToProjectDiagnostics(cwd, r),
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
id: "govulncheck",
|
|
45
|
-
cacheKeys: ["govulncheck"],
|
|
46
|
-
adapt: (cwd, r) => govulncheckResultToProjectDiagnostics(cwd, r),
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
// #584: opengrep's full-workspace findings, sourced from a single
|
|
50
|
-
// project-wide CLI scan (`opengrep-client.ts`) instead of the per-file
|
|
51
|
-
// LSP sweep — see the constant's doc in `clients/lsp/index.ts`.
|
|
52
|
-
id: "opengrep",
|
|
53
|
-
cacheKeys: ["opengrep"],
|
|
54
|
-
adapt: (cwd, r) => opengrepResultToProjectDiagnostics(cwd, r),
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: "trivy",
|
|
58
|
-
cacheKeys: ["trivy"],
|
|
59
|
-
adapt: (cwd, r) => trivyResultToProjectDiagnostics(cwd, r),
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
// Per-language cache (`dead-code-<id>`). Only Python (vulture) exists today;
|
|
63
|
-
// add a key here as each new dead-code language lands.
|
|
64
|
-
id: "dead-code",
|
|
65
|
-
cacheKeys: ["dead-code-python"],
|
|
66
|
-
adapt: (cwd, r) => deadCodeResultToProjectDiagnostics(cwd, r),
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
// #628 item 4: per-edit test-runner findings (turn_end, `runtime-turn.ts`).
|
|
70
|
-
// Cache-only like every row here — never relaunches a test run. `cwd` is
|
|
71
|
-
// unused (results already carry absolute file paths).
|
|
72
|
-
id: "test-runner",
|
|
73
|
-
cacheKeys: ["test-runner-findings"],
|
|
74
|
-
adapt: (_cwd, r) => testRunnerFindingsToProjectDiagnostics(r),
|
|
75
|
-
},
|
|
76
|
-
];
|
|
77
23
|
/**
|
|
78
|
-
* #533: which trigger warms each
|
|
79
|
-
*
|
|
80
|
-
*
|
|
24
|
+
* #533: which trigger warms each analyzer, surfaced in the "cold" honesty note
|
|
25
|
+
* so the note is actionable (names what to do), matching the #511/#514 house
|
|
26
|
+
* shape. Keyed by the ids `fetchFreshProjectDiagnostics` (./fresh-fetch.ts)
|
|
27
|
+
* reports in its `cold` list — keep in sync with that module's `ANALYZER_IDS`.
|
|
81
28
|
*/
|
|
82
29
|
const WARM_TRIGGER = {
|
|
83
30
|
knip: "runs at session-start",
|
|
@@ -85,47 +32,11 @@ const WARM_TRIGGER = {
|
|
|
85
32
|
madge: "runs at session-start",
|
|
86
33
|
gitleaks: "runs at session-start (config opt-in), or on any git repo via mode=full (#608)",
|
|
87
34
|
govulncheck: "runs at session-start (Go projects only)",
|
|
35
|
+
opengrep: "runs at session-start",
|
|
88
36
|
trivy: "runs at session-start",
|
|
89
37
|
"dead-code": "runs at session-start (Python projects only)",
|
|
90
|
-
opengrep: "runs at session-start",
|
|
91
38
|
"test-runner": "fires per-edit at turn_end (only after a source file with a discoverable test companion is edited)",
|
|
92
39
|
};
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
* into the private `EXTRACTORS` table. */
|
|
96
|
-
export const PROJECT_DIAGNOSTIC_EXTRACTOR_IDS = EXTRACTORS.map((e) => e.id);
|
|
97
|
-
export function warmTriggerFor(extractorId) {
|
|
98
|
-
return WARM_TRIGGER[extractorId] ?? "runs at session-start";
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Read every registered analyzer's cached result and adapt it to project
|
|
102
|
-
* diagnostics. Returns the merged diagnostics, the ids of the analyzers that
|
|
103
|
-
* actually contributed findings (for the snapshot's `runners` list), and the
|
|
104
|
-
* ids of analyzers with NO cache entry at all yet (`cold`) — distinct from an
|
|
105
|
-
* analyzer that ran and found nothing. Cache-only: no scans.
|
|
106
|
-
*/
|
|
107
|
-
export function extractCachedProjectDiagnostics(cacheManager, cwd) {
|
|
108
|
-
const diagnostics = [];
|
|
109
|
-
const runners = [];
|
|
110
|
-
const cold = [];
|
|
111
|
-
for (const extractor of EXTRACTORS) {
|
|
112
|
-
let data;
|
|
113
|
-
for (const key of extractor.cacheKeys) {
|
|
114
|
-
const entry = cacheManager.readCache(key, cwd);
|
|
115
|
-
if (entry?.data) {
|
|
116
|
-
data = entry.data;
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
if (data === undefined) {
|
|
121
|
-
cold.push(extractor.id);
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
const adapted = extractor.adapt(cwd, data);
|
|
125
|
-
if (adapted.length > 0) {
|
|
126
|
-
diagnostics.push(...adapted);
|
|
127
|
-
runners.push(extractor.id);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return { diagnostics, runners, cold };
|
|
40
|
+
export function warmTriggerFor(analyzerId) {
|
|
41
|
+
return WARM_TRIGGER[analyzerId] ?? "runs at session-start";
|
|
131
42
|
}
|