@herbertgao/pi-extensions 2026.8.4 → 2026.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +11 -3
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +11 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +53 -14
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +18 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
- package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +20 -4
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
- package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +15 -6
- package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
- package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +9 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +2 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +20 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +6 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
- package/node_modules/@narumitw/pi-btw/README.md +32 -24
- package/node_modules/@narumitw/pi-btw/package.json +6 -6
- package/node_modules/@narumitw/pi-btw/src/btw.ts +4 -2
- package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -13
- package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
- package/node_modules/pi-lens/CHANGELOG.md +2535 -954
- package/node_modules/pi-lens/README.md +13 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +314 -0
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +14 -7
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
- package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
- package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
- package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
- package/node_modules/pi-lens/dist/clients/biome-client.js +136 -24
- package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
- package/node_modules/pi-lens/dist/clients/bus-publish.js +19 -4
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
- package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
- package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
- package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
- package/node_modules/pi-lens/dist/clients/cascade-format.js +57 -2
- package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
- package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
- package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
- package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +26 -0
- package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +137 -31
- package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
- package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +361 -77
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +65 -13
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +18 -4
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +176 -29
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
- package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
- package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1172 -683
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
- package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
- package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +47 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +226 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +69 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +473 -42
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +97 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +18 -4
- package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
- package/node_modules/pi-lens/dist/clients/extension-log.js +432 -0
- package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
- package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
- package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
- package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
- package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +51 -7
- package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
- package/node_modules/pi-lens/dist/clients/git-guard.js +678 -11
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
- package/node_modules/pi-lens/dist/clients/go-client.js +23 -40
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +124 -7
- package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
- package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +395 -100
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +27 -55
- package/node_modules/pi-lens/dist/clients/knip-client.js +58 -42
- package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
- package/node_modules/pi-lens/dist/clients/latency-logger.js +72 -1
- package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
- package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
- package/node_modules/pi-lens/dist/clients/lens-events.js +63 -9
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +73 -0
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +59 -14
- package/node_modules/pi-lens/dist/clients/lsp/client.js +662 -56
- package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +115 -0
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2025 -800
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
- package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
- package/node_modules/pi-lens/dist/clients/lsp/server.js +437 -86
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +96 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +18 -3
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +127 -32
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
- package/node_modules/pi-lens/dist/clients/mcp/session.js +235 -14
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
- package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
- package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
- package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
- package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
- package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
- package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
- package/node_modules/pi-lens/dist/clients/pipeline.js +76 -15
- package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
- package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
- package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
- package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
- package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
- package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
- package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +24 -0
- package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
- package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
- package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +257 -77
- package/node_modules/pi-lens/dist/clients/runtime-context.js +129 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +242 -26
- package/node_modules/pi-lens/dist/clients/runtime-session.js +428 -117
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +342 -33
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +619 -93
- package/node_modules/pi-lens/dist/clients/rust-client.js +23 -38
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +90 -6
- package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
- package/node_modules/pi-lens/dist/clients/sg-runner.js +424 -196
- package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +231 -0
- package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
- package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
- package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
- package/node_modules/pi-lens/dist/clients/startup-timing.js +7 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +469 -32
- package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
- package/node_modules/pi-lens/dist/clients/tool-policy.js +390 -39
- package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
- package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
- package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
- package/node_modules/pi-lens/dist/clients/warm-attach.js +50 -13
- package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
- package/node_modules/pi-lens/dist/clients/word-index.js +680 -81
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
- package/node_modules/pi-lens/dist/index.js +51785 -36118
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
- package/node_modules/pi-lens/dist/mcp/server.js +219 -46
- package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
- package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +203 -38
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +210 -42
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
- package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
- package/node_modules/pi-lens/docs/agent-guide.md +40 -15
- package/node_modules/pi-lens/docs/agent-tools.md +21 -10
- package/node_modules/pi-lens/docs/analysisall.md +192 -0
- package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +14 -3
- package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
- package/node_modules/pi-lens/docs/environment-variables.md +44 -0
- package/node_modules/pi-lens/docs/fable.md +27 -0
- package/node_modules/pi-lens/docs/features.md +45 -3
- package/node_modules/pi-lens/docs/globalconfig.md +56 -1
- package/node_modules/pi-lens/docs/language-coverage.md +4 -3
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
- package/node_modules/pi-lens/docs/mcp.md +37 -4
- package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
- package/node_modules/pi-lens/docs/settings.md +18 -1
- package/node_modules/pi-lens/docs/tools.md +4 -1
- package/node_modules/pi-lens/docs/usage.md +41 -5
- package/node_modules/pi-lens/docs/word-index.md +35 -0
- package/node_modules/pi-lens/package.json +10 -10
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +27 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
- package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +134 -2
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +10 -11
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +8 -114
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +129 -0
- package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +38 -0
- package/node_modules/pi-mcp-adapter/README.md +38 -4
- package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
- package/node_modules/pi-mcp-adapter/cli.js +25 -4
- package/node_modules/pi-mcp-adapter/commands.ts +15 -7
- package/node_modules/pi-mcp-adapter/config.ts +4 -4
- package/node_modules/pi-mcp-adapter/direct-tools.ts +58 -8
- package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
- package/node_modules/pi-mcp-adapter/index.ts +8 -3
- package/node_modules/pi-mcp-adapter/init.ts +29 -5
- package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +37 -4
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
- package/node_modules/pi-mcp-adapter/package.json +4 -3
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +336 -0
- package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
- package/node_modules/pi-mcp-adapter/server-manager.ts +41 -7
- package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
- package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +62 -4
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
- package/node_modules/pi-mcp-adapter/types.ts +176 -27
- package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
- package/node_modules/pi-web-access/CHANGELOG.md +14 -0
- package/node_modules/pi-web-access/README.md +18 -12
- package/node_modules/pi-web-access/auth-fetch.ts +148 -0
- package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
- package/node_modules/pi-web-access/curator-page.ts +5 -3
- package/node_modules/pi-web-access/curator-server.ts +2 -1
- package/node_modules/pi-web-access/extract.ts +106 -34
- package/node_modules/pi-web-access/fetch-params.ts +17 -3
- package/node_modules/pi-web-access/firecrawl.ts +172 -12
- package/node_modules/pi-web-access/gemini-search.ts +18 -4
- package/node_modules/pi-web-access/index.ts +120 -48
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/summary-review.ts +11 -5
- package/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +12 -13
- package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
- package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
- package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
- package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
- package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
- package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
- package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
- package/node_modules/pi-lens/dist/clients/types.js +0 -11
|
@@ -4,12 +4,17 @@
|
|
|
4
4
|
* Builds a bidirectional function-level call graph by resolving symbol
|
|
5
5
|
* references across files. Provides BFS impact analysis with severity tiers.
|
|
6
6
|
* Uses the Symbol/SymbolRef data produced by TreeSitterSymbolExtractor and
|
|
7
|
-
* persists the result
|
|
7
|
+
* persists the result under the canonical review-graph identity; freshness is
|
|
8
|
+
* owned exclusively by the review-graph snapshot.
|
|
8
9
|
*/
|
|
9
10
|
import * as fs from "node:fs";
|
|
10
11
|
import * as path from "node:path";
|
|
11
12
|
import { getProjectDataDir } from "./file-utils.js";
|
|
12
|
-
|
|
13
|
+
import { writeFileAtomic } from "./atomic-write.js";
|
|
14
|
+
import { parseSymbolKey as parseCanonicalSymbolKey } from "./review-graph/symbol-id.js";
|
|
15
|
+
import { normalizeMapKey, toProjectRelativePath } from "./path-utils.js";
|
|
16
|
+
/** Persisted call-graph cache format version. Bump on breaking format changes. */
|
|
17
|
+
export const CALL_GRAPH_CACHE_VERSION = 5;
|
|
13
18
|
function severityForDepth(depth) {
|
|
14
19
|
if (depth === 1)
|
|
15
20
|
return "WillBreak";
|
|
@@ -75,15 +80,8 @@ export function impact(graph, startKey, maxDepth = 3, minWeight = 0.1) {
|
|
|
75
80
|
* `"file:"` fallback is matched as a literal prefix first so it isn't
|
|
76
81
|
* misparsed by the same last-colon rule.
|
|
77
82
|
*/
|
|
78
|
-
export function parseSymbolKey(key) {
|
|
79
|
-
|
|
80
|
-
return { filePath: key.slice("file:".length) };
|
|
81
|
-
}
|
|
82
|
-
const idx = key.lastIndexOf(":");
|
|
83
|
-
if (idx === -1)
|
|
84
|
-
return { filePath: key };
|
|
85
|
-
const symbolName = key.slice(idx + 1);
|
|
86
|
-
return { filePath: key.slice(0, idx), symbolName: symbolName || undefined };
|
|
83
|
+
export function parseSymbolKey(key, knownFilePath) {
|
|
84
|
+
return parseCanonicalSymbolKey(key, knownFilePath);
|
|
87
85
|
}
|
|
88
86
|
/**
|
|
89
87
|
* Format an impact result set as a compact human-readable summary.
|
|
@@ -97,12 +95,9 @@ export function formatImpact(results, projectRoot) {
|
|
|
97
95
|
const review = results.filter((r) => r.severity === "Review");
|
|
98
96
|
const parts = [];
|
|
99
97
|
const label = (r) => {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const file = r.symbolKey.includes(":")
|
|
104
|
-
? r.symbolKey.split(":").slice(0, -1).join(":").replace(projectRoot, "").replace(/^[/\\]/, "")
|
|
105
|
-
: "";
|
|
98
|
+
const parsed = parseSymbolKey(r.symbolKey);
|
|
99
|
+
const name = parsed.symbolName ?? r.symbolKey;
|
|
100
|
+
const file = toProjectRelativePath(parsed.filePath, projectRoot);
|
|
106
101
|
return file ? `${name} (${file})` : name;
|
|
107
102
|
};
|
|
108
103
|
if (willBreak.length > 0) {
|
|
@@ -133,15 +128,15 @@ const STDLIB_NAMES = new Set([
|
|
|
133
128
|
"open", "isinstance", "issubclass", "type", "super", "hasattr", "getattr",
|
|
134
129
|
"setattr", "enumerate", "zip", "map", "filter", "sorted", "reversed",
|
|
135
130
|
// Go
|
|
136
|
-
"fmt", "log", "os", "io", "err", "make", "append", "
|
|
131
|
+
"fmt", "log", "os", "io", "err", "make", "append", "cap", "copy",
|
|
137
132
|
"close", "delete", "panic", "recover", "new",
|
|
138
133
|
// Rust
|
|
139
|
-
"
|
|
140
|
-
"Box", "Rc", "Arc", "
|
|
134
|
+
"eprintln", "eprint", "vec", "Some", "None", "Ok", "Err",
|
|
135
|
+
"Box", "Rc", "Arc", "Vec", "HashMap", "HashSet", "format",
|
|
141
136
|
// Java/Kotlin
|
|
142
|
-
"System", "
|
|
137
|
+
"System", "toString", "equals", "hashCode", "Objects",
|
|
143
138
|
// Generic
|
|
144
|
-
"
|
|
139
|
+
"this", "self", "nil", "null", "undefined", "true", "false",
|
|
145
140
|
]);
|
|
146
141
|
// ── Core resolution ────────────────────────────────────────────────────────────
|
|
147
142
|
/**
|
|
@@ -150,29 +145,34 @@ const STDLIB_NAMES = new Set([
|
|
|
150
145
|
* discounts edges when many files define the same name.
|
|
151
146
|
*/
|
|
152
147
|
function buildDefIndex(allSymbols) {
|
|
153
|
-
const
|
|
148
|
+
const byName = new Map();
|
|
149
|
+
const byId = new Map();
|
|
154
150
|
for (const [, symbols] of allSymbols) {
|
|
155
151
|
for (const sym of symbols) {
|
|
156
|
-
if (!sym.name)
|
|
152
|
+
if (!sym.name || !sym.id)
|
|
157
153
|
continue;
|
|
158
|
-
const key =
|
|
159
|
-
|
|
154
|
+
const key = sym.id;
|
|
155
|
+
byId.set(key, sym);
|
|
156
|
+
const existing = byName.get(sym.name) ?? [];
|
|
160
157
|
if (!existing.includes(key))
|
|
161
158
|
existing.push(key);
|
|
162
|
-
|
|
159
|
+
byName.set(sym.name, existing);
|
|
163
160
|
}
|
|
164
161
|
}
|
|
165
|
-
return
|
|
162
|
+
return { byName, byId };
|
|
166
163
|
}
|
|
167
164
|
/**
|
|
168
|
-
* Find the enclosing function/method for a ref at `refLine
|
|
169
|
-
*
|
|
170
|
-
*
|
|
165
|
+
* Find the enclosing function/method for a ref at `refLine`. A symbol with
|
|
166
|
+
* an end line is eligible only while the reference is inside its inclusive
|
|
167
|
+
* source range; symbols without an end line retain the conservative start-line
|
|
168
|
+
* fallback. Returns undefined when no symbol encloses the reference so callers
|
|
169
|
+
* can use the file-level module key.
|
|
171
170
|
*/
|
|
172
171
|
function findEnclosingSymbol(symbols, refLine) {
|
|
173
172
|
let best;
|
|
174
173
|
for (const sym of symbols) {
|
|
175
174
|
if (sym.line <= refLine &&
|
|
175
|
+
(sym.endLine === undefined || refLine <= sym.endLine) &&
|
|
176
176
|
(sym.kind === "function" || sym.kind === "method")) {
|
|
177
177
|
if (!best || sym.line > best.line)
|
|
178
178
|
best = sym;
|
|
@@ -181,74 +181,224 @@ function findEnclosingSymbol(symbols, refLine) {
|
|
|
181
181
|
return best;
|
|
182
182
|
}
|
|
183
183
|
/**
|
|
184
|
-
* Build the function-level call graph from
|
|
184
|
+
* Build the function-level call graph from normalized review-graph evidence.
|
|
185
185
|
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
186
|
+
* The adapter supplies canonical target ids for graph evidence. Such evidence
|
|
187
|
+
* is accepted only when it points at a concrete symbol and has a non-ambiguous
|
|
188
|
+
* resolution. The old name-only path remains for legacy SymbolRef callers, but
|
|
189
|
+
* it is deliberately not used by the review-graph adapter (#1070).
|
|
189
190
|
*/
|
|
190
|
-
export function buildCallGraph(
|
|
191
|
-
|
|
191
|
+
export function buildCallGraph(
|
|
192
|
+
/** Keys are normalizeMapKey-canonical file paths; Symbol.filePath values retain their display spelling. */
|
|
193
|
+
allSymbols,
|
|
194
|
+
/** Keys are normalizeMapKey-canonical caller file paths; SymbolRef.filePath retains its display spelling. */
|
|
195
|
+
allRefs, inputCoverage) {
|
|
196
|
+
// Keep the lookup index canonical even for legacy direct callers that still
|
|
197
|
+
// construct these maps from display paths; persisted/review-graph callers
|
|
198
|
+
// already satisfy the canonical-key contract documented above. A caller that
|
|
199
|
+
// supplies TWO raw spellings of the same file gets last-writer-wins here —
|
|
200
|
+
// raw compatibility is best-effort tolerance, not a merge contract.
|
|
201
|
+
const canonicalSymbols = new Map();
|
|
202
|
+
for (const [filePath, symbols] of allSymbols)
|
|
203
|
+
canonicalSymbols.set(normalizeMapKey(filePath), symbols);
|
|
204
|
+
const defIndex = buildDefIndex(canonicalSymbols);
|
|
192
205
|
const callees = new Map();
|
|
193
206
|
const callers = new Map();
|
|
194
207
|
const inDegree = new Map();
|
|
195
|
-
const
|
|
208
|
+
const edgeByPair = new Map();
|
|
196
209
|
let unresolvedRefs = 0;
|
|
197
210
|
let totalRefs = 0;
|
|
211
|
+
const hasInputCoverage = inputCoverage !== undefined;
|
|
212
|
+
const coverage = inputCoverage
|
|
213
|
+
? { ...inputCoverage }
|
|
214
|
+
: {
|
|
215
|
+
totalEvidence: 0,
|
|
216
|
+
callsEvidence: 0,
|
|
217
|
+
referencesEvidence: 0,
|
|
218
|
+
eligibleEvidence: 0,
|
|
219
|
+
resolvedEvidence: 0,
|
|
220
|
+
unresolvedEvidence: 0,
|
|
221
|
+
typeOnlyEvidence: 0,
|
|
222
|
+
unsupportedEvidence: 0,
|
|
223
|
+
sameFileEvidence: 0,
|
|
224
|
+
duplicateEvidence: 0,
|
|
225
|
+
complete: false,
|
|
226
|
+
};
|
|
198
227
|
for (const [callerFile, refs] of allRefs) {
|
|
199
|
-
const
|
|
228
|
+
const callerFileKey = normalizeMapKey(callerFile);
|
|
229
|
+
const callerSymbols = canonicalSymbols.get(callerFileKey) ?? [];
|
|
200
230
|
for (const ref of refs) {
|
|
201
231
|
totalRefs++;
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
232
|
+
if (!hasInputCoverage) {
|
|
233
|
+
// Legacy callers do not carry an evidenceKind, but each ref is still
|
|
234
|
+
// raw call-graph evidence and must contribute to coverage totals.
|
|
235
|
+
coverage.totalEvidence++;
|
|
236
|
+
if (ref.evidenceKind === "references" || ref.referenceKind === "type")
|
|
237
|
+
coverage.referencesEvidence++;
|
|
238
|
+
else
|
|
239
|
+
coverage.callsEvidence++;
|
|
240
|
+
}
|
|
241
|
+
if (ref.referenceKind === "type") {
|
|
242
|
+
if (!hasInputCoverage)
|
|
243
|
+
coverage.typeOnlyEvidence++;
|
|
205
244
|
continue;
|
|
206
|
-
|
|
207
|
-
if (
|
|
208
|
-
|
|
245
|
+
}
|
|
246
|
+
if (ref.evidenceKind && ref.referenceKind !== "call") {
|
|
247
|
+
if (!hasInputCoverage)
|
|
248
|
+
coverage.unsupportedEvidence++;
|
|
209
249
|
continue;
|
|
210
250
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
251
|
+
const candidates = [];
|
|
252
|
+
if (ref.targetId) {
|
|
253
|
+
// Canonical graph identity is authoritative. Never recover a name by
|
|
254
|
+
// splitting an id: symbol ids contain kind and line components.
|
|
255
|
+
const callee = defIndex.byId.get(ref.targetId);
|
|
256
|
+
if (!callee || ref.resolution === "name-only" || ref.resolution === "unresolved") {
|
|
257
|
+
if (!hasInputCoverage) {
|
|
258
|
+
unresolvedRefs++;
|
|
259
|
+
coverage.unresolvedEvidence++;
|
|
260
|
+
}
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
candidates.push({
|
|
264
|
+
callee,
|
|
265
|
+
resolution: ref.resolution ?? "exact",
|
|
266
|
+
candidateCount: 1,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
// Compatibility for pre-#1070 callers that supplied only name refs.
|
|
271
|
+
// There is no typed/import evidence to select one definition, so retain
|
|
272
|
+
// every cross-file candidate and discount each edge. Picking [0] made
|
|
273
|
+
// graph iteration order decide which caller was reported.
|
|
274
|
+
const refName = ref.symbolName ?? parseSymbolKey(ref.symbolId, callerFile).symbolName ?? "";
|
|
275
|
+
if (STDLIB_NAMES.has(refName) || !refName) {
|
|
276
|
+
if (!hasInputCoverage)
|
|
277
|
+
coverage.unsupportedEvidence++;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const defs = defIndex.byName.get(refName);
|
|
281
|
+
if (!defs || defs.length === 0) {
|
|
282
|
+
if (!hasInputCoverage) {
|
|
283
|
+
unresolvedRefs++;
|
|
284
|
+
coverage.unresolvedEvidence++;
|
|
285
|
+
}
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const crossFileDefs = defs
|
|
289
|
+
.map((id) => defIndex.byId.get(id))
|
|
290
|
+
.filter((candidate) => candidate !== undefined && normalizeMapKey(candidate.filePath) !== callerFileKey);
|
|
291
|
+
if (crossFileDefs.length === 0) {
|
|
292
|
+
if (!hasInputCoverage)
|
|
293
|
+
coverage.unsupportedEvidence++;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const resolution = crossFileDefs.length === 1 ? "exact" : "name-only";
|
|
297
|
+
for (const callee of crossFileDefs) {
|
|
298
|
+
candidates.push({ callee, resolution, candidateCount: crossFileDefs.length });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (ref.evidenceKind && candidates.some(({ resolution }) => resolution === "name-only")) {
|
|
302
|
+
if (!hasInputCoverage) {
|
|
303
|
+
unresolvedRefs++;
|
|
304
|
+
coverage.unresolvedEvidence++;
|
|
305
|
+
}
|
|
214
306
|
continue;
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
307
|
+
}
|
|
308
|
+
const caller = ref.callerSymbolId
|
|
309
|
+
? defIndex.byId.get(ref.callerSymbolId)
|
|
310
|
+
: undefined;
|
|
311
|
+
const enclosing = caller ?? findEnclosingSymbol(callerSymbols, ref.line);
|
|
312
|
+
const callerKey = enclosing?.id ?? `file:${callerFile}`;
|
|
313
|
+
let producedEdge = false;
|
|
314
|
+
let duplicateRef = false;
|
|
315
|
+
for (const { callee, resolution, candidateCount } of candidates) {
|
|
316
|
+
if (normalizeMapKey(callee.filePath) === callerFileKey) {
|
|
317
|
+
// This projection is intentionally cross-file-only. A canonical
|
|
318
|
+
// same-file target is resolved by the source graph, but is represented
|
|
319
|
+
// by an explicit coverage category rather than as unsupported evidence.
|
|
320
|
+
// That keeps the projection contract while allowing complete coverage
|
|
321
|
+
// to gate user-facing cross-file impact honestly.
|
|
322
|
+
coverage.sameFileEvidence++;
|
|
323
|
+
if (hasInputCoverage) {
|
|
324
|
+
coverage.eligibleEvidence--;
|
|
325
|
+
coverage.resolvedEvidence--;
|
|
326
|
+
}
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
producedEdge = true;
|
|
330
|
+
const pairKey = `${callerKey}\u0000${callee.id}`;
|
|
331
|
+
const existing = edgeByPair.get(pairKey);
|
|
332
|
+
if (existing) {
|
|
333
|
+
existing.evidenceCount = (existing.evidenceCount ?? 1) + 1;
|
|
334
|
+
existing.evidenceKind = existing.evidenceKind === ref.evidenceKind
|
|
335
|
+
? existing.evidenceKind
|
|
336
|
+
: "mixed";
|
|
337
|
+
// Duplicate evidence is a property of the logical pair, not of
|
|
338
|
+
// whether the producer happened to tag the record. Keep the edge,
|
|
339
|
+
// coverage, and centrality views consistent for legacy refs too.
|
|
340
|
+
duplicateRef = true;
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const weight = ref.evidenceKind ? 1 : 1 / Math.max(1, candidateCount);
|
|
344
|
+
const edge = {
|
|
234
345
|
callerFile,
|
|
235
346
|
callerSymbol: enclosing?.name,
|
|
236
347
|
callerKey,
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
348
|
+
callerKind: enclosing?.kind,
|
|
349
|
+
callerLine: enclosing?.line,
|
|
350
|
+
callerColumn: enclosing?.column,
|
|
351
|
+
calleeFile: callee.filePath,
|
|
352
|
+
calleeSymbol: callee.name,
|
|
353
|
+
calleeKey: callee.id,
|
|
354
|
+
calleeKind: callee.kind,
|
|
355
|
+
calleeLine: callee.line,
|
|
356
|
+
calleeColumn: callee.column,
|
|
357
|
+
evidenceKind: ref.evidenceKind,
|
|
358
|
+
resolution,
|
|
359
|
+
evidenceCount: 1,
|
|
240
360
|
weight,
|
|
241
|
-
}
|
|
361
|
+
};
|
|
362
|
+
edgeByPair.set(pairKey, edge);
|
|
363
|
+
const callerCallees = callees.get(callerKey) ?? new Set();
|
|
364
|
+
callerCallees.add(callee.id);
|
|
365
|
+
callees.set(callerKey, callerCallees);
|
|
366
|
+
const calleeCallers = callers.get(callee.id) ?? new Set();
|
|
367
|
+
calleeCallers.add(callerKey);
|
|
368
|
+
callers.set(callee.id, calleeCallers);
|
|
369
|
+
inDegree.set(callee.id, (inDegree.get(callee.id) ?? 0) + weight);
|
|
242
370
|
}
|
|
371
|
+
if (!hasInputCoverage && producedEdge) {
|
|
372
|
+
// Legacy name-only ambiguity can fan one raw record out to several
|
|
373
|
+
// weighted edges. Coverage remains a count of raw records, so account
|
|
374
|
+
// for eligibility and duplicates once per reference, not once per
|
|
375
|
+
// candidate edge.
|
|
376
|
+
coverage.eligibleEvidence++;
|
|
377
|
+
coverage.resolvedEvidence++;
|
|
378
|
+
if (duplicateRef)
|
|
379
|
+
coverage.duplicateEvidence++;
|
|
380
|
+
}
|
|
381
|
+
if (hasInputCoverage && producedEdge && duplicateRef)
|
|
382
|
+
coverage.duplicateEvidence++;
|
|
243
383
|
}
|
|
244
384
|
}
|
|
385
|
+
// With adapter-supplied coverage, totalRefs is the raw evidence count. Some
|
|
386
|
+
// raw records (for example an external node with no caller file) cannot be
|
|
387
|
+
// represented in allRefs, but they must not disappear from accounting.
|
|
388
|
+
if (hasInputCoverage)
|
|
389
|
+
totalRefs = coverage.totalEvidence;
|
|
390
|
+
const languagesKnownComplete = Object.values(coverage.languages ?? {}).every((status) => status === "complete");
|
|
391
|
+
coverage.complete = coverage.complete && hasInputCoverage &&
|
|
392
|
+
coverage.unsupportedEvidence === 0 && languagesKnownComplete &&
|
|
393
|
+
(allSymbols.size > 0 || coverage.totalEvidence > 0);
|
|
245
394
|
return {
|
|
246
395
|
callees,
|
|
247
396
|
callers,
|
|
248
397
|
inDegree,
|
|
249
|
-
edges,
|
|
250
|
-
unresolvedRefs,
|
|
398
|
+
edges: [...edgeByPair.values()],
|
|
399
|
+
unresolvedRefs: hasInputCoverage ? coverage.unresolvedEvidence : unresolvedRefs,
|
|
251
400
|
totalRefs,
|
|
401
|
+
coverage,
|
|
252
402
|
builtAt: new Date().toISOString(),
|
|
253
403
|
};
|
|
254
404
|
}
|
|
@@ -260,39 +410,252 @@ function metaFilePath(cwd) {
|
|
|
260
410
|
return path.join(getProjectDataDir(cwd), "cache", "call-graph.meta.json");
|
|
261
411
|
}
|
|
262
412
|
/**
|
|
263
|
-
* Persist
|
|
264
|
-
*
|
|
413
|
+
* Persist a call-graph projection. Current callers pass the canonical
|
|
414
|
+
* review-graph identity. There is deliberately no independent file/mtime
|
|
415
|
+
* freshness argument: callers must use the review graph's canonical identity.
|
|
265
416
|
*/
|
|
266
|
-
export function saveCallGraph(cwd, graph,
|
|
417
|
+
export function saveCallGraph(cwd, graph, identity) {
|
|
267
418
|
const cacheFile = cacheFilePath(cwd);
|
|
268
419
|
const metaFile = metaFilePath(cwd);
|
|
269
420
|
try {
|
|
270
421
|
fs.mkdirSync(path.dirname(cacheFile), { recursive: true });
|
|
271
422
|
const persisted = {
|
|
272
|
-
version:
|
|
423
|
+
version: CALL_GRAPH_CACHE_VERSION,
|
|
273
424
|
builtAt: graph.builtAt,
|
|
274
|
-
|
|
425
|
+
reviewGraphVersion: identity.reviewGraphVersion,
|
|
426
|
+
reviewGraphSignature: identity.reviewGraphSignature,
|
|
275
427
|
edges: graph.edges,
|
|
276
428
|
callees: [...graph.callees.entries()].map(([k, v]) => [k, [...v]]),
|
|
277
429
|
callers: [...graph.callers.entries()].map(([k, v]) => [k, [...v]]),
|
|
278
430
|
inDegree: [...graph.inDegree.entries()],
|
|
431
|
+
totalRefs: graph.totalRefs,
|
|
432
|
+
unresolvedRefs: graph.unresolvedRefs,
|
|
433
|
+
coverage: graph.coverage,
|
|
279
434
|
};
|
|
280
|
-
|
|
281
|
-
|
|
435
|
+
writeFileAtomic(cacheFile, JSON.stringify(persisted));
|
|
436
|
+
writeFileAtomic(metaFile, JSON.stringify({ savedAt: new Date().toISOString(), edgeCount: graph.edges.length }));
|
|
282
437
|
}
|
|
283
438
|
catch {
|
|
284
439
|
// Non-fatal — next session rebuilds from scratch.
|
|
285
440
|
}
|
|
286
441
|
}
|
|
442
|
+
function inferLegacyCoverage(edges) {
|
|
443
|
+
let weightedTotalEvidence = 0;
|
|
444
|
+
let weightedReferencesEvidence = 0;
|
|
445
|
+
let weightedDuplicateEvidence = 0;
|
|
446
|
+
for (const edge of edges) {
|
|
447
|
+
const count = Number.isFinite(edge.evidenceCount) && (edge.evidenceCount ?? 0) > 0
|
|
448
|
+
? Math.floor(edge.evidenceCount)
|
|
449
|
+
: 1;
|
|
450
|
+
const weight = Number.isFinite(edge.weight) && edge.weight >= 0 ? edge.weight : 1;
|
|
451
|
+
weightedTotalEvidence += count * weight;
|
|
452
|
+
weightedDuplicateEvidence += Math.max(0, count - 1) * weight;
|
|
453
|
+
if (edge.evidenceKind === "references")
|
|
454
|
+
weightedReferencesEvidence += count * weight;
|
|
455
|
+
}
|
|
456
|
+
const totalEvidence = Math.max(0, Math.round(weightedTotalEvidence));
|
|
457
|
+
const referencesEvidence = Math.max(0, Math.round(weightedReferencesEvidence));
|
|
458
|
+
const duplicateEvidence = Math.max(0, Math.round(weightedDuplicateEvidence));
|
|
459
|
+
return {
|
|
460
|
+
totalEvidence,
|
|
461
|
+
callsEvidence: totalEvidence - referencesEvidence,
|
|
462
|
+
referencesEvidence,
|
|
463
|
+
eligibleEvidence: totalEvidence,
|
|
464
|
+
resolvedEvidence: totalEvidence,
|
|
465
|
+
unresolvedEvidence: 0,
|
|
466
|
+
typeOnlyEvidence: 0,
|
|
467
|
+
unsupportedEvidence: 0,
|
|
468
|
+
sameFileEvidence: 0,
|
|
469
|
+
duplicateEvidence,
|
|
470
|
+
// A legacy graph can still be queried, but it cannot prove that the old
|
|
471
|
+
// extractor covered every source/reference kind.
|
|
472
|
+
complete: false,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function loadCoverage(rawCoverage, edges) {
|
|
476
|
+
const inferred = inferLegacyCoverage(edges);
|
|
477
|
+
return rawCoverage
|
|
478
|
+
? {
|
|
479
|
+
...inferred,
|
|
480
|
+
...rawCoverage,
|
|
481
|
+
}
|
|
482
|
+
: inferred;
|
|
483
|
+
}
|
|
484
|
+
function finiteCount(value) {
|
|
485
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
486
|
+
}
|
|
487
|
+
function countsMatch(left, right) {
|
|
488
|
+
return Math.abs(left - right) <= 1e-9 * Math.max(1, Math.abs(left), Math.abs(right));
|
|
489
|
+
}
|
|
490
|
+
function validatePersistedCallGraph(raw, coverage) {
|
|
491
|
+
const nonEmptyString = (value) => typeof value === "string" && value.trim().length > 0;
|
|
492
|
+
const enumValue = (value, values) => typeof value === "string" && values.includes(value);
|
|
493
|
+
const edgeKinds = ["calls", "references", "mixed"];
|
|
494
|
+
const resolutions = ["exact", "import", "receiver-type", "name-only", "unresolved"];
|
|
495
|
+
if (!raw || raw.version !== CALL_GRAPH_CACHE_VERSION || !nonEmptyString(raw.builtAt))
|
|
496
|
+
return false;
|
|
497
|
+
if (!nonEmptyString(raw.reviewGraphVersion) || !nonEmptyString(raw.reviewGraphSignature))
|
|
498
|
+
return false;
|
|
499
|
+
if (raw.coverage !== undefined &&
|
|
500
|
+
(!raw.coverage || typeof raw.coverage !== "object" || Array.isArray(raw.coverage)))
|
|
501
|
+
return false;
|
|
502
|
+
if (!Array.isArray(raw.edges) || !Array.isArray(raw.callees) || !Array.isArray(raw.callers) || !Array.isArray(raw.inDegree))
|
|
503
|
+
return false;
|
|
504
|
+
const coverageValues = [
|
|
505
|
+
coverage.totalEvidence,
|
|
506
|
+
coverage.callsEvidence,
|
|
507
|
+
coverage.referencesEvidence,
|
|
508
|
+
coverage.eligibleEvidence,
|
|
509
|
+
coverage.resolvedEvidence,
|
|
510
|
+
coverage.unresolvedEvidence,
|
|
511
|
+
coverage.typeOnlyEvidence,
|
|
512
|
+
coverage.unsupportedEvidence,
|
|
513
|
+
coverage.sameFileEvidence,
|
|
514
|
+
coverage.duplicateEvidence,
|
|
515
|
+
];
|
|
516
|
+
if (typeof coverage.complete !== "boolean" || coverageValues.some((value) => !finiteCount(value)))
|
|
517
|
+
return false;
|
|
518
|
+
if (coverage.languages !== undefined &&
|
|
519
|
+
(!coverage.languages || typeof coverage.languages !== "object" || Array.isArray(coverage.languages) ||
|
|
520
|
+
Object.entries(coverage.languages).some(([language, status]) => !nonEmptyString(language) || !enumValue(status, ["complete", "partial", "unavailable"]))))
|
|
521
|
+
return false;
|
|
522
|
+
if (coverage.callsEvidence + coverage.referencesEvidence !== coverage.totalEvidence)
|
|
523
|
+
return false;
|
|
524
|
+
if (coverage.resolvedEvidence > coverage.eligibleEvidence)
|
|
525
|
+
return false;
|
|
526
|
+
if (coverage.eligibleEvidence !== coverage.resolvedEvidence)
|
|
527
|
+
return false;
|
|
528
|
+
if (coverage.resolvedEvidence + coverage.unresolvedEvidence + coverage.typeOnlyEvidence + coverage.unsupportedEvidence + coverage.sameFileEvidence !== coverage.totalEvidence)
|
|
529
|
+
return false;
|
|
530
|
+
if (coverage.complete && coverage.unsupportedEvidence > 0)
|
|
531
|
+
return false;
|
|
532
|
+
if (coverage.complete && Object.values(coverage.languages ?? {}).some((status) => status !== "complete"))
|
|
533
|
+
return false;
|
|
534
|
+
const expectedCallees = new Map();
|
|
535
|
+
const expectedCallers = new Map();
|
|
536
|
+
const expectedInDegree = new Map();
|
|
537
|
+
const pairKeys = new Set();
|
|
538
|
+
let weightedEdgeEvidence = 0;
|
|
539
|
+
let weightedDuplicateEvidence = 0;
|
|
540
|
+
for (const edge of raw.edges) {
|
|
541
|
+
if (!edge || typeof edge !== "object" || !nonEmptyString(edge.callerKey) || !nonEmptyString(edge.calleeKey) ||
|
|
542
|
+
!nonEmptyString(edge.callerFile) || !nonEmptyString(edge.calleeFile) ||
|
|
543
|
+
!nonEmptyString(edge.calleeSymbol) || typeof edge.weight !== "number" || !Number.isFinite(edge.weight) || edge.weight <= 0 || edge.weight > 1)
|
|
544
|
+
return false;
|
|
545
|
+
if (edge.callerSymbol !== undefined && !nonEmptyString(edge.callerSymbol))
|
|
546
|
+
return false;
|
|
547
|
+
if (edge.callerKind !== undefined && !nonEmptyString(edge.callerKind))
|
|
548
|
+
return false;
|
|
549
|
+
if (edge.calleeKind !== undefined && !nonEmptyString(edge.calleeKind))
|
|
550
|
+
return false;
|
|
551
|
+
if (edge.evidenceKind !== undefined && !enumValue(edge.evidenceKind, edgeKinds))
|
|
552
|
+
return false;
|
|
553
|
+
if (edge.resolution !== undefined && !enumValue(edge.resolution, resolutions))
|
|
554
|
+
return false;
|
|
555
|
+
if (edge.evidenceCount !== undefined && (!finiteCount(edge.evidenceCount) || edge.evidenceCount < 1))
|
|
556
|
+
return false;
|
|
557
|
+
const evidenceCount = edge.evidenceCount ?? 1;
|
|
558
|
+
const callerIdentity = parseCanonicalSymbolKey(edge.callerKey, edge.callerFile);
|
|
559
|
+
const calleeIdentity = parseCanonicalSymbolKey(edge.calleeKey, edge.calleeFile);
|
|
560
|
+
if (normalizeMapKey(callerIdentity.filePath) !== normalizeMapKey(edge.callerFile) ||
|
|
561
|
+
normalizeMapKey(calleeIdentity.filePath) !== normalizeMapKey(edge.calleeFile) ||
|
|
562
|
+
(calleeIdentity.symbolName !== undefined && calleeIdentity.symbolName !== edge.calleeSymbol) ||
|
|
563
|
+
(callerIdentity.symbolName !== undefined && edge.callerSymbol !== undefined && callerIdentity.symbolName !== edge.callerSymbol))
|
|
564
|
+
return false;
|
|
565
|
+
const pair = `${edge.callerKey}\u0000${edge.calleeKey}`;
|
|
566
|
+
if (pairKeys.has(pair))
|
|
567
|
+
return false;
|
|
568
|
+
pairKeys.add(pair);
|
|
569
|
+
// Ambiguous legacy name-only evidence may fan one raw record out to
|
|
570
|
+
// multiple weighted edges. Weight the persisted evidence back to raw
|
|
571
|
+
// records so semantic validation agrees with coverage accounting.
|
|
572
|
+
weightedEdgeEvidence += evidenceCount * edge.weight;
|
|
573
|
+
weightedDuplicateEvidence += (evidenceCount - 1) * edge.weight;
|
|
574
|
+
const callees = expectedCallees.get(edge.callerKey) ?? new Set();
|
|
575
|
+
callees.add(edge.calleeKey);
|
|
576
|
+
expectedCallees.set(edge.callerKey, callees);
|
|
577
|
+
const callers = expectedCallers.get(edge.calleeKey) ?? new Set();
|
|
578
|
+
callers.add(edge.callerKey);
|
|
579
|
+
expectedCallers.set(edge.calleeKey, callers);
|
|
580
|
+
expectedInDegree.set(edge.calleeKey, (expectedInDegree.get(edge.calleeKey) ?? 0) + edge.weight);
|
|
581
|
+
}
|
|
582
|
+
if (!countsMatch(weightedEdgeEvidence, coverage.resolvedEvidence))
|
|
583
|
+
return false;
|
|
584
|
+
if (!countsMatch(weightedDuplicateEvidence, coverage.duplicateEvidence))
|
|
585
|
+
return false;
|
|
586
|
+
if (!countsMatch(coverage.totalEvidence, weightedEdgeEvidence + coverage.unresolvedEvidence + coverage.typeOnlyEvidence + coverage.unsupportedEvidence + coverage.sameFileEvidence))
|
|
587
|
+
return false;
|
|
588
|
+
if (raw.totalRefs !== undefined && !finiteCount(raw.totalRefs))
|
|
589
|
+
return false;
|
|
590
|
+
if (raw.unresolvedRefs !== undefined && !finiteCount(raw.unresolvedRefs))
|
|
591
|
+
return false;
|
|
592
|
+
if (raw.totalRefs !== undefined && raw.totalRefs !== coverage.totalEvidence)
|
|
593
|
+
return false;
|
|
594
|
+
if (raw.unresolvedRefs !== undefined && raw.unresolvedRefs !== coverage.unresolvedEvidence)
|
|
595
|
+
return false;
|
|
596
|
+
const readAdjacency = (entries) => {
|
|
597
|
+
const result = new Map();
|
|
598
|
+
if (!Array.isArray(entries))
|
|
599
|
+
return undefined;
|
|
600
|
+
for (const entry of entries) {
|
|
601
|
+
if (!Array.isArray(entry) || !nonEmptyString(entry[0]) || !Array.isArray(entry[1]) || entry[1].some((key) => !nonEmptyString(key)))
|
|
602
|
+
return undefined;
|
|
603
|
+
const keys = entry[1];
|
|
604
|
+
if (new Set(keys).size !== keys.length || result.has(entry[0]))
|
|
605
|
+
return undefined;
|
|
606
|
+
result.set(entry[0], new Set(keys));
|
|
607
|
+
}
|
|
608
|
+
return result;
|
|
609
|
+
};
|
|
610
|
+
const actualCallees = readAdjacency(raw.callees);
|
|
611
|
+
const actualCallers = readAdjacency(raw.callers);
|
|
612
|
+
if (!actualCallees || !actualCallers || actualCallees.size !== expectedCallees.size || actualCallers.size !== expectedCallers.size)
|
|
613
|
+
return false;
|
|
614
|
+
const sameSets = (actual, expected) => {
|
|
615
|
+
for (const [key, values] of expected) {
|
|
616
|
+
const got = actual.get(key);
|
|
617
|
+
if (!got || got.size !== values.size || [...values].some((value) => !got.has(value)))
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
return true;
|
|
621
|
+
};
|
|
622
|
+
if (!sameSets(actualCallees, expectedCallees) || !sameSets(actualCallers, expectedCallers))
|
|
623
|
+
return false;
|
|
624
|
+
const actualInDegree = new Map();
|
|
625
|
+
for (const entry of raw.inDegree) {
|
|
626
|
+
if (!Array.isArray(entry) || !nonEmptyString(entry[0]) || typeof entry[1] !== "number" || !Number.isFinite(entry[1]) || entry[1] < 0 || actualInDegree.has(entry[0]))
|
|
627
|
+
return false;
|
|
628
|
+
actualInDegree.set(entry[0], entry[1]);
|
|
629
|
+
}
|
|
630
|
+
if (actualInDegree.size !== expectedInDegree.size)
|
|
631
|
+
return false;
|
|
632
|
+
for (const [key, value] of expectedInDegree) {
|
|
633
|
+
if (actualInDegree.get(key) !== value)
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
return true;
|
|
637
|
+
}
|
|
287
638
|
/**
|
|
288
639
|
* Load the persisted call graph from disk.
|
|
289
640
|
* Returns undefined if the cache is missing, version-mismatched, or corrupt.
|
|
290
641
|
*/
|
|
291
|
-
export function loadCallGraph(cwd) {
|
|
642
|
+
export function loadCallGraph(cwd, expectedIdentity) {
|
|
292
643
|
const cacheFile = cacheFilePath(cwd);
|
|
293
644
|
try {
|
|
294
645
|
const raw = JSON.parse(fs.readFileSync(cacheFile, "utf-8"));
|
|
295
|
-
if (raw.version !==
|
|
646
|
+
if (raw.version !== CALL_GRAPH_CACHE_VERSION)
|
|
647
|
+
return undefined;
|
|
648
|
+
if (typeof raw.reviewGraphVersion !== "string" || typeof raw.reviewGraphSignature !== "string")
|
|
649
|
+
return undefined;
|
|
650
|
+
const identity = {
|
|
651
|
+
reviewGraphVersion: raw.reviewGraphVersion,
|
|
652
|
+
reviewGraphSignature: raw.reviewGraphSignature,
|
|
653
|
+
};
|
|
654
|
+
if (expectedIdentity && (identity.reviewGraphVersion !== expectedIdentity.reviewGraphVersion ||
|
|
655
|
+
identity.reviewGraphSignature !== expectedIdentity.reviewGraphSignature))
|
|
656
|
+
return undefined;
|
|
657
|
+
const coverage = loadCoverage(raw.coverage, raw.edges);
|
|
658
|
+
if (!validatePersistedCallGraph(raw, coverage))
|
|
296
659
|
return undefined;
|
|
297
660
|
return {
|
|
298
661
|
graph: {
|
|
@@ -300,46 +663,15 @@ export function loadCallGraph(cwd) {
|
|
|
300
663
|
callers: new Map(raw.callers.map(([k, v]) => [k, new Set(v)])),
|
|
301
664
|
inDegree: new Map(raw.inDegree),
|
|
302
665
|
edges: raw.edges,
|
|
303
|
-
unresolvedRefs:
|
|
304
|
-
totalRefs:
|
|
666
|
+
unresolvedRefs: raw.unresolvedRefs ?? coverage.unresolvedEvidence,
|
|
667
|
+
totalRefs: raw.totalRefs ?? coverage.totalEvidence,
|
|
668
|
+
coverage,
|
|
305
669
|
builtAt: raw.builtAt,
|
|
306
670
|
},
|
|
307
|
-
|
|
671
|
+
identity,
|
|
308
672
|
};
|
|
309
673
|
}
|
|
310
674
|
catch {
|
|
311
675
|
return undefined;
|
|
312
676
|
}
|
|
313
677
|
}
|
|
314
|
-
/**
|
|
315
|
-
* Returns the set of file paths whose mtime has changed since the cache was saved.
|
|
316
|
-
* Files not in the mtime map are treated as new (stale).
|
|
317
|
-
*/
|
|
318
|
-
export function staleFiles(fileMtimes, currentFiles) {
|
|
319
|
-
return currentFiles.filter((f) => {
|
|
320
|
-
const cached = fileMtimes.get(f);
|
|
321
|
-
if (cached === undefined)
|
|
322
|
-
return true; // new file
|
|
323
|
-
try {
|
|
324
|
-
return fs.statSync(f).mtimeMs !== cached;
|
|
325
|
-
}
|
|
326
|
-
catch {
|
|
327
|
-
return true; // deleted or unreadable
|
|
328
|
-
}
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
/**
|
|
332
|
-
* Read current mtimes for a set of files.
|
|
333
|
-
*/
|
|
334
|
-
export function readMtimes(files) {
|
|
335
|
-
const mtimes = new Map();
|
|
336
|
-
for (const f of files) {
|
|
337
|
-
try {
|
|
338
|
-
mtimes.set(f, fs.statSync(f).mtimeMs);
|
|
339
|
-
}
|
|
340
|
-
catch {
|
|
341
|
-
// skip
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
return mtimes;
|
|
345
|
-
}
|