@herbertgao/pi-extensions 2026.9.0 → 2026.9.2
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/node_modules/@czottmann/pi-automode/CHANGELOG.md +13 -1
- package/node_modules/@czottmann/pi-automode/README.md +20 -0
- package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +1 -1
- package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +2 -1
- package/node_modules/@czottmann/pi-automode/docs/defaults.md +3 -2
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +38 -2
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +5 -3
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +11 -3
- package/node_modules/@czottmann/pi-automode/package.json +1 -1
- package/node_modules/@narumitw/pi-btw/README.md +57 -141
- package/node_modules/@narumitw/pi-btw/dist/index.ts +466 -41
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
- package/node_modules/@narumitw/pi-btw/docs/workflows.md +74 -0
- package/node_modules/@narumitw/pi-btw/package.json +2 -1
- package/node_modules/@narumitw/pi-btw/src/btw.ts +15 -1
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +32 -2
- package/node_modules/@narumitw/pi-btw/src/keybindings.ts +330 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +136 -19
- package/node_modules/@narumitw/pi-btw/src/settings.ts +32 -0
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +57 -5
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +52 -30
- package/node_modules/@pi-plugins/fast-mode/README.md +5 -3
- package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +20 -55
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
- package/node_modules/pi-lens/CHANGELOG.md +1072 -0
- package/node_modules/pi-lens/README.md +3 -0
- package/node_modules/pi-lens/config/biome/core.jsonc +11 -2
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +13 -1
- package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +2 -9
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +927 -80
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +1 -1
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +13 -4
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +123 -3
- package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +60 -5
- package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +2 -9
- package/node_modules/pi-lens/dist/clients/ast-grep-types.js +2 -0
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +133 -3
- package/node_modules/pi-lens/dist/clients/biome-client.js +9 -2
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +14 -6
- package/node_modules/pi-lens/dist/clients/bootstrap.js +509 -73
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +152 -12
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +1 -1
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +59 -9
- package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +113 -0
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +3 -11
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +31 -4
- package/node_modules/pi-lens/dist/clients/cache-manager.js +105 -6
- package/node_modules/pi-lens/dist/clients/cache-observability.js +40 -19
- package/node_modules/pi-lens/dist/clients/cargo-manifest.js +422 -0
- package/node_modules/pi-lens/dist/clients/cascade-logger.js +2 -9
- package/node_modules/pi-lens/dist/clients/child-unref.js +1 -19
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +13 -3
- package/node_modules/pi-lens/dist/clients/complexity-client.js +16 -5
- package/node_modules/pi-lens/dist/clients/config-core/deny.js +221 -0
- package/node_modules/pi-lens/dist/clients/config-core/index.js +50 -0
- package/node_modules/pi-lens/dist/clients/config-core/merge.js +357 -0
- package/node_modules/pi-lens/dist/clients/config-core/normalize.js +340 -0
- package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +248 -0
- package/node_modules/pi-lens/dist/clients/config-core/provenance.js +164 -0
- package/node_modules/pi-lens/dist/clients/config-core/records.js +218 -0
- package/node_modules/pi-lens/dist/clients/config-core/resolve.js +125 -0
- package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +78 -0
- package/node_modules/pi-lens/dist/clients/config-core/schema.js +165 -0
- package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +281 -0
- package/node_modules/pi-lens/dist/clients/config-locations.js +160 -0
- package/node_modules/pi-lens/dist/clients/config-resolve.js +789 -0
- package/node_modules/pi-lens/dist/clients/config-schema.js +234 -0
- package/node_modules/pi-lens/dist/clients/config-warn.js +407 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +19 -14
- package/node_modules/pi-lens/dist/clients/dead-code-logger.js +2 -6
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +178 -0
- package/node_modules/pi-lens/dist/clients/debug-handles.js +2 -2
- package/node_modules/pi-lens/dist/clients/debug-heap.js +3 -3
- package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +106 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +113 -7
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +8 -19
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +1 -1
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +12 -9
- package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +15 -5
- package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -4
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +162 -44
- package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +27 -8
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -6
- package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -3
- package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +2 -2
- package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +383 -27
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +4 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +1 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +1 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +14 -18
- package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +6 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +7 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +1 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +1 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +1 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +4 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -67
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +194 -80
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +84 -15
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -21
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +2 -20
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +2 -2
- package/node_modules/pi-lens/dist/clients/disposition-logger.js +3 -10
- package/node_modules/pi-lens/dist/clients/effective-config.js +403 -0
- package/node_modules/pi-lens/dist/clients/error-class.js +23 -0
- package/node_modules/pi-lens/dist/clients/event-loop-hold.js +274 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +2 -2
- package/node_modules/pi-lens/dist/clients/extension-log.js +2 -2
- package/node_modules/pi-lens/dist/clients/feature-hints.js +2 -1
- package/node_modules/pi-lens/dist/clients/file-kinds.js +1 -38
- package/node_modules/pi-lens/dist/clients/file-time.js +7 -2
- package/node_modules/pi-lens/dist/clients/file-utils.js +117 -10
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +50 -13
- package/node_modules/pi-lens/dist/clients/format-service.js +6 -2
- package/node_modules/pi-lens/dist/clients/formatters.js +385 -104
- package/node_modules/pi-lens/dist/clients/freshness-cadence.js +17 -0
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +116 -22
- package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -9
- package/node_modules/pi-lens/dist/clients/git-guard.js +6 -1
- package/node_modules/pi-lens/dist/clients/go-client.js +39 -0
- package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +252 -0
- package/node_modules/pi-lens/dist/clients/grammar-source.js +3 -3
- package/node_modules/pi-lens/dist/clients/hashline-anchor.js +424 -0
- package/node_modules/pi-lens/dist/clients/hook-budgets.js +76 -0
- package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +5 -2
- package/node_modules/pi-lens/dist/clients/host-ports.js +1 -1
- package/node_modules/pi-lens/dist/clients/installer/index.js +289 -59
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +84 -18
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +110 -236
- package/node_modules/pi-lens/dist/clients/instance-registry.js +6 -3
- package/node_modules/pi-lens/dist/clients/language-profile.js +0 -9
- package/node_modules/pi-lens/dist/clients/language-registry.js +599 -0
- package/node_modules/pi-lens/dist/clients/latency-logger.js +165 -9
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +34 -1
- package/node_modules/pi-lens/dist/clients/lens-config.js +160 -30
- package/node_modules/pi-lens/dist/clients/lens-engine.js +33 -41
- package/node_modules/pi-lens/dist/clients/lens-events.js +1 -1
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +77 -6
- package/node_modules/pi-lens/dist/clients/lens-map.js +1 -1
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -38
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +3 -1
- package/node_modules/pi-lens/dist/clients/lsp/client.js +172 -33
- package/node_modules/pi-lens/dist/clients/lsp/config.js +484 -158
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +18 -5
- package/node_modules/pi-lens/dist/clients/lsp/index.js +892 -96
- package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/language.js +18 -177
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +5 -18
- package/node_modules/pi-lens/dist/clients/lsp/server.js +284 -147
- package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +116 -23
- package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +2 -7
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -1
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +14 -7
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +6 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +163 -21
- package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +36 -0
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +33 -4
- package/node_modules/pi-lens/dist/clients/mcp/session.js +5 -16
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +8 -3
- package/node_modules/pi-lens/dist/clients/metrics-history.js +28 -107
- package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +3 -5
- package/node_modules/pi-lens/dist/clients/module-report.js +21 -46
- package/node_modules/pi-lens/dist/clients/mutating-tool.js +651 -0
- package/node_modules/pi-lens/dist/clients/mutation-attribution.js +368 -0
- package/node_modules/pi-lens/dist/clients/mutation-bridge.js +240 -0
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +247 -24
- package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +101 -0
- package/node_modules/pi-lens/dist/clients/observed-mutation.js +1215 -0
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +70 -28
- package/node_modules/pi-lens/dist/clients/opengrep-config.js +5 -1
- package/node_modules/pi-lens/dist/clients/package-manager.js +195 -26
- package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +359 -80
- package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +23 -7
- package/node_modules/pi-lens/dist/clients/path-keyed-map.js +21 -2
- package/node_modules/pi-lens/dist/clients/path-utils.js +451 -15
- package/node_modules/pi-lens/dist/clients/performance-report.js +2 -2
- package/node_modules/pi-lens/dist/clients/persist-debounce.js +8 -1
- package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +114 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +79 -18
- package/node_modules/pi-lens/dist/clients/probe-home-state.js +228 -0
- package/node_modules/pi-lens/dist/clients/process-bridge.js +66 -0
- package/node_modules/pi-lens/dist/clients/process-snapshot.js +68 -0
- package/node_modules/pi-lens/dist/clients/project-changes.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +28 -4
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +51 -20
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +438 -122
- package/node_modules/pi-lens/dist/clients/project-scan-policy.js +3 -51
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +52 -24
- package/node_modules/pi-lens/dist/clients/python-environment.js +217 -0
- package/node_modules/pi-lens/dist/clients/python-provenance.js +639 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +6 -1
- package/node_modules/pi-lens/dist/clients/read-bridge.js +13 -21
- package/node_modules/pi-lens/dist/clients/read-expansion.js +9 -46
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +16 -5
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +364 -212
- package/node_modules/pi-lens/dist/clients/read-guard.js +26 -2
- package/node_modules/pi-lens/dist/clients/recent-touches.js +2 -2
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +195 -96
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +155 -114
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +1 -1
- package/node_modules/pi-lens/dist/clients/review-graph/service.js +55 -3
- package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +7 -15
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +10 -74
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +9 -3
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +19 -3
- package/node_modules/pi-lens/dist/clients/runtime-context.js +49 -16
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +25 -6
- package/node_modules/pi-lens/dist/clients/runtime-session.js +184 -32
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +179 -36
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +797 -243
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +1028 -53
- package/node_modules/pi-lens/dist/clients/rust-client.js +31 -0
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +33 -10
- package/node_modules/pi-lens/dist/clients/sanitize.js +7 -12
- package/node_modules/pi-lens/dist/clients/scan-utils.js +1 -56
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +1 -3
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +3 -0
- package/node_modules/pi-lens/dist/clients/session-lifecycle.js +1 -1
- package/node_modules/pi-lens/dist/clients/session-start-observability.js +79 -0
- package/node_modules/pi-lens/dist/clients/session-summary.js +0 -24
- package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +12 -3
- package/node_modules/pi-lens/dist/clients/sgconfig.js +7 -6
- package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +1 -1
- package/node_modules/pi-lens/dist/clients/skills-resolver.js +105 -0
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +4 -4
- package/node_modules/pi-lens/dist/clients/startup-scan.js +2 -10
- package/node_modules/pi-lens/dist/clients/string-utils.js +13 -0
- package/node_modules/pi-lens/dist/clients/subagent-mode.js +17 -4
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +176 -39
- package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +244 -0
- package/node_modules/pi-lens/dist/clients/tool-definition.js +41 -1
- package/node_modules/pi-lens/dist/clients/tool-policy.js +92 -22
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +40 -40
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +87 -100
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +81 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +51 -46
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +28 -0
- package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -4
- package/node_modules/pi-lens/dist/clients/typos-config.js +5 -0
- package/node_modules/pi-lens/dist/clients/user-notify.js +6 -6
- package/node_modules/pi-lens/dist/clients/widget-state.js +191 -26
- package/node_modules/pi-lens/dist/clients/word-index-logger.js +2 -9
- package/node_modules/pi-lens/dist/clients/word-index-store.js +23 -12
- package/node_modules/pi-lens/dist/clients/word-index.js +252 -47
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +8 -1
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +4 -1
- package/node_modules/pi-lens/dist/index.js +65899 -55340
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -1
- package/node_modules/pi-lens/dist/mcp/build-staleness.js +1 -1
- package/node_modules/pi-lens/dist/mcp/server.js +127 -5
- package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +583 -0
- package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +129 -0
- package/node_modules/pi-lens/dist/tools/effective-config.js +89 -0
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +96 -25
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +104 -86
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +37 -16
- package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +3 -3
- package/node_modules/pi-lens/dist/tools/render-compact.js +1 -1
- package/node_modules/pi-lens/dist/tools/shared.js +0 -1
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -2
- package/node_modules/pi-lens/docs/agent-guide.md +64 -1
- package/node_modules/pi-lens/docs/configuration.md +222 -0
- package/node_modules/pi-lens/docs/dependencies.md +3 -3
- package/node_modules/pi-lens/docs/features.md +44 -5
- package/node_modules/pi-lens/docs/globalconfig.md +20 -0
- package/node_modules/pi-lens/docs/language-coverage.md +2 -2
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +9 -1
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +25 -0
- package/node_modules/pi-lens/docs/pi-lens-investigator.md +25 -0
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +33 -0
- package/node_modules/pi-lens/docs/pi-lens-subagent.md +49 -0
- package/node_modules/pi-lens/docs/pi-lens-warden.md +55 -0
- package/node_modules/pi-lens/docs/public-api-stability.md +359 -0
- package/node_modules/pi-lens/docs/release-qa-baseline.md +190 -0
- package/node_modules/pi-lens/docs/servercapabilities.md +7 -3
- package/node_modules/pi-lens/docs/subagent-compat.md +110 -30
- package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +2 -2
- package/node_modules/pi-lens/docs/word-index.md +1 -1
- package/node_modules/pi-lens/package.json +18 -35
- package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
- package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
- package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
- package/node_modules/pi-lens/rules/typos/_typos.toml +4 -2
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +192 -1
- package/node_modules/pi-lens/scripts/install-selftest.mjs +58 -2
- package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +129 -0
- package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +1 -1
- package/node_modules/pi-lens/scripts/rpc-load-check.mjs +3 -1
- package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +4 -2
- package/node_modules/pi-web-access/CHANGELOG.md +28 -0
- package/node_modules/pi-web-access/README.md +62 -15
- package/node_modules/pi-web-access/curator-page.ts +3 -1
- package/node_modules/pi-web-access/curator-server.ts +5 -1
- package/node_modules/pi-web-access/gemini-search.ts +8 -4
- package/node_modules/pi-web-access/github-extract.ts +242 -1
- package/node_modules/pi-web-access/index.ts +118 -64
- package/node_modules/pi-web-access/mistral-search.ts +281 -0
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/perplexity.ts +14 -1
- package/node_modules/pi-web-access/utils.ts +23 -6
- package/node_modules/pi-web-access/xai-search.ts +96 -33
- package/package.json +6 -6
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
// Process-table access for BOTH trees, in two halves.
|
|
2
|
+
//
|
|
3
|
+
// The PURE half (#476, Layer B assertion 3: zero surviving LSP-server child
|
|
4
|
+
// processes after pi exits — the #472 orphan class) answers "does this
|
|
5
|
+
// command line look like a leaked LSP server, and is it NEW since the
|
|
6
|
+
// baseline snapshot", and is unit-testable with fake process tables.
|
|
7
|
+
//
|
|
8
|
+
// The IMPURE half is the platform listing itself: `buildProcessQuery` (the
|
|
9
|
+
// one place a Windows CIM/WQL or POSIX `ps` command is composed),
|
|
10
|
+
// `parseProcessTable` (the one place its output is read back), and
|
|
11
|
+
// `snapshotProcesses` (the scripts-side bounded spawn around the pair).
|
|
12
|
+
//
|
|
13
|
+
// #2443 folded the LAST copies in here. Before it there were five: this
|
|
14
|
+
// file's two scripts/ callers plus `clients/instance-reaper.ts` (three
|
|
15
|
+
// queries) and `clients/resource-sampler.ts` (two) — the same projection,
|
|
16
|
+
// the same fail-to-an-empty-table shape, written out five times, which is
|
|
17
|
+
// why PR #2438's exit-code hardening (a `ps` that prints a partial table and
|
|
18
|
+
// then dies must not read as a complete one) reached one copy and not the
|
|
19
|
+
// rest. `tests/config/process-table-seam.test.ts` is the guard that keeps it
|
|
20
|
+
// at one.
|
|
21
|
+
//
|
|
22
|
+
// WHY THE SEAM LIVES IN scripts/ AND NOT IN clients/ (#2443 boundary
|
|
23
|
+
// decision). The obvious alternative — a `clients/process-snapshot.ts`
|
|
24
|
+
// compiled to `.js` and imported by scripts/ — is how ~20 other scripts
|
|
25
|
+
// consume client code, and it was rejected for ONE reason: `clients/*.js` is
|
|
26
|
+
// gitignored build output, and `scripts/prune-agent-worktrees.mjs` is a
|
|
27
|
+
// SessionStart/SubagentStop hook that runs inside freshly created agent
|
|
28
|
+
// worktrees, which have neither `node_modules` nor a build yet. A hygiene
|
|
29
|
+
// hook that throws ERR_MODULE_NOT_FOUND is strictly worse than the
|
|
30
|
+
// duplication it removes. This file therefore stays dependency-free (node:
|
|
31
|
+
// builtins only) and clients/ reaches it through
|
|
32
|
+
// `clients/process-snapshot.ts`, the single crossing point, typed by the
|
|
33
|
+
// sibling `process-scan.d.mts`.
|
|
34
|
+
import { spawn } from "node:child_process";
|
|
35
|
+
import fs from "node:fs";
|
|
36
|
+
import path from "node:path";
|
|
37
|
+
/**
|
|
38
|
+
* Read the platform LIVE rather than as a module-load constant, so both the
|
|
39
|
+
* Windows CIM branch and the POSIX `ps` branch of `buildProcessQuery` are
|
|
40
|
+
* exercisable in unit tests regardless of the host OS (the same reasoning
|
|
41
|
+
* `clients/resource-sampler.ts` states for its own platform check — the
|
|
42
|
+
* Windows half of this seam ships to machines no POSIX CI runner can cover,
|
|
43
|
+
* and vice versa).
|
|
44
|
+
*/
|
|
45
|
+
function runningOnWindows() {
|
|
46
|
+
return process.platform === "win32";
|
|
47
|
+
}
|
|
48
|
+
/** Default ceiling for one process listing. */
|
|
49
|
+
const DEFAULT_SNAPSHOT_TIMEOUT_MS = 4_000;
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {"pid" | "ppid" | "ageMs" | "rssBytes" | "cpuKernel100ns" | "cpuUser100ns" | "startedAt" | "command"} ProcessField
|
|
52
|
+
* @typedef {{ pid: number, ppid: number, command: string, ageMs?: number, rssBytes?: number, cpuKernel100ns?: number, cpuUser100ns?: number, startedAt?: string, cwd?: string }} ProcRow
|
|
53
|
+
* @typedef {{ column: "ProcessId" | "Name" | "CommandLine", op: "eq" | "like", values: ReadonlyArray<string|number> }} ProcessFilter
|
|
54
|
+
*/
|
|
55
|
+
/**
|
|
56
|
+
* Per-field platform column names and token shapes. `ps`'s `args` and CIM's
|
|
57
|
+
* `CommandLine` are the same question asked twice; keeping the mapping in one
|
|
58
|
+
* table is what lets a caller say `["pid", "command"]` and get the right
|
|
59
|
+
* query, and the right parse, on both platforms.
|
|
60
|
+
*
|
|
61
|
+
* `ps: null` marks a field the POSIX listing cannot answer. Asking for one on
|
|
62
|
+
* POSIX THROWS rather than yielding a silently absent column — the callers
|
|
63
|
+
* that need those fields (the resource sampler's CPU/RSS query) are
|
|
64
|
+
* Windows-only by construction, and a wrong-shaped `ps` invocation would
|
|
65
|
+
* return zero rows, which reads exactly like "no such process".
|
|
66
|
+
*/
|
|
67
|
+
const FIELD_COLUMNS = Object.freeze({
|
|
68
|
+
pid: { wmi: "ProcessId", ps: "pid", token: "int" },
|
|
69
|
+
ppid: { wmi: "ParentProcessId", ps: "ppid", token: "int" },
|
|
70
|
+
ageMs: { wmi: "CreationDate", ps: "etime", token: "age" },
|
|
71
|
+
rssBytes: { wmi: "WorkingSetSize", ps: null, token: "int" },
|
|
72
|
+
cpuKernel100ns: { wmi: "KernelModeTime", ps: null, token: "int" },
|
|
73
|
+
cpuUser100ns: { wmi: "UserModeTime", ps: null, token: "int" },
|
|
74
|
+
startedAt: { wmi: "CreationDate", ps: null, token: "text" },
|
|
75
|
+
command: { wmi: "CommandLine", ps: "args", token: "tail" },
|
|
76
|
+
});
|
|
77
|
+
/**
|
|
78
|
+
* The canonical column ORDER, which every parser assumes. `command` is LAST
|
|
79
|
+
* because it is the only column that can contain the delimiter — both parsers
|
|
80
|
+
* take it as the tail of the line. Derived from `FIELD_COLUMNS` rather than
|
|
81
|
+
* written out again, so a field cannot exist in one list and not the other.
|
|
82
|
+
*/
|
|
83
|
+
const FIELD_ORDER = Object.freeze(Object.keys(FIELD_COLUMNS));
|
|
84
|
+
/**
|
|
85
|
+
* The DEFAULT projection: the three columns every platform can answer, which
|
|
86
|
+
* is what a caller that names no fields gets. It is deliberately NOT the full
|
|
87
|
+
* field set — `rssBytes`/`cpuKernel100ns`/`cpuUser100ns`/`startedAt` exist
|
|
88
|
+
* only on the Windows side, so defaulting to them would make an unprojected
|
|
89
|
+
* listing throw on POSIX instead of doing the obvious thing.
|
|
90
|
+
*/
|
|
91
|
+
export const ALL_PROCESS_FIELDS = Object.freeze(["pid", "ppid", "command"]);
|
|
92
|
+
/** Columns a filter may name. A closed set, so the column half of a WQL
|
|
93
|
+
* clause can never carry caller-supplied text. */
|
|
94
|
+
const FILTERABLE_COLUMNS = new Set(["ProcessId", "Name", "CommandLine"]);
|
|
95
|
+
/**
|
|
96
|
+
* Absolute path to a System32 executable. Windows resolves a bare
|
|
97
|
+
* `powershell.exe` through PATH, which a caller can shadow; the callers spawn
|
|
98
|
+
* this to decide what to KILL, so the interpreter is named absolutely.
|
|
99
|
+
*
|
|
100
|
+
* @param {string} name
|
|
101
|
+
* @returns {string}
|
|
102
|
+
*/
|
|
103
|
+
export function windowsExe(name) {
|
|
104
|
+
return path.join(process.env.SystemRoot ?? path.join("C:", "Windows"), "System32", name);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Absolute path to POSIX `ps` (S4036: never spawn via a bare PATH lookup) —
|
|
108
|
+
* the same reasoning as `windowsExe` applied to the other platform, since
|
|
109
|
+
* this listing decides what gets killed. Prefers `/bin/ps`, then
|
|
110
|
+
* `/usr/bin/ps`, and falls back to `/bin/ps` when neither probe succeeds, so
|
|
111
|
+
* the spawn fails CLOSED instead of quietly resolving through a PATH a caller
|
|
112
|
+
* can shadow. (#2443 adopted the reaper's stricter form over this file's
|
|
113
|
+
* earlier bare-`ps` fallback: one seam, the safer of the two behaviours.)
|
|
114
|
+
*
|
|
115
|
+
* @returns {string}
|
|
116
|
+
*/
|
|
117
|
+
export function posixPsPath() {
|
|
118
|
+
try {
|
|
119
|
+
if (fs.existsSync("/bin/ps"))
|
|
120
|
+
return "/bin/ps";
|
|
121
|
+
if (fs.existsSync("/usr/bin/ps"))
|
|
122
|
+
return "/usr/bin/ps";
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
/* unreadable filesystem — fall through to the absolute default */
|
|
126
|
+
}
|
|
127
|
+
return "/bin/ps";
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Normalize and order a requested field list. `pid` is always present (it is
|
|
131
|
+
* the row identity) and the order always follows `ALL_PROCESS_FIELDS`, so the
|
|
132
|
+
* parser never has to be told the layout it is reading.
|
|
133
|
+
*
|
|
134
|
+
* @param {ReadonlyArray<ProcessField>|null|undefined} fields
|
|
135
|
+
* @returns {ProcessField[]}
|
|
136
|
+
*/
|
|
137
|
+
export function normalizeProcessFields(fields) {
|
|
138
|
+
const requested = new Set((fields ?? ALL_PROCESS_FIELDS).filter((field) => field in FIELD_COLUMNS));
|
|
139
|
+
requested.add("pid");
|
|
140
|
+
return FIELD_ORDER.filter((field) => requested.has(field));
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Escape a value for embedding in a WQL LIKE clause: WQL uses `'` as the
|
|
144
|
+
* string delimiter (doubled to escape) and `%`/`_` as wildcards — a marker is
|
|
145
|
+
* an opaque path string, so escape all three before interpolating.
|
|
146
|
+
*
|
|
147
|
+
* @param {string|number} value
|
|
148
|
+
* @returns {string}
|
|
149
|
+
*/
|
|
150
|
+
export function escapeWqlLikeValue(value) {
|
|
151
|
+
return String(value)
|
|
152
|
+
.replaceAll("'", "''")
|
|
153
|
+
.replaceAll(/[%_]/g, (ch) => `[${ch}]`);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Escape a value for a WQL EQUALITY comparison: only the string delimiter
|
|
157
|
+
* needs escaping — `%`/`_` are literal outside `LIKE`.
|
|
158
|
+
*
|
|
159
|
+
* @param {string|number} value
|
|
160
|
+
* @returns {string}
|
|
161
|
+
*/
|
|
162
|
+
export function escapeWqlStringValue(value) {
|
|
163
|
+
return String(value).replaceAll("'", "''");
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* A pid about to be interpolated into a query, validated as a positive
|
|
167
|
+
* integer so the numeric branch can never carry query text.
|
|
168
|
+
*
|
|
169
|
+
* @param {string|number} value
|
|
170
|
+
* @returns {number}
|
|
171
|
+
*/
|
|
172
|
+
function assertPid(value) {
|
|
173
|
+
const pid = Number(value);
|
|
174
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
175
|
+
throw new Error(`process filter: invalid pid ${String(value)}`);
|
|
176
|
+
}
|
|
177
|
+
return pid;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* One WQL boolean expression for a filter. `ProcessId` is compared
|
|
181
|
+
* numerically (every value validated as a positive integer first, so nothing
|
|
182
|
+
* caller-supplied reaches the query text); every other column is compared as
|
|
183
|
+
* an escaped string.
|
|
184
|
+
*
|
|
185
|
+
* @param {ProcessFilter} filter
|
|
186
|
+
* @returns {string}
|
|
187
|
+
*/
|
|
188
|
+
function wqlFilterExpression(filter) {
|
|
189
|
+
const { column, op, values } = filter;
|
|
190
|
+
if (!FILTERABLE_COLUMNS.has(column)) {
|
|
191
|
+
throw new Error(`process filter: unsupported column ${String(column)}`);
|
|
192
|
+
}
|
|
193
|
+
const list = [...values];
|
|
194
|
+
if (list.length === 0) {
|
|
195
|
+
throw new Error(`process filter: ${column} filter has no values`);
|
|
196
|
+
}
|
|
197
|
+
if (column === "ProcessId") {
|
|
198
|
+
return list.map((value) => `ProcessId=${assertPid(value)}`).join(" OR ");
|
|
199
|
+
}
|
|
200
|
+
if (op === "like") {
|
|
201
|
+
return list
|
|
202
|
+
.map((value) => `${column} LIKE '%${escapeWqlLikeValue(value)}%'`)
|
|
203
|
+
.join(" OR ");
|
|
204
|
+
}
|
|
205
|
+
return list
|
|
206
|
+
.map((value) => `${column} = '${escapeWqlStringValue(value)}'`)
|
|
207
|
+
.join(" OR ");
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Compose the platform command that lists processes.
|
|
211
|
+
*
|
|
212
|
+
* Windows uses `Get-CimInstance` through `powershell -NoProfile` with an
|
|
213
|
+
* explicit WQL projection (measured ~316ms for the query vs ~570ms for the
|
|
214
|
+
* unprojected form on the #2435 box, plus ~208ms powershell startup) —
|
|
215
|
+
* `tasklist` exposes no parent pid and no command line, and `wmic` is gone
|
|
216
|
+
* from Windows 11. POSIX uses `ps`, either `-eo <cols>` (every process) or
|
|
217
|
+
* `-p <pids> -o <cols>` when the filter is a pid set, which is the only
|
|
218
|
+
* filter `ps` can apply server-side.
|
|
219
|
+
*
|
|
220
|
+
* `serverSideFiltered` reports whether the platform actually applied the
|
|
221
|
+
* filter. A `Name`/`CommandLine` filter has no `ps` equivalent, so on POSIX
|
|
222
|
+
* the caller gets the whole table and must narrow it itself — said out loud
|
|
223
|
+
* here rather than left as a per-caller assumption.
|
|
224
|
+
*
|
|
225
|
+
* `ageMs` on Windows is computed IN PowerShell as a millisecond delta, never
|
|
226
|
+
* exported as `CreationDate.Ticks`: `Ticks` is the LOCAL-time representation,
|
|
227
|
+
* so subtracting the Unix epoch in JS produced an age wrong by the machine's
|
|
228
|
+
* UTC offset (#1857 — measured on a UTC+3 host as -8358s for a process
|
|
229
|
+
* started 40 minutes earlier). A delta computed on one side of the boundary
|
|
230
|
+
* has no timezone to get wrong. The explicit `[datetime]` test matters too:
|
|
231
|
+
* subtracting `$null` from `(Get-Date)` yields a two-thousand-year TimeSpan,
|
|
232
|
+
* which would read as "ancient" and defeat a spawn-grace guard exactly where
|
|
233
|
+
* the data is missing.
|
|
234
|
+
*
|
|
235
|
+
* @param {ReadonlyArray<ProcessField>} [fields]
|
|
236
|
+
* @param {{ filter?: ProcessFilter, excludeSelfPid?: boolean }} [options]
|
|
237
|
+
* @returns {{ command: string, args: string[], tabSeparated: boolean, fields: ProcessField[], serverSideFiltered: boolean }}
|
|
238
|
+
*/
|
|
239
|
+
export function buildProcessQuery(fields = ALL_PROCESS_FIELDS, options = {}) {
|
|
240
|
+
const layout = normalizeProcessFields(fields);
|
|
241
|
+
const filter = options.filter;
|
|
242
|
+
if (runningOnWindows()) {
|
|
243
|
+
const columns = [
|
|
244
|
+
...new Set(layout.map((field) => FIELD_COLUMNS[field].wmi)),
|
|
245
|
+
];
|
|
246
|
+
const where = filter ? ` WHERE ${wqlFilterExpression(filter)}` : "";
|
|
247
|
+
// A CommandLine filter's own marker is embedded in the command line of
|
|
248
|
+
// the powershell.exe running the query, so the search would match
|
|
249
|
+
// itself. `$PID` is the only identity that can exclude it, and it is
|
|
250
|
+
// knowable only inside the child.
|
|
251
|
+
const excludeSelf = options.excludeSelfPid
|
|
252
|
+
? " | Where-Object { $_.ProcessId -ne $PID }"
|
|
253
|
+
: "";
|
|
254
|
+
const prelude = layout.includes("ageMs")
|
|
255
|
+
? "$age = if ($_.CreationDate -is [datetime]) { [int64]((Get-Date) - $_.CreationDate).TotalMilliseconds } else { '' }; "
|
|
256
|
+
: "";
|
|
257
|
+
const row = layout
|
|
258
|
+
.map((field) => field === "ageMs" ? "$age" : `$($_.${FIELD_COLUMNS[field].wmi})`)
|
|
259
|
+
.join("`t");
|
|
260
|
+
return {
|
|
261
|
+
command: windowsExe("WindowsPowerShell\\v1.0\\powershell.exe"),
|
|
262
|
+
args: [
|
|
263
|
+
"-NoProfile",
|
|
264
|
+
"-NonInteractive",
|
|
265
|
+
"-Command",
|
|
266
|
+
`Get-CimInstance -Query "SELECT ${columns.join(",")} FROM Win32_Process${where}"${excludeSelf} | ForEach-Object { ${prelude}"${row}" }`,
|
|
267
|
+
],
|
|
268
|
+
tabSeparated: true,
|
|
269
|
+
fields: layout,
|
|
270
|
+
serverSideFiltered: Boolean(filter),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const unsupported = layout.filter((field) => !FIELD_COLUMNS[field].ps);
|
|
274
|
+
if (unsupported.length > 0) {
|
|
275
|
+
throw new Error(`process query: ${unsupported.join(", ")} has no POSIX ps column`);
|
|
276
|
+
}
|
|
277
|
+
const columns = layout
|
|
278
|
+
.map((field) => `${FIELD_COLUMNS[field].ps}=`)
|
|
279
|
+
.join(",");
|
|
280
|
+
const pidFilter = filter && filter.column === "ProcessId" && filter.op === "eq"
|
|
281
|
+
? [...filter.values].map((value) => assertPid(value))
|
|
282
|
+
: null;
|
|
283
|
+
return {
|
|
284
|
+
command: posixPsPath(),
|
|
285
|
+
args: pidFilter
|
|
286
|
+
? ["-p", pidFilter.join(","), "-o", columns]
|
|
287
|
+
: ["-eo", columns],
|
|
288
|
+
tabSeparated: false,
|
|
289
|
+
fields: layout,
|
|
290
|
+
serverSideFiltered: Boolean(pidFilter),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Parse a non-negative integer token, rejecting anything else. A nonsensical
|
|
295
|
+
* value means the value is UNKNOWN, not zero — a caller reasoning from a
|
|
296
|
+
* fabricated 0 is how the #1857 local-time age bug stayed invisible.
|
|
297
|
+
*
|
|
298
|
+
* @param {string|undefined} raw
|
|
299
|
+
* @returns {number|undefined}
|
|
300
|
+
*/
|
|
301
|
+
function parseNonNegativeInt(raw) {
|
|
302
|
+
const token = String(raw ?? "").trim();
|
|
303
|
+
if (!/^\d+$/.test(token))
|
|
304
|
+
return undefined;
|
|
305
|
+
const value = Number(token);
|
|
306
|
+
return Number.isFinite(value) ? value : undefined;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Parse a `ps -o etime` token: `[[dd-]hh:]mm:ss`. Returns undefined for any
|
|
310
|
+
* shape the column did not produce (a `ps` without the column emits the
|
|
311
|
+
* command line where the token was expected).
|
|
312
|
+
*
|
|
313
|
+
* @param {string} raw
|
|
314
|
+
* @returns {number|undefined}
|
|
315
|
+
*/
|
|
316
|
+
export function ageMsFromPosixEtime(raw) {
|
|
317
|
+
const token = String(raw ?? "").trim();
|
|
318
|
+
if (!/^(?:\d+-)?(?:\d+:)?\d+:\d+$/.test(token))
|
|
319
|
+
return undefined;
|
|
320
|
+
let days = 0;
|
|
321
|
+
let rest = token;
|
|
322
|
+
const dash = rest.indexOf("-");
|
|
323
|
+
if (dash >= 0) {
|
|
324
|
+
days = Number(rest.slice(0, dash));
|
|
325
|
+
rest = rest.slice(dash + 1);
|
|
326
|
+
}
|
|
327
|
+
const parts = rest.split(":").map(Number);
|
|
328
|
+
if (parts.some((part) => !Number.isFinite(part)))
|
|
329
|
+
return undefined;
|
|
330
|
+
const [hours, minutes, seconds] = parts.length === 3 ? parts : [0, parts[0], parts[1]];
|
|
331
|
+
return ((days * 24 + hours) * 60 * 60 + minutes * 60 + seconds) * 1000;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* POSIX token pattern per field: every column but the trailing command is a
|
|
335
|
+
* single whitespace-delimited token, and `etime` is not purely numeric.
|
|
336
|
+
*
|
|
337
|
+
* @param {ProcessField} field
|
|
338
|
+
* @returns {string}
|
|
339
|
+
*/
|
|
340
|
+
function posixTokenPattern(field) {
|
|
341
|
+
return FIELD_COLUMNS[field].token === "age" ? "(\\S+)" : "(\\d+)";
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Parse a platform listing into rows.
|
|
345
|
+
*
|
|
346
|
+
* Windows CIM emits tab-joined fields, one per requested column. `ps` emits
|
|
347
|
+
* whitespace-aligned columns whose LAST field (`args`) contains spaces, so it
|
|
348
|
+
* is parsed positionally with the tail taken whole.
|
|
349
|
+
*
|
|
350
|
+
* `pid`, `ppid` and `command` are always present on the row at their zero
|
|
351
|
+
* value (`ppid: 0`, `command: ""`) so consumers see one row shape regardless
|
|
352
|
+
* of the projection; every other field appears only when it was requested,
|
|
353
|
+
* and is `undefined` when the platform emitted a value that could not be
|
|
354
|
+
* trusted.
|
|
355
|
+
*
|
|
356
|
+
* @param {string} out
|
|
357
|
+
* @param {boolean} tabSeparated
|
|
358
|
+
* @param {ReadonlyArray<ProcessField>} [fields]
|
|
359
|
+
* @returns {ProcRow[]}
|
|
360
|
+
*/
|
|
361
|
+
export function parseProcessTable(out, tabSeparated, fields = ALL_PROCESS_FIELDS) {
|
|
362
|
+
const layout = normalizeProcessFields(fields);
|
|
363
|
+
const commandIndex = layout.indexOf("command");
|
|
364
|
+
const positional = tabSeparated
|
|
365
|
+
? null
|
|
366
|
+
: new RegExp(`^\\s*${layout
|
|
367
|
+
.filter((field) => field !== "command")
|
|
368
|
+
.map((field) => posixTokenPattern(field))
|
|
369
|
+
.join("\\s+")}${commandIndex === -1 ? "\\s*$" : "\\s+(.*)$"}`);
|
|
370
|
+
const rows = [];
|
|
371
|
+
for (const line of String(out ?? "").split(/\r?\n/)) {
|
|
372
|
+
if (!line.trim())
|
|
373
|
+
continue;
|
|
374
|
+
/** @type {string[]} */
|
|
375
|
+
let parts;
|
|
376
|
+
if (tabSeparated) {
|
|
377
|
+
const split = line.split("\t");
|
|
378
|
+
if (split.length < layout.length - (commandIndex === -1 ? 0 : 1))
|
|
379
|
+
continue;
|
|
380
|
+
// The command is the tail: it may itself contain tabs.
|
|
381
|
+
parts =
|
|
382
|
+
commandIndex === -1
|
|
383
|
+
? split
|
|
384
|
+
: [
|
|
385
|
+
...split.slice(0, commandIndex),
|
|
386
|
+
split.slice(commandIndex).join("\t"),
|
|
387
|
+
];
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
const match = positional?.exec(line);
|
|
391
|
+
if (!match)
|
|
392
|
+
continue;
|
|
393
|
+
parts = match.slice(1);
|
|
394
|
+
}
|
|
395
|
+
const value = (field) => {
|
|
396
|
+
const at = layout.indexOf(field);
|
|
397
|
+
return at === -1 ? undefined : parts[at];
|
|
398
|
+
};
|
|
399
|
+
const pid = Number(value("pid"));
|
|
400
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
401
|
+
continue;
|
|
402
|
+
const ppid = Number(value("ppid"));
|
|
403
|
+
/** @type {ProcRow} */
|
|
404
|
+
const row = {
|
|
405
|
+
pid,
|
|
406
|
+
ppid: Number.isInteger(ppid) && ppid > 0 ? ppid : 0,
|
|
407
|
+
command: value("command") ?? "",
|
|
408
|
+
};
|
|
409
|
+
for (const field of layout) {
|
|
410
|
+
if (field === "pid" || field === "ppid" || field === "command")
|
|
411
|
+
continue;
|
|
412
|
+
const raw = value(field);
|
|
413
|
+
if (field === "ageMs") {
|
|
414
|
+
row.ageMs = tabSeparated
|
|
415
|
+
? parseNonNegativeInt(raw)
|
|
416
|
+
: ageMsFromPosixEtime(raw ?? "");
|
|
417
|
+
}
|
|
418
|
+
else if (field === "startedAt") {
|
|
419
|
+
row.startedAt = String(raw ?? "").trim();
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
row[field] = parseNonNegativeInt(raw);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
rows.push(row);
|
|
426
|
+
}
|
|
427
|
+
return rows;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Snapshot the process table, for scripts/ — one bounded spawn around
|
|
431
|
+
* `buildProcessQuery` plus `parseProcessTable`.
|
|
432
|
+
*
|
|
433
|
+
* clients/ does NOT call this: it needs the extension's own spawn rails (an
|
|
434
|
+
* unref'd child and piped stdout, a tree-kill-and-verify timeout handler, the
|
|
435
|
+
* degradation ledger), so `clients/process-snapshot.ts` composes the same two
|
|
436
|
+
* halves over `spawnCollectStdoutResult` instead. The QUERY and the PARSE are
|
|
437
|
+
* shared; only the spawn differs, and it differs for a reason.
|
|
438
|
+
*
|
|
439
|
+
* Never rejects. Returns `{ rows, ok }`: `ok` is false when the listing
|
|
440
|
+
* failed, timed out, never spawned, or EXITED NON-ZERO — the last of which
|
|
441
|
+
* used to be ignored (PR #2438 review S5), so a `ps` that printed a partial
|
|
442
|
+
* table and then died read as a complete one. `ok` is the only evidence a
|
|
443
|
+
* caller has that an ABSENCE from the table means anything. Bounded by
|
|
444
|
+
* `timeoutMs` so a hook can never hang a session on a wedged WMI service.
|
|
445
|
+
*
|
|
446
|
+
* The timed-out child is killed through the HELD HANDLE, never by spawning a
|
|
447
|
+
* killer (#234: spawning during teardown aborts libuv on Windows).
|
|
448
|
+
*
|
|
449
|
+
* @param {ReadonlyArray<ProcessField>} [fields]
|
|
450
|
+
* @param {number} [timeoutMs]
|
|
451
|
+
* @param {{ filter?: ProcessFilter, excludeSelfPid?: boolean }} [options]
|
|
452
|
+
* @returns {Promise<{ rows: ProcRow[], ok: boolean }>}
|
|
453
|
+
*/
|
|
454
|
+
export function snapshotProcesses(fields = ALL_PROCESS_FIELDS, timeoutMs = DEFAULT_SNAPSHOT_TIMEOUT_MS, options = {}) {
|
|
455
|
+
let query;
|
|
456
|
+
try {
|
|
457
|
+
query = buildProcessQuery(fields, options);
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
// An unbuildable query (a Windows-only column asked for on POSIX, an
|
|
461
|
+
// empty filter) is a caller bug, not a runtime outage — but this seam's
|
|
462
|
+
// contract is never to throw at a best-effort caller, so it reports the
|
|
463
|
+
// same not-ok empty table every other failure reports.
|
|
464
|
+
return Promise.resolve({ rows: [], ok: false });
|
|
465
|
+
}
|
|
466
|
+
return new Promise((resolve) => {
|
|
467
|
+
let settled = false;
|
|
468
|
+
const finish = (rows, ok) => {
|
|
469
|
+
if (settled)
|
|
470
|
+
return;
|
|
471
|
+
settled = true;
|
|
472
|
+
clearTimeout(timer);
|
|
473
|
+
resolve({ rows, ok });
|
|
474
|
+
};
|
|
475
|
+
let child;
|
|
476
|
+
const timer = setTimeout(() => {
|
|
477
|
+
try {
|
|
478
|
+
child?.kill();
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
/* already gone */
|
|
482
|
+
}
|
|
483
|
+
finish([], false);
|
|
484
|
+
}, timeoutMs);
|
|
485
|
+
try {
|
|
486
|
+
child = spawn(query.command, query.args, {
|
|
487
|
+
shell: false,
|
|
488
|
+
windowsHide: true,
|
|
489
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
493
|
+
finish([], false);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
let out = "";
|
|
497
|
+
child.stdout.on("data", (chunk) => {
|
|
498
|
+
out += chunk.toString();
|
|
499
|
+
});
|
|
500
|
+
child.once("error", () => finish([], false));
|
|
501
|
+
// A non-zero exit (or a signal) means the listing is PARTIAL at best.
|
|
502
|
+
// Whatever it printed is not evidence of what is NOT running, which is
|
|
503
|
+
// exactly the question the orphan predicate asks of it.
|
|
504
|
+
child.once("close", (code, signal) => finish(parseProcessTable(out, query.tabSeparated, query.fields), code === 0 && !signal));
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Command-line substrings that identify an LSP server process we launch.
|
|
509
|
+
* Deliberately narrow — matching on the distinctive binary/module name, not
|
|
510
|
+
* a generic "node" or "language-server" fragment, so the scan doesn't flag
|
|
511
|
+
* unrelated node processes on a shared CI runner.
|
|
512
|
+
*/
|
|
513
|
+
export const LSP_PROCESS_MARKERS = [
|
|
514
|
+
"typescript-language-server",
|
|
515
|
+
"ast-grep lsp",
|
|
516
|
+
"ast-grep-lsp",
|
|
517
|
+
"pyright-langserver",
|
|
518
|
+
"vscode-json-languageserver",
|
|
519
|
+
];
|
|
520
|
+
/**
|
|
521
|
+
* @typedef {{ pid: number, command: string }} ProcessRow
|
|
522
|
+
*/
|
|
523
|
+
/**
|
|
524
|
+
* True iff `command` looks like one of the LSP servers pi-lens spawns.
|
|
525
|
+
*
|
|
526
|
+
* @param {string} command
|
|
527
|
+
*/
|
|
528
|
+
export function isLspServerCommand(command) {
|
|
529
|
+
const lower = command.toLowerCase();
|
|
530
|
+
return LSP_PROCESS_MARKERS.some((marker) => lower.includes(marker.toLowerCase()));
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Diff a `before` and `after` process snapshot and return the LSP-server
|
|
534
|
+
* rows that are NEW in `after` (i.e. survived/spawned during the run and are
|
|
535
|
+
* still alive after pi exited — the orphan class #472 fixed). Matches by pid
|
|
536
|
+
* — a row present in both snapshots with the same pid is presumed to be an
|
|
537
|
+
* unrelated pre-existing process, not something this run leaked.
|
|
538
|
+
*
|
|
539
|
+
* @param {ProcessRow[]} before
|
|
540
|
+
* @param {ProcessRow[]} after
|
|
541
|
+
* @returns {ProcessRow[]}
|
|
542
|
+
*/
|
|
543
|
+
export function diffSurvivingLspProcesses(before, after) {
|
|
544
|
+
const beforePids = new Set(before.map((r) => r.pid));
|
|
545
|
+
return after.filter((row) => !beforePids.has(row.pid) && isLspServerCommand(row.command));
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* @typedef {{ rows: ProcessRow[], ok: boolean }} ProcessSnapshot
|
|
549
|
+
*/
|
|
550
|
+
/**
|
|
551
|
+
* The "no surviving LSP process" assertion (compat-smoke-behavioral.mjs,
|
|
552
|
+
* Layer B assertion 3), extracted so it is unit-testable without spawning a
|
|
553
|
+
* real `ps`/CIM listing.
|
|
554
|
+
*
|
|
555
|
+
* An absence of a row from `after` only means something when BOTH snapshots
|
|
556
|
+
* are known-complete: `snapshotProcesses`'s `ok` is the only evidence of
|
|
557
|
+
* that (see its doc comment). A failed or timed-out listing yields an empty
|
|
558
|
+
* table, which must never read as "no leak" — that is silence, not
|
|
559
|
+
* evidence, and would make a caller-side outage report a false pass
|
|
560
|
+
* (PR #2438 review round 4, F-A).
|
|
561
|
+
*
|
|
562
|
+
* @param {ProcessSnapshot} before
|
|
563
|
+
* @param {ProcessSnapshot} after
|
|
564
|
+
* @returns {{ id: string, pass: boolean, detail: string }}
|
|
565
|
+
*/
|
|
566
|
+
export function evaluateNoSurvivingLspProcesses(before, after) {
|
|
567
|
+
const id = "no-surviving-lsp-processes";
|
|
568
|
+
if (!before.ok || !after.ok) {
|
|
569
|
+
return {
|
|
570
|
+
id,
|
|
571
|
+
pass: false,
|
|
572
|
+
detail: `process listing unavailable (before.ok=${before.ok}, after.ok=${after.ok}); cannot verify no LSP process survived`,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
const surviving = diffSurvivingLspProcesses(before.rows, after.rows);
|
|
576
|
+
return {
|
|
577
|
+
id,
|
|
578
|
+
pass: surviving.length === 0,
|
|
579
|
+
detail: surviving.length === 0
|
|
580
|
+
? "no new LSP-server processes survived pi's exit"
|
|
581
|
+
: `${surviving.length} surviving process(es): ${surviving.map((p) => `pid=${p.pid} ${p.command.slice(0, 80)}`).join("; ")}`,
|
|
582
|
+
};
|
|
583
|
+
}
|