pi-lens 3.8.73 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2614 -12
- package/README.md +50 -21
- package/dist/clients/actionable-warnings.js +73 -18
- package/dist/clients/agent-behavior-client.js +21 -4
- package/dist/clients/ast-grep-client.js +68 -16
- package/dist/clients/ast-grep-types.js +0 -1
- package/dist/clients/ast-grep-yaml-synth.js +37 -10
- package/dist/clients/atomic-write-staging.js +63 -0
- package/dist/clients/atomic-write.js +116 -16
- package/dist/clients/bash-file-access.js +193 -23
- package/dist/clients/biome-client.js +29 -25
- package/dist/clients/bootstrap.js +11 -2
- package/dist/clients/bounded-cache.js +34 -0
- package/dist/clients/bounded-pid-file-lock.js +192 -0
- package/dist/clients/bus-events-logger.js +5 -1
- package/dist/clients/bus-publish.js +10 -3
- package/dist/clients/cache/rule-cache.js +89 -6
- package/dist/clients/cache-manager.js +97 -9
- package/dist/clients/cache-observability.js +498 -0
- package/dist/clients/call-graph.js +460 -108
- package/dist/clients/cascade-logger.js +5 -1
- package/dist/clients/child-unref.js +85 -0
- package/dist/clients/codebase-model.js +52 -23
- package/dist/clients/collateral-test-role.js +40 -0
- package/dist/clients/complexity-client.js +6 -8
- package/dist/clients/console-guard-install.js +14 -0
- package/dist/clients/cooperative-budget.js +42 -0
- package/dist/clients/dead-code-client.js +2 -1
- package/dist/clients/debug-handles.js +206 -0
- package/dist/clients/debug-heap.js +167 -0
- package/dist/clients/degradation-ledger.js +153 -0
- package/dist/clients/dependency-checker.js +466 -63
- package/dist/clients/diagnostic-dispositions.js +83 -13
- package/dist/clients/diagnostics-publish.js +10 -3
- package/dist/clients/dispatch/auxiliary-lsp.js +34 -0
- package/dist/clients/dispatch/dispatcher.js +71 -13
- package/dist/clients/dispatch/fact-store.js +11 -0
- package/dist/clients/dispatch/facts/function-facts.js +37 -7
- package/dist/clients/dispatch/facts/import-facts.js +108 -119
- package/dist/clients/dispatch/facts/tree-sitter-facts.js +12 -12
- package/dist/clients/dispatch/facts/try-catch-facts.js +13 -0
- package/dist/clients/dispatch/indent-detect.js +35 -0
- package/dist/clients/dispatch/inline-suppressions.js +17 -8
- package/dist/clients/dispatch/integration.js +1094 -611
- package/dist/clients/dispatch/lazy.js +14 -0
- package/dist/clients/dispatch/plan.js +11 -1
- package/dist/clients/dispatch/rule-id-normalize.js +50 -0
- package/dist/clients/dispatch/rule-policy.js +135 -0
- package/dist/clients/dispatch/rules/async-noise.js +1 -0
- package/dist/clients/dispatch/rules/unsafe-boundary.js +7 -0
- package/dist/clients/dispatch/runners/ast-grep-napi.js +47 -6
- package/dist/clients/dispatch/runners/biome-check.js +5 -9
- package/dist/clients/dispatch/runners/detekt.js +3 -2
- package/dist/clients/dispatch/runners/hadolint.js +2 -1
- package/dist/clients/dispatch/runners/helm-lint.js +154 -0
- package/dist/clients/dispatch/runners/htmlhint.js +3 -2
- package/dist/clients/dispatch/runners/index.js +5 -1
- package/dist/clients/dispatch/runners/lsp.js +14 -6
- package/dist/clients/dispatch/runners/markdownlint.js +4 -9
- package/dist/clients/dispatch/runners/psscriptanalyzer.js +10 -1
- package/dist/clients/dispatch/runners/pyright.js +9 -5
- package/dist/clients/dispatch/runners/ruff.js +4 -8
- package/dist/clients/dispatch/runners/shellcheck.js +2 -3
- package/dist/clients/dispatch/runners/shfmt.js +2 -3
- package/dist/clients/dispatch/runners/spotbugs.js +2 -1
- package/dist/clients/dispatch/runners/taplo.js +2 -1
- package/dist/clients/dispatch/runners/terragrunt.js +163 -0
- package/dist/clients/dispatch/runners/tflint.js +37 -5
- package/dist/clients/dispatch/runners/tree-sitter.js +155 -166
- package/dist/clients/dispatch/runners/trivy-config.js +13 -7
- package/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
- package/dist/clients/dispatch/runners/utils/runner-helpers.js +284 -34
- package/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
- package/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
- package/dist/clients/disposition-publish.js +10 -3
- package/dist/clients/durable-store.js +97 -0
- package/dist/clients/event-loop-monitor.js +94 -3
- package/dist/clients/extension-log.js +139 -0
- package/dist/clients/extension-mode.js +73 -0
- package/dist/clients/feature-hints.js +53 -7
- package/dist/clients/file-kinds.js +143 -31
- package/dist/clients/file-role.js +18 -3
- package/dist/clients/file-utils.js +56 -34
- package/dist/clients/fix-worklog.js +14 -2
- package/dist/clients/format-events-publish.js +16 -3
- package/dist/clients/format-service.js +24 -8
- package/dist/clients/formatters-lazy.js +8 -0
- package/dist/clients/formatters.js +358 -61
- package/dist/clients/generated-artifacts.js +169 -15
- package/dist/clients/git-guard.js +681 -11
- package/dist/clients/git-tracked-ignore.js +4 -0
- package/dist/clients/go-client.js +2 -1
- package/dist/clients/govulncheck-client.js +8 -0
- package/dist/clients/grammar-source.js +12 -4
- package/dist/clients/gzip-stage-write.js +102 -0
- package/dist/clients/host-ports.js +31 -0
- package/dist/clients/installer/index.js +733 -213
- package/dist/clients/instance-reaper.js +142 -172
- package/dist/clients/jscpd-client.js +31 -54
- package/dist/clients/knip-client.js +78 -36
- package/dist/clients/language-policy.js +14 -2
- package/dist/clients/language-profile.js +72 -77
- package/dist/clients/latency-logger.js +28 -16
- package/dist/clients/lens-config.js +140 -166
- package/dist/clients/lens-engine.js +124 -21
- package/dist/clients/lens-events.js +27 -4
- package/dist/clients/lens-flag-registry.js +293 -0
- package/dist/clients/lens-map.js +96 -26
- package/dist/clients/live-bus-emitter.js +30 -0
- package/dist/clients/log-cleanup.js +8 -18
- package/dist/clients/lsp/cascade-tier.js +23 -3
- package/dist/clients/lsp/client.js +566 -57
- package/dist/clients/lsp/config.js +74 -10
- package/dist/clients/lsp/diagnostic-binding.js +86 -0
- package/dist/clients/lsp/edits.js +1012 -121
- package/dist/clients/lsp/index.js +1840 -803
- package/dist/clients/lsp/language.js +4 -1
- package/dist/clients/lsp/launch.js +40 -32
- package/dist/clients/lsp/path-utils.js +1 -1
- package/dist/clients/lsp/ruby-drive-dirs.js +83 -0
- package/dist/clients/lsp/server.js +279 -60
- package/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
- package/dist/clients/lsp-document-symbols.js +112 -0
- package/dist/clients/lsp-lazy.js +8 -0
- package/dist/clients/lsp-mutation.js +248 -0
- package/dist/clients/mcp/analyze.js +128 -36
- package/dist/clients/mcp/ipc.js +157 -24
- package/dist/clients/mcp/review.js +36 -3
- package/dist/clients/mcp/session.js +216 -8
- package/dist/clients/memory-sampler.js +115 -0
- package/dist/clients/metrics-client.js +2 -1
- package/dist/clients/metrics-history.js +2 -1
- package/dist/clients/module-report-lsp.js +5 -0
- package/dist/clients/module-report.js +267 -82
- package/dist/clients/ndjson-logger.js +334 -103
- package/dist/clients/opengrep-client.js +1 -1
- package/dist/clients/partial-edit-apply.js +120 -5
- package/dist/clients/path-keyed-map.js +76 -0
- package/dist/clients/path-utils.js +141 -5
- package/dist/clients/performance-report.js +327 -0
- package/dist/clients/pipeline.js +54 -13
- package/dist/clients/print-mode.js +21 -0
- package/dist/clients/project-changes.js +117 -4
- package/dist/clients/project-diagnostics/cache.js +3 -2
- package/dist/clients/project-diagnostics/extractors.js +26 -115
- package/dist/clients/project-diagnostics/fresh-fetch.js +149 -19
- package/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +117 -0
- package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +37 -5
- package/dist/clients/project-diagnostics/scanner.js +268 -128
- package/dist/clients/project-lens-config.js +138 -34
- package/dist/clients/project-report.js +64 -25
- package/dist/clients/project-scale.js +8 -10
- package/dist/clients/project-snapshot-persist-worker.js +6 -0
- package/dist/clients/project-snapshot.js +767 -8
- package/dist/clients/project-trust.js +181 -0
- package/dist/clients/quiet-window.js +27 -5
- package/dist/clients/read-bridge.js +147 -0
- package/dist/clients/read-expansion.js +22 -12
- package/dist/clients/read-guard-logger.js +159 -1
- package/dist/clients/read-guard-tool-lines.js +150 -29
- package/dist/clients/read-guard.js +262 -3
- package/dist/clients/redact/secrets.js +449 -0
- package/dist/clients/resource-sampler.js +144 -39
- package/dist/clients/reverse-deps.js +48 -0
- package/dist/clients/review-graph/builder.js +2570 -326
- package/dist/clients/review-graph/git-identity.js +2 -1
- package/dist/clients/review-graph/import-resolvers.js +21 -10
- package/dist/clients/review-graph/persist-worker.js +10 -0
- package/dist/clients/review-graph/query.js +5 -0
- package/dist/clients/review-graph/shared-extraction-ir.js +90 -0
- package/dist/clients/review-graph/symbol-id.js +46 -0
- package/dist/clients/review-graph/tsconfig-paths.js +57 -6
- package/dist/clients/review-graph/workspace-modules.js +51 -3
- package/dist/clients/review-graph-logger.js +53 -0
- package/dist/clients/ruff-client.js +27 -44
- package/dist/clients/runtime-agent-end.js +111 -75
- package/dist/clients/runtime-context.js +36 -12
- package/dist/clients/runtime-coordinator.js +76 -8
- package/dist/clients/runtime-session.js +799 -137
- package/dist/clients/runtime-tool-call.js +117 -62
- package/dist/clients/runtime-tool-result.js +218 -23
- package/dist/clients/runtime-turn.js +284 -40
- package/dist/clients/rust-client.js +2 -1
- package/dist/clients/safe-spawn.js +873 -71
- package/dist/clients/security-scan-client.js +2 -1
- package/dist/clients/session-start-observability.js +15 -0
- package/dist/clients/session-state-store.js +5 -3
- package/dist/clients/sessionstart-logger.js +18 -0
- package/dist/clients/sg-runner.js +284 -159
- package/dist/clients/slow-fs.js +2 -1
- package/dist/clients/smells-rollup.js +224 -0
- package/dist/clients/source-filter.js +231 -43
- package/dist/clients/source-walker.js +66 -10
- package/dist/clients/startup-scan.js +15 -16
- package/dist/clients/startup-timing.js +9 -0
- package/dist/clients/test-runner-client.js +38 -8
- package/dist/clients/todo-scanner.js +30 -3
- package/dist/clients/tool-event.js +35 -0
- package/dist/clients/tool-policy.js +388 -39
- package/dist/clients/tool-render.js +155 -0
- package/dist/clients/tree-sitter-cache.js +153 -140
- package/dist/clients/tree-sitter-client.js +1722 -80
- package/dist/clients/tree-sitter-logger.js +64 -1
- package/dist/clients/tree-sitter-query-loader.js +117 -12
- package/dist/clients/tree-sitter-shared.js +48 -19
- package/dist/clients/tree-sitter-symbol-extractor.js +115 -11
- package/dist/clients/user-notify.js +51 -0
- package/dist/clients/vanished-instance-marker.js +57 -0
- package/dist/clients/warm-attach.js +33 -13
- package/dist/clients/widget-state.js +342 -49
- package/dist/clients/word-index-logger.js +44 -0
- package/dist/clients/word-index.js +612 -68
- package/dist/clients/workspace-topology.js +100 -7
- package/dist/index.js +65923 -47161
- package/dist/mcp/analyze-cli.js +185 -29
- package/dist/mcp/cli.js +110 -0
- package/dist/mcp/server.js +272 -50
- package/dist/tools/ast-grep-search.js +93 -27
- package/dist/tools/lens-diagnostics.js +247 -41
- package/dist/tools/lsp-diagnostics.js +153 -40
- package/dist/tools/lsp-navigation.js +51 -31
- package/dist/tools/module-report.js +9 -0
- package/dist/tools/symbol-search.js +21 -2
- package/docs/agent-guide.md +247 -0
- package/docs/agent-tools.md +21 -10
- package/docs/analysisall.md +192 -0
- package/docs/api-ports-inventory.md +165 -0
- package/docs/ast-grep_rules_catalog.md +5 -2
- package/docs/astplayground.md +3 -4
- package/docs/audit1.md +5 -5
- package/docs/durable-store-audit-1202.md +32 -0
- package/docs/environment-variables.md +197 -11
- package/docs/fable.md +27 -0
- package/docs/features.md +35 -5
- package/docs/globalconfig.md +101 -5
- package/docs/language-coverage.md +6 -5
- package/docs/lsp-capability-matrix.md +2 -2
- package/docs/mcp.md +54 -20
- package/docs/servercapabilities.md +7 -4
- package/docs/settings.md +192 -0
- package/docs/tools.md +18 -1
- package/docs/tree-sitter_rules_catalog.md +11 -31
- package/docs/usage.md +31 -0
- package/package.json +139 -138
- package/rules/ast-grep-rules/rule-tests/hardcoded-url-js-test.yml +1 -0
- package/rules/ast-grep-rules/rule-tests/hardcoded-url-test.yml +2 -0
- package/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
- package/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
- package/rules/ast-grep-rules/rule-tests/no-case-declarations-js-test.yml +10 -0
- package/rules/ast-grep-rules/rule-tests/no-case-declarations-test.yml +10 -0
- package/rules/ast-grep-rules/rule-tests/no-javascript-url-js-test.yml +10 -0
- package/rules/ast-grep-rules/rule-tests/no-javascript-url-test.yml +10 -0
- package/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
- package/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
- package/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
- package/rules/ast-grep-rules/rule-tests/strict-equality-js-test.yml +3 -0
- package/rules/ast-grep-rules/rule-tests/strict-equality-test.yml +3 -0
- package/rules/ast-grep-rules/rule-tests/strict-inequality-js-test.yml +3 -0
- package/rules/ast-grep-rules/rule-tests/strict-inequality-test.yml +4 -0
- package/rules/ast-grep-rules/rules/hardcoded-url-js.yml +22 -4
- package/rules/ast-grep-rules/rules/hardcoded-url.yml +22 -4
- package/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
- package/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
- package/rules/ast-grep-rules/rules/no-case-declarations-js.yml +4 -3
- package/rules/ast-grep-rules/rules/no-case-declarations.yml +4 -3
- package/rules/ast-grep-rules/rules/no-console-except-error-js.yml +10 -0
- package/rules/ast-grep-rules/rules/no-console-except-error.yml +10 -0
- package/rules/ast-grep-rules/rules/no-javascript-url-js.yml +42 -6
- package/rules/ast-grep-rules/rules/no-javascript-url.yml +42 -6
- package/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
- package/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
- package/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
- package/rules/ast-grep-rules/rules/strict-equality-js.yml +14 -1
- package/rules/ast-grep-rules/rules/strict-equality.yml +14 -1
- package/rules/ast-grep-rules/rules/strict-inequality-js.yml +14 -1
- package/rules/ast-grep-rules/rules/strict-inequality.yml +14 -1
- package/rules/tree-sitter-queries/cpp/no-memset-sensitive-data.yml +9 -3
- package/rules/tree-sitter-queries/cpp/no-scoped-lock-without-args.yml +6 -6
- package/rules/tree-sitter-queries/css/calc-spacing.yml +8 -6
- package/rules/tree-sitter-queries/go/go-mutex-copy.yml +21 -18
- package/rules/tree-sitter-queries/go/go-shared-map-write-goroutine.yml +3 -3
- package/rules/tree-sitter-queries/java/infinite-loop-java.yml +3 -3
- package/rules/tree-sitter-queries/java/junit-call-super.yml +6 -4
- package/rules/tree-sitter-queries/java/no-double-checked-locking.yml +10 -8
- package/rules/tree-sitter-queries/java/no-field-shadowing.yml +1 -1
- package/rules/tree-sitter-queries/java/prepared-statement-valid-indices.yml +2 -3
- package/rules/tree-sitter-queries/java/resources-closed.yml +2 -2
- package/rules/tree-sitter-queries/java/springboot-default-package.yml +6 -3
- package/rules/tree-sitter-queries/java/switch-fall-through.yml +6 -4
- package/rules/tree-sitter-queries/java/switch-non-case-labels.yml +4 -3
- package/rules/tree-sitter-queries/java/tests-include-assertions.yml +6 -5
- package/rules/tree-sitter-queries/javascript/switch-case-termination-js.yml +5 -8
- package/rules/tree-sitter-queries/javascript/switch-non-case-labels.yml +3 -2
- package/rules/tree-sitter-queries/kotlin/prepared-statement-indices.yml +9 -5
- package/rules/tree-sitter-queries/php/this-in-static-context.yml +5 -5
- package/rules/tree-sitter-queries/python/exit-signature-check.yml +2 -9
- package/rules/tree-sitter-queries/python/in-operator-unsupported.yml +3 -4
- package/rules/tree-sitter-queries/python/no-super-torchscript.yml +4 -8
- package/rules/tree-sitter-queries/python/notimplemented-boolean-context.yml +1 -5
- package/rules/tree-sitter-queries/python/python-empty-except.yml +1 -1
- package/rules/tree-sitter-queries/python/return-in-init.yml +0 -2
- package/rules/tree-sitter-queries/python/yield-return-outside-function.yml +0 -3
- package/rules/tree-sitter-queries/ruby/ruby-command-injection.yml +1 -12
- package/rules/tree-sitter-queries/ruby/ruby-eval.yml +1 -1
- package/rules/tree-sitter-queries/ruby/ruby-insecure-deserialization.yml +4 -4
- package/rules/tree-sitter-queries/ruby/ruby-insecure-random.yml +4 -11
- package/rules/tree-sitter-queries/ruby/ruby-open-struct.yml +1 -1
- package/rules/tree-sitter-queries/ruby/ruby-string-eval.yml +2 -3
- package/rules/tree-sitter-queries/ruby/ruby-weak-hash.yml +4 -4
- package/rules/tree-sitter-queries/rust/rust-lock-held-across-await.yml +4 -3
- package/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
- package/rules/tree-sitter-queries/typescript/console-statement.yml +11 -0
- package/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +4 -9
- package/rules/tree-sitter-queries/typescript/empty-switch-case.yml +13 -9
- package/rules/tree-sitter-queries/typescript/infinite-loop.yml +2 -2
- package/rules/tree-sitter-queries/typescript/mixed-async-styles.yml +10 -1
- package/rules/tree-sitter-queries/typescript/switch-case-termination.yml +5 -8
- package/rules/tree-sitter-queries/typescript/ts-insecure-random.yml +7 -10
- package/rules/tree-sitter-queries/typescript/unsafe-regex.yml +4 -2
- package/rules/typos/_typos.toml +17 -0
- package/scripts/analyze-pi-lens-logs.mjs +55 -2
- package/skills/pi-lens-write-tree-sitter-rule/SKILL.md +5 -0
- package/dist/clients/ast-grep-parser.js +0 -86
- package/dist/clients/config-enabled-shape.js +0 -39
- package/dist/clients/lsp/interactive-install.js +0 -367
- package/dist/clients/lsp/lsp-index.js +0 -10
- package/dist/clients/lsp/server-strategies.js +0 -5
- package/dist/clients/project-metadata.js +0 -690
- package/dist/clients/source-groups.js +0 -96
- package/dist/clients/types.js +0 -11
- package/docs/env_variables.md +0 -10
- package/rules/tree-sitter-queries/abap-disabled/delete-where.yml +0 -46
- package/rules/tree-sitter-queries/cpp/no-confused-move-forward.yml +0 -59
- package/rules/tree-sitter-queries/cpp/noexcept-functions.yml +0 -58
- package/rules/tree-sitter-queries/java/mockito-initialized.yml +0 -66
- package/rules/tree-sitter-queries/java/no-wait-notify-on-thread.yml +0 -58
- package/rules/tree-sitter-queries/java/short-circuit-logic.yml +0 -57
- package/rules/tree-sitter-queries/java/spring-session-attributes-setcomplete.yml +0 -75
- package/rules/tree-sitter-queries/plsql-disabled/delete-update-where.yml +0 -56
- package/rules/tree-sitter-queries/plsql-disabled/end-loop-semicolon.yml +0 -51
- package/rules/tree-sitter-queries/plsql-disabled/fetch-bulk-collect-limit.yml +0 -47
- package/rules/tree-sitter-queries/plsql-disabled/forallsave-exceptions.yml +0 -51
- package/rules/tree-sitter-queries/plsql-disabled/nchar-nvarchar2-bytes.yml +0 -54
- package/rules/tree-sitter-queries/plsql-disabled/not-null-initialization.yml +0 -59
- package/rules/tree-sitter-queries/plsql-disabled/raise-application-error-codes.yml +0 -50
- package/rules/tree-sitter-queries/typescript-disabled/ts-path-traversal.yml +0 -71
package/dist/mcp/server.js
CHANGED
|
@@ -24,13 +24,14 @@ import * as path from "node:path";
|
|
|
24
24
|
import { fileURLToPath } from "node:url";
|
|
25
25
|
import { AstGrepClient } from "../clients/ast-grep-client.js";
|
|
26
26
|
import { CacheManager } from "../clients/cache-manager.js";
|
|
27
|
-
import {
|
|
28
|
-
import { analyzeFile, analyzeFileFresh, createMcpHost, diagnosticStats, ensureLspConfig, ipcPathForCwd, lspStatus, moduleReport, projectReport, projectScan, readEnclosing, readSymbol, recentLatency, renderCompactModuleReport, renderCompactProjectReport, resolveRebuildScript, resourceFootprint, runRebuild, runSessionStart, runTurnEnd, scanTruncationNotice, summarizeScan, symbolSearch, } from "../clients/lens-engine.js";
|
|
27
|
+
import { getDegradationSummary, renderDegradationLines, } from "../clients/degradation-ledger.js";
|
|
28
|
+
import { acknowledgeTurnEnd, analyzeFile, analyzeFileFresh, canRebuildPiLens, createMcpHost, createWarmIpcLineReader, createWarmIpcRequestQueue, diagnosticStats, ensureLspConfig, generatedSkipNotice, ipcPathForCwd, lspStatus, moduleReport, projectReport, projectScan, readEnclosing, readSymbol, readTurnEndStatus, recentLatency, renderCompactModuleReport, renderCompactProjectReport, renderLspBrokenStatusLines, resolveRebuildScript, resourceFootprint, runRebuild, runSessionStart, runTurnEnd, runTurnEndForIpc, scanTruncationNotice, summarizeScan, symbolSearch, treeSitterRuntimeStatus, WARM_TURN_END_SCHEMA_VERSION, } from "../clients/lens-engine.js";
|
|
29
29
|
import { createAstGrepReplaceTool } from "../tools/ast-grep-replace.js";
|
|
30
30
|
import { createAstGrepSearchTool } from "../tools/ast-grep-search.js";
|
|
31
31
|
import { createLensDiagnosticsTool } from "../tools/lens-diagnostics.js";
|
|
32
32
|
import { createLspDiagnosticsTool } from "../tools/lsp-diagnostics.js";
|
|
33
33
|
import { createLspNavigationTool } from "../tools/lsp-navigation.js";
|
|
34
|
+
import { computeBuildStamp, STALE_SERVED_BY_FRESH, STALE_WARN_ONLY, StalenessGate, stalenessCheckEnabled, } from "./build-staleness.js";
|
|
34
35
|
// Any stray stdout write corrupts the JSON-RPC stream; force it onto stderr.
|
|
35
36
|
console.log = (...args) => {
|
|
36
37
|
console.error(...args);
|
|
@@ -75,7 +76,21 @@ const REBUILD_SCRIPT = resolveRebuildScript(SERVER_FILE);
|
|
|
75
76
|
// under parallel vitest execution.
|
|
76
77
|
const STALENESS_STAT_PATH = process.env.PI_LENS_MCP_STALENESS_STAT_PATH ?? SERVER_FILE;
|
|
77
78
|
const BUILD_STAMP = computeBuildStamp(STALENESS_STAT_PATH);
|
|
78
|
-
|
|
79
|
+
// `PI_LENS_MCP_STALENESS_INTERVAL_MS` (test-only override): shrinks the
|
|
80
|
+
// gate's re-stat throttle below its 1000ms default so a staleness smoke test
|
|
81
|
+
// doesn't have to sleep out a full second per assertion. Parsed explicitly
|
|
82
|
+
// rather than `Number(raw) || undefined` — that idiom would coerce a real
|
|
83
|
+
// "0" override (disable the throttle entirely) back to the 1000ms default.
|
|
84
|
+
const stalenessIntervalOverride = (() => {
|
|
85
|
+
const raw = process.env.PI_LENS_MCP_STALENESS_INTERVAL_MS;
|
|
86
|
+
if (raw === undefined)
|
|
87
|
+
return undefined;
|
|
88
|
+
const parsed = Number(raw);
|
|
89
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
|
|
90
|
+
})();
|
|
91
|
+
const STALENESS_GATE = new StalenessGate(BUILD_STAMP, {
|
|
92
|
+
checkIntervalMs: stalenessIntervalOverride,
|
|
93
|
+
});
|
|
79
94
|
/**
|
|
80
95
|
* True when the warm server's loaded code is older than what's on disk right
|
|
81
96
|
* now. Mtime-gated (at most one `fs.stat` per second, like the #492
|
|
@@ -108,7 +123,11 @@ function findRepoRoot(start) {
|
|
|
108
123
|
}
|
|
109
124
|
return path.resolve(start, "..", "..");
|
|
110
125
|
}
|
|
111
|
-
|
|
126
|
+
// Test-only override lets the stdio smoke exercise the published-package shape
|
|
127
|
+
// without copying the whole compiled server tree under node_modules.
|
|
128
|
+
const REPO_ROOT = process.env.PI_LENS_MCP_REPO_ROOT
|
|
129
|
+
? path.resolve(process.env.PI_LENS_MCP_REPO_ROOT)
|
|
130
|
+
: findRepoRoot(SERVER_DIR);
|
|
112
131
|
async function ensureReady(cwd) {
|
|
113
132
|
const normalized = path.resolve(cwd);
|
|
114
133
|
if (lspReadyCwds.has(normalized))
|
|
@@ -173,6 +192,32 @@ function getAutoSessionStatus() {
|
|
|
173
192
|
// runs in THIS warm process (LSP-complete) instead of a cold hook process.
|
|
174
193
|
// Responses go over the socket — never stdout — so the MCP stream is untouched.
|
|
175
194
|
const IPC_PATH = ipcPathForCwd(DEFAULT_CWD);
|
|
195
|
+
/**
|
|
196
|
+
* #1273: the turn-end route runs the TARGET directory's configured test runner
|
|
197
|
+
* and spins up an LSP for it, from a client-supplied `cwd`. Every legitimate
|
|
198
|
+
* client derived the socket path from that same cwd, so a workspace check costs
|
|
199
|
+
* nothing legitimate — but without it, anything that can reach this endpoint
|
|
200
|
+
* (same-uid processes, a permissive-umask shared host, a Windows named pipe's
|
|
201
|
+
* more generous default DACL) can make the server execute an arbitrary
|
|
202
|
+
* directory's test suite. "Lint one file" and "run this directory's test suite"
|
|
203
|
+
* are different blast radii, so the guard is on turn-end and its ack.
|
|
204
|
+
*/
|
|
205
|
+
function rejectForeignTurnCwd(route, cwd) {
|
|
206
|
+
const message = `${route} cwd outside this server's workspace: ${path.resolve(cwd)} not within ${DEFAULT_CWD}`;
|
|
207
|
+
console.error(`[pi-lens-mcp] rejected ${message}`);
|
|
208
|
+
return message;
|
|
209
|
+
}
|
|
210
|
+
function isWithinServerWorkspace(cwd) {
|
|
211
|
+
const target = path.resolve(cwd);
|
|
212
|
+
// `path.relative` applies the platform's own path semantics (including
|
|
213
|
+
// win32 case-insensitivity), which is what "inside the workspace" means
|
|
214
|
+
// here — do NOT hand-roll a case fold. An empty result means `target`
|
|
215
|
+
// IS `DEFAULT_CWD` (win32 can reach that via a pure case/drive-letter
|
|
216
|
+
// difference, since path.relative there is case-insensitive) and must
|
|
217
|
+
// be accepted, not rejected — do NOT reintroduce a `rel !== ""` guard.
|
|
218
|
+
const rel = path.relative(DEFAULT_CWD, target);
|
|
219
|
+
return !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
220
|
+
}
|
|
176
221
|
function startIpcServer() {
|
|
177
222
|
// POSIX: a stale socket file blocks listen; remove it first. (Named pipes on
|
|
178
223
|
// Windows don't need this.)
|
|
@@ -184,33 +229,77 @@ function startIpcServer() {
|
|
|
184
229
|
// no stale socket — fine
|
|
185
230
|
}
|
|
186
231
|
}
|
|
232
|
+
const requestQueue = createWarmIpcRequestQueue();
|
|
187
233
|
const ipc = net.createServer((socket) => {
|
|
188
234
|
socket.setEncoding("utf8");
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const line = buffer.slice(0, newline);
|
|
196
|
-
void (async () => {
|
|
235
|
+
// One-shot per connection (#1219): clients write exactly one request and
|
|
236
|
+
// read one reply, so a handler that kept re-reading the same buffered
|
|
237
|
+
// line re-dispatched the request on stray bytes. The reader consumes the
|
|
238
|
+
// line and ignores anything after it.
|
|
239
|
+
socket.on("data", createWarmIpcLineReader((line) => {
|
|
240
|
+
void requestQueue.enqueue(async () => {
|
|
197
241
|
try {
|
|
198
|
-
// #535: the PostToolUse hook bin (mcp/analyze-cli.ts) already treats
|
|
199
|
-
// ANY error response as "no usable warm server" and falls back to
|
|
200
|
-
// its own cold, load-fresh-from-disk analysis path — so on a stale
|
|
201
|
-
// warm build, replying with an error IS the fresh-fork behavior for
|
|
202
|
-
// this channel, for free. No separate fresh-fork plumbing needed
|
|
203
|
-
// here: the client-side fallback already loads current code.
|
|
204
242
|
if (isWarmBuildStale()) {
|
|
205
|
-
console.error("[pi-lens-mcp] warm
|
|
206
|
-
socket.end(`${JSON.stringify({ error: "warm build stale
|
|
243
|
+
console.error("[pi-lens-mcp] warm request: build stale, replying with an error");
|
|
244
|
+
socket.end(`${JSON.stringify({ error: "warm build stale" })}\n`);
|
|
207
245
|
return;
|
|
208
246
|
}
|
|
209
|
-
const
|
|
247
|
+
const parsed = JSON.parse(line);
|
|
248
|
+
if (parsed.route === "turn-end-ack") {
|
|
249
|
+
if (parsed.version !== WARM_TURN_END_SCHEMA_VERSION ||
|
|
250
|
+
typeof parsed.deliveryId !==
|
|
251
|
+
"string") {
|
|
252
|
+
socket.end(`${JSON.stringify({ error: `turn-end ack schema ${parsed.version} != ${WARM_TURN_END_SCHEMA_VERSION}` })}\n`);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const turnCwd = parsed.cwd ?? DEFAULT_CWD;
|
|
256
|
+
if (!isWithinServerWorkspace(turnCwd)) {
|
|
257
|
+
socket.end(`${JSON.stringify({ error: rejectForeignTurnCwd("turn-end-ack", turnCwd) })}\n`);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const acknowledged = acknowledgeTurnEnd(turnCwd, parsed.deliveryId);
|
|
261
|
+
socket.end(`${JSON.stringify({
|
|
262
|
+
result: {
|
|
263
|
+
route: "turn-end-ack",
|
|
264
|
+
version: WARM_TURN_END_SCHEMA_VERSION,
|
|
265
|
+
acknowledged,
|
|
266
|
+
},
|
|
267
|
+
})}\n`);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (parsed.route === "turn-end") {
|
|
271
|
+
if (parsed.version !== WARM_TURN_END_SCHEMA_VERSION) {
|
|
272
|
+
socket.end(`${JSON.stringify({ error: `turn-end schema ${parsed.version} != ${WARM_TURN_END_SCHEMA_VERSION}` })}\n`);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const turnCwd = parsed.cwd ?? DEFAULT_CWD;
|
|
276
|
+
if (!isWithinServerWorkspace(turnCwd)) {
|
|
277
|
+
socket.end(`${JSON.stringify({ error: rejectForeignTurnCwd("turn-end", turnCwd) })}\n`);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
// #1274: a client that already gave up (hook timeout, killed
|
|
281
|
+
// Claude Code) leaves a destroyed socket. Starting a heavy
|
|
282
|
+
// pass whose reply nobody can read is pure waste; the findings
|
|
283
|
+
// stay in the cache for the next Stop either way.
|
|
284
|
+
if (socket.destroyed) {
|
|
285
|
+
console.error(`[pi-lens-mcp] warm turn-end: client gone before the pass started (${turnCwd})`);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
console.error(`[pi-lens-mcp] warm turn-end: ${turnCwd}`);
|
|
289
|
+
await ensureReady(turnCwd);
|
|
290
|
+
const delivery = await runTurnEndForIpc(turnCwd);
|
|
291
|
+
const result = {
|
|
292
|
+
route: "turn-end",
|
|
293
|
+
version: WARM_TURN_END_SCHEMA_VERSION,
|
|
294
|
+
turnEnd: delivery.outcome.turnEnd,
|
|
295
|
+
tests: delivery.outcome.tests,
|
|
296
|
+
deliveryId: delivery.deliveryId,
|
|
297
|
+
};
|
|
298
|
+
socket.end(`${JSON.stringify({ result })}\n`);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const req = parsed;
|
|
210
302
|
console.error(`[pi-lens-mcp] warm analyze: ${req.file}`);
|
|
211
|
-
// Warm = full LSP + an edit-detection path (register turn-state) +
|
|
212
|
-
// review-graph maintenance (#536 — this is an in-process, long-lived
|
|
213
|
-
// path, unlike the ephemeral `fresh` worker).
|
|
214
303
|
const result = await analyzeFile(req.file, req.cwd, {
|
|
215
304
|
registerTurnState: true,
|
|
216
305
|
updateGraph: true,
|
|
@@ -220,8 +309,8 @@ function startIpcServer() {
|
|
|
220
309
|
catch (err) {
|
|
221
310
|
socket.end(`${JSON.stringify({ error: String(err) })}\n`);
|
|
222
311
|
}
|
|
223
|
-
})
|
|
224
|
-
});
|
|
312
|
+
});
|
|
313
|
+
}));
|
|
225
314
|
socket.on("error", () => socket.destroy());
|
|
226
315
|
});
|
|
227
316
|
ipc.on("error", (err) => {
|
|
@@ -352,7 +441,7 @@ function schemaWithCwd(parameters) {
|
|
|
352
441
|
...(p.required ? { required: p.required } : {}),
|
|
353
442
|
};
|
|
354
443
|
}
|
|
355
|
-
const
|
|
444
|
+
const ALL_TOOLS = [
|
|
356
445
|
{
|
|
357
446
|
name: "pilens_analyze",
|
|
358
447
|
description: "Run pi-lens's per-edit dispatch pipeline (LSP + linters + structural " +
|
|
@@ -377,7 +466,7 @@ const TOOLS = [
|
|
|
377
466
|
},
|
|
378
467
|
flags: {
|
|
379
468
|
type: "object",
|
|
380
|
-
description:
|
|
469
|
+
description: 'Optional pi-lens flag overrides for this run, e.g. {"no-lsp": true} to bench the non-LSP path.',
|
|
381
470
|
},
|
|
382
471
|
},
|
|
383
472
|
required: ["file"],
|
|
@@ -397,8 +486,14 @@ const TOOLS = [
|
|
|
397
486
|
inputSchema: {
|
|
398
487
|
type: "object",
|
|
399
488
|
properties: {
|
|
400
|
-
limit: {
|
|
401
|
-
|
|
489
|
+
limit: {
|
|
490
|
+
type: "number",
|
|
491
|
+
description: "Max reports to return (default 5).",
|
|
492
|
+
},
|
|
493
|
+
file: {
|
|
494
|
+
type: "string",
|
|
495
|
+
description: "Only reports whose path ends with this.",
|
|
496
|
+
},
|
|
402
497
|
},
|
|
403
498
|
},
|
|
404
499
|
},
|
|
@@ -421,6 +516,13 @@ const TOOLS = [
|
|
|
421
516
|
properties: {
|
|
422
517
|
cwd: { type: "string" },
|
|
423
518
|
maxFiles: { type: "number", description: "Cap files scanned." },
|
|
519
|
+
includeGenerated: {
|
|
520
|
+
type: "boolean",
|
|
521
|
+
description: "Scan WITHOUT the generated/artifact NAME-heuristic filter " +
|
|
522
|
+
"(lockfiles, gen.ts-style names, generated/ dirs, …). Default " +
|
|
523
|
+
"false. Use when a scan's 'excluded by generated-name " +
|
|
524
|
+
"heuristics' notice suggests a real file was skipped.",
|
|
525
|
+
},
|
|
424
526
|
},
|
|
425
527
|
},
|
|
426
528
|
},
|
|
@@ -477,7 +579,9 @@ const TOOLS = [
|
|
|
477
579
|
"(this path never calls LSP). `semantic.source` reports whether graph " +
|
|
478
580
|
"data was used. Pass `blastRadius: true` for the cross-file blast radius " +
|
|
479
581
|
"(transitive dependents as ranked file reads, read-only over the cached " +
|
|
480
|
-
|
|
582
|
+
"graph). Pass `callGraph: true` for bounded derived callers/callees; a " +
|
|
583
|
+
"cold or stale call-graph cache is reported explicitly, never as zero calls. " +
|
|
584
|
+
'`view: "compact"` returns a line-oriented text rendering ' +
|
|
481
585
|
"(cheapest option) instead of JSON. An outline shows shape, not bodies.",
|
|
482
586
|
inputSchema: {
|
|
483
587
|
type: "object",
|
|
@@ -508,6 +612,14 @@ const TOOLS = [
|
|
|
508
612
|
type: "number",
|
|
509
613
|
description: "Max hops for the blast-radius walk (default 3). Only used with blastRadius.",
|
|
510
614
|
},
|
|
615
|
+
callGraph: {
|
|
616
|
+
type: "boolean",
|
|
617
|
+
description: "Include bounded derived callers/callees from the cached FunctionCallGraph; cold or stale cache state is explicit.",
|
|
618
|
+
},
|
|
619
|
+
maxCallGraphEntries: {
|
|
620
|
+
type: "number",
|
|
621
|
+
description: "Per-direction cap for call-graph relations (default 20).",
|
|
622
|
+
},
|
|
511
623
|
},
|
|
512
624
|
required: ["file"],
|
|
513
625
|
},
|
|
@@ -667,8 +779,11 @@ const TOOLS = [
|
|
|
667
779
|
{
|
|
668
780
|
name: "pilens_ast_grep_search",
|
|
669
781
|
description: "Structural (AST) code search via ast-grep — match by code structure, not " +
|
|
670
|
-
"text. Use
|
|
671
|
-
"'function $NAME() { $$$ }')
|
|
782
|
+
"text. Use complete patterns with arguments (e.g. 'console.log($MSG)', " +
|
|
783
|
+
"'function $NAME($$$ARGS) { $$$BODY }'), or use nodeKind to find every " +
|
|
784
|
+
"node of a known grammar kind. Metavariables do not match text inside " +
|
|
785
|
+
"quoted string literals; use an exact string or grep for wildcard text. " +
|
|
786
|
+
"This is the same schema and synthesis path as the pi tool.",
|
|
672
787
|
inputSchema: schemaWithCwd(astGrepSearchTool.parameters),
|
|
673
788
|
},
|
|
674
789
|
{
|
|
@@ -695,6 +810,12 @@ const TOOLS = [
|
|
|
695
810
|
inputSchema: schemaWithCwd(lspDiagnosticsTool.parameters),
|
|
696
811
|
},
|
|
697
812
|
];
|
|
813
|
+
// #920: published packages cannot rebuild themselves safely because their
|
|
814
|
+
// build config is intentionally not shipped. Do not advertise the destructive
|
|
815
|
+
// capability to clients/subagent allowlists; callTool retains its own guard.
|
|
816
|
+
const TOOLS = canRebuildPiLens(REPO_ROOT)
|
|
817
|
+
? ALL_TOOLS
|
|
818
|
+
: ALL_TOOLS.filter((tool) => tool.name !== "pilens_rebuild");
|
|
698
819
|
function formatAnalyze(result, cwd, mode, servedBy) {
|
|
699
820
|
// Surface the LSP outcome so a cold/indexing server's "0" is never silently
|
|
700
821
|
// read as "clean" — a known limit on large projects (warm mode / re-run once
|
|
@@ -715,7 +836,10 @@ async function callTool(name, args) {
|
|
|
715
836
|
if (name === "pilens_analyze") {
|
|
716
837
|
const file = args.file;
|
|
717
838
|
if (typeof file !== "string" || file.length === 0) {
|
|
718
|
-
return {
|
|
839
|
+
return {
|
|
840
|
+
...toolText("pilens_analyze requires a 'file' string."),
|
|
841
|
+
isError: true,
|
|
842
|
+
};
|
|
719
843
|
}
|
|
720
844
|
const cwd = typeof args.cwd === "string" ? args.cwd : DEFAULT_CWD;
|
|
721
845
|
const requestedMode = args.mode === "fresh" ? "fresh" : "warm";
|
|
@@ -759,6 +883,16 @@ async function callTool(name, args) {
|
|
|
759
883
|
}
|
|
760
884
|
if (name === "pilens_rebuild") {
|
|
761
885
|
const outcome = await runRebuild(REPO_ROOT, REBUILD_SCRIPT);
|
|
886
|
+
if (!outcome.packageManager) {
|
|
887
|
+
return {
|
|
888
|
+
...toolText(outcome.output, {
|
|
889
|
+
ok: false,
|
|
890
|
+
script: outcome.script,
|
|
891
|
+
repoRoot: REPO_ROOT,
|
|
892
|
+
}),
|
|
893
|
+
isError: true,
|
|
894
|
+
};
|
|
895
|
+
}
|
|
762
896
|
const runCmd = `${outcome.packageManager} run ${outcome.script}`;
|
|
763
897
|
const headline = outcome.ok
|
|
764
898
|
? `✓ rebuild succeeded (${runCmd}, ${outcome.durationMs}ms). Fresh analyses now reflect the latest build.`
|
|
@@ -780,7 +914,8 @@ async function callTool(name, args) {
|
|
|
780
914
|
const maxFiles = typeof args.maxFiles === "number" && Number.isFinite(args.maxFiles)
|
|
781
915
|
? Math.max(1, Math.floor(args.maxFiles))
|
|
782
916
|
: undefined;
|
|
783
|
-
const
|
|
917
|
+
const includeGenerated = args.includeGenerated === true;
|
|
918
|
+
const snapshot = await projectScan(cwd, maxFiles, includeGenerated);
|
|
784
919
|
// #747: a cwd at/above $HOME refuses to walk — say so instead of letting
|
|
785
920
|
// "Scanned 0 file(s) → 0 diagnostics" read as a clean project.
|
|
786
921
|
if (snapshot.unsafeRoot) {
|
|
@@ -806,6 +941,11 @@ async function callTool(name, args) {
|
|
|
806
941
|
const truncationNotice = scanTruncationNotice(snapshot);
|
|
807
942
|
if (truncationNotice)
|
|
808
943
|
summaryLines.push(truncationNotice);
|
|
944
|
+
// #1107 phase 2: same "reached the seam but nothing rendered it" gap as
|
|
945
|
+
// #784's scanTruncationNotice, for the generated-name skip counters.
|
|
946
|
+
const skipNotice = generatedSkipNotice(snapshot);
|
|
947
|
+
if (skipNotice)
|
|
948
|
+
summaryLines.push(skipNotice);
|
|
809
949
|
return toolText(summaryLines.join("\n"), {
|
|
810
950
|
filesScanned: snapshot.filesScanned,
|
|
811
951
|
runners: snapshot.runners,
|
|
@@ -815,6 +955,18 @@ async function callTool(name, args) {
|
|
|
815
955
|
topFiles,
|
|
816
956
|
sample: deduped.slice(0, 40),
|
|
817
957
|
...(snapshot.scanTruncated ? { scanTruncated: true } : {}),
|
|
958
|
+
...(snapshot.treeSitterStatus
|
|
959
|
+
? { treeSitterStatus: snapshot.treeSitterStatus }
|
|
960
|
+
: {}),
|
|
961
|
+
...(snapshot.generatedFileSkips
|
|
962
|
+
? { generatedFileSkips: snapshot.generatedFileSkips }
|
|
963
|
+
: {}),
|
|
964
|
+
...(snapshot.generatedNameOnlySkips
|
|
965
|
+
? { generatedNameOnlySkips: snapshot.generatedNameOnlySkips }
|
|
966
|
+
: {}),
|
|
967
|
+
...(snapshot.generatedDirSkips
|
|
968
|
+
? { generatedDirSkips: snapshot.generatedDirSkips }
|
|
969
|
+
: {}),
|
|
818
970
|
});
|
|
819
971
|
}
|
|
820
972
|
if (name === "pilens_symbol_search") {
|
|
@@ -829,16 +981,22 @@ async function callTool(name, args) {
|
|
|
829
981
|
? args.paths.filter((p) => typeof p === "string")
|
|
830
982
|
: undefined;
|
|
831
983
|
const lang = typeof args.lang === "string" ? args.lang : undefined;
|
|
832
|
-
const { available, results, hint, snapshotGeneratedAt } = await symbolSearch(query, cwd, limit, { paths, lang });
|
|
984
|
+
const { available, results, hint, unavailableReason, coverage, snapshotGeneratedAt, } = await symbolSearch(query, cwd, limit, { paths, lang });
|
|
833
985
|
if (!available) {
|
|
834
|
-
return toolText(hint ??
|
|
986
|
+
return toolText(hint ??
|
|
987
|
+
"No word index for this workspace yet — run pilens_session_start first.", { available: false, query, hint, unavailableReason }, true);
|
|
835
988
|
}
|
|
836
989
|
const stalenessNote = graphStalenessNote(snapshotGeneratedAt, "Project snapshot");
|
|
837
990
|
if (results.length === 0) {
|
|
838
|
-
return toolText(`No files matched "${query}".` +
|
|
991
|
+
return toolText(`No files matched "${query}".` +
|
|
992
|
+
(coverage
|
|
993
|
+
? `\nIndex covers ${coverage.files} files${coverage.truncated ? " (capped — results may be incomplete)" : ""}.`
|
|
994
|
+
: "") +
|
|
995
|
+
(stalenessNote ? `\n\n${stalenessNote}` : ""), {
|
|
839
996
|
available: true,
|
|
840
997
|
query,
|
|
841
998
|
results: [],
|
|
999
|
+
coverage,
|
|
842
1000
|
...(stalenessNote ? { staleness: stalenessNote } : {}),
|
|
843
1001
|
}, true);
|
|
844
1002
|
}
|
|
@@ -848,6 +1006,11 @@ async function callTool(name, args) {
|
|
|
848
1006
|
`(score ${result.score.toFixed(2)}, ${result.hits} hit(s), ` +
|
|
849
1007
|
`line ${result.startLine})`),
|
|
850
1008
|
...(stalenessNote ? ["", stalenessNote] : []),
|
|
1009
|
+
...(coverage
|
|
1010
|
+
? [
|
|
1011
|
+
`Index covers ${coverage.files} files${coverage.truncated ? " (capped — results may be incomplete)" : ""}.`,
|
|
1012
|
+
]
|
|
1013
|
+
: []),
|
|
851
1014
|
];
|
|
852
1015
|
// Compact (unindented) JSON — matches the module_report / read_symbol
|
|
853
1016
|
// convention (#517): an agent parses this payload, it doesn't read it
|
|
@@ -855,6 +1018,7 @@ async function callTool(name, args) {
|
|
|
855
1018
|
// `read` block — startLine/endLine already derive offset/limit.
|
|
856
1019
|
return toolText(lines.join("\n"), {
|
|
857
1020
|
query,
|
|
1021
|
+
coverage,
|
|
858
1022
|
results: results.map((result) => {
|
|
859
1023
|
const relFile = path.relative(cwd, result.file);
|
|
860
1024
|
return {
|
|
@@ -886,12 +1050,21 @@ async function callTool(name, args) {
|
|
|
886
1050
|
Number.isFinite(args.blastRadiusDepth)
|
|
887
1051
|
? Math.max(1, Math.floor(args.blastRadiusDepth))
|
|
888
1052
|
: undefined;
|
|
889
|
-
const
|
|
1053
|
+
const callGraph = args.callGraph === true;
|
|
1054
|
+
const maxCallGraphEntries = typeof args.maxCallGraphEntries === "number" &&
|
|
1055
|
+
Number.isFinite(args.maxCallGraphEntries)
|
|
1056
|
+
? Math.max(1, Math.floor(args.maxCallGraphEntries))
|
|
1057
|
+
: undefined;
|
|
1058
|
+
const view = args.view === "summary" || args.view === "compact"
|
|
1059
|
+
? args.view
|
|
1060
|
+
: undefined;
|
|
890
1061
|
const focus = typeof args.focus === "string" ? args.focus : undefined;
|
|
891
1062
|
const report = await moduleReport(file, cwd, {
|
|
892
1063
|
maxRefsPerSymbol,
|
|
893
1064
|
blastRadius,
|
|
894
1065
|
blastRadiusDepth,
|
|
1066
|
+
callGraph,
|
|
1067
|
+
maxCallGraphEntries,
|
|
895
1068
|
view,
|
|
896
1069
|
focus,
|
|
897
1070
|
});
|
|
@@ -912,14 +1085,18 @@ async function callTool(name, args) {
|
|
|
912
1085
|
content: [
|
|
913
1086
|
{
|
|
914
1087
|
type: "text",
|
|
915
|
-
text: graphStaleness
|
|
1088
|
+
text: graphStaleness
|
|
1089
|
+
? `${compactText}\n\n${graphStaleness}`
|
|
1090
|
+
: compactText,
|
|
916
1091
|
},
|
|
917
1092
|
],
|
|
918
1093
|
};
|
|
919
1094
|
}
|
|
920
1095
|
// Compact (unindented) JSON — matches the pi tool's mirror (#512); an
|
|
921
1096
|
// agent parses this payload, it doesn't read it formatted.
|
|
922
|
-
return toolText(summary, graphStaleness
|
|
1097
|
+
return toolText(summary, graphStaleness
|
|
1098
|
+
? { ...report, graphStalenessNote: graphStaleness }
|
|
1099
|
+
: report, true);
|
|
923
1100
|
}
|
|
924
1101
|
if (name === "pilens_project_report") {
|
|
925
1102
|
const cwd = typeof args.cwd === "string" ? args.cwd : DEFAULT_CWD;
|
|
@@ -944,7 +1121,9 @@ async function callTool(name, args) {
|
|
|
944
1121
|
content: [
|
|
945
1122
|
{
|
|
946
1123
|
type: "text",
|
|
947
|
-
text: graphStaleness
|
|
1124
|
+
text: graphStaleness
|
|
1125
|
+
? `${compactText}\n\n${graphStaleness}`
|
|
1126
|
+
: compactText,
|
|
948
1127
|
},
|
|
949
1128
|
],
|
|
950
1129
|
};
|
|
@@ -953,7 +1132,9 @@ async function callTool(name, args) {
|
|
|
953
1132
|
`${report.entryPoints?.length ?? 0} entry point(s), ` +
|
|
954
1133
|
`${report.riskHotspots?.length ?? 0} risk hotspot(s)` +
|
|
955
1134
|
(graphStaleness ? `\n\n${graphStaleness}` : "");
|
|
956
|
-
return toolText(summary, graphStaleness
|
|
1135
|
+
return toolText(summary, graphStaleness
|
|
1136
|
+
? { ...report, graphStalenessNote: graphStaleness }
|
|
1137
|
+
: report, true);
|
|
957
1138
|
}
|
|
958
1139
|
if (name === "pilens_read_symbol") {
|
|
959
1140
|
const file = typeof args.file === "string" ? args.file : "";
|
|
@@ -984,13 +1165,20 @@ async function callTool(name, args) {
|
|
|
984
1165
|
? ` (${result.ambiguous.count} matches — returned the ${result.kind}; pass \`kind\` to disambiguate: ${result.ambiguous.kinds.join(", ")})`
|
|
985
1166
|
: "";
|
|
986
1167
|
const header = `${result.kind} ${result.name}${ambiguityNote}${sigSuffix} ${path.relative(cwd, result.path)}:${result.startLine}-${result.endLine}`;
|
|
987
|
-
return {
|
|
1168
|
+
return {
|
|
1169
|
+
content: [
|
|
1170
|
+
{ type: "text", text: `${header}\n\n${result.source ?? ""}` },
|
|
1171
|
+
],
|
|
1172
|
+
};
|
|
988
1173
|
}
|
|
989
1174
|
if (name === "pilens_read_enclosing") {
|
|
990
1175
|
const file = typeof args.file === "string" ? args.file : "";
|
|
991
1176
|
const line = typeof args.line === "number" ? args.line : Number.NaN;
|
|
992
1177
|
if (!file.trim() || !Number.isFinite(line)) {
|
|
993
|
-
return {
|
|
1178
|
+
return {
|
|
1179
|
+
...toolText("Provide `file` and a numeric `line`."),
|
|
1180
|
+
isError: true,
|
|
1181
|
+
};
|
|
994
1182
|
}
|
|
995
1183
|
const cwd = typeof args.cwd === "string" ? args.cwd : DEFAULT_CWD;
|
|
996
1184
|
const kinds = Array.isArray(args.kinds)
|
|
@@ -1034,19 +1222,39 @@ async function callTool(name, args) {
|
|
|
1034
1222
|
? `${result.startLine}-${result.endLine} (partial of ${result.enclosingStartLine}-${result.enclosingEndLine})`
|
|
1035
1223
|
: `${result.startLine}-${result.endLine}`;
|
|
1036
1224
|
const header = `${result.kind} ${result.name} ${path.relative(cwd, result.path)}:${range}`;
|
|
1037
|
-
return {
|
|
1225
|
+
return {
|
|
1226
|
+
content: [
|
|
1227
|
+
{ type: "text", text: `${header}\n\n${result.source ?? ""}` },
|
|
1228
|
+
],
|
|
1229
|
+
};
|
|
1038
1230
|
}
|
|
1039
1231
|
if (name === "pilens_health") {
|
|
1040
|
-
const { aliveClients, servers } = lspStatus();
|
|
1232
|
+
const { aliveClients, servers, brokenServers } = lspStatus();
|
|
1041
1233
|
const last = recentLatency(1)[0];
|
|
1042
1234
|
const stats = diagnosticStats();
|
|
1043
1235
|
const autoSession = getAutoSessionStatus();
|
|
1236
|
+
const treeSitter = treeSitterRuntimeStatus();
|
|
1237
|
+
const degradations = getDegradationSummary();
|
|
1238
|
+
// #1272, following the #544 precedent: the Stop hook is a separate,
|
|
1239
|
+
// short-lived process, so a turn-end it skipped left no trace here at all
|
|
1240
|
+
// — a dead integration looked exactly like a clean turn, forever. The
|
|
1241
|
+
// hook records its outcome per workspace; this is where it becomes
|
|
1242
|
+
// visible without `claude --debug` log spelunking.
|
|
1243
|
+
const turnEnd = readTurnEndStatus(DEFAULT_CWD) ?? null;
|
|
1044
1244
|
// #620: best-effort — a footprint read failure must never break the rest
|
|
1045
1245
|
// of pilens_health's (much older, more load-bearing) reporting.
|
|
1046
1246
|
const footprint = await resourceFootprint().catch(() => null);
|
|
1047
1247
|
const lines = [
|
|
1248
|
+
treeSitter.wasmAborted
|
|
1249
|
+
? `Tree-sitter: DEGRADED — WASM runtime aborted${treeSitter.abortedAt ? ` at ${treeSitter.abortedAt}` : ""}; restart this server to recover`
|
|
1250
|
+
: "Tree-sitter: available",
|
|
1048
1251
|
`LSP: ${aliveClients} alive client(s)`,
|
|
1049
|
-
...servers.
|
|
1252
|
+
...servers.flatMap((server) => [
|
|
1253
|
+
` ${server.connected ? "✓" : "✗"} ${server.serverId} (${server.root})`,
|
|
1254
|
+
...server.pullFailureHistory.slice(-1).map((failure) => ` ⚠ diagnostics pull failed: ${failure.method}${failure.code !== undefined ? ` (${failure.code})` : ""} — ${failure.message.length > 200 ? `${failure.message.slice(0, 200)}…` : failure.message}`),
|
|
1255
|
+
]),
|
|
1256
|
+
...renderLspBrokenStatusLines(brokenServers),
|
|
1257
|
+
...renderDegradationLines(degradations),
|
|
1050
1258
|
last
|
|
1051
1259
|
? `Last dispatch: ${path.basename(last.filePath)} — ${last.totalDurationMs}ms, ${last.totalDiagnostics} diagnostic(s)`
|
|
1052
1260
|
: "Last dispatch: none yet",
|
|
@@ -1054,6 +1262,11 @@ async function callTool(name, args) {
|
|
|
1054
1262
|
autoSession
|
|
1055
1263
|
? `Auto session_start: ${autoSession.succeeded ? "succeeded" : autoSession.error ? "FAILED" : autoSession.attempted ? "in progress" : "not yet attempted"}${autoSession.firedAt ? ` (fired ${autoSession.firedAt})` : ""}${autoSession.error ? ` — ${autoSession.error}` : ""}`
|
|
1056
1264
|
: "Auto session_start: disabled (PI_LENS_MCP_AUTO_SESSION not set)",
|
|
1265
|
+
turnEnd
|
|
1266
|
+
? `Stop-hook turn-end: ${turnEnd.ran} ran · ${turnEnd.skipped} skipped` +
|
|
1267
|
+
`${turnEnd.lastRunAt ? ` (last ran ${turnEnd.lastRunAt})` : ""}` +
|
|
1268
|
+
`${turnEnd.lastSkipReason ? ` — last skip: ${turnEnd.lastSkipReason}${turnEnd.lastSkipAt ? ` at ${turnEnd.lastSkipAt}` : ""}` : ""}`
|
|
1269
|
+
: "Stop-hook turn-end: no activity recorded (hook not installed, or no Stop yet)",
|
|
1057
1270
|
footprint
|
|
1058
1271
|
? `Resource footprint: ${footprint.instanceCount} pi-lens instance(s) · ` +
|
|
1059
1272
|
`${(footprint.totalRssBytes / 1024 / 1024).toFixed(0)}MB RSS · ` +
|
|
@@ -1064,6 +1277,7 @@ async function callTool(name, args) {
|
|
|
1064
1277
|
return toolText(lines.join("\n"), {
|
|
1065
1278
|
aliveClients,
|
|
1066
1279
|
servers,
|
|
1280
|
+
brokenServers,
|
|
1067
1281
|
lastDispatch: last
|
|
1068
1282
|
? {
|
|
1069
1283
|
filePath: last.filePath,
|
|
@@ -1077,7 +1291,10 @@ async function callTool(name, args) {
|
|
|
1077
1291
|
unresolved: stats.totalUnresolved,
|
|
1078
1292
|
},
|
|
1079
1293
|
autoSession,
|
|
1294
|
+
treeSitter,
|
|
1295
|
+
turnEnd,
|
|
1080
1296
|
resourceFootprint: footprint,
|
|
1297
|
+
degradations,
|
|
1081
1298
|
});
|
|
1082
1299
|
}
|
|
1083
1300
|
if (name === "pilens_diagnostics") {
|
|
@@ -1131,7 +1348,9 @@ async function callTool(name, args) {
|
|
|
1131
1348
|
}
|
|
1132
1349
|
if (name === "pilens_ast_grep_search" || name === "pilens_ast_grep_replace") {
|
|
1133
1350
|
const cwd = typeof args.cwd === "string" ? args.cwd : DEFAULT_CWD;
|
|
1134
|
-
const tool = name === "pilens_ast_grep_search"
|
|
1351
|
+
const tool = name === "pilens_ast_grep_search"
|
|
1352
|
+
? astGrepSearchTool
|
|
1353
|
+
: astGrepReplaceTool;
|
|
1135
1354
|
const out = (await tool.execute("mcp", args, new AbortController().signal, undefined, { cwd }));
|
|
1136
1355
|
return { content: out.content };
|
|
1137
1356
|
}
|
|
@@ -1215,7 +1434,10 @@ function withStaleWarning(result) {
|
|
|
1215
1434
|
...result,
|
|
1216
1435
|
content: [
|
|
1217
1436
|
...result.content.slice(0, -1),
|
|
1218
|
-
{
|
|
1437
|
+
{
|
|
1438
|
+
...last,
|
|
1439
|
+
text: `${last.text}\n\nwarmCodeStale: true\n${STALE_WARN_ONLY}`,
|
|
1440
|
+
},
|
|
1219
1441
|
],
|
|
1220
1442
|
};
|
|
1221
1443
|
}
|