@herbertgao/pi-extensions 2026.9.6 → 2026.9.8
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/grok-mermaid/CHANGELOG.md +46 -0
- package/node_modules/grok-mermaid/LICENSE +205 -0
- package/node_modules/grok-mermaid/README.md +191 -0
- package/node_modules/grok-mermaid/dist/ansi.d.ts +16 -0
- package/node_modules/grok-mermaid/dist/ansi.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/ansi.js +23 -0
- package/node_modules/grok-mermaid/dist/ansi.js.map +1 -0
- package/node_modules/grok-mermaid/dist/canvas.d.ts +87 -0
- package/node_modules/grok-mermaid/dist/canvas.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/canvas.js +366 -0
- package/node_modules/grok-mermaid/dist/canvas.js.map +1 -0
- package/node_modules/grok-mermaid/dist/graph.d.ts +74 -0
- package/node_modules/grok-mermaid/dist/graph.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/graph.js +91 -0
- package/node_modules/grok-mermaid/dist/graph.js.map +1 -0
- package/node_modules/grok-mermaid/dist/index.d.ts +32 -0
- package/node_modules/grok-mermaid/dist/index.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/index.js +100 -0
- package/node_modules/grok-mermaid/dist/index.js.map +1 -0
- package/node_modules/grok-mermaid/dist/labels.d.ts +62 -0
- package/node_modules/grok-mermaid/dist/labels.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/labels.js +324 -0
- package/node_modules/grok-mermaid/dist/labels.js.map +1 -0
- package/node_modules/grok-mermaid/dist/layout-seq.d.ts +12 -0
- package/node_modules/grok-mermaid/dist/layout-seq.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/layout-seq.js +194 -0
- package/node_modules/grok-mermaid/dist/layout-seq.js.map +1 -0
- package/node_modules/grok-mermaid/dist/layout.d.ts +87 -0
- package/node_modules/grok-mermaid/dist/layout.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/layout.js +881 -0
- package/node_modules/grok-mermaid/dist/layout.js.map +1 -0
- package/node_modules/grok-mermaid/dist/parse.d.ts +83 -0
- package/node_modules/grok-mermaid/dist/parse.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/parse.js +1151 -0
- package/node_modules/grok-mermaid/dist/parse.js.map +1 -0
- package/node_modules/grok-mermaid/dist/source-box.d.ts +18 -0
- package/node_modules/grok-mermaid/dist/source-box.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/source-box.js +78 -0
- package/node_modules/grok-mermaid/dist/source-box.js.map +1 -0
- package/node_modules/grok-mermaid/dist/types.d.ts +42 -0
- package/node_modules/grok-mermaid/dist/types.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/types.js +1 -0
- package/node_modules/grok-mermaid/dist/types.js.map +1 -0
- package/node_modules/grok-mermaid/dist/width-data.d.ts +2 -0
- package/node_modules/grok-mermaid/dist/width-data.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/width-data.js +994 -0
- package/node_modules/grok-mermaid/dist/width-data.js.map +1 -0
- package/node_modules/grok-mermaid/dist/width.d.ts +18 -0
- package/node_modules/grok-mermaid/dist/width.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/width.js +76 -0
- package/node_modules/grok-mermaid/dist/width.js.map +1 -0
- package/node_modules/grok-mermaid/package.json +49 -0
- package/node_modules/grok-mermaid/src/ansi.ts +34 -0
- package/node_modules/grok-mermaid/src/canvas.ts +373 -0
- package/node_modules/grok-mermaid/src/graph.ts +142 -0
- package/node_modules/grok-mermaid/src/index.ts +104 -0
- package/node_modules/grok-mermaid/src/labels.ts +326 -0
- package/node_modules/grok-mermaid/src/layout-seq.ts +203 -0
- package/node_modules/grok-mermaid/src/layout.ts +1015 -0
- package/node_modules/grok-mermaid/src/parse.ts +1189 -0
- package/node_modules/grok-mermaid/src/source-box.ts +89 -0
- package/node_modules/grok-mermaid/src/types.ts +43 -0
- package/node_modules/grok-mermaid/src/width-data.ts +993 -0
- package/node_modules/grok-mermaid/src/width.ts +74 -0
- 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 +6 -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
|
@@ -266,15 +266,19 @@ export class ReadGuard {
|
|
|
266
266
|
// pi Write tool authored, independent of filesystem mtime granularity
|
|
267
267
|
// or clock skew (NFS, FAT32, etc.).
|
|
268
268
|
writtenThisSession = new Set();
|
|
269
|
+
unchangedThisSession = new Set();
|
|
269
270
|
// Existence-independent index for hasKnownPath/forgetPath (#1668 review
|
|
270
271
|
// F1). `this.key()` (normalizeFilePath) branches on whether `filePath`
|
|
271
|
-
// currently exists on disk
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
//
|
|
272
|
+
// currently exists on disk, on EVERY platform since #3098 — an existing
|
|
273
|
+
// file resolves to its on-disk casing (win32: `realpathSync.native`;
|
|
274
|
+
// POSIX: the on-disk casing of the trailing segments), a missing one to a
|
|
275
|
+
// lowercased tail on win32 and to the caller's own spelling on POSIX.
|
|
276
|
+
// recordRead/recordWritten always key while the file is still on disk
|
|
277
|
+
// (real casing); hasKnownPath/forgetPath are queried AFTER an external
|
|
278
|
+
// delete already landed, when the path no longer exists — recomputing
|
|
276
279
|
// `this.key()` at that point returns a DIFFERENT string for any
|
|
277
|
-
// mixed-case basename (`MyModule.ts` → `mymodule.ts
|
|
280
|
+
// mixed-case basename (win32 `MyModule.ts` → `mymodule.ts`; POSIX
|
|
281
|
+
// `Components/x.ts` held against an on-disk `components/`), so a lookup
|
|
278
282
|
// against `reads`/`writtenThisSession` silently misses. This index maps
|
|
279
283
|
// a purely syntactic key (`normalizeEphemeralMapKey` — slash-fold +
|
|
280
284
|
// lowercase, no filesystem access, so it never depends on current disk
|
|
@@ -282,6 +286,17 @@ export class ReadGuard {
|
|
|
282
286
|
// time, so a post-delete lookup finds the same entry regardless of what
|
|
283
287
|
// happened to the file since. Pruned inside `evictFile` so it never
|
|
284
288
|
// outlives the record it points at.
|
|
289
|
+
//
|
|
290
|
+
// ONLY hasKnownPath/forgetPath route through this index, because only they
|
|
291
|
+
// need the real key BACK. `noteCreatedFile` (keys while the file is still
|
|
292
|
+
// absent) and `recordWritten` (keys after it exists) straddle the same
|
|
293
|
+
// state change, and used to do it through `this.key()` directly — a
|
|
294
|
+
// spelling whose key moved orphaned the `pendingCreations` entry and the
|
|
295
|
+
// creation read was never injected (#3163, pre-existing on win32 and
|
|
296
|
+
// reachable on POSIX since #3098). They now key `pendingCreations` by the
|
|
297
|
+
// same existence-independent `normalizeEphemeralMapKey` spelling this index
|
|
298
|
+
// is keyed by; no index is needed there, since the real key is derived by
|
|
299
|
+
// `recordWritten` itself at the moment it is used.
|
|
285
300
|
knownPathIndex = new Map();
|
|
286
301
|
/** Running per-file record-cap trim totals for this session (#1913 F1). */
|
|
287
302
|
trimAccumulators = new Map();
|
|
@@ -424,8 +439,15 @@ export class ReadGuard {
|
|
|
424
439
|
* Record that a file was read.
|
|
425
440
|
* Call this from the tool_call handler after any LSP expansion.
|
|
426
441
|
*/
|
|
427
|
-
recordRead(record) {
|
|
442
|
+
recordRead(record, opts) {
|
|
428
443
|
const filePath = this.key(record.filePath);
|
|
444
|
+
if (opts?.supersedes) {
|
|
445
|
+
const records = this.reads.get(filePath);
|
|
446
|
+
const provisionalSource = `native-read:${opts.supersedes.toolCallId}:provisional`;
|
|
447
|
+
const provisionalIndex = records?.findIndex((candidate) => candidate.source === provisionalSource);
|
|
448
|
+
if (provisionalIndex !== undefined && provisionalIndex >= 0)
|
|
449
|
+
records.splice(provisionalIndex, 1);
|
|
450
|
+
}
|
|
429
451
|
// #1668 review F1: index by the existence-independent syntactic key
|
|
430
452
|
// while the file is (presumably) still on disk, so a later
|
|
431
453
|
// hasKnownPath/forgetPath lookup after an external delete can still
|
|
@@ -865,9 +887,26 @@ export class ReadGuard {
|
|
|
865
887
|
* Must be called from the tool_call handler before the write lands so
|
|
866
888
|
* isNewFile() still returns true. recordWritten will inject a synthetic
|
|
867
889
|
* read so immediate follow-up edits are not blocked by zero_read.
|
|
890
|
+
*
|
|
891
|
+
* Keyed by the existence-INDEPENDENT syntactic spelling
|
|
892
|
+
* (`normalizeEphemeralMapKey`, the same key `knownPathIndex` uses), NOT by
|
|
893
|
+
* `this.key()` (#3163). This entry is written while the file is still
|
|
894
|
+
* ABSENT and read back by `recordWritten` once it EXISTS — the one state
|
|
895
|
+
* change `normalizeFilePath` itself branches on — so a `this.key()` on each
|
|
896
|
+
* side answered with two different strings for any spelling whose key moves
|
|
897
|
+
* when the file appears (win32: a mixed-case basename, lower-cased in the
|
|
898
|
+
* absent branch by `resolveNonExisting`; POSIX since #3098: a parent whose
|
|
899
|
+
* canonical casing is adopted once `realpathSync.native` succeeds). The
|
|
900
|
+
* entry was then orphaned — nothing prunes `pendingCreations` — and the
|
|
901
|
+
* creation read never injected. The syntactic key never touches the
|
|
902
|
+
* filesystem, so it cannot move; the REAL key the injected read needs is
|
|
903
|
+
* derived by `recordWritten` itself and is not stored here.
|
|
868
904
|
*/
|
|
869
905
|
noteCreatedFile(filePath, turnIndex, writeIndex) {
|
|
870
|
-
this.pendingCreations.set(
|
|
906
|
+
this.pendingCreations.set(normalizeEphemeralMapKey(filePath), {
|
|
907
|
+
turnIndex,
|
|
908
|
+
writeIndex,
|
|
909
|
+
});
|
|
871
910
|
}
|
|
872
911
|
/**
|
|
873
912
|
* Refresh the FileTime stamp after the model's own write lands on disk.
|
|
@@ -876,6 +915,7 @@ export class ReadGuard {
|
|
|
876
915
|
*/
|
|
877
916
|
recordWritten(rawFilePath) {
|
|
878
917
|
const filePath = this.key(rawFilePath);
|
|
918
|
+
this.unchangedThisSession.delete(filePath);
|
|
879
919
|
// #1668 review F1: index by the existence-independent syntactic key
|
|
880
920
|
// (see `knownPathIndex`) so a later hasKnownPath/forgetPath lookup
|
|
881
921
|
// after an external delete can still find this entry's real key.
|
|
@@ -886,12 +926,25 @@ export class ReadGuard {
|
|
|
886
926
|
this.consumedReadFiles.add(filePath);
|
|
887
927
|
this.touchFile(filePath);
|
|
888
928
|
this.enforceFileCap();
|
|
889
|
-
|
|
929
|
+
// #3163: `noteCreatedFile` announced this creation while the file was
|
|
930
|
+
// still absent, so the announcement is keyed by the syntactic spelling —
|
|
931
|
+
// look it up the same way, never through the existence-dependent
|
|
932
|
+
// `filePath` above. `injectCreationRead` still receives the real key.
|
|
933
|
+
const creationKey = normalizeEphemeralMapKey(rawFilePath);
|
|
934
|
+
const creation = this.pendingCreations.get(creationKey);
|
|
890
935
|
if (creation) {
|
|
891
|
-
this.pendingCreations.delete(
|
|
936
|
+
this.pendingCreations.delete(creationKey);
|
|
892
937
|
this.injectCreationRead(filePath, creation.turnIndex, creation.writeIndex);
|
|
893
938
|
}
|
|
894
939
|
}
|
|
940
|
+
/** Record that a recognized mutation had complete evidence but changed no bytes. */
|
|
941
|
+
recordUnchanged(rawFilePath) {
|
|
942
|
+
const filePath = this.key(rawFilePath);
|
|
943
|
+
// A no-op command is scoped to this command. It must not erase a
|
|
944
|
+
// confirmed Write from earlier in the session.
|
|
945
|
+
if (!this.writtenThisSession.has(filePath))
|
|
946
|
+
this.unchangedThisSession.add(filePath);
|
|
947
|
+
}
|
|
895
948
|
/**
|
|
896
949
|
* Add a one-time exemption for a file.
|
|
897
950
|
* Called via /lens-allow-edit command.
|
|
@@ -1074,6 +1127,8 @@ export class ReadGuard {
|
|
|
1074
1127
|
});
|
|
1075
1128
|
}
|
|
1076
1129
|
wasWrittenThisSession(filePath) {
|
|
1130
|
+
if (this.unchangedThisSession.has(filePath))
|
|
1131
|
+
return false;
|
|
1077
1132
|
// Authoritative path: we observed a write of this file via recordWritten.
|
|
1078
1133
|
// Survives mtime granularity (FAT32 ~2s), clock skew (NFS), and external
|
|
1079
1134
|
// tools that touch mtime backward.
|
|
@@ -1338,6 +1393,8 @@ export class ReadGuard {
|
|
|
1338
1393
|
const reads = this.reads.get(filePath) ?? [];
|
|
1339
1394
|
// First pass: check symbol coverage and any single read that covers the edit.
|
|
1340
1395
|
for (const read of reads) {
|
|
1396
|
+
if (read.provisional === true)
|
|
1397
|
+
continue;
|
|
1341
1398
|
const readStart = Math.max(1, read.effectiveOffset - this.config.contextLines);
|
|
1342
1399
|
const readEnd = read.effectiveOffset +
|
|
1343
1400
|
read.effectiveLimit -
|
|
@@ -1356,7 +1413,9 @@ export class ReadGuard {
|
|
|
1356
1413
|
}
|
|
1357
1414
|
// Second pass: merge all read intervals and check if their union covers
|
|
1358
1415
|
// [editStart, editEnd]. Handles multi-chunk reads (e.g. 1-100 + 101-200).
|
|
1359
|
-
const intervals = reads
|
|
1416
|
+
const intervals = reads
|
|
1417
|
+
.filter((read) => read.provisional !== true)
|
|
1418
|
+
.map((read) => [
|
|
1360
1419
|
Math.max(1, read.effectiveOffset - this.config.contextLines),
|
|
1361
1420
|
read.effectiveOffset +
|
|
1362
1421
|
read.effectiveLimit -
|
|
@@ -44,10 +44,20 @@
|
|
|
44
44
|
* clients/instance-reaper.ts (`decideOrphanReaping` vs `sweepOrphans`).
|
|
45
45
|
*/
|
|
46
46
|
import pidusage from "pidusage";
|
|
47
|
-
import { recordDegradationOnce } from "./degradation-ledger.js";
|
|
47
|
+
import { incrementDegradationCount, recordDegradationOnce, } from "./degradation-ledger.js";
|
|
48
48
|
import { terminateScannerChild } from "./instance-reaper.js";
|
|
49
49
|
import { queryProcessTable } from "./process-snapshot.js";
|
|
50
50
|
export const RESOURCE_SAMPLE_QUERY_TIMEOUT_MS = 2_000;
|
|
51
|
+
/**
|
|
52
|
+
* ONE ledger subject for every spawn sampler in the session (#2968). The
|
|
53
|
+
* per-spawn identity (pid, command) is NOT the subject: a subject is a
|
|
54
|
+
* `tallies` key, and one key per spawn would make the ledger grow with the
|
|
55
|
+
* session's spawn count — catalog shape 9, the same leak this fix exists to
|
|
56
|
+
* close. The command that was sampled is already on the `spawn_resource_usage`
|
|
57
|
+
* latency row; what the ledger answers is "did sampling hit its bounds, and
|
|
58
|
+
* how often", which is a per-session tally.
|
|
59
|
+
*/
|
|
60
|
+
const SPAWN_SAMPLER_LEDGER_SUBJECT = "spawn-usage-sampler";
|
|
51
61
|
function recordQueryFailure(subject, status, exitCode) {
|
|
52
62
|
const exitReason = status === "exit-error" ? ` (exit code ${exitCode ?? "unknown"})` : "";
|
|
53
63
|
recordDegradationOnce({
|
|
@@ -434,13 +444,89 @@ export async function sampleProcessTreeCpuPercent(pid, windowMs = 1200, floorPer
|
|
|
434
444
|
cpuPercent: observed,
|
|
435
445
|
};
|
|
436
446
|
}
|
|
437
|
-
|
|
447
|
+
/**
|
|
448
|
+
* Base poll cadence — inside #620's suggested 500ms-1s band, cheap enough not
|
|
449
|
+
* to become measurable overhead for the (usually sub-few-second) analyzer
|
|
450
|
+
* children this brackets.
|
|
451
|
+
*/
|
|
452
|
+
const SPAWN_SAMPLE_INTERVAL_MS = 750;
|
|
453
|
+
/**
|
|
454
|
+
* #2968: how many ticks keep the full `intervalMs` cadence before the backoff
|
|
455
|
+
* starts. 8 × 750ms = the first 6 seconds, which covers the short-lived
|
|
456
|
+
* analyzer children the short interval exists for; a child still running past
|
|
457
|
+
* that is not short-lived and does not need sub-second resolution.
|
|
458
|
+
*/
|
|
459
|
+
const SPAWN_SAMPLE_FULL_RATE_TICKS = 8;
|
|
460
|
+
/**
|
|
461
|
+
* #2968: backoff ceiling, as a multiple of `intervalMs` (16 × 750ms = 12s).
|
|
462
|
+
* Past the full-rate window the delay doubles each tick up to this, so a
|
|
463
|
+
* long-lived child costs a bounded ~5 polls/minute instead of 80.
|
|
464
|
+
*/
|
|
465
|
+
const SPAWN_SAMPLE_MAX_INTERVAL_MULTIPLIER = 16;
|
|
466
|
+
/**
|
|
467
|
+
* Brackets one transient spawn with a short-interval poll. Usage:
|
|
468
|
+
*
|
|
469
|
+
* const sampler = startSpawnUsageSampler(child.pid, interval, capMs);
|
|
470
|
+
* child.on("close", () => {
|
|
471
|
+
* const usage = sampler.stop(); // null if never got a single sample
|
|
472
|
+
* });
|
|
473
|
+
*
|
|
474
|
+
* Best-effort: a poll tick that throws (pid already gone, sampling error) is
|
|
475
|
+
* silently skipped — it never stops the polling or the spawn early, and
|
|
476
|
+
* `stop()` is always safe to call even if zero samples ever landed.
|
|
477
|
+
*
|
|
478
|
+
* Windows note: `clients/safe-spawn.ts` spawns with `shell: true` on Windows,
|
|
479
|
+
* so `pid` here is `cmd.exe`'s pid, not the real tool's — sampling it alone
|
|
480
|
+
* would report near-zero usage for the whole invocation. Each Windows tick
|
|
481
|
+
* resolves `pid`'s live descendant tree (`findDescendantPidsWindows`) and
|
|
482
|
+
* sums usage across `pid` + every descendant, so a `node`/`npx`-wrapped tool
|
|
483
|
+
* (or one that re-execs itself) is actually captured. POSIX spawns are
|
|
484
|
+
* unwrapped (`shell: false`), so `pid` there is already the real tool.
|
|
485
|
+
*
|
|
486
|
+
* ## Backpressure: three bounds, three axes (#2968, external report)
|
|
487
|
+
*
|
|
488
|
+
* A Windows tick is not free — it is TWO `powershell.exe` CIM queries (the
|
|
489
|
+
* descendant walk plus the usage read), and a query that blows
|
|
490
|
+
* `RESOURCE_SAMPLE_QUERY_TIMEOUT_MS` adds a `taskkill.exe`. With a `setInterval`
|
|
491
|
+
* that neither waited for its own tick nor ever expired, four children that
|
|
492
|
+
* hung for 5-6 hours left 234 live `powershell.exe`/`taskkill.exe` processes
|
|
493
|
+
* (~10GB) parented by the host. Each bound below closes one axis; catalog
|
|
494
|
+
* shape 9 is exactly the shape where closing only one of them is not a fix:
|
|
495
|
+
*
|
|
496
|
+
* 1. CONCURRENCY — a tick never starts while the previous one is unsettled
|
|
497
|
+
* (`inFlight`). The old `setInterval` fired regardless, so a query slower
|
|
498
|
+
* than the interval stacked one more pair of children every tick.
|
|
499
|
+
* 2. RATE — past `SPAWN_SAMPLE_FULL_RATE_TICKS` the delay doubles per tick up
|
|
500
|
+
* to `SPAWN_SAMPLE_MAX_INTERVAL_MULTIPLIER × intervalMs`. The short
|
|
501
|
+
* interval exists to catch short-lived children; a 180s runner (the
|
|
502
|
+
* longest spawn timeout in the tree) costs ~26 polls instead of 240.
|
|
503
|
+
* 3. LIFETIME — polling stops for good at `lifetimeCapMs`, measured from
|
|
504
|
+
* start. `stop()` still returns everything gathered up to that point; a
|
|
505
|
+
* capped sampler loses resolution, never the reading it already had. The
|
|
506
|
+
* cap is read at TICK granularity, so the last poll can land up to one
|
|
507
|
+
* backed-off interval (≤ 12s by default) before polling ends — the tick
|
|
508
|
+
* that discovers the cap does no sampling and arms nothing.
|
|
509
|
+
*
|
|
510
|
+
* Ticks skipped for (1) and the cap in (3) are both recorded, bounded, on the
|
|
511
|
+
* degradation ledger — a sampler that quietly stops sampling is the #1863 /
|
|
512
|
+
* #2132 shape one level up.
|
|
513
|
+
*
|
|
514
|
+
* `lifetimeCapMs` has no default ON PURPOSE: an unbounded sampler is the whole
|
|
515
|
+
* defect, so the bound is the caller's to state rather than something a future
|
|
516
|
+
* call site can forget. The cadence keeps its default, which is policy this
|
|
517
|
+
* module owns.
|
|
518
|
+
*/
|
|
519
|
+
export function startSpawnUsageSampler(pid, intervalMs = SPAWN_SAMPLE_INTERVAL_MS, lifetimeCapMs) {
|
|
438
520
|
if (!Number.isFinite(pid) || pid <= 0) {
|
|
439
521
|
return { stop: () => null };
|
|
440
522
|
}
|
|
441
523
|
const targetPid = pid;
|
|
442
524
|
const accumulator = new UsageAccumulator();
|
|
525
|
+
const startedAt = Date.now();
|
|
443
526
|
let stopped = false;
|
|
527
|
+
let inFlight = false;
|
|
528
|
+
let tickCount = 0;
|
|
529
|
+
let timer;
|
|
444
530
|
const tick = async () => {
|
|
445
531
|
if (stopped)
|
|
446
532
|
return;
|
|
@@ -470,20 +556,72 @@ export function startSpawnUsageSampler(pid, intervalMs = 750) {
|
|
|
470
556
|
// Best-effort: a failed poll tick just misses one sample.
|
|
471
557
|
}
|
|
472
558
|
};
|
|
559
|
+
const releaseInFlight = () => {
|
|
560
|
+
inFlight = false;
|
|
561
|
+
};
|
|
562
|
+
/**
|
|
563
|
+
* Delay before the NEXT tick, given how many have already run. Full rate
|
|
564
|
+
* through the short-lived window, then doubling to the ceiling (#2968).
|
|
565
|
+
*/
|
|
566
|
+
const nextDelayMs = () => {
|
|
567
|
+
if (tickCount <= SPAWN_SAMPLE_FULL_RATE_TICKS)
|
|
568
|
+
return intervalMs;
|
|
569
|
+
const grown = intervalMs * 2 ** (tickCount - SPAWN_SAMPLE_FULL_RATE_TICKS);
|
|
570
|
+
return Math.min(grown, intervalMs * SPAWN_SAMPLE_MAX_INTERVAL_MULTIPLIER);
|
|
571
|
+
};
|
|
572
|
+
const arm = (delayMs) => {
|
|
573
|
+
timer = setTimeout(runTick, delayMs);
|
|
574
|
+
// Never let this timer keep the process alive on its own.
|
|
575
|
+
timer.unref?.();
|
|
576
|
+
};
|
|
577
|
+
function runTick() {
|
|
578
|
+
timer = undefined;
|
|
579
|
+
if (stopped)
|
|
580
|
+
return;
|
|
581
|
+
if (Date.now() - startedAt >= lifetimeCapMs) {
|
|
582
|
+
// LIFETIME bound: deliberately NOT re-armed. The accumulator is kept,
|
|
583
|
+
// so `stop()` still answers with everything gathered before the cap.
|
|
584
|
+
incrementDegradationCount({
|
|
585
|
+
kind: "resource-sampler-lifetime-capped",
|
|
586
|
+
subject: SPAWN_SAMPLER_LEDGER_SUBJECT,
|
|
587
|
+
reason: `spawn sampler stopped polling at its ${lifetimeCapMs}ms lifetime cap; the child was still running`,
|
|
588
|
+
});
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
tickCount++;
|
|
592
|
+
if (inFlight) {
|
|
593
|
+
// CONCURRENCY bound: the previous tick's process-table queries have
|
|
594
|
+
// not settled, so starting another would stack a second set of
|
|
595
|
+
// children on top of them (#2968's Windows pile-up).
|
|
596
|
+
incrementDegradationCount({
|
|
597
|
+
kind: "resource-sampler-tick-overlapped",
|
|
598
|
+
subject: SPAWN_SAMPLER_LEDGER_SUBJECT,
|
|
599
|
+
reason: `spawn sampler skipped a poll tick: the previous sample was still in flight after ${intervalMs}ms`,
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
inFlight = true;
|
|
604
|
+
// `tick` never rejects (it is fully guarded), but settle BOTH ways
|
|
605
|
+
// anyway: a rejection that escaped would otherwise latch `inFlight`
|
|
606
|
+
// true and silently end all sampling.
|
|
607
|
+
void tick().then(releaseInFlight, releaseInFlight);
|
|
608
|
+
}
|
|
609
|
+
arm(nextDelayMs());
|
|
610
|
+
}
|
|
473
611
|
// Fire one tick immediately (short-lived children can exit before the
|
|
474
612
|
// first interval elapses) plus a recurring poll.
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
// Never let this timer keep the process alive on its own.
|
|
480
|
-
timer.unref?.();
|
|
613
|
+
tickCount = 1;
|
|
614
|
+
inFlight = true;
|
|
615
|
+
void tick().then(releaseInFlight, releaseInFlight);
|
|
616
|
+
arm(nextDelayMs());
|
|
481
617
|
return {
|
|
482
618
|
stop() {
|
|
483
619
|
if (stopped)
|
|
484
620
|
return accumulator.summarize();
|
|
485
621
|
stopped = true;
|
|
486
|
-
|
|
622
|
+
if (timer !== undefined)
|
|
623
|
+
clearTimeout(timer);
|
|
624
|
+
timer = undefined;
|
|
487
625
|
return accumulator.summarize();
|
|
488
626
|
},
|
|
489
627
|
};
|
|
@@ -79,7 +79,7 @@ export class RuffClient {
|
|
|
79
79
|
// nested `pyproject.toml` whenever the caller passed a workspace root.
|
|
80
80
|
const ruffCwd = resolveToolCwd("runner", "ruff", absolutePath, {
|
|
81
81
|
...(cwd !== undefined && { cwd }),
|
|
82
|
-
});
|
|
82
|
+
}).cwd;
|
|
83
83
|
// Shared config-args seam (#1247): the lint runner consumes the same
|
|
84
84
|
// builder, so `check --fix` can never drift to ruff's default rule
|
|
85
85
|
// set when the project lacks its own config and the package-owned
|
|
@@ -6,6 +6,8 @@ import { publishAutofixStart, publishFormatStart, } from "./format-events-publis
|
|
|
6
6
|
import { logLatency } from "./latency-logger.js";
|
|
7
7
|
import { isPathIgnoredByProject } from "./file-utils.js";
|
|
8
8
|
import { admitBounded, emitBounded } from "./bounded-telemetry.js";
|
|
9
|
+
import { bounded } from "./deadline-utils.js";
|
|
10
|
+
import { HOOK_WALL_BUDGET_MS } from "./hook-budgets.js";
|
|
9
11
|
import { newLspMutationCorrelationId, } from "./lsp-mutation.js";
|
|
10
12
|
import { getGlobalActionableWarningMaxFixes, } from "./lens-config.js";
|
|
11
13
|
import { resyncLspFile, runAutofix, runFormatPhase } from "./pipeline.js";
|
|
@@ -32,7 +34,7 @@ function recordProjectChange(args) {
|
|
|
32
34
|
onAppendError: (err) => args.dbg(`project change log append failed for ${args.filePath}: ${err}`),
|
|
33
35
|
});
|
|
34
36
|
}
|
|
35
|
-
export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, dbg, runtime, cacheManager, getFormatService, biomeClient, ruffClient, getAutofixClients, currentSessionId, staleAfterMs = DEFERRED_FORMAT_STALE_AFTER_MS, }) {
|
|
37
|
+
export async function handleAgentEnd({ signal, ctxCwd, getFlag, getFlagSource, notify, dbg, runtime, cacheManager, getFormatService, biomeClient, ruffClient, getAutofixClients, currentSessionId, staleAfterMs = DEFERRED_FORMAT_STALE_AFTER_MS, }) {
|
|
36
38
|
// #791: ownership-filtered drain — records queued by a DIFFERENT known
|
|
37
39
|
// session (e.g. a concurrent in-process secondary/subagent) stay queued
|
|
38
40
|
// for their owner's own agent_end, unless they've been stale long enough
|
|
@@ -204,7 +206,7 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
|
|
|
204
206
|
const deferredAutofixChanged = new Set();
|
|
205
207
|
// Mutation ordering is intentional: lint --write may disturb wrapping, so
|
|
206
208
|
// autofix reaches the final edited state first and formatting stabilizes it.
|
|
207
|
-
const ambientSignal = getAmbientAbortSignal();
|
|
209
|
+
const ambientSignal = signal ?? getAmbientAbortSignal();
|
|
208
210
|
const executedAutofixScopes = new Set();
|
|
209
211
|
const autofixRecords = records.filter((candidate) => candidate.kinds.has("autofix"));
|
|
210
212
|
if (autofixRecords.length > 0 &&
|
|
@@ -367,7 +369,13 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
|
|
|
367
369
|
const filePath = path.resolve(record.filePath);
|
|
368
370
|
started.add(index);
|
|
369
371
|
if (!nodeFs.existsSync(filePath)) {
|
|
370
|
-
work[index] = {
|
|
372
|
+
work[index] = {
|
|
373
|
+
record,
|
|
374
|
+
filePath,
|
|
375
|
+
fileStart,
|
|
376
|
+
result: undefined,
|
|
377
|
+
missing: true,
|
|
378
|
+
};
|
|
371
379
|
continue;
|
|
372
380
|
}
|
|
373
381
|
try {
|
|
@@ -375,7 +383,12 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
|
|
|
375
383
|
record,
|
|
376
384
|
filePath,
|
|
377
385
|
fileStart,
|
|
378
|
-
result: await runFormatPhase(filePath, getFormatService, dbg),
|
|
386
|
+
result: await bounded(runFormatPhase(filePath, getFormatService, dbg, ambientSignal, 30_000, "agent_settled"), {
|
|
387
|
+
ms: HOOK_WALL_BUDGET_MS.agent_settled,
|
|
388
|
+
signal: ambientSignal,
|
|
389
|
+
hook: "agent_settled",
|
|
390
|
+
label: "deferred-format",
|
|
391
|
+
}),
|
|
379
392
|
};
|
|
380
393
|
}
|
|
381
394
|
catch (err) {
|
|
@@ -383,6 +396,7 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
|
|
|
383
396
|
record,
|
|
384
397
|
filePath,
|
|
385
398
|
fileStart,
|
|
399
|
+
result: undefined,
|
|
386
400
|
error: err instanceof Error ? err.message : String(err),
|
|
387
401
|
};
|
|
388
402
|
}
|
|
@@ -423,8 +437,22 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
|
|
|
423
437
|
continue;
|
|
424
438
|
}
|
|
425
439
|
const result = entry.result;
|
|
426
|
-
if (!result)
|
|
440
|
+
if (!result) {
|
|
441
|
+
// The abort branch above already requeues work that never started;
|
|
442
|
+
// preserve its established ownership for an in-flight caller abort.
|
|
443
|
+
if (ambientSignal?.aborted)
|
|
444
|
+
continue;
|
|
445
|
+
const reason = entry.error ?? "deferred formatter exceeded agent_settled budget";
|
|
446
|
+
summary.failed.push({ filePath, errors: [reason] });
|
|
447
|
+
requeue([
|
|
448
|
+
{
|
|
449
|
+
...record,
|
|
450
|
+
kinds: new Set(["format"]),
|
|
451
|
+
toolNames: new Set(record.toolNames),
|
|
452
|
+
},
|
|
453
|
+
], "format-failed");
|
|
427
454
|
continue;
|
|
455
|
+
}
|
|
428
456
|
summary.formatted++;
|
|
429
457
|
if (result.formatFailures.length > 0) {
|
|
430
458
|
summary.failed.push({ filePath, errors: result.formatFailures });
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { provenanceStamp, validateAdvisoryProvenance, } from "./advisory-provenance.js";
|
|
2
2
|
import { logLatency } from "./latency-logger.js";
|
|
3
|
+
import { translateGuidanceToolNames, } from "./tool-config.js";
|
|
3
4
|
// Exported so the Stop-hook bin strips exactly what these bridges prepend.
|
|
4
5
|
export const AUTOMATION_FRAMING = "[pi-lens automated check — not a user request] ";
|
|
5
6
|
// #1432 review (S3b): a multi-file bash write can carry hundreds of changed
|
|
@@ -184,7 +185,7 @@ export function acknowledgeTestFindings(cacheManager, cwd) {
|
|
|
184
185
|
return;
|
|
185
186
|
retireTestFindings(cacheManager, cwd);
|
|
186
187
|
}
|
|
187
|
-
export function consumeSessionStartGuidance(cacheManager, cwd) {
|
|
188
|
+
export function consumeSessionStartGuidance(cacheManager, cwd, host = "pi") {
|
|
188
189
|
const guidance = cacheManager.readCache("session-start-guidance", cwd);
|
|
189
190
|
if (!guidance?.data?.content)
|
|
190
191
|
return;
|
|
@@ -200,7 +201,10 @@ export function consumeSessionStartGuidance(cacheManager, cwd) {
|
|
|
200
201
|
messages: [
|
|
201
202
|
{
|
|
202
203
|
role: "user",
|
|
203
|
-
|
|
204
|
+
// #2535: the stored guidance names pi tools; MCP agents receive
|
|
205
|
+
// the registry-translated rendering. The stored record keeps
|
|
206
|
+
// its shape — resolution happens here, at delivery.
|
|
207
|
+
content: `[pi-lens automated context — not a user request]\n\n${translateGuidanceToolNames(guidance.data.content, host)}`,
|
|
204
208
|
},
|
|
205
209
|
],
|
|
206
210
|
};
|
|
@@ -12,6 +12,9 @@ import { RUNTIME_CONFIG } from "./runtime-config.js";
|
|
|
12
12
|
import { TurnSummaryCollector } from "./turn-summary.js";
|
|
13
13
|
import { deriveProviderFromModelId } from "./model-provider.js";
|
|
14
14
|
import { beginTurnContext, setTurnContextSession } from "./turn-context.js";
|
|
15
|
+
import { recordDegradationOnce } from "./degradation-ledger.js";
|
|
16
|
+
/** Keep deferred cascade admission bounded without dropping late findings. */
|
|
17
|
+
export const MAX_PENDING_CASCADE_RUNS = 32;
|
|
15
18
|
/**
|
|
16
19
|
* Bounded receipt ring capacity (defect shape 9: bound the axis that grows —
|
|
17
20
|
* per-mutation receipts are unbounded across a long session). Oldest receipts
|
|
@@ -286,8 +289,8 @@ export class RuntimeCoordinator {
|
|
|
286
289
|
}
|
|
287
290
|
this._mutationReceipts.push({
|
|
288
291
|
seq: projectSeq,
|
|
289
|
-
// Reuse the bump's normalized key (
|
|
290
|
-
// never re-derive it here.
|
|
292
|
+
// Reuse the bump's normalized key (a realpath syscall: ~200us on
|
|
293
|
+
// Windows, ~1.8us on POSIX since #3098) — never re-derive it here.
|
|
291
294
|
filePath: key,
|
|
292
295
|
source: args.source,
|
|
293
296
|
turnIndex: this._turnIndex,
|
|
@@ -455,8 +458,9 @@ export class RuntimeCoordinator {
|
|
|
455
458
|
}
|
|
456
459
|
}
|
|
457
460
|
bumpFileSeq(filePath) {
|
|
458
|
-
// normalizeMapKey costs ~200us
|
|
459
|
-
//
|
|
461
|
+
// normalizeMapKey costs a realpath syscall per call — ~200us on Windows,
|
|
462
|
+
// ~1.8us on POSIX since #3098; every caller that also needs the key must
|
|
463
|
+
// reuse this one instead of paying it twice.
|
|
460
464
|
const key = normalizeMapKey(path.resolve(filePath));
|
|
461
465
|
this._projectSeq += 1;
|
|
462
466
|
const fileSeq = (this._fileSeq.get(key) ?? 0) + 1;
|
|
@@ -542,7 +546,24 @@ export class RuntimeCoordinator {
|
|
|
542
546
|
this._cascadeRuns.push(run);
|
|
543
547
|
}
|
|
544
548
|
appendCascadePromise(p) {
|
|
545
|
-
this._pendingCascadeRuns.
|
|
549
|
+
if (this._pendingCascadeRuns.length < MAX_PENDING_CASCADE_RUNS) {
|
|
550
|
+
this._pendingCascadeRuns.push(p);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
// Preserve delivery for overflow rather than growing the per-edit array.
|
|
554
|
+
// The settled run enters the same accumulator off-hook and is therefore
|
|
555
|
+
// visible to the next turn-end drain.
|
|
556
|
+
recordDegradationOnce({
|
|
557
|
+
kind: "cascade-pending-cap",
|
|
558
|
+
subject: "runtime-coordinator",
|
|
559
|
+
reason: `deferred cascade admission capped at ${MAX_PENDING_CASCADE_RUNS}`,
|
|
560
|
+
});
|
|
561
|
+
void p
|
|
562
|
+
.then((run) => this.appendCascadeRun(run))
|
|
563
|
+
.catch(() => {
|
|
564
|
+
// Pipeline promises are normally non-rejecting; preserve the existing
|
|
565
|
+
// failure sink if a caller violates that contract.
|
|
566
|
+
});
|
|
546
567
|
}
|
|
547
568
|
/**
|
|
548
569
|
* The active turn_end settle clock, or undefined outside that wait.
|
|
@@ -644,16 +665,24 @@ export class RuntimeCoordinator {
|
|
|
644
665
|
// that actually settles it delivers it.
|
|
645
666
|
return this._cascadeRuns.length > 0;
|
|
646
667
|
}
|
|
647
|
-
recordInlineBlockers(filePath, summary, writeIndex, sources, lines) {
|
|
668
|
+
recordInlineBlockers(filePath, summary, writeIndex, sources, lines, contentBaseline) {
|
|
669
|
+
const recordedAtMs = Date.now();
|
|
648
670
|
this._pendingInlineBlockers.set(path.resolve(filePath), {
|
|
649
671
|
filePath,
|
|
650
672
|
summary,
|
|
651
673
|
writeIndex,
|
|
652
674
|
sources,
|
|
653
675
|
lines,
|
|
654
|
-
recordedAtMs
|
|
676
|
+
recordedAtMs,
|
|
655
677
|
stale: false,
|
|
678
|
+
...(contentBaseline
|
|
679
|
+
? {
|
|
680
|
+
recordedSize: contentBaseline.size,
|
|
681
|
+
recordedHash: contentBaseline.sha256,
|
|
682
|
+
}
|
|
683
|
+
: {}),
|
|
656
684
|
});
|
|
685
|
+
return recordedAtMs;
|
|
657
686
|
}
|
|
658
687
|
clearInlineBlockers(filePath) {
|
|
659
688
|
this._pendingInlineBlockers.delete(path.resolve(filePath));
|
|
@@ -708,6 +737,47 @@ export class RuntimeCoordinator {
|
|
|
708
737
|
});
|
|
709
738
|
return true;
|
|
710
739
|
}
|
|
740
|
+
/**
|
|
741
|
+
* #2982: re-derive the self-drift demotion for one inline-blocker record.
|
|
742
|
+
*
|
|
743
|
+
* Modelled on {@link setInlineBlockerPastEofStale}, not on
|
|
744
|
+
* {@link markInlineBlockerStale}: it RE-ARMS rather than latching, so a
|
|
745
|
+
* file whose bytes drift and then come back (a checkout, a revert, an
|
|
746
|
+
* editor writing the original content) un-demotes on the next sweep
|
|
747
|
+
* instead of staying demoted for the session. That re-arm is what lets
|
|
748
|
+
* this axis skip the #1950 delivery cap entirely — a record that can heal
|
|
749
|
+
* needs no bounded-noise retirement, and applying the cap here would walk
|
|
750
|
+
* an ast-grep or tree-sitter security finding out of turn-end rendering
|
|
751
|
+
* permanently (#2982 review).
|
|
752
|
+
*
|
|
753
|
+
* Composes with the sibling gates the same way they compose with each
|
|
754
|
+
* other: it never touches a record another gate demoted, and never heals a
|
|
755
|
+
* demotion it did not make. Returns true only on an actual transition, so
|
|
756
|
+
* the caller counts exactly one edge.
|
|
757
|
+
*/
|
|
758
|
+
setInlineBlockerSelfDriftStale(filePath, isSelfDrift) {
|
|
759
|
+
const key = path.resolve(filePath);
|
|
760
|
+
const existing = this._pendingInlineBlockers.get(key);
|
|
761
|
+
if (!existing)
|
|
762
|
+
return false;
|
|
763
|
+
if (existing.stale && existing.staleReason !== "self-drift")
|
|
764
|
+
return false;
|
|
765
|
+
const currentlySelfDrift = !!existing.stale && existing.staleReason === "self-drift";
|
|
766
|
+
if (currentlySelfDrift === isSelfDrift)
|
|
767
|
+
return false;
|
|
768
|
+
// Omit the key rather than assigning `undefined` to it: under
|
|
769
|
+
// `exactOptionalPropertyTypes` those are different, and writing the
|
|
770
|
+
// `undefined` is a strictness spike the ratchet counts. Healing has to
|
|
771
|
+
// drop `staleReason` off the rebuilt record, so destructure it away
|
|
772
|
+
// instead of letting `...existing` carry the old value through.
|
|
773
|
+
const { staleReason: _cleared, ...rest } = existing;
|
|
774
|
+
this._pendingInlineBlockers.set(key, {
|
|
775
|
+
...rest,
|
|
776
|
+
stale: isSelfDrift,
|
|
777
|
+
...(isSelfDrift ? { staleReason: "self-drift" } : {}),
|
|
778
|
+
});
|
|
779
|
+
return true;
|
|
780
|
+
}
|
|
711
781
|
/**
|
|
712
782
|
* #1944: retire a past-EOF demotion after its ONE degraded delivery.
|
|
713
783
|
*
|
|
@@ -14,7 +14,7 @@ import { resetDispatchAvailabilityState } from "./dispatch/runners/utils/runner-
|
|
|
14
14
|
import { resetObservedRunnerLatency } from "./dispatch/collect-later-tier.js";
|
|
15
15
|
import { resetPendingRunnerFindings } from "./dispatch/pending-runner-findings.js";
|
|
16
16
|
import { clearAllSessions as clearFileTimeSessions } from "./file-time.js";
|
|
17
|
-
import { getGlobalPiLensDir, getKnipIgnorePatterns, getProjectDataDir, } from "./file-utils.js";
|
|
17
|
+
import { drainProjectDataDirMigrations, getGlobalPiLensDir, getKnipIgnorePatterns, getProjectDataDir, resetProjectDataDirSessionState, } from "./file-utils.js";
|
|
18
18
|
import { GitleaksClient } from "./gitleaks-client.js";
|
|
19
19
|
import { resetGoAvailability } from "./go-client.js";
|
|
20
20
|
import { GovulncheckClient, } from "./govulncheck-client.js";
|
|
@@ -1369,7 +1369,7 @@ function scheduleDeferredToolProbesWithClients(deps, languageProfile, startupDef
|
|
|
1369
1369
|
export const SESSION_START_GUIDANCE = [
|
|
1370
1370
|
"📌 pi-lens active — automated checks run on every edit/write; blocking errors (including pre-existing) show inline and must be fixed.\n" +
|
|
1371
1371
|
"Key tools (see each tool's own description for args):\n" +
|
|
1372
|
-
"• lens_diagnostics — source=session reads cache; empty cache ≠ clean; use source=lsp scope=paths for changed files with absent or stale findings.\n" +
|
|
1372
|
+
"• lens_diagnostics — source=session reads cache; empty cache ≠ clean; use source=lsp scope=paths for changed files with absent or stale findings (aggregate hosts: lens(action=diagnostics)).\n" +
|
|
1373
1373
|
"• symbol_search → module_report → read_symbol/read_enclosing — ranked identifier search, then navigable outline/callback handles + exact body reads; cheaper than reading a whole file before editing.\n" +
|
|
1374
1374
|
"• Situational (activate via pi_lens_activate_tools): lsp_navigation, ast_grep_search, ast_grep_replace. Use ast_grep_search with dump=true to inspect AST nodes.",
|
|
1375
1375
|
];
|
|
@@ -1381,6 +1381,7 @@ export async function handleSessionStart(deps) {
|
|
|
1381
1381
|
// every analyzer refused for the rest of the process — AGENTS.md defect
|
|
1382
1382
|
// shape 17. The resident clients themselves are deliberately kept.
|
|
1383
1383
|
resetAnalyzerBootstrapSessionState();
|
|
1384
|
+
resetProjectDataDirSessionState();
|
|
1384
1385
|
resetTestRunnerDelivery();
|
|
1385
1386
|
// #2450 fix round 3, catalog shape 17: the "bridge unavailable" dbg latch
|
|
1386
1387
|
// (`clients/lsp-mutation.ts`) is a process-lifetime once-per-session flag,
|
|
@@ -1900,6 +1901,24 @@ export async function handleSessionStart(deps) {
|
|
|
1900
1901
|
// project data roots and machine-global registry root once per session start;
|
|
1901
1902
|
// this is fire-and-forget and bounded so it never delays startup.
|
|
1902
1903
|
const projectDataDir = getProjectDataDir(cwd);
|
|
1904
|
+
// #2874: `getProjectDataDir` queues one migration per old-slug directory
|
|
1905
|
+
// it renames (or finds coexisting with its hashed successor). Drain here
|
|
1906
|
+
// so each migration emits one bounded record per session at most.
|
|
1907
|
+
for (const migration of drainProjectDataDirMigrations()) {
|
|
1908
|
+
const targetName = path.basename(migration.to);
|
|
1909
|
+
const hash = targetName.match(/([0-9a-f]{8})$/)?.[1] ?? "unknown";
|
|
1910
|
+
recordDegradationOnce({
|
|
1911
|
+
kind: "data_dir_migrated",
|
|
1912
|
+
subject: hash,
|
|
1913
|
+
reason: migration.outcome === "renamed"
|
|
1914
|
+
? "using hashed directory after renaming legacy directory"
|
|
1915
|
+
: migration.outcome === "coexisting"
|
|
1916
|
+
? "using hashed directory because legacy and hashed directories both exist"
|
|
1917
|
+
: migration.outcome === "rename-failed"
|
|
1918
|
+
? "using legacy directory after hashed-directory rename failure"
|
|
1919
|
+
: "using resolved directory after realpath fallback",
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1903
1922
|
// #1609 review F1: sweepOwnStagingFiles does not recurse, so the installer's
|
|
1904
1923
|
// bin/ and tools/ subdirectories (clients/installer/index.ts's
|
|
1905
1924
|
// GITHUB_BIN_DIR / TOOLS_DIR, now atomic-write.js writers too) need their
|