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
|
@@ -57,14 +57,34 @@
|
|
|
57
57
|
* patterns apply to files inside that package, in addition to (and with
|
|
58
58
|
* higher precedence than) the root config's `ignore` patterns.
|
|
59
59
|
*/
|
|
60
|
+
import { logExtension } from "./extension-log.js";
|
|
61
|
+
import { notifyUserDegradation } from "./user-notify.js";
|
|
60
62
|
import * as fs from "node:fs";
|
|
61
63
|
import * as os from "node:os";
|
|
62
64
|
import * as path from "node:path";
|
|
63
|
-
import { parseEnabledShape } from "./config-enabled-shape.js";
|
|
64
65
|
import { toPositiveFinite } from "./env-utils.js";
|
|
66
|
+
import { assignFlagConfigSection, flagConfigSectionKeys, GLOBAL_NON_FLAG_CONFIG_SECTIONS, LENS_FLAGS, PROJECT_FOREIGN_CONFIG_NAMESPACES, PROJECT_SCOPED_LENS_FLAGS, readFlagConfigValue, } from "./lens-flag-registry.js";
|
|
65
67
|
import { isAtOrAboveHomeDir, walkUpDirs } from "./path-utils.js";
|
|
66
68
|
import { findPiLensConfigMarkerInDir } from "./workspace-topology.js";
|
|
67
69
|
const PROJECT_CONFIG_BASENAMES = [".pi-lens.json", "pi-lens.json"];
|
|
70
|
+
/**
|
|
71
|
+
* The project loader's OWN recognized top-level keys — pi-lens-native sections,
|
|
72
|
+
* whether parsed here into typed fields (`ignore`, `rules`, `maxProjectFiles`,
|
|
73
|
+
* `reviewGraph`) or read off `PiLensProjectConfig.raw` by another pi-lens
|
|
74
|
+
* consumer (`trivy`, read via `.raw` in `trivy-client.ts`). The project-scoped
|
|
75
|
+
* flag sections (`format`, `autofix`, `actionableWarnings`) are NOT listed here;
|
|
76
|
+
* they are derived from `PROJECT_SCOPED_LENS_FLAGS` so the registry stays the
|
|
77
|
+
* single source of truth (#883). Foreign (non-pi-lens) namespaces the shared
|
|
78
|
+
* file also carries live in `PROJECT_FOREIGN_CONFIG_NAMESPACES` beside the
|
|
79
|
+
* registry.
|
|
80
|
+
*/
|
|
81
|
+
const PROJECT_OWN_CONFIG_KEYS = [
|
|
82
|
+
"ignore",
|
|
83
|
+
"rules",
|
|
84
|
+
"maxProjectFiles",
|
|
85
|
+
"reviewGraph",
|
|
86
|
+
"trivy",
|
|
87
|
+
];
|
|
68
88
|
/** Clamp bounds for `reviewGraph.maxFiles` — see the field's doc comment above. */
|
|
69
89
|
const REVIEW_GRAPH_MAX_FILES_MIN = 100;
|
|
70
90
|
const REVIEW_GRAPH_MAX_FILES_MAX = 20_000;
|
|
@@ -89,11 +109,17 @@ export function loadPiLensProjectConfig(startDir, preloadedInfo = findPiLensProj
|
|
|
89
109
|
if (!configInfo)
|
|
90
110
|
return EMPTY_PROJECT_CONFIG;
|
|
91
111
|
const cached = configCache.get(configInfo.path);
|
|
92
|
-
if (cached &&
|
|
112
|
+
if (cached &&
|
|
113
|
+
cached.mtimeMs === configInfo.mtimeMs &&
|
|
114
|
+
cached.size === configInfo.size) {
|
|
93
115
|
return cached.config;
|
|
94
116
|
}
|
|
95
117
|
const config = parseConfigFile(configInfo.path);
|
|
96
|
-
configCache.set(configInfo.path, {
|
|
118
|
+
configCache.set(configInfo.path, {
|
|
119
|
+
mtimeMs: configInfo.mtimeMs,
|
|
120
|
+
size: configInfo.size,
|
|
121
|
+
config,
|
|
122
|
+
});
|
|
97
123
|
return config;
|
|
98
124
|
}
|
|
99
125
|
/** For tests + callers that need to force a re-read (e.g. config-watcher hooks). */
|
|
@@ -119,14 +145,19 @@ export function findPiLensConfigInDir(dir) {
|
|
|
119
145
|
const marker = findPiLensConfigMarkerInDir(dir);
|
|
120
146
|
if (!marker)
|
|
121
147
|
return undefined;
|
|
122
|
-
return {
|
|
148
|
+
return {
|
|
149
|
+
path: marker.path,
|
|
150
|
+
dir: marker.dir,
|
|
151
|
+
mtimeMs: marker.mtimeMs,
|
|
152
|
+
size: marker.size,
|
|
153
|
+
};
|
|
123
154
|
}
|
|
124
155
|
/**
|
|
125
156
|
* Find the closest config, between an edited file and the project root, that
|
|
126
157
|
* explicitly defines one mutation flag. The walk uses the shared primitive
|
|
127
158
|
* and refuses to inspect HOME or any ancestor of HOME.
|
|
128
159
|
*/
|
|
129
|
-
export function findNestedProjectMutationValue(
|
|
160
|
+
export function findNestedProjectMutationValue(spec, editedFilePath, projectRoot, homeDir = os.homedir()) {
|
|
130
161
|
const root = path.resolve(projectRoot);
|
|
131
162
|
const start = path.dirname(path.resolve(editedFilePath));
|
|
132
163
|
for (const dir of walkUpDirs(start)) {
|
|
@@ -136,11 +167,7 @@ export function findNestedProjectMutationValue(name, editedFilePath, projectRoot
|
|
|
136
167
|
if (rel.startsWith("..") || path.isAbsolute(rel))
|
|
137
168
|
break;
|
|
138
169
|
const config = loadPiLensConfigInDir(dir);
|
|
139
|
-
const enabled =
|
|
140
|
-
? config.format?.enabled
|
|
141
|
-
: name === "no-autofix"
|
|
142
|
-
? config.autofix?.enabled
|
|
143
|
-
: config.actionableWarnings?.autoFix?.enabled;
|
|
170
|
+
const enabled = readFlagConfigValue(config, spec.configKey);
|
|
144
171
|
if (enabled !== undefined)
|
|
145
172
|
return { value: enabled, dir };
|
|
146
173
|
if (dir === root)
|
|
@@ -160,11 +187,11 @@ export function loadPiLensConfigInDir(dir) {
|
|
|
160
187
|
if (!info)
|
|
161
188
|
return EMPTY_PROJECT_CONFIG;
|
|
162
189
|
const cached = configCache.get(info.path);
|
|
163
|
-
if (cached && cached.mtimeMs === info.mtimeMs) {
|
|
190
|
+
if (cached && cached.mtimeMs === info.mtimeMs && cached.size === info.size) {
|
|
164
191
|
return cached.config;
|
|
165
192
|
}
|
|
166
193
|
const config = parseConfigFile(info.path);
|
|
167
|
-
configCache.set(info.path, { mtimeMs: info.mtimeMs, config });
|
|
194
|
+
configCache.set(info.path, { mtimeMs: info.mtimeMs, size: info.size, config });
|
|
168
195
|
return config;
|
|
169
196
|
}
|
|
170
197
|
export function findPiLensProjectConfig(startDir) {
|
|
@@ -175,7 +202,7 @@ export function findPiLensProjectConfig(startDir) {
|
|
|
175
202
|
return undefined;
|
|
176
203
|
const stat = safeFileStat(cached.info.path);
|
|
177
204
|
if (stat?.isFile())
|
|
178
|
-
return { ...cached.info, mtimeMs: stat.mtimeMs };
|
|
205
|
+
return { ...cached.info, mtimeMs: stat.mtimeMs, size: stat.size };
|
|
179
206
|
}
|
|
180
207
|
const discovered = discoverPiLensProjectConfig(cacheKey);
|
|
181
208
|
discoveryCache.set(cacheKey, discovered);
|
|
@@ -209,7 +236,7 @@ function discoverPiLensProjectConfig(startDir) {
|
|
|
209
236
|
const stat = safeFileStat(candidate);
|
|
210
237
|
if (stat?.isFile()) {
|
|
211
238
|
return {
|
|
212
|
-
info: { path: candidate, dir, mtimeMs: stat.mtimeMs },
|
|
239
|
+
info: { path: candidate, dir, mtimeMs: stat.mtimeMs, size: stat.size },
|
|
213
240
|
dirMtimes,
|
|
214
241
|
};
|
|
215
242
|
}
|
|
@@ -222,10 +249,41 @@ function warnInvalidConfigOnce(configPath, reason) {
|
|
|
222
249
|
if (warnedInvalidConfigs.has(key))
|
|
223
250
|
return;
|
|
224
251
|
warnedInvalidConfigs.add(key);
|
|
225
|
-
|
|
252
|
+
const message = `ignoring invalid project config ${configPath}: ${reason}`;
|
|
253
|
+
logExtension({
|
|
254
|
+
subsystem: "project-lens-config",
|
|
255
|
+
level: "warn",
|
|
256
|
+
message,
|
|
257
|
+
metadata: { configPath, reason },
|
|
258
|
+
});
|
|
259
|
+
// HUMAN-audience too: the user's own `.pi-lens.json` is being ignored.
|
|
260
|
+
notifyUserDegradation(`pi-lens: ${message}`);
|
|
226
261
|
}
|
|
227
|
-
function
|
|
228
|
-
|
|
262
|
+
function parseRulePolicyList(configPath, ruleId, key, value) {
|
|
263
|
+
if (!Array.isArray(value)) {
|
|
264
|
+
warnInvalidConfigOnce(configPath, `rules.${ruleId}.${key} must be an array of strings`);
|
|
265
|
+
return { list: [], invalid: true };
|
|
266
|
+
}
|
|
267
|
+
const list = [];
|
|
268
|
+
for (const entry of value) {
|
|
269
|
+
if (typeof entry !== "string")
|
|
270
|
+
continue;
|
|
271
|
+
const trimmed = entry.trim();
|
|
272
|
+
if (trimmed.length > 0)
|
|
273
|
+
list.push(trimmed);
|
|
274
|
+
}
|
|
275
|
+
if (list.length === 0) {
|
|
276
|
+
// #1087: an explicitly empty array (`"disable": []`) is a well-formed
|
|
277
|
+
// no-op, not an error — don't warn. Only warn when the array HAD entries
|
|
278
|
+
// but none were usable strings (all blank / non-string), which is a real
|
|
279
|
+
// authoring mistake that must not fail silently.
|
|
280
|
+
if (value.length > 0) {
|
|
281
|
+
warnInvalidConfigOnce(configPath, `rules.${ruleId}.${key} must contain at least one non-empty string`);
|
|
282
|
+
return { list: [], invalid: true };
|
|
283
|
+
}
|
|
284
|
+
return { list: [], invalid: false };
|
|
285
|
+
}
|
|
286
|
+
return { list, invalid: false };
|
|
229
287
|
}
|
|
230
288
|
function parseConfigFile(configPath) {
|
|
231
289
|
let raw;
|
|
@@ -245,36 +303,56 @@ function parseConfigFile(configPath) {
|
|
|
245
303
|
const ignore = Array.isArray(obj.ignore)
|
|
246
304
|
? obj.ignore.filter((p) => typeof p === "string")
|
|
247
305
|
: [];
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
typeof obj.actionableWarnings === "object" &&
|
|
252
|
-
!Array.isArray(obj.actionableWarnings)
|
|
253
|
-
? obj.actionableWarnings
|
|
254
|
-
: undefined;
|
|
255
|
-
if (obj.actionableWarnings !== undefined && !actionableWarningsRaw) {
|
|
256
|
-
warnInvalidConfigOnce(configPath, "actionableWarnings must be an object");
|
|
306
|
+
const mutations = {};
|
|
307
|
+
for (const spec of PROJECT_SCOPED_LENS_FLAGS) {
|
|
308
|
+
assignFlagConfigSection(obj, mutations, spec.configKey, (reason) => warnInvalidConfigOnce(configPath, reason));
|
|
257
309
|
}
|
|
258
|
-
const actionableWarningsAutoFix = parseEnabledConfig(configPath, "actionableWarnings.autoFix", actionableWarningsRaw?.autoFix);
|
|
259
|
-
const actionableWarnings = actionableWarningsRaw
|
|
260
|
-
? { autoFix: actionableWarningsAutoFix }
|
|
261
|
-
: undefined;
|
|
262
310
|
const rules = {};
|
|
263
311
|
if (obj.rules && typeof obj.rules === "object" && !Array.isArray(obj.rules)) {
|
|
264
312
|
const rawRules = obj.rules;
|
|
265
313
|
for (const [ruleId, ruleCfg] of Object.entries(rawRules)) {
|
|
314
|
+
// #444's own example writes the lists directly under `rules` (`rules.
|
|
315
|
+
// disable`), which lands here as an array and would otherwise be
|
|
316
|
+
// dropped without a word — the one shape a user is most likely to try.
|
|
266
317
|
if (!ruleCfg || typeof ruleCfg !== "object" || Array.isArray(ruleCfg)) {
|
|
318
|
+
warnInvalidConfigOnce(configPath, `rules.${ruleId} must be an object with threshold, disable, or select; ignored`);
|
|
267
319
|
continue;
|
|
268
320
|
}
|
|
269
321
|
const r = ruleCfg;
|
|
322
|
+
const entry = {};
|
|
270
323
|
if (typeof r.threshold === "number" &&
|
|
271
324
|
Number.isFinite(r.threshold) &&
|
|
272
325
|
r.threshold > 0) {
|
|
273
|
-
|
|
326
|
+
entry.threshold = r.threshold;
|
|
274
327
|
}
|
|
275
328
|
else if ("threshold" in r) {
|
|
276
329
|
warnInvalidConfigOnce(configPath, `rules.${ruleId}.threshold must be a positive finite number`);
|
|
277
330
|
}
|
|
331
|
+
if ("disable" in r) {
|
|
332
|
+
const parsed = parseRulePolicyList(configPath, ruleId, "disable", r.disable);
|
|
333
|
+
// #1087: an explicitly empty list is valid-but-empty (no warning);
|
|
334
|
+
// don't store a pointless no-op entry for it.
|
|
335
|
+
if (!parsed.invalid && parsed.list.length > 0)
|
|
336
|
+
entry.disable = parsed.list;
|
|
337
|
+
}
|
|
338
|
+
if ("select" in r) {
|
|
339
|
+
const parsed = parseRulePolicyList(configPath, ruleId, "select", r.select);
|
|
340
|
+
if (!parsed.invalid && parsed.list.length > 0)
|
|
341
|
+
entry.select = parsed.list;
|
|
342
|
+
}
|
|
343
|
+
// Honor both threshold-only and policy-only entries; only drop if
|
|
344
|
+
// the entry had no recognized fields at all (e.g. { unrelated: true }).
|
|
345
|
+
// A recognized-but-malformed field already warned above, so only warn
|
|
346
|
+
// here when nothing recognized was spelled at all — #444 proposed
|
|
347
|
+
// `only` rather than `select`, and that typo must not fail silent.
|
|
348
|
+
if (entry.threshold !== undefined || entry.disable || entry.select) {
|
|
349
|
+
rules[ruleId] = entry;
|
|
350
|
+
}
|
|
351
|
+
else if (!("threshold" in r) &&
|
|
352
|
+
!("disable" in r) &&
|
|
353
|
+
!("select" in r)) {
|
|
354
|
+
warnInvalidConfigOnce(configPath, `rules.${ruleId} has no recognized setting (threshold, disable, select); ignored`);
|
|
355
|
+
}
|
|
278
356
|
}
|
|
279
357
|
}
|
|
280
358
|
let maxProjectFiles;
|
|
@@ -309,12 +387,38 @@ function parseConfigFile(configPath) {
|
|
|
309
387
|
}
|
|
310
388
|
}
|
|
311
389
|
}
|
|
390
|
+
// #533 hygiene: mirror the global loader's unknown-key warn so a typo in a
|
|
391
|
+
// shared `.pi-lens.json` (e.g. `maxProjectFile`, `lps`) produces a signal
|
|
392
|
+
// instead of silently doing nothing. The recognized set is single-sourced
|
|
393
|
+
// (#883): the project loader's own keys + the project-scoped flag sections
|
|
394
|
+
// (registry-derived) + the foreign namespaces the LSP loader reads from this
|
|
395
|
+
// same file. A key recognized ONLY at global scope (e.g. `lsp`, `tests`,
|
|
396
|
+
// `delta`) gets a distinct, honest signal that it does nothing here rather
|
|
397
|
+
// than being lumped in with typos — docs previously called this "silently
|
|
398
|
+
// ignored".
|
|
399
|
+
const knownProjectKeys = new Set([
|
|
400
|
+
...PROJECT_OWN_CONFIG_KEYS,
|
|
401
|
+
...flagConfigSectionKeys(PROJECT_SCOPED_LENS_FLAGS),
|
|
402
|
+
...PROJECT_FOREIGN_CONFIG_NAMESPACES,
|
|
403
|
+
]);
|
|
404
|
+
const globalScopeOnlyKeys = new Set([
|
|
405
|
+
...flagConfigSectionKeys(LENS_FLAGS),
|
|
406
|
+
...GLOBAL_NON_FLAG_CONFIG_SECTIONS,
|
|
407
|
+
].filter((key) => !knownProjectKeys.has(key)));
|
|
408
|
+
for (const key of Object.keys(obj)) {
|
|
409
|
+
if (knownProjectKeys.has(key))
|
|
410
|
+
continue;
|
|
411
|
+
if (globalScopeOnlyKeys.has(key)) {
|
|
412
|
+
warnInvalidConfigOnce(configPath, `"${key}" is a global-only pi-lens setting and is not honored in a project .pi-lens.json (set it in ~/.pi-lens/config.json or pass the matching CLI flag); ignored`);
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
warnInvalidConfigOnce(configPath, `unknown key "${key}" is not a recognized pi-lens setting (check for a typo); ignored`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
312
418
|
return {
|
|
313
419
|
ignore,
|
|
314
420
|
rules,
|
|
315
|
-
|
|
316
|
-
autofix,
|
|
317
|
-
actionableWarnings,
|
|
421
|
+
...mutations,
|
|
318
422
|
maxProjectFiles,
|
|
319
423
|
reviewGraph,
|
|
320
424
|
raw,
|
|
@@ -34,9 +34,8 @@
|
|
|
34
34
|
* into symbol-node metadata so this path can read it for free.
|
|
35
35
|
*/
|
|
36
36
|
import * as path from "node:path";
|
|
37
|
-
import { normalizeMapKey } from "./path-utils.js";
|
|
38
|
-
import {
|
|
39
|
-
import { loadProjectSnapshot } from "./project-snapshot.js";
|
|
37
|
+
import { normalizeMapKey, toProjectRelativePath } from "./path-utils.js";
|
|
38
|
+
import { loadProjectSnapshotWithoutWordIndex } from "./project-snapshot.js";
|
|
40
39
|
const DEFAULT_LIMIT = 10;
|
|
41
40
|
const STALE_THRESHOLD_MS = 15 * 60_000; // 15 minutes
|
|
42
41
|
const LOW_COVERAGE_THRESHOLD = 0.8;
|
|
@@ -48,14 +47,19 @@ function clampLimit(limit) {
|
|
|
48
47
|
}
|
|
49
48
|
// Same display-path convention as module-report.ts's toDisplayPath: cwd-relative
|
|
50
49
|
// + forward-slashed under the project root, else the absolute (slash-normalized)
|
|
51
|
-
// path.
|
|
50
|
+
// path. Delegates to the shape-aware toProjectRelativePath (#1163/#1194) instead
|
|
51
|
+
// of hand-rolling with host-default path.isAbsolute/path.relative, which
|
|
52
|
+
// mis-parses a Windows-shaped path (e.g. a persisted graph symbol-key path) on
|
|
53
|
+
// Linux CI: path.isAbsolute("C:\\repo\\x.ts") is false there, short-circuiting
|
|
54
|
+
// to the raw absolute path instead of relativizing (#1024 divergence class).
|
|
52
55
|
function toDisplayPath(p, projectRoot) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
return toProjectRelativePath(p, projectRoot);
|
|
57
|
+
}
|
|
58
|
+
/** Test-only export (refs #1194) so the shape-aware delegation can be verified
|
|
59
|
+
* directly without standing up a full review graph — mirrors the
|
|
60
|
+
* `_reset*ForTests` underscore convention used elsewhere in this file. */
|
|
61
|
+
export function _toDisplayPathForTests(p, projectRoot) {
|
|
62
|
+
return toDisplayPath(p, projectRoot);
|
|
59
63
|
}
|
|
60
64
|
function suggestedNext(displayPath) {
|
|
61
65
|
return { tool: "module_report", path: displayPath };
|
|
@@ -111,9 +115,10 @@ function buildFileDegrees(graph) {
|
|
|
111
115
|
// --- section 1: trust header --------------------------------------------------
|
|
112
116
|
function computeTrust(graph, cwd) {
|
|
113
117
|
const filesCovered = graph.fileNodes.size;
|
|
114
|
-
const snapshot =
|
|
118
|
+
const snapshot = loadProjectSnapshotWithoutWordIndex(cwd);
|
|
115
119
|
const snapshotFileCount = snapshot ? Object.keys(snapshot.files).length : 0;
|
|
116
|
-
const
|
|
120
|
+
const persistedTotal = graph.persistCoverage?.totalFiles ?? 0;
|
|
121
|
+
const filesTotal = Math.max(filesCovered, snapshotFileCount, persistedTotal);
|
|
117
122
|
const coverage = filesTotal > 0 ? filesCovered / filesTotal : 1;
|
|
118
123
|
let exact = 0;
|
|
119
124
|
let imp = 0;
|
|
@@ -143,14 +148,28 @@ function computeTrust(graph, cwd) {
|
|
|
143
148
|
const frac = (n) => (sampleSize > 0 ? n / sampleSize : 0);
|
|
144
149
|
const ageMs = Date.now() - Date.parse(graph.builtAt);
|
|
145
150
|
const stale = Number.isFinite(ageMs) && ageMs > STALE_THRESHOLD_MS;
|
|
146
|
-
const lowCoverage = coverage < LOW_COVERAGE_THRESHOLD
|
|
151
|
+
const lowCoverage = coverage < LOW_COVERAGE_THRESHOLD ||
|
|
152
|
+
graph.persistCoverage?.partial === true;
|
|
147
153
|
const notes = [];
|
|
148
154
|
if (stale) {
|
|
149
155
|
const ageMin = Math.round(ageMs / 60_000);
|
|
150
156
|
notes.push(`Graph is stale: built ${ageMin}m ago. Sections below may miss recent edits — run pilens_rebuild or re-analyze to refresh.`);
|
|
151
157
|
}
|
|
152
158
|
if (lowCoverage) {
|
|
153
|
-
|
|
159
|
+
if (graph.persistCoverage?.partial) {
|
|
160
|
+
const p = graph.persistCoverage;
|
|
161
|
+
const totalFiles = p.totalFiles ?? filesCovered;
|
|
162
|
+
const persistedFiles = p.persistedFiles ?? filesCovered;
|
|
163
|
+
if (p.sourceFilesTruncated) {
|
|
164
|
+
notes.push(`Partial source walk: at least ${totalFiles} source files were represented before the visited-entry budget stopped enumeration; ${persistedFiles} files are in this graph.`);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
notes.push(`Partial persisted graph: ${p.persistedNodes}/${p.totalNodes} nodes, ${p.persistedEdges}/${p.totalEdges} edges, and ${persistedFiles}/${totalFiles} files were retained under the ${p.cap}-element cap — whole subsystems may be invisible below.`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
notes.push(`Low coverage: only ${filesCovered}/${filesTotal} project files (${Math.round(coverage * 100)}%) are in the graph — whole subsystems may be invisible below.`);
|
|
172
|
+
}
|
|
154
173
|
}
|
|
155
174
|
return {
|
|
156
175
|
graphBuiltAt: graph.builtAt,
|
|
@@ -166,6 +185,9 @@ function computeTrust(graph, cwd) {
|
|
|
166
185
|
},
|
|
167
186
|
stale,
|
|
168
187
|
lowCoverage,
|
|
188
|
+
persistCoverage: graph.persistCoverage?.partial
|
|
189
|
+
? graph.persistCoverage
|
|
190
|
+
: undefined,
|
|
169
191
|
notes,
|
|
170
192
|
};
|
|
171
193
|
}
|
|
@@ -473,10 +495,8 @@ export function _resetProjectReportBuildGuardForTests() {
|
|
|
473
495
|
}
|
|
474
496
|
function triggerBackgroundGraphBuild(cwd) {
|
|
475
497
|
const key = normalizeMapKey(path.resolve(cwd));
|
|
476
|
-
if (isAtOrAboveHomeDir(key))
|
|
477
|
-
return;
|
|
478
498
|
if (inFlightGraphBuilds.has(key))
|
|
479
|
-
return;
|
|
499
|
+
return false;
|
|
480
500
|
inFlightGraphBuilds.add(key);
|
|
481
501
|
void (async () => {
|
|
482
502
|
try {
|
|
@@ -485,12 +505,13 @@ function triggerBackgroundGraphBuild(cwd) {
|
|
|
485
505
|
await buildOrUpdateGraph(key, [], new FactStore());
|
|
486
506
|
}
|
|
487
507
|
catch {
|
|
488
|
-
//
|
|
508
|
+
// buildOrUpdateGraph records the durable failure and surfaced status.
|
|
489
509
|
}
|
|
490
510
|
finally {
|
|
491
511
|
inFlightGraphBuilds.delete(key);
|
|
492
512
|
}
|
|
493
513
|
})();
|
|
514
|
+
return true;
|
|
494
515
|
}
|
|
495
516
|
// --- entry point ---------------------------------------------------------------
|
|
496
517
|
/**
|
|
@@ -502,7 +523,7 @@ export async function projectReport(cwd, options) {
|
|
|
502
523
|
const limit = clampLimit(options?.limit);
|
|
503
524
|
const focusTerms = normalizeFocus(options?.focus);
|
|
504
525
|
const view = options?.view;
|
|
505
|
-
const { getCachedReviewGraph, getReviewGraphSizeSkipVerdict } = await import("./review-graph/builder.js");
|
|
526
|
+
const { getCachedReviewGraph, getReviewGraphSizeSkipVerdict, getLastReviewGraphBuildAttempt, } = await import("./review-graph/builder.js");
|
|
506
527
|
let graph;
|
|
507
528
|
try {
|
|
508
529
|
graph = getCachedReviewGraph(cwd);
|
|
@@ -523,19 +544,29 @@ export async function projectReport(cwd, options) {
|
|
|
523
544
|
sizeSkip = undefined;
|
|
524
545
|
}
|
|
525
546
|
if (sizeSkip) {
|
|
547
|
+
const lastBuildAttempt = getLastReviewGraphBuildAttempt(cwd);
|
|
526
548
|
return {
|
|
527
549
|
available: false,
|
|
528
|
-
hint: `review graph disabled: project has ${sizeSkip.
|
|
529
|
-
`cap
|
|
530
|
-
"or set PI_LENS_REVIEW_GRAPH_MAX_FILES"
|
|
550
|
+
hint: `review graph disabled: project has more than ${sizeSkip.maxFileCount} files ` +
|
|
551
|
+
`(cap ${sizeSkip.maxFileCount}) — raise maxProjectFiles in .pi-lens.json ` +
|
|
552
|
+
"or set PI_LENS_REVIEW_GRAPH_MAX_FILES; for CI/cron, run " +
|
|
553
|
+
"npx pi-lens build-graph after configuring the cap",
|
|
554
|
+
...(lastBuildAttempt ? { lastBuildAttempt } : {}),
|
|
531
555
|
...(view ? { view } : {}),
|
|
532
556
|
};
|
|
533
557
|
}
|
|
534
|
-
|
|
558
|
+
const previousAttempt = getLastReviewGraphBuildAttempt(cwd);
|
|
559
|
+
const kickedOff = triggerBackgroundGraphBuild(cwd);
|
|
560
|
+
const lastBuildAttempt = previousAttempt ?? getLastReviewGraphBuildAttempt(cwd);
|
|
535
561
|
return {
|
|
536
562
|
available: false,
|
|
537
|
-
hint:
|
|
538
|
-
|
|
563
|
+
hint: lastBuildAttempt?.outcome === "failed" ||
|
|
564
|
+
lastBuildAttempt?.outcome === "skipped"
|
|
565
|
+
? `Review graph unavailable: ${lastBuildAttempt.reason ?? lastBuildAttempt.outcome}. A retry was ${kickedOff ? "started" : "not started"}.`
|
|
566
|
+
: kickedOff
|
|
567
|
+
? "No review graph cached for this workspace yet — a build was kicked off in the background; retry this call shortly."
|
|
568
|
+
: "No review graph cached for this workspace yet — the background build is still running; retry this call shortly.",
|
|
569
|
+
...(lastBuildAttempt ? { lastBuildAttempt } : {}),
|
|
539
570
|
...(view ? { view } : {}),
|
|
540
571
|
};
|
|
541
572
|
}
|
|
@@ -546,8 +577,10 @@ export async function projectReport(cwd, options) {
|
|
|
546
577
|
const subsystems = computeSubsystems(graph, cwd, limit);
|
|
547
578
|
const riskHotspots = computeRiskHotspots(graph, degrees, cwd, limit, focusTerms);
|
|
548
579
|
const deadWeight = computeDeadWeight(graph, degrees, entryPointFiles, cwd, limit);
|
|
580
|
+
const lastBuildAttempt = getLastReviewGraphBuildAttempt(cwd);
|
|
549
581
|
return {
|
|
550
582
|
available: true,
|
|
583
|
+
...(lastBuildAttempt ? { lastBuildAttempt } : {}),
|
|
551
584
|
...(view ? { view } : {}),
|
|
552
585
|
trust,
|
|
553
586
|
hubs,
|
|
@@ -571,6 +604,12 @@ export function renderCompactProjectReport(report) {
|
|
|
571
604
|
return `project_report — unavailable${report.hint ? `: ${report.hint}` : ""}`;
|
|
572
605
|
}
|
|
573
606
|
const lines = [];
|
|
607
|
+
// #919: an available graph whose persist failed serves this process fine
|
|
608
|
+
// but leaves the NEXT session cold — compact view must say so, not only
|
|
609
|
+
// the JSON view.
|
|
610
|
+
if (report.lastBuildAttempt?.reason) {
|
|
611
|
+
lines.push(`! build: ${report.lastBuildAttempt.reason}`);
|
|
612
|
+
}
|
|
574
613
|
const t = report.trust;
|
|
575
614
|
if (t) {
|
|
576
615
|
lines.push(`TRUST: built ${t.graphBuiltAt} — ${t.filesCovered}/${t.filesTotal} files (${fmtPct(t.coverage)} coverage)` +
|
|
@@ -151,19 +151,17 @@ export function getProjectDiagnosticsScannerMaxFiles(cwd) {
|
|
|
151
151
|
*
|
|
152
152
|
* {@link REVIEW_GRAPH_HARD_CEILING} (5,000) is grounded FIRST by the
|
|
153
153
|
* strongest real constraint on this number — `review-graph/builder.ts`'s
|
|
154
|
-
* persist circuit-breaker (`GRAPH_PERSIST_MAX_ELEMENTS_DEFAULT`,
|
|
154
|
+
* persist circuit-breaker (`GRAPH_PERSIST_MAX_ELEMENTS_DEFAULT`, 500,000
|
|
155
155
|
* graph elements: file/symbol nodes + cross-file edges). Above that element
|
|
156
|
-
* count
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* timing note below) engaging exactly once. For symbol-dense repos, element
|
|
156
|
+
* count only a centrality-ranked, honestly-marked partial graph is persisted
|
|
157
|
+
* (#936). Read-only orientation can use it, but the build tier rejects it as
|
|
158
|
+
* a complete incremental base. For symbol-dense repos, element
|
|
160
159
|
* count runs on the order of ~30/file (file node + per-symbol nodes +
|
|
161
160
|
* import/call/reference edges), so a hard ceiling must leave real headroom
|
|
162
|
-
* below
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* the opposite of this issue's "no silent caps" goal. 5,000 is also still
|
|
161
|
+
* below 500,000 elements. The file ceiling remains conservative because it
|
|
162
|
+
* also bounds cold-build latency; the persist cap has independent measured
|
|
163
|
+
* headroom through a roughly 420,000-element / 10,000-file graph (#936).
|
|
164
|
+
* 5,000 is also still
|
|
167
165
|
* coherent with the jscpd/wordIndex 3x-ratio precedent (6,000 at the
|
|
168
166
|
* default base) as "the ceiling sits just below that established top
|
|
169
167
|
* tier" rather than an exact match, so it isn't a fresh unrelated
|