@herbertgao/pi-extensions 2026.9.6 → 2026.9.7
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 +3 -8
- package/THIRD_PARTY_NOTICES.md +0 -4
- package/node_modules/@narumitw/pi-btw/README.md +2 -0
- package/node_modules/@narumitw/pi-btw/dist/index.ts +284 -365
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
- package/node_modules/@narumitw/pi-btw/package.json +52 -53
- package/node_modules/@narumitw/pi-btw/src/bring-to-main.ts +494 -547
- package/node_modules/@narumitw/pi-btw/src/btw.ts +814 -856
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +673 -674
- package/node_modules/@narumitw/pi-btw/src/keybindings.ts +228 -270
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +309 -318
- package/node_modules/@narumitw/pi-btw/src/menu.ts +416 -454
- package/node_modules/@narumitw/pi-btw/src/settings.ts +203 -219
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +173 -201
- package/node_modules/@narumitw/pi-btw/src/text.ts +21 -23
- package/node_modules/@narumitw/pi-btw/src/transcript-markdown.ts +65 -0
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +611 -662
- package/node_modules/pi-lens/CHANGELOG.md +239 -0
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +30 -4
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +2 -0
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +26 -0
- package/node_modules/pi-lens/dist/clients/biome-client.js +1 -1
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +403 -35
- package/node_modules/pi-lens/dist/clients/bootstrap.js +6 -1
- package/node_modules/pi-lens/dist/clients/cascade-format.js +200 -4
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +10 -2
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +16 -1
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +12 -1
- package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +65 -0
- package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +28 -0
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +9 -2
- package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +222 -0
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +389 -13
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +175 -25
- package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +62 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +21 -41
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +6 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +7 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -12
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +2 -21
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/types.js +4 -1
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +1 -0
- package/node_modules/pi-lens/dist/clients/effective-config.js +1 -1
- package/node_modules/pi-lens/dist/clients/file-kinds.js +26 -1
- package/node_modules/pi-lens/dist/clients/file-role.js +21 -21
- package/node_modules/pi-lens/dist/clients/file-utils.js +156 -10
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +56 -9
- package/node_modules/pi-lens/dist/clients/format-service.js +39 -21
- package/node_modules/pi-lens/dist/clients/formatters.js +60 -13
- package/node_modules/pi-lens/dist/clients/generation-guard.js +2 -2
- package/node_modules/pi-lens/dist/clients/git-guard.js +11 -3
- package/node_modules/pi-lens/dist/clients/indent-retarget.js +78 -8
- package/node_modules/pi-lens/dist/clients/installer/index.js +333 -114
- package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +7 -0
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +20 -10
- package/node_modules/pi-lens/dist/clients/language-profile.js +23 -1
- package/node_modules/pi-lens/dist/clients/language-registry.js +5 -1
- package/node_modules/pi-lens/dist/clients/latency-logger.js +4 -1
- package/node_modules/pi-lens/dist/clients/lens-engine.js +12 -3
- package/node_modules/pi-lens/dist/clients/lens-events.js +2 -1
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +79 -0
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/client.js +25 -21
- package/node_modules/pi-lens/dist/clients/lsp/config.js +2 -2
- package/node_modules/pi-lens/dist/clients/lsp/index.js +7 -0
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +16 -4
- package/node_modules/pi-lens/dist/clients/lsp/server.js +19 -2
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +5 -3
- package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +12 -8
- package/node_modules/pi-lens/dist/clients/mcp/session.js +36 -4
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +24 -7
- package/node_modules/pi-lens/dist/clients/observed-mutation.js +40 -2
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +23 -3
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +110 -15
- package/node_modules/pi-lens/dist/clients/path-utils.js +123 -3
- package/node_modules/pi-lens/dist/clients/performance-report.js +5 -1
- package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +1 -1
- package/node_modules/pi-lens/dist/clients/pipeline.js +74 -5
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +37 -5
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +145 -18
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +4 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +59 -3
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +45 -2
- package/node_modules/pi-lens/dist/clients/read-guard.js +70 -11
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +147 -9
- package/node_modules/pi-lens/dist/clients/ruff-client.js +1 -1
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +33 -5
- package/node_modules/pi-lens/dist/clients/runtime-context.js +6 -2
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +77 -7
- package/node_modules/pi-lens/dist/clients/runtime-session.js +21 -2
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +43 -12
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +330 -85
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +147 -17
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +258 -5
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +6 -0
- package/node_modules/pi-lens/dist/clients/session-event-guard.js +50 -5
- package/node_modules/pi-lens/dist/clients/sg-runner.js +23 -11
- package/node_modules/pi-lens/dist/clients/sgconfig.js +79 -2
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +6 -41
- package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +43 -0
- package/node_modules/pi-lens/dist/clients/tool-agreement.js +423 -0
- package/node_modules/pi-lens/dist/clients/tool-config.js +50 -0
- package/node_modules/pi-lens/dist/clients/tool-cwd.js +47 -76
- package/node_modules/pi-lens/dist/clients/tool-policy.js +163 -7
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +120 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +127 -189
- package/node_modules/pi-lens/dist/clients/widget-state.js +98 -0
- package/node_modules/pi-lens/dist/clients/word-index.js +5 -2
- package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +7 -0
- package/node_modules/pi-lens/dist/index.js +27416 -24153
- package/node_modules/pi-lens/dist/mcp/server.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +7 -1
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +168 -53
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +244 -72
- package/node_modules/pi-lens/docs/audit1.md +5 -5
- package/node_modules/pi-lens/docs/dependencies.md +11 -0
- package/node_modules/pi-lens/docs/dispositions.md +52 -0
- package/node_modules/pi-lens/docs/environment-variables.md +2 -2
- package/node_modules/pi-lens/docs/features.md +4 -4
- package/node_modules/pi-lens/docs/globalconfig.md +3 -1
- package/node_modules/pi-lens/docs/language-coverage.md +1 -0
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +53 -94
- package/node_modules/pi-lens/docs/pi-lens-investigator.md +43 -38
- package/node_modules/pi-lens/docs/pi-lens-retro.md +97 -0
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +53 -66
- package/node_modules/pi-lens/docs/release-qa-baseline.md +22 -0
- package/node_modules/pi-lens/docs/settings.md +7 -4
- package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
- package/node_modules/pi-lens/docs/usage.md +2 -0
- package/node_modules/pi-lens/package.json +1 -1
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +24 -12
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +40 -0
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +15 -0
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +7 -0
- package/package.json +5 -11
- package/node_modules/@luxusai/pi-hindsight/CHANGELOG.md +0 -376
- package/node_modules/@luxusai/pi-hindsight/README.md +0 -92
- package/node_modules/@luxusai/pi-hindsight/docs/adr/001-memory-lifecycle-and-scope.md +0 -56
- package/node_modules/@luxusai/pi-hindsight/docs/adr/002-explicit-routing-strategy-seam.md +0 -169
- package/node_modules/@luxusai/pi-hindsight/docs/adr/003-tui-memory-mode-vocabulary.md +0 -107
- package/node_modules/@luxusai/pi-hindsight/docs/adr/004-lifeos-dual-bank-design.md +0 -89
- package/node_modules/@luxusai/pi-hindsight/docs/adr/005-domain-banks-and-agent-first-surface.md +0 -189
- package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark.webp +0 -0
- package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark@2x.webp +0 -0
- package/node_modules/@luxusai/pi-hindsight/docs/coding-memory-evaluation.md +0 -54
- package/node_modules/@luxusai/pi-hindsight/docs/compatibility.md +0 -75
- package/node_modules/@luxusai/pi-hindsight/docs/hindsight-core-functions.md +0 -300
- package/node_modules/@luxusai/pi-hindsight/docs/mission-and-mental-model-quality.md +0 -158
- package/node_modules/@luxusai/pi-hindsight/docs/next-opt-out-design.md +0 -164
- package/node_modules/@luxusai/pi-hindsight/docs/risky-memory-modes.md +0 -139
- package/node_modules/@luxusai/pi-hindsight/docs/starter-mental-model-suggestions.md +0 -74
- package/node_modules/@luxusai/pi-hindsight/docs/surface-reference.md +0 -261
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-operations.ts +0 -151
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-selection.ts +0 -18
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-settings-presenter.ts +0 -46
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-templates.ts +0 -383
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/banking.ts +0 -240
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/knowledge-page-seed.ts +0 -176
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/retain-strategies.ts +0 -178
- package/node_modules/@luxusai/pi-hindsight/extensions/client/client-retry.ts +0 -41
- package/node_modules/@luxusai/pi-hindsight/extensions/client/client.ts +0 -386
- package/node_modules/@luxusai/pi-hindsight/extensions/client/fetch-compat.ts +0 -39
- package/node_modules/@luxusai/pi-hindsight/extensions/client/timeout.ts +0 -34
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-defaults.ts +0 -140
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-model.ts +0 -61
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-registry.ts +0 -925
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-field-paths.ts +0 -247
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-normalize.ts +0 -547
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-writer.ts +0 -480
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config.ts +0 -182
- package/node_modules/@luxusai/pi-hindsight/extensions/config/setup-gate.ts +0 -86
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-execute.ts +0 -1010
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-parse.ts +0 -175
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-plan.ts +0 -425
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-presentation.ts +0 -210
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-sessions.ts +0 -817
- package/node_modules/@luxusai/pi-hindsight/extensions/index.ts +0 -25
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/git-seed.ts +0 -319
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-recall.ts +0 -196
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-retain.ts +0 -189
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-runtime.ts +0 -65
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle.ts +0 -260
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/mental-models.ts +0 -244
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/observation-scopes.ts +0 -59
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-cleanup.ts +0 -82
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-visibility.ts +0 -45
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall.ts +0 -331
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-cursor.ts +0 -354
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-job-builder.ts +0 -66
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-receipts.ts +0 -132
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain.ts +0 -228
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-bank-template-operations.ts +0 -74
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-config-operations.ts +0 -16
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-control-operations.ts +0 -648
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-diagnostics-operations.ts +0 -105
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-identity.ts +0 -68
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-service.ts +0 -71
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-types.ts +0 -13
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-recall-operations.ts +0 -162
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-retain-operations.ts +0 -131
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-scope.ts +0 -104
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-session-operations.ts +0 -37
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/operation-catalog.ts +0 -893
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/reflect-presenter.ts +0 -35
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/scope-migrate.ts +0 -177
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/tools.ts +0 -7
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/flush-presenter.ts +0 -21
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/jsonl-queue-store.ts +0 -112
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-delivery.ts +0 -111
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-lock.ts +0 -214
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-operations.ts +0 -70
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue.ts +0 -392
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/bank-template-presentation.ts +0 -45
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/commands.ts +0 -9
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/guided-setup.ts +0 -860
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/prefill-input.ts +0 -105
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-flow.ts +0 -189
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-server-probe.ts +0 -299
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-actions.ts +0 -201
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-facts.ts +0 -47
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-render.ts +0 -239
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-types.ts +0 -50
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui.ts +0 -259
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/tool-presenters.ts +0 -77
- package/node_modules/@luxusai/pi-hindsight/extensions/types.ts +0 -534
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/diagnostics.ts +0 -319
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/messages.ts +0 -325
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/sanitize.ts +0 -42
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-memory-meta.ts +0 -244
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-operations.ts +0 -56
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/session.ts +0 -50
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-fields.ts +0 -167
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-health.ts +0 -207
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/status.ts +0 -120
- package/node_modules/@luxusai/pi-hindsight/extensions/version.ts +0 -8
- package/node_modules/@luxusai/pi-hindsight/package.json +0 -118
- package/node_modules/@luxusai/pi-hindsight/skills/hindsight-memory-doctor/SKILL.md +0 -75
|
@@ -70,6 +70,7 @@ import { acquireQuarantinePidFileLock } from "./bounded-pid-file-lock.js";
|
|
|
70
70
|
import { unrefChildAndPipes, } from "./child-unref.js";
|
|
71
71
|
import { incrementDegradationCount, recordDegradationOnce, } from "./degradation-ledger.js";
|
|
72
72
|
import { getGlobalPiLensDir } from "./file-utils.js";
|
|
73
|
+
import { resolveBackstopStateDir } from "./instance-reaper-state.js";
|
|
73
74
|
import { isInstanceRegistryEnabled, pruneDeadInstances as pruneDeadRegistryInstances, readInstanceRegistry, } from "./instance-registry.js";
|
|
74
75
|
import { logLatency } from "./latency-logger.js";
|
|
75
76
|
import { queryProcessTable, windowsExe } from "./process-snapshot.js";
|
|
@@ -717,7 +718,7 @@ function matchesManagedBinary(command) {
|
|
|
717
718
|
export async function sweepUntrackedOrphans(options = {}) {
|
|
718
719
|
const startedAt = Date.now();
|
|
719
720
|
if (!isInstanceRegistryEnabled()) {
|
|
720
|
-
return logBackstopOutcome("disabled", startedAt, {});
|
|
721
|
+
return logBackstopOutcome("disabled", startedAt, {}, options.onComplete);
|
|
721
722
|
}
|
|
722
723
|
const cooldownMs = Math.max(0, options.cooldownMs ?? BACKSTOP_COOLDOWN_MS);
|
|
723
724
|
let release = null;
|
|
@@ -735,13 +736,13 @@ export async function sweepUntrackedOrphans(options = {}) {
|
|
|
735
736
|
// Another process holds the sweep right now. Not an error, and not a
|
|
736
737
|
// cooldown either — a distinct record, because "somebody else is
|
|
737
738
|
// sweeping" and "we swept recently" have different causes.
|
|
738
|
-
return logBackstopOutcome("concurrent", startedAt, {});
|
|
739
|
+
return logBackstopOutcome("concurrent", startedAt, {}, options.onComplete);
|
|
739
740
|
}
|
|
740
741
|
const throttled = await isWithinCooldown(Date.now(), cooldownMs, options);
|
|
741
742
|
if (throttled !== undefined) {
|
|
742
743
|
return logBackstopOutcome("cooldown", startedAt, {
|
|
743
744
|
sinceLastMs: throttled,
|
|
744
|
-
});
|
|
745
|
+
}, options.onComplete);
|
|
745
746
|
}
|
|
746
747
|
// Claim the cooldown slot BEFORE the scan, not after: a sweep that dies
|
|
747
748
|
// mid-scan must not re-run at full cost on every retry.
|
|
@@ -766,7 +767,7 @@ export async function sweepUntrackedOrphans(options = {}) {
|
|
|
766
767
|
scanStatus: scan.status,
|
|
767
768
|
scannerKill: scan.timeoutKill,
|
|
768
769
|
scanned: scan.processes.length,
|
|
769
|
-
});
|
|
770
|
+
}, options.onComplete);
|
|
770
771
|
}
|
|
771
772
|
const partition = partitionBackstopCandidates(scan.processes, registry, realIsPidAlive, { graceMs: options.graceMs });
|
|
772
773
|
for (const proc of partition.unknownAge) {
|
|
@@ -811,7 +812,7 @@ export async function sweepUntrackedOrphans(options = {}) {
|
|
|
811
812
|
killedProcesses: killed.slice(0, BACKSTOP_IDENTITY_LOG_LIMIT),
|
|
812
813
|
unverifiedProcesses: unverified.slice(0, BACKSTOP_IDENTITY_LOG_LIMIT),
|
|
813
814
|
graceRetryInMs: retryAt,
|
|
814
|
-
});
|
|
815
|
+
}, options.onComplete);
|
|
815
816
|
}
|
|
816
817
|
catch (error) {
|
|
817
818
|
// The sweep must never throw out of session_start — but "threw" is now
|
|
@@ -821,7 +822,7 @@ export async function sweepUntrackedOrphans(options = {}) {
|
|
|
821
822
|
subject: "sweep",
|
|
822
823
|
reason: `sweep threw: ${error instanceof Error ? error.message : String(error)}`,
|
|
823
824
|
});
|
|
824
|
-
return logBackstopOutcome("error", startedAt, { threw: true });
|
|
825
|
+
return logBackstopOutcome("error", startedAt, { threw: true }, options.onComplete);
|
|
825
826
|
}
|
|
826
827
|
finally {
|
|
827
828
|
if (release) {
|
|
@@ -909,13 +910,14 @@ function scheduleGraceRetryIfNeeded(partition, options) {
|
|
|
909
910
|
force: true,
|
|
910
911
|
allowGraceRetry: false,
|
|
911
912
|
});
|
|
913
|
+
options.onGraceRetryScheduled?.(delayMs);
|
|
912
914
|
return delayMs;
|
|
913
915
|
}
|
|
914
916
|
/** Serialize the sweep across processes. Returns null when another process
|
|
915
917
|
* holds it — the caller skips rather than waits (`waitMs: 0`). */
|
|
916
918
|
async function acquireBackstopLock() {
|
|
917
919
|
try {
|
|
918
|
-
return await acquireQuarantinePidFileLock(path.join(getGlobalPiLensDir(), "orphan-backstop.lock"), {
|
|
920
|
+
return await acquireQuarantinePidFileLock(path.join(resolveBackstopStateDir(getGlobalPiLensDir()), "orphan-backstop.lock"), {
|
|
919
921
|
waitMs: 0,
|
|
920
922
|
retryMs: 50,
|
|
921
923
|
staleMs: BACKSTOP_LOCK_STALE_MS,
|
|
@@ -952,7 +954,7 @@ function describeManagedProcess(proc) {
|
|
|
952
954
|
const matched = MANAGED_BINARY_NAMES.find((name) => lower.includes(name.toLowerCase()));
|
|
953
955
|
return `${matched ?? "unknown"}#${proc.pid}`;
|
|
954
956
|
}
|
|
955
|
-
function logBackstopOutcome(outcome, startedAt, metadata) {
|
|
957
|
+
function logBackstopOutcome(outcome, startedAt, metadata, onComplete) {
|
|
956
958
|
try {
|
|
957
959
|
logLatency({
|
|
958
960
|
type: "phase",
|
|
@@ -965,13 +967,19 @@ function logBackstopOutcome(outcome, startedAt, metadata) {
|
|
|
965
967
|
catch {
|
|
966
968
|
// best-effort logging only
|
|
967
969
|
}
|
|
970
|
+
try {
|
|
971
|
+
onComplete?.(outcome);
|
|
972
|
+
}
|
|
973
|
+
catch {
|
|
974
|
+
// Test observers must not alter the best-effort sweep outcome.
|
|
975
|
+
}
|
|
968
976
|
return outcome;
|
|
969
977
|
}
|
|
970
978
|
/** Machine-wide cooldown stamp. A file, not a module-level flag: the sweep is
|
|
971
979
|
* machine-scoped (every pi-lens process shares one OS process table), and
|
|
972
980
|
* process-lifetime state cannot express "30 minutes have passed". */
|
|
973
981
|
function backstopStampPath() {
|
|
974
|
-
return path.join(getGlobalPiLensDir(), "orphan-backstop.json");
|
|
982
|
+
return path.join(resolveBackstopStateDir(getGlobalPiLensDir()), "orphan-backstop.json");
|
|
975
983
|
}
|
|
976
984
|
async function readBackstopStamp() {
|
|
977
985
|
try {
|
|
@@ -990,7 +998,9 @@ async function readBackstopStamp() {
|
|
|
990
998
|
}
|
|
991
999
|
async function writeBackstopStamp(at) {
|
|
992
1000
|
try {
|
|
993
|
-
await fs.promises.mkdir(getGlobalPiLensDir(), {
|
|
1001
|
+
await fs.promises.mkdir(resolveBackstopStateDir(getGlobalPiLensDir()), {
|
|
1002
|
+
recursive: true,
|
|
1003
|
+
});
|
|
994
1004
|
await writeFileAtomicAsync(backstopStampPath(), JSON.stringify({ lastSweepAt: at }));
|
|
995
1005
|
}
|
|
996
1006
|
catch {
|
|
@@ -52,6 +52,8 @@ const PROJECT_MARKERS_BY_KIND = {
|
|
|
52
52
|
};
|
|
53
53
|
const ROOT_MARKERS_BY_KIND = {
|
|
54
54
|
jsts: [
|
|
55
|
+
"biome.json",
|
|
56
|
+
"biome.jsonc",
|
|
55
57
|
"package.json",
|
|
56
58
|
"tsconfig.json",
|
|
57
59
|
"jsconfig.json",
|
|
@@ -100,6 +102,26 @@ const ROOT_MARKERS_BY_KIND = {
|
|
|
100
102
|
csharp: DOTNET_CSHARP_ROOT_MARKERS,
|
|
101
103
|
fsharp: DOTNET_FSHARP_ROOT_MARKERS,
|
|
102
104
|
};
|
|
105
|
+
// Tool-owned configuration belongs beside the shared language vocabulary, but
|
|
106
|
+
// not in ROOT_MARKERS_BY_KIND: these files are discovered by the tool, not by
|
|
107
|
+
// the language root detector. Keeping them here lets runner cwd resolution
|
|
108
|
+
// consume one canonical vocabulary without restoring a second seam-local map.
|
|
109
|
+
const TOOL_MARKERS_BY_RUNNER = {
|
|
110
|
+
ruff: ["ruff.toml", ".ruff.toml"],
|
|
111
|
+
oxlint: [".oxlintrc.json", "oxlint.config.js"],
|
|
112
|
+
"spellcheck/typos": ["_typos.toml", "typos.toml"],
|
|
113
|
+
yamllint: ["yamllint.yaml", "yamllint.yml"],
|
|
114
|
+
prettier: [".prettierignore"],
|
|
115
|
+
};
|
|
116
|
+
/** Return the one shared marker vocabulary used to anchor this file kind. */
|
|
117
|
+
export function rootMarkersForFile(filePath, runner) {
|
|
118
|
+
const kind = detectFileKind(path.resolve(filePath));
|
|
119
|
+
const languageMarkers = kind ? (ROOT_MARKERS_BY_KIND[kind] ?? []) : [];
|
|
120
|
+
const toolMarkers = runner ? (TOOL_MARKERS_BY_RUNNER[runner] ?? []) : [];
|
|
121
|
+
return toolMarkers.length
|
|
122
|
+
? [...new Set([...languageMarkers, ...toolMarkers])]
|
|
123
|
+
: languageMarkers;
|
|
124
|
+
}
|
|
103
125
|
function hasProjectMarker(projectRoot, marker) {
|
|
104
126
|
if (!marker.includes("*"))
|
|
105
127
|
return fs.existsSync(path.join(projectRoot, marker));
|
|
@@ -192,7 +214,7 @@ export function resolveLanguageRootForFile(filePath, workspaceRoot) {
|
|
|
192
214
|
const kind = detectFileKind(absoluteFilePath);
|
|
193
215
|
if (!kind)
|
|
194
216
|
return path.resolve(workspaceRoot);
|
|
195
|
-
const markers =
|
|
217
|
+
const markers = rootMarkersForFile(absoluteFilePath);
|
|
196
218
|
if (!markers || markers.length === 0) {
|
|
197
219
|
return path.resolve(workspaceRoot);
|
|
198
220
|
}
|
|
@@ -147,7 +147,9 @@ export const LANGUAGES = [
|
|
|
147
147
|
id: "cpp",
|
|
148
148
|
kind: "cxx",
|
|
149
149
|
// The clang extension table (file-kinds.ts KIND_EXTENSIONS.cxx) minus the
|
|
150
|
-
// C header/source pair, which the `c` entry owns.
|
|
150
|
+
// C header/source pair, which the `c` entry owns. `.cuh` deliberately
|
|
151
|
+
// diverges from clang's table: CUDA/HIP projects use it for C++ headers,
|
|
152
|
+
// so it must reach the same cpp grammar and clangd path as `.cu`/`.hip`.
|
|
151
153
|
extensions: [
|
|
152
154
|
".c++",
|
|
153
155
|
".c++m",
|
|
@@ -158,6 +160,7 @@ export const LANGUAGES = [
|
|
|
158
160
|
".cpp",
|
|
159
161
|
".cppm",
|
|
160
162
|
".cu",
|
|
163
|
+
".cuh",
|
|
161
164
|
".cxx",
|
|
162
165
|
".cxxm",
|
|
163
166
|
".hh",
|
|
@@ -182,6 +185,7 @@ export const LANGUAGES = [
|
|
|
182
185
|
".cc",
|
|
183
186
|
".cpp",
|
|
184
187
|
".cu",
|
|
188
|
+
".cuh",
|
|
185
189
|
".cxx",
|
|
186
190
|
".hh",
|
|
187
191
|
".hip",
|
|
@@ -682,7 +682,10 @@ export function logLatency(entry) {
|
|
|
682
682
|
if (entry.type === "phase" &&
|
|
683
683
|
entry.phase &&
|
|
684
684
|
!LAST_PHASE_EXCLUDED.has(entry.phase)) {
|
|
685
|
-
recentPhases = [
|
|
685
|
+
recentPhases = [
|
|
686
|
+
{ phase: entry.phase, ts, metadata: entry.metadata },
|
|
687
|
+
...recentPhases,
|
|
688
|
+
].slice(0, RECENT_PHASE_CAP);
|
|
686
689
|
}
|
|
687
690
|
if (isTestMode()) {
|
|
688
691
|
return;
|
|
@@ -25,6 +25,7 @@ import { acquireWarmWordIndex } from "./mcp/analyze.js";
|
|
|
25
25
|
import { normalizeMapKey } from "./path-utils.js";
|
|
26
26
|
import { scanProjectDiagnostics } from "./project-diagnostics/scanner.js";
|
|
27
27
|
import { loadProjectSnapshotWithoutWordIndex } from "./project-snapshot.js";
|
|
28
|
+
import { resolveLensToolName } from "./tool-config.js";
|
|
28
29
|
import { getTreeSitterRuntimeStatus, } from "./tree-sitter-shared.js";
|
|
29
30
|
import { centralityFromReverseDeps, getWordIndexBuildStatus, searchWordIndex, triggerBackgroundWordIndexBuild, } from "./word-index.js";
|
|
30
31
|
// --- Facades (re-exported so adapters import only this module) ---------------
|
|
@@ -116,7 +117,7 @@ export function scanTruncationNotice(snapshot) {
|
|
|
116
117
|
*
|
|
117
118
|
* Returns `undefined` when neither counter is set (nothing to append).
|
|
118
119
|
*/
|
|
119
|
-
export function generatedSkipNotice(snapshot) {
|
|
120
|
+
export function generatedSkipNotice(snapshot, host = "pi") {
|
|
120
121
|
const files = snapshot.generatedNameOnlySkips ?? 0;
|
|
121
122
|
const dirs = snapshot.generatedDirSkips ?? 0;
|
|
122
123
|
if (files === 0 && dirs === 0)
|
|
@@ -126,11 +127,19 @@ export function generatedSkipNotice(snapshot) {
|
|
|
126
127
|
parts.push(`${files} file(s)`);
|
|
127
128
|
if (dirs > 0)
|
|
128
129
|
parts.push(`${dirs} director${dirs === 1 ? "y" : "ies"}`);
|
|
130
|
+
// #2535 F1: name the tools THIS host can call. pi has no project-scan
|
|
131
|
+
// surface, so it names only its diagnostics tool; MCP names both. A
|
|
132
|
+
// known-but-unmapped host resolves to undefined and is left out, never
|
|
133
|
+
// printed as a name the host cannot resolve.
|
|
134
|
+
const targets = [
|
|
135
|
+
resolveLensToolName("project_scan", host),
|
|
136
|
+
resolveLensToolName("lens_diagnostics", host),
|
|
137
|
+
].filter((tool) => tool !== undefined);
|
|
138
|
+
const scanRef = targets.length > 0 ? ` to ${targets.join(" / ")}` : "";
|
|
129
139
|
return (`ℹ ${parts.join(" and ")} excluded by generated-name heuristics with no ` +
|
|
130
140
|
"confirming evidence — a real hand-written file/directory whose name " +
|
|
131
141
|
"looks generated (e.g. `gen.ts`, `generated/`) can be excluded too; " +
|
|
132
|
-
|
|
133
|
-
"to scan without this filter if you suspect a false skip.");
|
|
142
|
+
`pass includeGenerated: true${scanRef} to scan without this filter if you suspect a false skip.`);
|
|
134
143
|
}
|
|
135
144
|
/** Process-wide tree-sitter health for host status surfaces. */
|
|
136
145
|
export function treeSitterRuntimeStatus() {
|
|
@@ -50,7 +50,8 @@ export function _resetForTests() {
|
|
|
50
50
|
function emitLensEvent(eventName, payload) {
|
|
51
51
|
setImmediate(() => {
|
|
52
52
|
const rawCwd = payload.cwd ?? process.cwd();
|
|
53
|
-
// M1: normalizeFilePath is a sync realpathSync.native on Windows
|
|
53
|
+
// M1: normalizeFilePath is a sync realpathSync.native on Windows, and on
|
|
54
|
+
// POSIX too since #3098 — build
|
|
54
55
|
// the resolveLiveBusEmitter log entry lazily so the ready-path (the
|
|
55
56
|
// common case) never pays it. Only the stale-session branch needs it.
|
|
56
57
|
const resolution = resolveLiveBusEmitter(liveEmitter, () => ({
|
|
@@ -173,6 +173,22 @@ export const LENS_FLAGS = [
|
|
|
173
173
|
default: false,
|
|
174
174
|
scope: "global",
|
|
175
175
|
},
|
|
176
|
+
{
|
|
177
|
+
name: "lens-compact-lsp-status",
|
|
178
|
+
description: "Opt-in (#3099): collapse the footer LSP status to one state glyph per group (LSP ✓ green, LSP ✗ red, dim LSP ✗ when nothing is warm) instead of listing the active server names. Default off. Also via ui.compactLspStatus=true in ~/.pi-lens/config.json.",
|
|
179
|
+
configKey: "ui.compactLspStatus",
|
|
180
|
+
negated: false,
|
|
181
|
+
default: false,
|
|
182
|
+
scope: "global",
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: "lens-hide-lsp-status",
|
|
186
|
+
description: "Opt-in (#3099): publish no pi-lens-lsp footer status at all, so a host that renders extension statuses stops showing the key. Outranks lens-compact-lsp-status when both are set. Default off. Also via ui.hideLspStatus=true in ~/.pi-lens/config.json.",
|
|
187
|
+
configKey: "ui.hideLspStatus",
|
|
188
|
+
negated: false,
|
|
189
|
+
default: false,
|
|
190
|
+
scope: "global",
|
|
191
|
+
},
|
|
176
192
|
{
|
|
177
193
|
name: "no-lazy-tools",
|
|
178
194
|
description: "Keep all pi-lens tools active to avoid tool-list cache changes. Also via tools.lazy=false in ~/.pi-lens/config.json.",
|
|
@@ -235,6 +251,40 @@ export const GLOBAL_NON_FLAG_CONFIG_SECTIONS = [
|
|
|
235
251
|
"startup",
|
|
236
252
|
"$schema",
|
|
237
253
|
];
|
|
254
|
+
/**
|
|
255
|
+
* Dotted GLOBAL-only config keys that are NOT `LENS_FLAGS` entries — no CLI
|
|
256
|
+
* flag, not a boolean — but still sit inside a section a project
|
|
257
|
+
* `.pi-lens.json` is otherwise recognized to touch (#3131). The project
|
|
258
|
+
* loader's mixed-scope sub-key scan (`project-lens-config.ts`) already derives
|
|
259
|
+
* its FLAG population from `LENS_FLAGS`; that derivation structurally cannot
|
|
260
|
+
* see a key like `actionableWarnings.autoFix.maxFixes` — a numeric cap with no
|
|
261
|
+
* `--flag` counterpart, documented global-only (`docs/settings.md`), read only
|
|
262
|
+
* through `getGlobalActionableWarningMaxFixes()` (`clients/lens-config.ts`) —
|
|
263
|
+
* because it was never a `LensFlagSpec` to begin with. `actionableWarnings` is
|
|
264
|
+
* itself recognized at project scope only because its SIBLING
|
|
265
|
+
* `actionableWarnings.autoFix.enabled` is a `scope: "project"` flag, so before
|
|
266
|
+
* this registry existed the loader parsed `maxFixes` away with no signal at
|
|
267
|
+
* all — the same silent-drop shape #2426 review round 2 (F3) fixed for
|
|
268
|
+
* `lsp.enabled` and #3112 fixed for `tools.lazy`.
|
|
269
|
+
*
|
|
270
|
+
* This is the single source that population is derived from: the mixed-scope
|
|
271
|
+
* scan reads `LENS_FLAGS` for flag keys and this list for everything else, so
|
|
272
|
+
* a future non-flag global-only key needs one line HERE, never a second
|
|
273
|
+
* hand-typed list inside the loader.
|
|
274
|
+
*
|
|
275
|
+
* A key belongs here ONLY when all three hold: (a) it is global-only — read
|
|
276
|
+
* solely off `~/.pi-lens/config.json`, never honored from a project document;
|
|
277
|
+
* (b) it is not a `LENS_FLAGS` entry; (c) its SECTION is recognized at project
|
|
278
|
+
* scope for some OTHER reason. A key whose whole section is unrecognized at
|
|
279
|
+
* project scope needs no entry — the top-level unknown-key scan already
|
|
280
|
+
* reports the section. Swept at #3131: every other non-flag dotted key either
|
|
281
|
+
* loader parses (`dispatch.runnerTimeoutFloorMs`, `widget.visible`,
|
|
282
|
+
* `startup.mode`, `startup.scans.enabled`) fails (a) or (c) — see that PR's
|
|
283
|
+
* body for the per-member table — so this is currently the only member.
|
|
284
|
+
*/
|
|
285
|
+
export const GLOBAL_ONLY_NON_FLAG_KEYS = [
|
|
286
|
+
"actionableWarnings.autoFix.maxFixes",
|
|
287
|
+
];
|
|
238
288
|
/**
|
|
239
289
|
* Recognized TOP-LEVEL sections of a project `.pi-lens.json` that are NOT
|
|
240
290
|
* derived from the flag registry — the project loader's own hand-parsed
|
|
@@ -256,6 +306,19 @@ export const PROJECT_NON_FLAG_CONFIG_SECTIONS = [
|
|
|
256
306
|
"trivy",
|
|
257
307
|
"helm",
|
|
258
308
|
"startup",
|
|
309
|
+
// `tools` is MIXED-SCOPE, and belongs here for the project-scoped half
|
|
310
|
+
// (#3112). `tools.<name>.enabled` is not a registry flag at all: it is
|
|
311
|
+
// hand-parsed by `readToolConfig` against `TOOL_REGISTRY` in BOTH loaders,
|
|
312
|
+
// and `resolveLensToolEnabled` reads the project document's value ahead of
|
|
313
|
+
// the global one — which is what `docs/settings.md` and
|
|
314
|
+
// `docs/globalconfig.md` document. Deriving the project-accepted sections
|
|
315
|
+
// from the flag registry alone therefore put the whole section in
|
|
316
|
+
// `globalScopeOnlyKeys` (its only registry flag, `tools.lazy`, IS global)
|
|
317
|
+
// and told every user of a documented per-tool override that it was being
|
|
318
|
+
// ignored. The global-only half is not lost: `tools.lazy` is still reported
|
|
319
|
+
// at project scope by the project loader's mixed-scope sub-key scan, which
|
|
320
|
+
// derives WHICH sub-keys those are from this same registry.
|
|
321
|
+
"tools",
|
|
259
322
|
];
|
|
260
323
|
/**
|
|
261
324
|
* The four LSP settings a PROJECT config file may carry — at its root during
|
|
@@ -343,6 +406,22 @@ function resolveFlagConfigPath(raw, configKey, warnInvalid) {
|
|
|
343
406
|
return undefined;
|
|
344
407
|
return { source: finalSource, segments };
|
|
345
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* Whether a parsed config document actually SETS the dotted `configKey` —
|
|
411
|
+
* presence, not validity (#3112). The project loader's mixed-scope scan must
|
|
412
|
+
* report `tools: { lazy: "yes" }` exactly as it reports `tools: { lazy: false }`:
|
|
413
|
+
* the user wrote a global-only setting in a project file either way, and
|
|
414
|
+
* {@link readFlagConfigValue} would return `undefined` for the malformed one and
|
|
415
|
+
* silently skip it. Shares {@link resolveFlagConfigPath} with every other reader
|
|
416
|
+
* so "which segments does this key name" is answered in exactly one place.
|
|
417
|
+
*/
|
|
418
|
+
export function hasFlagConfigPath(raw, configKey) {
|
|
419
|
+
const resolved = resolveFlagConfigPath(raw, configKey);
|
|
420
|
+
if (!resolved)
|
|
421
|
+
return false;
|
|
422
|
+
const leaf = resolved.segments.at(-1);
|
|
423
|
+
return leaf !== undefined && leaf in resolved.source;
|
|
424
|
+
}
|
|
346
425
|
/**
|
|
347
426
|
* Read the boolean at a spec's dotted `configKey` out of an already-parsed
|
|
348
427
|
* config object. Returns undefined when any segment is missing or the leaf is
|
|
@@ -54,7 +54,8 @@ export function createLiveBusEmitter() {
|
|
|
54
54
|
*
|
|
55
55
|
* `entry` is a THUNK, not a value (M1, #1415 review): building the log entry
|
|
56
56
|
* (every producer's version normalizes `cwd` via `normalizeFilePath`, a sync
|
|
57
|
-
* `realpathSync.native` call on Windows
|
|
57
|
+
* `realpathSync.native` call on Windows and, since #3098, on POSIX too) is
|
|
58
|
+
* real per-publish cost that used
|
|
58
59
|
* to be paid on EVERY call regardless of outcome, even though it is only
|
|
59
60
|
* consumed on the `stale-session` branch. Invoking the thunk only there means
|
|
60
61
|
* the common `ready` path pays nothing for it.
|
|
@@ -15,6 +15,7 @@ import { pathToFileURL } from "node:url";
|
|
|
15
15
|
import { emitBounded } from "../bounded-telemetry.js";
|
|
16
16
|
import { withTimeout } from "../deadline-utils.js";
|
|
17
17
|
import { minimatch } from "../deps/minimatch.js";
|
|
18
|
+
import { mapWithConcurrency } from "../map-with-concurrency.js";
|
|
18
19
|
import { incrementDegradationCount, recordDegradationOnce, } from "../degradation-ledger.js";
|
|
19
20
|
// vscode-jsonrpc v9 ships an `exports` map exposing the Node entry as the
|
|
20
21
|
// `./node` subpath (no `.js`); the old `/node.js` file path no longer resolves.
|
|
@@ -25,7 +26,7 @@ import { workspaceDiagnosticsCacheSessionStart } from "./workspace-diagnostics-s
|
|
|
25
26
|
import { logLatency } from "../latency-logger.js";
|
|
26
27
|
import { newLspMutationCorrelationId, } from "../lsp-mutation.js";
|
|
27
28
|
import { getProcessSingleton } from "../process-singletons.js";
|
|
28
|
-
import { getAmbientAbortSignal } from "../safe-spawn.js";
|
|
29
|
+
import { getAmbientAbortSignal, isOwnLiveChild, releaseOwnChildPid, } from "../safe-spawn.js";
|
|
29
30
|
import { raceToCompletion } from "./aggregation.js";
|
|
30
31
|
import { hashDiagnosticContent, } from "./diagnostic-binding.js";
|
|
31
32
|
import { applyWorkspaceEdit, normalizeWorkspaceEditToUtf16 } from "./edits.js";
|
|
@@ -216,25 +217,6 @@ const WORKSPACE_PULL_SCOPE = "*workspace*";
|
|
|
216
217
|
// load. Small and fixed — this is a background improvement after the
|
|
217
218
|
// protocol reply, never something worth tuning per project.
|
|
218
219
|
const REFRESH_REPULL_CONCURRENCY = 4;
|
|
219
|
-
/** Run `mapper` over `items` with at most `concurrency` in flight at once.
|
|
220
|
-
* Same shape as `dependency-checker.ts`'s helper of the same name — a
|
|
221
|
-
* worker-pool pattern repeated per-file by design in this codebase rather
|
|
222
|
-
* than shared, so each caller can keep it un-exported and file-local. */
|
|
223
|
-
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
224
|
-
if (items.length === 0)
|
|
225
|
-
return;
|
|
226
|
-
let nextIndex = 0;
|
|
227
|
-
const workerCount = Math.max(1, Math.min(concurrency, items.length));
|
|
228
|
-
const worker = async () => {
|
|
229
|
-
while (true) {
|
|
230
|
-
const index = nextIndex++;
|
|
231
|
-
if (index >= items.length)
|
|
232
|
-
return;
|
|
233
|
-
await mapper(items[index]);
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
237
|
-
}
|
|
238
220
|
// Anti-deadlock backstop for workspace/executeCommand. Deliberately generous
|
|
239
221
|
// (30s): the command is mutating and legitimately long-running (a real server
|
|
240
222
|
// refactor / organize-imports), so this must not truncate valid work — it only
|
|
@@ -383,6 +365,9 @@ export async function killProcessTree(proc, pid, options = {}) {
|
|
|
383
365
|
// the handle-based proc.kill() below is moot, so return early.
|
|
384
366
|
if ((proc.exitCode != null || proc.signalCode != null) &&
|
|
385
367
|
!options.processExiting) {
|
|
368
|
+
// #3091 F1-r2b: this pid will never be signalled again, so retire the
|
|
369
|
+
// ownership hold taken at spawn rather than leaving it to age out.
|
|
370
|
+
releaseOwnChildPid(pid);
|
|
386
371
|
proc.unref?.();
|
|
387
372
|
return;
|
|
388
373
|
}
|
|
@@ -458,7 +443,20 @@ export async function killProcessTree(proc, pid, options = {}) {
|
|
|
458
443
|
return;
|
|
459
444
|
}
|
|
460
445
|
const killPosixProcessGroup = (signal) => {
|
|
461
|
-
|
|
446
|
+
// #2042: one ownership predicate for every kill-by-raw-pid, replacing
|
|
447
|
+
// the `pid <= 0` sign check that let a test double's invented pid
|
|
448
|
+
// through. A pid we do not own falls back to `killDirectChild` below,
|
|
449
|
+
// which signals through the retained handle and can only ever reach
|
|
450
|
+
// our own child.
|
|
451
|
+
//
|
|
452
|
+
// #3091 F1: `proc` is deliberately NOT passed. The handle arm means
|
|
453
|
+
// "already exited ⇒ refuse", and this group kill must still fire when
|
|
454
|
+
// the direct child is dead — the early return at :1269 is skipped under
|
|
455
|
+
// `options.processExiting`, and a POSIX group outlives its leader, so
|
|
456
|
+
// the group signal is the only thing that reaps surviving grandchildren
|
|
457
|
+
// at host exit (#2026). Ownership here comes from the kernel and, once
|
|
458
|
+
// the leader is gone, from the verdict recorded while it was alive.
|
|
459
|
+
if (!isOwnLiveChild(pid, "lsp-stop-posix-group"))
|
|
462
460
|
return false;
|
|
463
461
|
try {
|
|
464
462
|
process.kill(-pid, signal);
|
|
@@ -516,6 +514,11 @@ export async function killProcessTree(proc, pid, options = {}) {
|
|
|
516
514
|
killDirectChild("SIGKILL");
|
|
517
515
|
}
|
|
518
516
|
}
|
|
517
|
+
// AFTER the escalation, never before it: this tick issues the
|
|
518
|
+
// last signal this pid can receive, and retiring the ownership
|
|
519
|
+
// hold first would make that very SIGKILL the thing that gets
|
|
520
|
+
// refused (#3091 F1-r2b).
|
|
521
|
+
releaseOwnChildPid(pid);
|
|
519
522
|
}, 1500);
|
|
520
523
|
timer.unref?.();
|
|
521
524
|
proc.unref?.();
|
|
@@ -553,6 +556,7 @@ export async function killProcessTree(proc, pid, options = {}) {
|
|
|
553
556
|
killDirectChild("SIGKILL");
|
|
554
557
|
}
|
|
555
558
|
}
|
|
559
|
+
releaseOwnChildPid(pid);
|
|
556
560
|
}
|
|
557
561
|
catch {
|
|
558
562
|
// ignore
|
|
@@ -344,8 +344,8 @@ export function createCustomServer(config, id) {
|
|
|
344
344
|
? async (file) => resolveToolCwd("lsp", id, file, {
|
|
345
345
|
cwd: process.cwd(),
|
|
346
346
|
...(config.rootMarkers ? { rootMarkers: config.rootMarkers } : {}),
|
|
347
|
-
})
|
|
348
|
-
: async (file) => resolveToolCwd("lsp", id, file, { cwd: process.cwd() }),
|
|
347
|
+
}).cwd
|
|
348
|
+
: async (file) => resolveToolCwd("lsp", id, file, { cwd: process.cwd() }).cwd,
|
|
349
349
|
async spawn(root) {
|
|
350
350
|
const proc = await launchLSP(config.command, config.args ?? ["--stdio"], {
|
|
351
351
|
cwd: root,
|
|
@@ -7390,9 +7390,16 @@ export class LSPService {
|
|
|
7390
7390
|
// duplicating what the per-edit path already suppresses. `content`
|
|
7391
7391
|
// was already read above for this file, so `detectFileRole` gets the
|
|
7392
7392
|
// higher-accuracy content-aware classification at no extra cost.
|
|
7393
|
+
// #3041: `filePath`/`scanRoot` additionally apply each rule's own
|
|
7394
|
+
// `ignores` carve-out (#965). ast-grep's LSP does NOT apply it to the
|
|
7395
|
+
// per-document diagnostics it publishes (only its own `scan` walk
|
|
7396
|
+
// does), so without this the sweep re-surfaces exactly what the NAPI
|
|
7397
|
+
// runner skips on the same file.
|
|
7393
7398
|
const filteredDiagnostics = diagnostics
|
|
7394
7399
|
? applyAuxiliarySuppressions(diagnostics, content, {
|
|
7395
7400
|
fileRole: detectFileRole(filePath, content),
|
|
7401
|
+
filePath,
|
|
7402
|
+
scanRoot: root,
|
|
7396
7403
|
})
|
|
7397
7404
|
: diagnostics;
|
|
7398
7405
|
results.push({
|
|
@@ -16,7 +16,7 @@ import { getGlobalPiLensLogDir } from "../probe-home-state.js";
|
|
|
16
16
|
import { isFullyQualified } from "../path-utils.js";
|
|
17
17
|
import { findGlobalBinary } from "../package-manager.js";
|
|
18
18
|
import { redactSecrets } from "../redact/secrets.js";
|
|
19
|
-
import { classifySpawnFailure, SpawnFailureError } from "../safe-spawn.js";
|
|
19
|
+
import { classifySpawnFailure, holdOwnChildPid, isOwnLiveChild, SpawnFailureError, } from "../safe-spawn.js";
|
|
20
20
|
import { getRubyVersionDirNamesAsync } from "./ruby-drive-dirs.js";
|
|
21
21
|
const isWindows = process.platform === "win32";
|
|
22
22
|
/**
|
|
@@ -327,6 +327,14 @@ function trySpawn(command, args, cwd, env, needsShell) {
|
|
|
327
327
|
throw new Error(`LSP server ${command} exited immediately (code: ${proc.exitCode}). ` +
|
|
328
328
|
`The binary may be missing or corrupted.`);
|
|
329
329
|
}
|
|
330
|
+
// #3091 F1-r2: take ownership HERE, while the child is provably alive. LSP
|
|
331
|
+
// servers are the one long-lived child pi-lens spawns outside
|
|
332
|
+
// `safeSpawnAsync`, so without this their first offer of verification is
|
|
333
|
+
// `stopLSP`'s group kill — by which time, on the `processExiting` path, the
|
|
334
|
+
// leader can already be dead, `/proc` is gone, and the group signal that
|
|
335
|
+
// reaps surviving grandchildren (#2026) is refused. The verdict is released
|
|
336
|
+
// by `killProcessTree` when the shutdown ladder is done with the pid.
|
|
337
|
+
holdOwnChildPid(proc.pid, "lsp-spawn");
|
|
330
338
|
return proc;
|
|
331
339
|
}
|
|
332
340
|
/**
|
|
@@ -654,7 +662,7 @@ export async function stopLSP(handle) {
|
|
|
654
662
|
handle.process.once("exit", done);
|
|
655
663
|
handle.process.once("error", done);
|
|
656
664
|
const killWindowsTree = () => {
|
|
657
|
-
if (!isWindows
|
|
665
|
+
if (!isWindows)
|
|
658
666
|
return false;
|
|
659
667
|
// If our child has already exited, its PID is dead and the OS may have
|
|
660
668
|
// RECYCLED it to an unrelated process. `taskkill /F /T` on a recycled PID
|
|
@@ -665,8 +673,12 @@ export async function stopLSP(handle) {
|
|
|
665
673
|
// tree-kill a PID we no longer own — fall back to handle.process.kill(),
|
|
666
674
|
// which on Windows signals via the retained process HANDLE (not the raw
|
|
667
675
|
// PID), so it's a safe no-op on an already-exited child.
|
|
668
|
-
|
|
669
|
-
|
|
676
|
+
//
|
|
677
|
+
// #2042 folded the sign check and this exited check into the one
|
|
678
|
+
// ownership predicate every kill-by-raw-pid now shares; the handle
|
|
679
|
+
// arm below IS this paragraph, and on Windows it is the only
|
|
680
|
+
// ownership evidence available (there is no /proc to read).
|
|
681
|
+
if (!isOwnLiveChild(handle.pid, "lsp-stop-windows-tree", handle.process))
|
|
670
682
|
return false;
|
|
671
683
|
try {
|
|
672
684
|
// Absolute path avoids PATH-resolution substitution on Windows.
|
|
@@ -15,6 +15,7 @@ import { augmentPythonEnvironment, detectPythonEnvironment, detectPythonVenv, py
|
|
|
15
15
|
export { detectPythonVenv };
|
|
16
16
|
import { STAGE_TMP_PATTERN } from "../atomic-write-staging.js";
|
|
17
17
|
import { DOTNET_CSHARP_ROOT_MARKERS, DOTNET_FSHARP_ROOT_MARKERS, KIND_EXTENSIONS, } from "../file-kinds.js";
|
|
18
|
+
import { extensionsForLanguage } from "../language-registry.js";
|
|
18
19
|
import { CARGO_WORKSPACE_MEMBER_DIALECT, direntsHaveMarkerGlobMatch, isAtOrAboveHomeDir, isFullyQualified, isWindowsPath, matchesWorkspaceMemberPattern, pathsEqual, } from "../path-utils.js";
|
|
19
20
|
import { ensureTool, findManagedToolBinary, getToolEnvironment, getToolPath, } from "../installer/index.js";
|
|
20
21
|
import * as installer from "../installer/index.js";
|
|
@@ -71,14 +72,14 @@ export async function resolveLspServerCwd(server, filePath, sessionCwd, onRootFa
|
|
|
71
72
|
return resolveToolCwd("lsp", server.id, filePath, {
|
|
72
73
|
cwd: path.dirname(path.resolve(filePath)),
|
|
73
74
|
rootMarkers,
|
|
74
|
-
});
|
|
75
|
+
}).cwd;
|
|
75
76
|
}
|
|
76
77
|
const boundedServerRoot = enforceLspRootCeiling(serverRoot, sessionCwd, filePath);
|
|
77
78
|
return resolveToolCwd("lsp", server.id, filePath, {
|
|
78
79
|
cwd: sessionCwd,
|
|
79
80
|
rootMarkers,
|
|
80
81
|
serverRoot: boundedServerRoot,
|
|
81
|
-
});
|
|
82
|
+
}).cwd;
|
|
82
83
|
}
|
|
83
84
|
const FIXTURE_ROOT_SEGMENTS = new Set(["__fixtures__", "testdata"]);
|
|
84
85
|
const FALLBACK_PROJECT_MARKERS = [
|
|
@@ -2493,6 +2494,21 @@ export const GleamServer = {
|
|
|
2493
2494
|
}, options?.allowInstall);
|
|
2494
2495
|
},
|
|
2495
2496
|
};
|
|
2497
|
+
export const TinymistServer = {
|
|
2498
|
+
id: "tinymist",
|
|
2499
|
+
name: "Tinymist",
|
|
2500
|
+
extensions: extensionsForLanguage("typst"),
|
|
2501
|
+
root: RootWithFallback(createRootDetector(["typst.toml", ".git"])),
|
|
2502
|
+
availabilityKey: "tinymist",
|
|
2503
|
+
async spawn(root, options) {
|
|
2504
|
+
return resolveAndLaunch({
|
|
2505
|
+
candidates: ["tinymist"],
|
|
2506
|
+
args: ["lsp"],
|
|
2507
|
+
cwd: root,
|
|
2508
|
+
managedToolId: "tinymist",
|
|
2509
|
+
}, options?.allowInstall);
|
|
2510
|
+
},
|
|
2511
|
+
};
|
|
2496
2512
|
export const MarksmanServer = {
|
|
2497
2513
|
id: "marksman",
|
|
2498
2514
|
name: "Marksman",
|
|
@@ -3146,6 +3162,7 @@ export const LSP_SERVERS = [
|
|
|
3146
3162
|
ElixirServer,
|
|
3147
3163
|
ElixirExpertServer,
|
|
3148
3164
|
GleamServer,
|
|
3165
|
+
TinymistServer,
|
|
3149
3166
|
MarksmanServer,
|
|
3150
3167
|
OCamlServer,
|
|
3151
3168
|
ClojureServer,
|
|
@@ -150,7 +150,8 @@ export function clearWorkspaceDiagnosticsCacheAtAndAbove(cwd, ceiling = process.
|
|
|
150
150
|
// "recompute", the same outcome a real refresh produces. The pre-#1754 map
|
|
151
151
|
// was unbounded; the bound is new and costs at most one extra sweep for a
|
|
152
152
|
// cwd that fell off the end.
|
|
153
|
-
// - `normalizeMapKey` runs `realpathSync.native` on Windows
|
|
153
|
+
// - `normalizeMapKey` runs `realpathSync.native` on Windows and, since #3098,
|
|
154
|
+
// on POSIX as well. A handle re-runs
|
|
154
155
|
// it only after the map has been invalidated, so an uninterrupted sweep pays
|
|
155
156
|
// that syscall once at context creation rather than once per `lookup()`, and
|
|
156
157
|
// an interrupted one pays it per check to stay correct if the cwd's realpath
|
|
@@ -85,9 +85,11 @@ function uniqueDetails(files, fileDetails) {
|
|
|
85
85
|
const byPath = new Map();
|
|
86
86
|
// #2016: `files` and `fileDetails` name the same paths, so without this the
|
|
87
87
|
// map-build loop and the lookup below each pay `realpathSync.native` for the
|
|
88
|
-
// same path (~200 microseconds per call on Windows; POSIX
|
|
89
|
-
//
|
|
90
|
-
//
|
|
88
|
+
// same path (~200 microseconds per call on Windows; since #3098 POSIX pays it
|
|
89
|
+
// too, measured at 1.8 microseconds for an existing path — still under any CI
|
|
90
|
+
// timing gate, so the waste stays invisible either way). The memo lives for
|
|
91
|
+
// one call, so it has no staleness window at all and needs no freshness
|
|
92
|
+
// design.
|
|
91
93
|
const keyMemo = new Map();
|
|
92
94
|
const keyFor = (filePath) => {
|
|
93
95
|
let key = keyMemo.get(filePath);
|