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
|
@@ -14,17 +14,21 @@ import * as path from "node:path";
|
|
|
14
14
|
import { Type } from "../clients/deps/typebox.js";
|
|
15
15
|
import { anchorsForDiagnostic, applyDispositions, applyWeakDispositions, getDisposition, } from "../clients/diagnostic-dispositions.js";
|
|
16
16
|
import { applyInlineSuppressions } from "../clients/dispatch/inline-suppressions.js";
|
|
17
|
+
import { normalizeRuleId } from "../clients/dispatch/rule-id-normalize.js";
|
|
18
|
+
import { applyRulePolicy, rulePolicyMapFromConfig } from "../clients/dispatch/rule-policy.js";
|
|
19
|
+
import { loadPiLensProjectConfig } from "../clients/project-lens-config.js";
|
|
17
20
|
import { compactRenderResult } from "./render-compact.js";
|
|
18
21
|
import { combineAbortSignals } from "../clients/deadline-utils.js";
|
|
19
22
|
import { getProjectIgnoreMatcher } from "../clients/file-utils.js";
|
|
20
23
|
import { isAtOrAboveHomeDir, normalizeFilePath, } from "../clients/path-utils.js";
|
|
21
24
|
import { getLSPService } from "../clients/lsp/index.js";
|
|
22
25
|
import { primaryServerId } from "../clients/lsp/config.js";
|
|
26
|
+
import { hashDiagnosticContent, } from "../clients/lsp/diagnostic-binding.js";
|
|
23
27
|
import { loadProjectDiagnosticsDeltaReport, loadProjectDiagnosticsSnapshot, PROJECT_DIAGNOSTICS_CACHE_VERSION, reconcileProjectDiagnosticsSnapshot, } from "../clients/project-diagnostics/cache.js";
|
|
24
28
|
import { warmTriggerFor } from "../clients/project-diagnostics/extractors.js";
|
|
25
29
|
import { fetchFreshProjectDiagnostics } from "../clients/project-diagnostics/fresh-fetch.js";
|
|
26
30
|
import { loadBootstrapClients } from "../clients/bootstrap.js";
|
|
27
|
-
import { scanTruncationNotice } from "../clients/lens-engine.js";
|
|
31
|
+
import { generatedSkipNotice, scanTruncationNotice, } from "../clients/lens-engine.js";
|
|
28
32
|
import { scanProjectDiagnostics } from "../clients/project-diagnostics/scanner.js";
|
|
29
33
|
import { getFileDiagnosticSummaries, reconcileScanDiagnostics, reconcileStaleWidgetFiles, } from "../clients/widget-state.js";
|
|
30
34
|
import { convertLspDiagnostics } from "../clients/dispatch/utils/lsp-diagnostics.js";
|
|
@@ -50,6 +54,48 @@ const FULL_SCAN_WALL_CLOCK_MS = (() => {
|
|
|
50
54
|
const raw = Number(process.env.PI_LENS_LENS_DIAGNOSTICS_FULL_TIMEOUT_MS);
|
|
51
55
|
return Number.isFinite(raw) && raw > 0 ? raw : 300_000; // 5 min default
|
|
52
56
|
})();
|
|
57
|
+
// Binding verification is a fallback for result producers that supplied a
|
|
58
|
+
// content hash without an already-computed disk verdict. Keep it one-file-at-a-
|
|
59
|
+
// time and yield periodically: a full scan can contain thousands of results,
|
|
60
|
+
// and a synchronous read loop here would freeze the host even though the LSP
|
|
61
|
+
// work itself was asynchronous (#1198 follow-up).
|
|
62
|
+
const FULL_SCAN_BINDING_YIELD_EVERY = 16;
|
|
63
|
+
async function findFullScanBindingMismatches(results) {
|
|
64
|
+
const mismatched = new WeakSet();
|
|
65
|
+
let sinceYield = 0;
|
|
66
|
+
for (const result of results) {
|
|
67
|
+
if (result.boundToCurrentDisk === false) {
|
|
68
|
+
mismatched.add(result);
|
|
69
|
+
}
|
|
70
|
+
else if (
|
|
71
|
+
// A `true` verdict came from the LSP/cache binding seam and has already
|
|
72
|
+
// compared its fingerprint with disk. Only legacy/partial producers need
|
|
73
|
+
// this fallback read; `unknown` remains unknown when no hash exists.
|
|
74
|
+
result.boundToCurrentDisk !== true &&
|
|
75
|
+
result.contentHash !== undefined) {
|
|
76
|
+
try {
|
|
77
|
+
// Promise-based, sequential reads bound both event-loop occupancy and
|
|
78
|
+
// outstanding file handles. A read failure deliberately leaves the
|
|
79
|
+
// verdict unknown (the historical fail-open behavior).
|
|
80
|
+
const content = await fs.readFile(result.filePath, "utf-8");
|
|
81
|
+
if (hashDiagnosticContent(content) !== result.contentHash) {
|
|
82
|
+
mismatched.add(result);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Unreadable/deleted files cannot disprove the binding. Leave them
|
|
87
|
+
// unknown for the existing caller policy, rather than manufacturing a
|
|
88
|
+
// mismatch or a clean verdict here.
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
sinceYield += 1;
|
|
92
|
+
if (sinceYield >= FULL_SCAN_BINDING_YIELD_EVERY) {
|
|
93
|
+
sinceYield = 0;
|
|
94
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return mismatched;
|
|
98
|
+
}
|
|
53
99
|
export function createLensDiagnosticsTool(cacheManager, getCwd, getLspService = getLSPService,
|
|
54
100
|
// Flush any debounced per-edit dispatches before reading, so files the agent
|
|
55
101
|
// fixed earlier in the turn are re-dispatched and the widget reflects the
|
|
@@ -68,7 +114,11 @@ flushPending = async () => { },
|
|
|
68
114
|
// write apart from a concurrent, genuinely newer per-edit write for the
|
|
69
115
|
// same file. Optional/undefined in tests — an omitted writeIndex always
|
|
70
116
|
// proceeds (see `reconcileScanDiagnostics`'s doc comment).
|
|
71
|
-
nextWriteIndex
|
|
117
|
+
nextWriteIndex,
|
|
118
|
+
// #798/#338: capture host UI methods synchronously from the active tool event.
|
|
119
|
+
// The returned closure is safe for the later async sweep to invoke because it
|
|
120
|
+
// never dereferences the session-guarded ctx.ui getter.
|
|
121
|
+
captureLspStatusRepaint) {
|
|
72
122
|
return {
|
|
73
123
|
name: "lens_diagnostics",
|
|
74
124
|
label: "Project Diagnostics",
|
|
@@ -113,22 +163,25 @@ nextWriteIndex) {
|
|
|
113
163
|
const coldSuffix = details?.coldRunners && details.coldRunners.length > 0
|
|
114
164
|
? ` (${details.coldRunners.length} cold: ${details.coldRunners.join(", ")})`
|
|
115
165
|
: "";
|
|
166
|
+
const failedSuffix = details?.failedAnalyzers && details.failedAnalyzers.length > 0
|
|
167
|
+
? ` (${details.failedAnalyzers.length} failed: ${details.failedAnalyzers.map((item) => item.id).join(", ")})`
|
|
168
|
+
: "";
|
|
116
169
|
if (mode === "delta") {
|
|
117
170
|
const aw = details?.actionableWarnings ?? 0;
|
|
118
171
|
const cq = details?.qualityIssues ?? 0;
|
|
119
172
|
const pd = details?.projectDiagnostics ?? 0;
|
|
120
173
|
if (aw + cq + pd === 0)
|
|
121
|
-
return `lens_diagnostics delta — clean${coldSuffix}`;
|
|
122
|
-
return `lens_diagnostics delta — ${aw} actionable · ${cq} quality · ${pd} project${coldSuffix}`;
|
|
174
|
+
return `lens_diagnostics delta — clean${coldSuffix}${failedSuffix}`;
|
|
175
|
+
return `lens_diagnostics delta — ${aw} actionable · ${cq} quality · ${pd} project${coldSuffix}${failedSuffix}`;
|
|
123
176
|
}
|
|
124
177
|
const b = details?.totalBlocking ?? 0;
|
|
125
178
|
const e = details?.totalErrors ?? 0;
|
|
126
179
|
const w = details?.totalWarnings ?? 0;
|
|
127
180
|
const files = details?.filesWithIssues ?? details?.filesChecked ?? 0;
|
|
128
181
|
if (b + e + w === 0) {
|
|
129
|
-
return `lens_diagnostics ${mode} — clean (${files} files)${coldSuffix}`;
|
|
182
|
+
return `lens_diagnostics ${mode} — clean (${files} files)${coldSuffix}${failedSuffix}`;
|
|
130
183
|
}
|
|
131
|
-
return `lens_diagnostics ${mode} — ${b} blocking · ${e} errors · ${w} warnings (${files} files)${coldSuffix}`;
|
|
184
|
+
return `lens_diagnostics ${mode} — ${b} blocking · ${e} errors · ${w} warnings (${files} files)${coldSuffix}${failedSuffix}`;
|
|
132
185
|
}),
|
|
133
186
|
parameters: Type.Object({
|
|
134
187
|
mode: Type.Optional(Type.String({
|
|
@@ -149,6 +202,9 @@ nextWriteIndex) {
|
|
|
149
202
|
maxLspFiles: Type.Optional(Type.Number({
|
|
150
203
|
description: "mode=full only: cap the number of files routed through the language server for the project-wide LSP sweep. On large projects (e.g. a Next.js app with thousands of source files) the uncapped sweep can take many minutes; set this to bound it. Default is generous (env PI_LENS_LSP_WORKSPACE_MAX_FILES, else 5000).",
|
|
151
204
|
})),
|
|
205
|
+
includeGenerated: Type.Optional(Type.Boolean({
|
|
206
|
+
description: "mode=full refreshRunners=cheap/all only (no effect with refreshRunners=cached/none, since no project scan runs to apply it to): scan WITHOUT the generated/artifact NAME-heuristic filter (lockfiles, gen.ts-style names, generated/ dirs, …). Default false. Use when a scan's 'excluded by generated-name heuristics' notice suggests a real file was skipped.",
|
|
207
|
+
})),
|
|
152
208
|
severity: Type.Optional(Type.String({
|
|
153
209
|
enum: ["error", "warning", "all"],
|
|
154
210
|
description: "Filter by severity (default: all).",
|
|
@@ -176,6 +232,7 @@ nextWriteIndex) {
|
|
|
176
232
|
})),
|
|
177
233
|
}),
|
|
178
234
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
235
|
+
const repaintLspStatus = captureLspStatusRepaint?.(ctx);
|
|
179
236
|
const mode = params.mode ?? "delta";
|
|
180
237
|
const severity = params.severity ?? "all";
|
|
181
238
|
const refreshRunners = params.refreshRunners;
|
|
@@ -184,6 +241,7 @@ nextWriteIndex) {
|
|
|
184
241
|
: undefined;
|
|
185
242
|
const maxProjectFiles = parsePositiveInt(params.maxProjectFiles);
|
|
186
243
|
const maxLspFiles = parsePositiveInt(params.maxLspFiles);
|
|
244
|
+
const includeGenerated = params.includeGenerated === true;
|
|
187
245
|
const cwd = ctx.cwd ?? getCwd();
|
|
188
246
|
let pathsScope;
|
|
189
247
|
try {
|
|
@@ -228,9 +286,11 @@ nextWriteIndex) {
|
|
|
228
286
|
refreshRunners,
|
|
229
287
|
maxProjectFiles,
|
|
230
288
|
maxLspFiles,
|
|
289
|
+
includeGenerated,
|
|
231
290
|
signal: fullSignal,
|
|
232
291
|
wallClockMs: FULL_SCAN_WALL_CLOCK_MS,
|
|
233
292
|
onProgress,
|
|
293
|
+
onServerReady: repaintLspStatus,
|
|
234
294
|
pathsScope,
|
|
235
295
|
nextWriteIndex,
|
|
236
296
|
});
|
|
@@ -272,23 +332,46 @@ function appendProjectDiagnosticsDeltaLines(lines, cwd, report, severity, includ
|
|
|
272
332
|
* delta mode re-serves them. Anchored via `projectDiagnosticToWidget` so the
|
|
273
333
|
* (tool, rule) an agent's mark binds to matches mode=full's own project-runner
|
|
274
334
|
* filter (`applyInlineSuppressionsToSummaries`), keeping the two paths in
|
|
275
|
-
* agreement. Weak-anchored → no file read.
|
|
335
|
+
* agreement. Weak-anchored → no file read. Also applies the project's
|
|
336
|
+
* `.pi-lens.json` `rules.<id>.disable`/`select` policy so a project's policy
|
|
337
|
+
* overlays the same delta report cache; the cache is otherwise insensitive to
|
|
338
|
+
* a project-config edit (the per-edit path picks it up immediately, but a
|
|
339
|
+
* non-dispatch tool query would replay the pre-edit state).
|
|
276
340
|
*/
|
|
277
|
-
function filterDeltaReportDispositions(report, cwd) {
|
|
341
|
+
function filterDeltaReportDispositions(report, cwd, policyMap) {
|
|
278
342
|
if (!report?.diagnostics.length)
|
|
279
343
|
return report;
|
|
280
344
|
const kept = report.diagnostics.filter((d) => applyWeakDispositions([projectDiagnosticToWidget(d)], cwd, d.filePath)
|
|
281
345
|
.length === 1);
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
346
|
+
const policyKept = applyRulePolicy(kept, policyMap);
|
|
347
|
+
if (policyKept.length === report.diagnostics.length)
|
|
348
|
+
return report;
|
|
349
|
+
return { ...report, diagnostics: policyKept };
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Load the rule-policy map from a project's `.pi-lens.json` — same source the
|
|
353
|
+
* per-edit dispatch path uses, so a project's policy applies consistently
|
|
354
|
+
* across every output surface. `loadPiLensProjectConfig` is mtime-cached, and
|
|
355
|
+
* the map is filtered to entries that actually have a `disable`/`select` list
|
|
356
|
+
* (thresholds are handled elsewhere), so the common case returns undefined and
|
|
357
|
+
* the filter step is skipped outright.
|
|
358
|
+
*/
|
|
359
|
+
function loadProjectRulePolicyMap(cwd) {
|
|
360
|
+
return rulePolicyMapFromConfig(loadPiLensProjectConfig(cwd).rules);
|
|
285
361
|
}
|
|
286
362
|
function formatDeltaMode(cacheManager, cwd, severity, pathsScope) {
|
|
287
363
|
const actionableEntry = cacheManager.readCache("actionable-warnings", cwd);
|
|
288
364
|
const qualityEntry = cacheManager.readCache("code-quality-warnings", cwd);
|
|
289
365
|
const actionable = actionableEntry?.data;
|
|
290
366
|
const quality = qualityEntry?.data;
|
|
291
|
-
|
|
367
|
+
// Project rule policy (`.pi-lens.json` `rules.<id>.disable` /
|
|
368
|
+
// `rules.<id>.select`) — output-only filtering applied consistently across
|
|
369
|
+
// every mode. The cache-only delta/all paths would otherwise leak project-
|
|
370
|
+
// policy oversight (the per-edit dispatch already filters, but the cache
|
|
371
|
+
// snapshots predate the user's project config edit and would replay
|
|
372
|
+
// filtered findings). Weak-anchored like weak disposition — zero I/O.
|
|
373
|
+
const policyMap = loadProjectRulePolicyMap(cwd);
|
|
374
|
+
const projectDelta = filterDeltaReportDispositions(loadProjectDiagnosticsDeltaReport(cwd), cwd, policyMap);
|
|
292
375
|
const ignoreFile = createCurrentIgnoreFilter(cwd);
|
|
293
376
|
const includeFile = (filePath) => ignoreFile(filePath) && (!pathsScope || pathsScope.includeFile(filePath));
|
|
294
377
|
// #755: delta re-serves the actionable/quality caches verbatim, but those
|
|
@@ -296,20 +379,15 @@ function formatDeltaMode(cacheManager, cwd, severity, pathsScope) {
|
|
|
296
379
|
// the weak disposition filter (suppress/defer) here so a mark converges
|
|
297
380
|
// immediately, not only on the next edit. Weak-anchored → zero file I/O, so
|
|
298
381
|
// this stays "instant" (see applyWeakDispositions).
|
|
299
|
-
const
|
|
382
|
+
const visibleWarningFiles = (files) => (files ?? [])
|
|
300
383
|
.filter((file) => includeFile(file.filePath))
|
|
301
384
|
.map((file) => ({
|
|
302
385
|
filePath: file.filePath,
|
|
303
|
-
warnings: applyWeakDispositions(file.warnings ?? [], cwd, file.filePath),
|
|
304
|
-
}))
|
|
305
|
-
.filter((file) => file.warnings.length > 0);
|
|
306
|
-
const qualityFiles = (quality?.files ?? [])
|
|
307
|
-
.filter((file) => includeFile(file.filePath))
|
|
308
|
-
.map((file) => ({
|
|
309
|
-
filePath: file.filePath,
|
|
310
|
-
warnings: applyWeakDispositions(file.warnings ?? [], cwd, file.filePath),
|
|
386
|
+
warnings: applyRulePolicy(applyWeakDispositions(file.warnings ?? [], cwd, file.filePath), policyMap),
|
|
311
387
|
}))
|
|
312
388
|
.filter((file) => file.warnings.length > 0);
|
|
389
|
+
const actionableFiles = visibleWarningFiles(actionable?.files);
|
|
390
|
+
const qualityFiles = visibleWarningFiles(quality?.files);
|
|
313
391
|
const lines = [];
|
|
314
392
|
// Fixable warnings from actionable-warnings
|
|
315
393
|
if (severity !== "error") {
|
|
@@ -344,7 +422,7 @@ function formatDeltaMode(cacheManager, cwd, severity, pathsScope) {
|
|
|
344
422
|
.filter((f) => includeFile(f.filePath))
|
|
345
423
|
.map((f) => ({
|
|
346
424
|
...f,
|
|
347
|
-
diagnostics: applyWeakDispositions(f.diagnostics, cwd, f.filePath),
|
|
425
|
+
diagnostics: applyRulePolicy(applyWeakDispositions(f.diagnostics, cwd, f.filePath), policyMap),
|
|
348
426
|
}))
|
|
349
427
|
.filter((f) => f.diagnostics.length > 0);
|
|
350
428
|
const carriedIssues = carried.reduce((n, f) => n + f.diagnostics.length, 0);
|
|
@@ -475,6 +553,20 @@ function filterProjectDiagnosticsDeltaReport(report, includeFile) {
|
|
|
475
553
|
diagnostics: report.diagnostics.filter((d) => includeFile(d.filePath)),
|
|
476
554
|
};
|
|
477
555
|
}
|
|
556
|
+
/**
|
|
557
|
+
* Apply the project rule policy to a project-diagnostics snapshot/delta
|
|
558
|
+
* report's `diagnostics` list, in place of the collection's shape. Used by
|
|
559
|
+
* `formatFullMode` so `projectSnapshot.diagnostics.length` /
|
|
560
|
+
* `projectDelta.diagnostics.length` — which feed both the merged summaries
|
|
561
|
+
* and the `details.projectDiagnostics`/`details.projectDiagnosticsDelta`
|
|
562
|
+
* counts — already reflect the policy instead of reporting a pre-policy
|
|
563
|
+
* count that disagrees with the rendered text.
|
|
564
|
+
*/
|
|
565
|
+
function applyProjectRulePolicy(value, policyMap) {
|
|
566
|
+
if (!value)
|
|
567
|
+
return undefined;
|
|
568
|
+
return { ...value, diagnostics: applyRulePolicy(value.diagnostics, policyMap) };
|
|
569
|
+
}
|
|
478
570
|
/** A diagnostic counts as error-like when it blocks or has error severity. */
|
|
479
571
|
function isErrorLike(d) {
|
|
480
572
|
return d.semantic === "blocking" || d.severity === "error";
|
|
@@ -544,12 +636,12 @@ function projectDiagnosticToWidget(diagnostic) {
|
|
|
544
636
|
// violation must collapse to one finding in mode=full's merge regardless of which
|
|
545
637
|
// engine produced it. Strip the `ast-grep:` source prefix and the `-js` language
|
|
546
638
|
// suffix (the napi runner already treats `<id>` / `<id>-js` as one rule) so the
|
|
547
|
-
// LSP sweep and the napi scan don't double-report the same line.
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
639
|
+
// LSP sweep and the napi scan don't double-report the same line. The
|
|
640
|
+
// normalization itself now lives in `clients/dispatch/rule-id-normalize.ts` so the
|
|
641
|
+
// inline suppression parser, the project rule-policy matcher, and dedup all apply
|
|
642
|
+
// the same canonical form.
|
|
551
643
|
function diagnosticDedupKey(filePath, diagnostic) {
|
|
552
|
-
const ruleId =
|
|
644
|
+
const ruleId = normalizeRuleId(diagnostic.rule ?? diagnostic.tool ?? "");
|
|
553
645
|
return [path.resolve(filePath), diagnostic.line ?? "?", ruleId].join(":");
|
|
554
646
|
}
|
|
555
647
|
function summarizeDiagnostics(filePath, diagnostics, hasFinalSnapshot) {
|
|
@@ -692,8 +784,14 @@ function mergeDiagnosticsWithWidgetSummaries(widgetSummaries, lspResults, projec
|
|
|
692
784
|
* a read failure is fail-safe (keep the diagnostics rather than hide a finding on
|
|
693
785
|
* an I/O error). Re-summarizes so the blocking/error/warning counts reflect the
|
|
694
786
|
* suppression.
|
|
787
|
+
*
|
|
788
|
+
* Also applies the project's `.pi-lens.json` `rules.<id>.disable`/`select`
|
|
789
|
+
* policy AFTER inline suppression / disposition so the same set of findings
|
|
790
|
+
* the per-edit `dispatcher.ts` filters is what's rendered here. The policy
|
|
791
|
+
* map is passed in (computed once per `formatFullMode` call) rather than
|
|
792
|
+
* re-deriving per file — see `loadProjectRulePolicyMap(cwd)`.
|
|
695
793
|
*/
|
|
696
|
-
async function applyInlineSuppressionsToSummaries(summaries, cwd) {
|
|
794
|
+
async function applyInlineSuppressionsToSummaries(summaries, cwd, policyMap) {
|
|
697
795
|
return Promise.all(summaries.map(async (summary) => {
|
|
698
796
|
if (!summary.diagnostics.length)
|
|
699
797
|
return summary;
|
|
@@ -702,7 +800,13 @@ async function applyInlineSuppressionsToSummaries(summaries, cwd) {
|
|
|
702
800
|
content = await fs.readFile(summary.filePath, "utf8");
|
|
703
801
|
}
|
|
704
802
|
catch {
|
|
705
|
-
|
|
803
|
+
// Inline suppression/disposition remains fail-open when the file cannot
|
|
804
|
+
// be read, but project policy does not depend on content and must still
|
|
805
|
+
// be applied so a fresh full-mode result cannot bypass `disable`/`select`.
|
|
806
|
+
const policyKept = applyRulePolicy(summary.diagnostics, policyMap);
|
|
807
|
+
return policyKept.length === summary.diagnostics.length
|
|
808
|
+
? summary
|
|
809
|
+
: summarizeDiagnostics(summary.filePath, policyKept, summary.hasFinalSnapshot);
|
|
706
810
|
}
|
|
707
811
|
const inlineKept = applyInlineSuppressions(summary.diagnostics, content);
|
|
708
812
|
// #690: same false-positive/suppress/defer disposition filter the
|
|
@@ -710,11 +814,16 @@ async function applyInlineSuppressionsToSummaries(summaries, cwd) {
|
|
|
710
814
|
// diagnostics from a fresh LSP sweep/project scan that never went
|
|
711
815
|
// through that path, so without this a disposed finding reappears here.
|
|
712
816
|
const kept = applyDispositions(inlineKept, cwd, summary.filePath, content);
|
|
817
|
+
// Project rule policy (`.pi-lens.json` `rules.<id>.disable`/`select`).
|
|
818
|
+
// Applied after inline suppression / disposition so the policy's
|
|
819
|
+
// output-only filtering affects the same surface the per-edit path
|
|
820
|
+
// produces (no double-counting, no leftover policy-rejected findings).
|
|
821
|
+
const policyKept = applyRulePolicy(kept, policyMap);
|
|
713
822
|
// Tag `flagged` diagnostics for the render loop (formatAllMode). Content
|
|
714
823
|
// is already in hand here (unlike mode=all/delta's cache-only path), so
|
|
715
824
|
// this is the one place the tag can be computed without adding I/O to
|
|
716
825
|
// the "instant" modes.
|
|
717
|
-
for (const d of
|
|
826
|
+
for (const d of policyKept) {
|
|
718
827
|
// flagged is weak-anchored (module doc, diagnostic-dispositions.ts) so
|
|
719
828
|
// the tag survives incidental edits to the flagged line itself.
|
|
720
829
|
const { weak } = anchorsForDiagnostic(cwd, summary.filePath, d, content);
|
|
@@ -722,9 +831,9 @@ async function applyInlineSuppressionsToSummaries(summaries, cwd) {
|
|
|
722
831
|
d.flagged = true;
|
|
723
832
|
}
|
|
724
833
|
}
|
|
725
|
-
if (
|
|
834
|
+
if (policyKept.length === summary.diagnostics.length)
|
|
726
835
|
return summary;
|
|
727
|
-
return summarizeDiagnostics(summary.filePath,
|
|
836
|
+
return summarizeDiagnostics(summary.filePath, policyKept, summary.hasFinalSnapshot);
|
|
728
837
|
}));
|
|
729
838
|
}
|
|
730
839
|
function shouldUseCachedProjectDiagnostics(value) {
|
|
@@ -772,6 +881,7 @@ async function getProjectDiagnosticsSnapshotForFullMode(cwd, options) {
|
|
|
772
881
|
maxFiles: options.maxProjectFiles,
|
|
773
882
|
signal: options.signal,
|
|
774
883
|
files: options.files,
|
|
884
|
+
includeGenerated: options.includeGenerated,
|
|
775
885
|
});
|
|
776
886
|
}
|
|
777
887
|
if (shouldUseCachedProjectDiagnostics(options.refreshRunners)) {
|
|
@@ -830,6 +940,7 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
830
940
|
diagnostics: [],
|
|
831
941
|
runners: [],
|
|
832
942
|
cold: [],
|
|
943
|
+
failed: [],
|
|
833
944
|
timings: {},
|
|
834
945
|
});
|
|
835
946
|
const [rawLspResults, rawProjectSnapshot, extracted] = await Promise.all([
|
|
@@ -837,6 +948,8 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
837
948
|
maxFiles: options.maxLspFiles,
|
|
838
949
|
signal,
|
|
839
950
|
onProgress: options.onProgress,
|
|
951
|
+
onServerReady: options.onServerReady,
|
|
952
|
+
nextWriteIndex: options.nextWriteIndex,
|
|
840
953
|
files: explicitFiles,
|
|
841
954
|
}),
|
|
842
955
|
getProjectDiagnosticsSnapshotForFullMode(cwd, {
|
|
@@ -847,6 +960,12 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
847
960
|
]);
|
|
848
961
|
const aborted = signal?.aborted ?? false;
|
|
849
962
|
const lspResults = rawLspResults.filter((result) => includeFile(result.filePath));
|
|
963
|
+
// A result bound to a different document must not replace the current
|
|
964
|
+
// widget state, even when it contains real diagnostics. Pull results may carry
|
|
965
|
+
// a content hash without the push binding verdict, so verify that hash against
|
|
966
|
+
// disk through the bounded async fallback above; unreadable files remain
|
|
967
|
+
// unknown and retain the existing fail-open policy.
|
|
968
|
+
const mismatchedLspResults = await findFullScanBindingMismatches(lspResults);
|
|
850
969
|
// #630: `timedOut`/`error` means the per-file check never actually
|
|
851
970
|
// completed or was inconclusive (#570's `touchFile().inconclusive`, or
|
|
852
971
|
// this sweep's own outer per-file deadline/throw — see
|
|
@@ -858,8 +977,12 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
858
977
|
// the unfiltered `lspResults` in, so a timed-out file's placeholder `[]`
|
|
859
978
|
// read as "0 diagnostics" — false-clean — in the rendered/`details`
|
|
860
979
|
// output (#630).
|
|
861
|
-
const confirmedLspResults = lspResults.filter((result) => !result.timedOut &&
|
|
862
|
-
|
|
980
|
+
const confirmedLspResults = lspResults.filter((result) => !result.timedOut &&
|
|
981
|
+
!result.error &&
|
|
982
|
+
!mismatchedLspResults.has(result));
|
|
983
|
+
const unconfirmedLspResults = lspResults.filter((result) => result.timedOut ||
|
|
984
|
+
result.error ||
|
|
985
|
+
mismatchedLspResults.has(result));
|
|
863
986
|
// #571: reconcile this scan's fresh, CONFIRMED per-file results into the
|
|
864
987
|
// footer cache. A footer write is never allowed to fail the tool call, so
|
|
865
988
|
// any unexpected throw is swallowed.
|
|
@@ -880,12 +1003,33 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
880
1003
|
// (`applyAuxiliarySuppressions`), so an empty content string here is a
|
|
881
1004
|
// safe no-op re-check rather than a behavior gap.
|
|
882
1005
|
const retagged = retagAuxiliaryDiagnostics(diagnostics, result.diagnostics, "", { cwd, fileRole: detectFileRole(result.filePath) });
|
|
883
|
-
reconcileScanDiagnostics(result.filePath, retagged, true,
|
|
1006
|
+
reconcileScanDiagnostics(result.filePath, retagged, true,
|
|
1007
|
+
// The service reserves this token when the file enters the scan. The
|
|
1008
|
+
// fallback preserves compatibility with older test doubles/services.
|
|
1009
|
+
result.writeIndex ?? nextWriteIndex?.(),
|
|
1010
|
+
// #1093: `observedAt` is set only when this result was served from
|
|
1011
|
+
// the workspace-diagnostics cache (a replay of an older scan) —
|
|
1012
|
+
// stamp `touchedAt` with when it was scanned, not now(), so a
|
|
1013
|
+
// mode=full that only re-serves the cache can't keep a resolved
|
|
1014
|
+
// finding on screen by re-arming the mtime gate. Undefined for
|
|
1015
|
+
// freshly-touched results (observed now).
|
|
1016
|
+
result.observedAt);
|
|
884
1017
|
}
|
|
885
1018
|
catch {
|
|
886
1019
|
// Never let a footer-reconciliation hiccup fail the scan itself.
|
|
887
1020
|
}
|
|
888
1021
|
}
|
|
1022
|
+
// Project rule policy (`.pi-lens.json` `rules.<id>.disable`/`select`) —
|
|
1023
|
+
// loaded once per mode=full call, BEFORE `projectSnapshot`/`projectDelta`
|
|
1024
|
+
// are built below, so `.diagnostics.length` on both (which feeds
|
|
1025
|
+
// `details.projectDiagnostics`/`details.projectDiagnosticsDelta` as well
|
|
1026
|
+
// as the merge into `summaries`) is the POST-policy count. Threaded into
|
|
1027
|
+
// `applyInlineSuppressionsToSummaries` too (idempotent re-apply there) so
|
|
1028
|
+
// the merged summaries honor the same policy the per-edit dispatch path
|
|
1029
|
+
// applies. A project config read failure is treated as "no policy"
|
|
1030
|
+
// (defensive, matches the `applyInlineSuppressionsToSummaries` read-error
|
|
1031
|
+
// discipline).
|
|
1032
|
+
const policyMap = loadProjectRulePolicyMap(cwd);
|
|
889
1033
|
const scannedSnapshot = filterProjectDiagnosticsSnapshot(rawProjectSnapshot, includeFile);
|
|
890
1034
|
// Heavyweight-analyzer findings (jscpd, madge, gitleaks, knip, govulncheck,
|
|
891
1035
|
// trivy, dead-code) — a FRESH run of each (#585), not a cache-only read:
|
|
@@ -900,14 +1044,14 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
900
1044
|
// computed above, in the SAME `Promise.all` as the LSP sweep (#613) — only
|
|
901
1045
|
// when the caller opted into project-runner state (otherwise it's the
|
|
902
1046
|
// `Promise.resolve({...})` stub from `analyzersPromise` above).
|
|
903
|
-
const projectSnapshot = foldExtraDiagnosticsIntoSnapshot(scannedSnapshot, extracted.diagnostics.filter((d) => includeFile(d.filePath)), extracted.runners, cwd);
|
|
904
|
-
const projectDelta = filterProjectDiagnosticsDeltaReport(loadProjectDiagnosticsDeltaReport(cwd), includeFile);
|
|
1047
|
+
const projectSnapshot = applyProjectRulePolicy(foldExtraDiagnosticsIntoSnapshot(scannedSnapshot, extracted.diagnostics.filter((d) => includeFile(d.filePath)), extracted.runners, cwd), policyMap);
|
|
1048
|
+
const projectDelta = applyProjectRulePolicy(filterProjectDiagnosticsDeltaReport(loadProjectDiagnosticsDeltaReport(cwd), includeFile), policyMap);
|
|
905
1049
|
// #630: only the CONFIRMED LSP results contribute diagnostics to the merge
|
|
906
1050
|
// — an unconfirmed (timed-out/errored) file's placeholder `[]` must not be
|
|
907
1051
|
// read as "0 issues, clean" via its LSP contribution. It can still
|
|
908
1052
|
// legitimately show diagnostics from widgetSummaries/project-runner state
|
|
909
1053
|
// below if those independently have entries for it.
|
|
910
|
-
const summaries = await applyInlineSuppressionsToSummaries(mergeDiagnosticsWithWidgetSummaries(getFileDiagnosticSummaries().filter((summary) => includeFile(summary.filePath)), confirmedLspResults, projectSnapshot, projectDelta), cwd);
|
|
1054
|
+
const summaries = await applyInlineSuppressionsToSummaries(mergeDiagnosticsWithWidgetSummaries(getFileDiagnosticSummaries().filter((summary) => includeFile(summary.filePath)), confirmedLspResults, projectSnapshot, projectDelta), cwd, policyMap);
|
|
911
1055
|
const result = formatAllMode(cwd, severity, summaries, {
|
|
912
1056
|
mode: "full",
|
|
913
1057
|
lspFilesChecked: rawLspResults.length,
|
|
@@ -1001,6 +1145,30 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1001
1145
|
.map((id) => `${id} — ${warmTriggerFor(id)}`)
|
|
1002
1146
|
.join(", ")}. These analyzers have not contributed to this result — absence of their findings is NOT a clean verdict.`
|
|
1003
1147
|
: "";
|
|
1148
|
+
// #1004: unlike every other analyzer above (knip/jscpd/madge/gitleaks/
|
|
1149
|
+
// govulncheck/opengrep/trivy/dead-code all run a FRESH whole-project scan
|
|
1150
|
+
// per the fresh-fetch.ts header, so "clean" there really does mean "the
|
|
1151
|
+
// whole project was checked this call"), test-runner has no whole-project
|
|
1152
|
+
// run to trigger: its cache only ever reflects the (targeted,
|
|
1153
|
+
// cascade-aware) test file(s) touched by the MOST RECENT edit's turn_end
|
|
1154
|
+
// fire (`runtime-turn.ts`). Folding it into `runners`/`diagnostics` with no
|
|
1155
|
+
// distinguishing note — as opposed to when it's cold, which already gets
|
|
1156
|
+
// `coldNote` above — would let a caller mistake "test-runner ran
|
|
1157
|
+
// project-wide and is clean" for "it only ever saw the 2 files edited an
|
|
1158
|
+
// hour ago", exactly the false-empty/false-confidence #533 this whole
|
|
1159
|
+
// module exists to prevent. Fires whenever test-runner CONTRIBUTED
|
|
1160
|
+
// (`runners`), independent of whether it also has cold/failed entries this
|
|
1161
|
+
// call — a caller needs this caveat on every mode=full response where
|
|
1162
|
+
// test-runner findings are present, not only some.
|
|
1163
|
+
const testRunnerEditScopedNote = extracted.runners.includes("test-runner")
|
|
1164
|
+
? `\n\ntest-runner coverage is edit-scoped, NOT a full-project run: its findings only reflect the test file(s) touched by the most recent edit's turn_end fire, not a fresh whole-project test suite run (mode=full never re-runs the suite — see fresh-fetch.ts). Absence of test-runner findings elsewhere in the project is NOT a clean verdict, and presence here does not mean "just checked" — it may be from several turns ago.`
|
|
1165
|
+
: "";
|
|
1166
|
+
const failedAnalyzers = extracted.failed ?? [];
|
|
1167
|
+
const failedNote = failedAnalyzers.length > 0
|
|
1168
|
+
? `\n\nfailed (ran, but no trustworthy result): ${failedAnalyzers
|
|
1169
|
+
.map(({ id, summary }) => `${id} — ${summary}`)
|
|
1170
|
+
.join(", ")}. These analyzers were not cached and will be retried; absence of their findings is NOT a clean verdict.`
|
|
1171
|
+
: "";
|
|
1004
1172
|
// #747/#250: the cheap project-diagnostics scan (scanProjectDiagnostics) and
|
|
1005
1173
|
// the LSP workspace sweep (collectWorkspaceDiagnosticFiles) both refuse to
|
|
1006
1174
|
// WALK from a cwd at/above $HOME — from there, walking would enumerate every
|
|
@@ -1022,6 +1190,14 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1022
1190
|
const scanTruncatedNote = scanTruncatedNoticeText
|
|
1023
1191
|
? `\n\n${scanTruncatedNoticeText}`
|
|
1024
1192
|
: "";
|
|
1193
|
+
// #1107 phase 2: same "reached the seam, nothing rendered it" gap as #784
|
|
1194
|
+
// above, for the generated-name/dir skip counters.
|
|
1195
|
+
const generatedSkipNoticeText = projectSnapshot
|
|
1196
|
+
? generatedSkipNotice(projectSnapshot)
|
|
1197
|
+
: undefined;
|
|
1198
|
+
const generatedSkipNote = generatedSkipNoticeText
|
|
1199
|
+
? `\n\n${generatedSkipNoticeText}`
|
|
1200
|
+
: "";
|
|
1025
1201
|
const abortedNote = abortedIds.size > 0
|
|
1026
1202
|
? `\n\nstopped mid-scan (still running in the background, not reflected in this result): ${[
|
|
1027
1203
|
...abortedIds,
|
|
@@ -1047,6 +1223,7 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1047
1223
|
details: {
|
|
1048
1224
|
...result.details,
|
|
1049
1225
|
coldRunners: extracted.cold,
|
|
1226
|
+
failedAnalyzers,
|
|
1050
1227
|
analyzerTimingsMs: extracted.timings,
|
|
1051
1228
|
analyzersAborted: extracted.aborted ?? false,
|
|
1052
1229
|
analyzersAbortedIds: extracted.abortedIds ?? [],
|
|
@@ -1074,6 +1251,19 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1074
1251
|
// #784: lets a caller check "was the cheap project scan truncated"
|
|
1075
1252
|
// without parsing the text note.
|
|
1076
1253
|
projectScanTruncated: projectSnapshot?.scanTruncated ?? false,
|
|
1254
|
+
// #1107 phase 2: same machine-readable convention, for the
|
|
1255
|
+
// generated-name/dir skip counters.
|
|
1256
|
+
...(projectSnapshot?.generatedFileSkips
|
|
1257
|
+
? { projectGeneratedFileSkips: projectSnapshot.generatedFileSkips }
|
|
1258
|
+
: {}),
|
|
1259
|
+
...(projectSnapshot?.generatedNameOnlySkips
|
|
1260
|
+
? {
|
|
1261
|
+
projectGeneratedNameOnlySkips: projectSnapshot.generatedNameOnlySkips,
|
|
1262
|
+
}
|
|
1263
|
+
: {}),
|
|
1264
|
+
...(projectSnapshot?.generatedDirSkips
|
|
1265
|
+
? { projectGeneratedDirSkips: projectSnapshot.generatedDirSkips }
|
|
1266
|
+
: {}),
|
|
1077
1267
|
},
|
|
1078
1268
|
};
|
|
1079
1269
|
// Stopped mid-scan: the results above are whatever completed before the abort.
|
|
@@ -1098,8 +1288,11 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1098
1288
|
unconfirmedLspNote +
|
|
1099
1289
|
lspPrimaryVsAuxiliaryNote +
|
|
1100
1290
|
coldNote +
|
|
1291
|
+
testRunnerEditScopedNote +
|
|
1292
|
+
failedNote +
|
|
1101
1293
|
walkUnsafeRootNote +
|
|
1102
1294
|
scanTruncatedNote +
|
|
1295
|
+
generatedSkipNote +
|
|
1103
1296
|
abortedNote +
|
|
1104
1297
|
freshNote +
|
|
1105
1298
|
missingNote,
|
|
@@ -1110,8 +1303,11 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1110
1303
|
}
|
|
1111
1304
|
if (missingNote ||
|
|
1112
1305
|
coldNote ||
|
|
1306
|
+
testRunnerEditScopedNote ||
|
|
1307
|
+
failedNote ||
|
|
1113
1308
|
walkUnsafeRootNote ||
|
|
1114
1309
|
scanTruncatedNote ||
|
|
1310
|
+
generatedSkipNote ||
|
|
1115
1311
|
abortedNote ||
|
|
1116
1312
|
freshNote ||
|
|
1117
1313
|
unconfirmedLspNote ||
|
|
@@ -1124,8 +1320,11 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1124
1320
|
unconfirmedLspNote +
|
|
1125
1321
|
lspPrimaryVsAuxiliaryNote +
|
|
1126
1322
|
coldNote +
|
|
1323
|
+
testRunnerEditScopedNote +
|
|
1324
|
+
failedNote +
|
|
1127
1325
|
walkUnsafeRootNote +
|
|
1128
1326
|
scanTruncatedNote +
|
|
1327
|
+
generatedSkipNote +
|
|
1129
1328
|
abortedNote +
|
|
1130
1329
|
freshNote +
|
|
1131
1330
|
missingNote,
|
|
@@ -1155,13 +1354,20 @@ function formatAllMode(cwd, severity, summaries = getFileDiagnosticSummaries(),
|
|
|
1155
1354
|
// post-dispatch lens_diagnostic_mark (suppress/defer) would otherwise still
|
|
1156
1355
|
// show here. Re-apply the weak filter (zero I/O) for the cache-only path and
|
|
1157
1356
|
// re-summarize so blocking/error/warning counts reflect the drop.
|
|
1357
|
+
// Same project rule policy (`.pi-lens.json` `rules.<id>.disable`/`select`)
|
|
1358
|
+
// overlays both modes: mode=full re-applies it after inline suppression /
|
|
1359
|
+
// disposition (zero I/O), mode=all applies it here on the cache-only path.
|
|
1360
|
+
// The full path's policyMover is loaded below in `applyInlineSuppressionsToSummaries`.
|
|
1361
|
+
const policyMap = loadProjectRulePolicyMap(cwd);
|
|
1158
1362
|
const dispositioned = isFullMode
|
|
1159
1363
|
? summaries
|
|
1160
1364
|
: summaries.map((s) => {
|
|
1161
1365
|
const kept = applyWeakDispositions(s.diagnostics ?? [], cwd, s.filePath);
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1366
|
+
const policyKept = applyRulePolicy(kept, policyMap);
|
|
1367
|
+
if (policyKept.length === kept.length &&
|
|
1368
|
+
kept.length === (s.diagnostics?.length ?? 0))
|
|
1369
|
+
return s;
|
|
1370
|
+
return summarizeDiagnostics(s.filePath, policyKept, s.hasFinalSnapshot);
|
|
1165
1371
|
});
|
|
1166
1372
|
const visibleSummaries = dispositioned.filter((s) => includeFile(s.filePath));
|
|
1167
1373
|
// Filter to files with actual issues
|