@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
|
@@ -31,11 +31,66 @@
|
|
|
31
31
|
* all runners, so an eslint, biome-check, or ast-grep security-rule (hardcoded
|
|
32
32
|
* secret, CVE) finding can land here too. Only a language-server verdict is
|
|
33
33
|
* actually invalidated by an IMPORT changing; an ast-grep secret match doesn't stop
|
|
34
|
-
* being true because a file it imports was edited.
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
34
|
+
* being true because a file it imports was edited.
|
|
35
|
+
*
|
|
36
|
+
* Self-drift axis (#1561 remainder). That reasoning covers the import axis and
|
|
37
|
+
* nothing else, but the original gate discarded BOTH axes for a non-LSP record:
|
|
38
|
+
* entries whose `sources` were not all `"lsp"` skipped the check entirely. A
|
|
39
|
+
* tree-sitter verdict about F's own syntax is emphatically invalidated by F's own
|
|
40
|
+
* bytes changing, and no other path could clear it —
|
|
41
|
+
* `retireInlineBlockerOnConfirmedClean` requires `coveredSources` to be a superset
|
|
42
|
+
* of the record's sources, `coveredSourcesForCheck` builds that set from the LSP
|
|
43
|
+
* server registry, and no registered server has id `"tree-sitter"`, so the retire
|
|
44
|
+
* could never fire however many clean checks ran. Live shape: a
|
|
45
|
+
* `ts-incomplete-assertion` blocker re-served every turn for the rest of a session
|
|
46
|
+
* against a file already proven clean by grep, LSP, and a passing test.
|
|
47
|
+
*
|
|
48
|
+
* So the sweep now runs over every entry that HAS recorded provenance, and picks
|
|
49
|
+
* the axis from the sources: all-`"lsp"` gets the own-file-plus-forward-imports
|
|
50
|
+
* walk, anything else (tree-sitter, ast-grep, mixed, or `"unknown"`) gets an
|
|
51
|
+
* own-file-only check. A record with NO recorded sources stays fail-closed and
|
|
52
|
+
* untouched, matching the test `retireInlineBlockerOnConfirmedClean` applies before
|
|
53
|
+
* it will retire.
|
|
54
|
+
*
|
|
55
|
+
* The self axis is CONTENT-CONFIRMED and RE-ARMING, and those two properties are
|
|
56
|
+
* why it carries its own `"self-drift"` reason rather than reusing
|
|
57
|
+
* `"dependency-drift"` (#2982 review).
|
|
58
|
+
*
|
|
59
|
+
* Content-confirmed: mtime moving is not evidence a byte moved. A `touch`, a
|
|
60
|
+
* `git checkout` restoring identical bytes, or a no-op formatter pass all move
|
|
61
|
+
* it. #2449 round 2 F7 settled this for `observed-mutation.ts` ("mtime-only
|
|
62
|
+
* drift has to be confirmed against content before anything is replayed"), and
|
|
63
|
+
* it binds harder here, where an unconfirmed demotion walks a finding out of the
|
|
64
|
+
* authoritative channel. `detectSelfDrift` applies that rule at both tiers:
|
|
65
|
+
* `size` first, then a sha256 of the bytes when size cannot separate a
|
|
66
|
+
* one-character edit from a `touch`. A same-LENGTH change is the common shape,
|
|
67
|
+
* not an exotic one (a renamed identifier of equal length, a flipped comparison,
|
|
68
|
+
* a changed digit), so a size-only tier would leave a genuinely changed record
|
|
69
|
+
* authoritative, which is #2982 arriving from the other side. The baseline is
|
|
70
|
+
* carried by `PipelineResult` beside the inline-blocker evidence and stamped
|
|
71
|
+
* synchronously by `recordInlineBlockers`. Every bound that expires, and every missing baseline, yields
|
|
72
|
+
* `unverifiable`, which changes no state in either direction and is counted so
|
|
73
|
+
* it is visible rather than silent.
|
|
74
|
+
*
|
|
75
|
+
* Re-arming: `setInlineBlockerSelfDriftStale` re-derives the verdict every turn
|
|
76
|
+
* and un-demotes a record whose bytes come back, exactly as
|
|
77
|
+
* `setInlineBlockerPastEofStale` does for a transient shrink-then-restore. That
|
|
78
|
+
* is what lets this axis stay OUT of the #1950 delivery cap. The cap retires a
|
|
79
|
+
* record permanently after `DEPENDENCY_DRIFT_MAX_DELIVERIES` stale deliveries,
|
|
80
|
+
* which suits recoverable LSP dependency drift; a self-drift record can carry
|
|
81
|
+
* ast-grep or tree-sitter security provenance, and retiring one because an
|
|
82
|
+
* advisory was shown three times would walk a hardcoded secret out of turn-end
|
|
83
|
+
* rendering with no dispatch to re-raise it. A record that heals on its own
|
|
84
|
+
* needs no bounded-noise retirement.
|
|
85
|
+
*
|
|
86
|
+
* Defect shape 24 is satisfied by composition, not by reusing a string: the new
|
|
87
|
+
* reason has per-writer semantics (re-arm, no cap), and like every sibling gate
|
|
88
|
+
* it never touches a demotion another gate made and never heals one it did not
|
|
89
|
+
* make. The commit gate is unaffected either way. `updateGitGuardStatus` counts
|
|
90
|
+
* `getInlineBlockersSnapshot().length` with no `stale` filter, so a demoted
|
|
91
|
+
* record still gates a commit; `tests/clients/blocker-freshness.test.ts` pins
|
|
92
|
+
* that, because it is what makes widening this sweep to security provenance
|
|
93
|
+
* safe at all.
|
|
39
94
|
*
|
|
40
95
|
* Resolution boundary (#1631 review F8): `extractForwardImportPaths` parses static
|
|
41
96
|
* import/require syntax via tree-sitter. It does not, and cannot, resolve a
|
|
@@ -76,13 +131,29 @@
|
|
|
76
131
|
* with this module is the one number both caps retire against,
|
|
77
132
|
* `DEPENDENCY_DRIFT_MAX_DELIVERIES`, rather than inventing a second one.
|
|
78
133
|
*/
|
|
134
|
+
import { createHash } from "node:crypto";
|
|
79
135
|
import * as fs from "node:fs";
|
|
136
|
+
import { bounded } from "./deadline-utils.js";
|
|
137
|
+
import { recordDegradationOnce } from "./degradation-ledger.js";
|
|
138
|
+
import { HOOK_WALL_BUDGET_MS } from "./hook-budgets.js";
|
|
80
139
|
import { normalizeEphemeralMapKey } from "./path-utils.js";
|
|
140
|
+
import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
|
|
81
141
|
import { resolveImportToFiles } from "./review-graph/import-resolvers.js";
|
|
82
142
|
import { getSharedTreeSitterClient, resolveTreeSitterLanguage, } from "./tree-sitter-shared.js";
|
|
83
143
|
import { TreeSitterSymbolExtractor } from "./tree-sitter-symbol-extractor.js";
|
|
84
144
|
/** See the module doc's "Delivery cap (#1950)" section above. */
|
|
85
145
|
export const DEPENDENCY_DRIFT_MAX_DELIVERIES = 3;
|
|
146
|
+
/**
|
|
147
|
+
* #2982: total bytes the self-drift axis may hash in ONE sweep.
|
|
148
|
+
*
|
|
149
|
+
* Each read is individually bounded by `bounded()`, but a turn with many
|
|
150
|
+
* non-LSP blockers would still issue many whole-file reads on a hook path, which
|
|
151
|
+
* is defect shape 9 (bounded on one axis, growing on another). Once a sweep has
|
|
152
|
+
* spent this budget the remaining same-size records are reported `unverifiable`
|
|
153
|
+
* and left exactly as they are, the same fail-closed direction as a read that
|
|
154
|
+
* times out.
|
|
155
|
+
*/
|
|
156
|
+
const SELF_DRIFT_HASH_BUDGET_BYTES = 8 * 1024 * 1024;
|
|
86
157
|
/**
|
|
87
158
|
* Upper bound on the number of forward imports stat-checked for a single blocker.
|
|
88
159
|
* Blocking entries are rare, but one file can import many modules; the sweep must
|
|
@@ -219,6 +290,95 @@ async function statMtimeMs(filePath) {
|
|
|
219
290
|
return undefined;
|
|
220
291
|
}
|
|
221
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Confirm a self-axis record against content, not mtime alone.
|
|
295
|
+
*
|
|
296
|
+
* mtime moving is not evidence that content changed: a `touch`, a `git
|
|
297
|
+
* checkout` restoring identical bytes, a no-op formatter pass, or a `chmod`
|
|
298
|
+
* all move it while every byte stays put. #2449 round 2 F7 settled this for
|
|
299
|
+
* `observed-mutation.ts` ("a `touch` bumps it without a byte moving, so
|
|
300
|
+
* mtime-only drift has to be confirmed against content before anything is
|
|
301
|
+
* replayed"), and the same reasoning governs here, where an unconfirmed
|
|
302
|
+
* demotion would walk a finding out of the authoritative channel.
|
|
303
|
+
*
|
|
304
|
+
* Three gates, in cost order. `size` decides most cases from the stat already
|
|
305
|
+
* taken, and it runs BEFORE the mtime gate: an out-of-band write (a formatter,
|
|
306
|
+
* a checkout) can land at-or-before the `recordedAtMs` baseline, so mtime alone
|
|
307
|
+
* is blind to the own-file drift this axis exists to catch — the size gate fires
|
|
308
|
+
* regardless of the mtime relationship. When the size matches, the mtime gate is
|
|
309
|
+
* a fast path that skips the expensive hash tier for non-LSP records (size same
|
|
310
|
+
* AND mtime never moved → the file almost certainly did not change, so we do
|
|
311
|
+
* not read and hash every unchanged file on the hook path). All-LSP records
|
|
312
|
+
* with an available hash baseline force the hash tier after the size check, so
|
|
313
|
+
* a same-size rewrite at-or-before the baseline cannot remain authoritative.
|
|
314
|
+
* When hashing is not forced and mtime moved, the hash separates a one-character
|
|
315
|
+
* edit from a `touch`, reading the bytes and comparing against the baseline
|
|
316
|
+
* `setInlineBlockerContentBaseline` attached off the dispatch path.
|
|
317
|
+
*
|
|
318
|
+
* Both tiers fail toward `"unverifiable"`, never toward `"drift"`: a bound that
|
|
319
|
+
* expires, a baseline that never landed, or a file past the per-sweep hash
|
|
320
|
+
* budget all leave the record exactly as it is. Demoting on a verdict the tier
|
|
321
|
+
* could not actually reach is the failure this function exists to prevent, and
|
|
322
|
+
* `bounded()` returning `undefined` on timeout makes that an easy mistake to
|
|
323
|
+
* write, so every bound's result is checked explicitly.
|
|
324
|
+
*/
|
|
325
|
+
async function detectSelfDrift(args) {
|
|
326
|
+
const boundOptions = (label) => ({
|
|
327
|
+
ms: HOOK_WALL_BUDGET_MS.turn_end,
|
|
328
|
+
signal: args.signal,
|
|
329
|
+
hook: "turn_end",
|
|
330
|
+
label,
|
|
331
|
+
});
|
|
332
|
+
// EVERY bound that expires yields `undefined`, and every one of those maps to
|
|
333
|
+
// `unverifiable`, never to drift. A timed-out stat compared against the
|
|
334
|
+
// recorded size would read as a size change and demote the record, which is
|
|
335
|
+
// the exact failure this tier exists to prevent.
|
|
336
|
+
const stat = await bounded(fs.promises.stat(args.filePath), boundOptions("selfDriftStat"));
|
|
337
|
+
if (stat === undefined)
|
|
338
|
+
return { verdict: "unverifiable", unverifiableReason: "stat-unavailable" };
|
|
339
|
+
// Size gate FIRST, before the mtime gate. mtime is a blind signal for the
|
|
340
|
+
// out-of-band-rewrite case: an external write (a formatter, a checkout) can
|
|
341
|
+
// land at-or-before the `recordedAtMs` baseline, so `freshnessFromMtime`
|
|
342
|
+
// reports the own file unchanged even though its bytes differ. Size is cheap
|
|
343
|
+
// content confirmation from the same stat, and it fires regardless of the
|
|
344
|
+
// mtime relationship — the all-LSP own-file case the mtime gate alone misses.
|
|
345
|
+
if (args.recordedSize === undefined)
|
|
346
|
+
return { verdict: "unverifiable", unverifiableReason: "missing-baseline" };
|
|
347
|
+
if (stat.size !== args.recordedSize)
|
|
348
|
+
return { verdict: "drift" };
|
|
349
|
+
// Size matches. Non-LSP records retain the mtime fast path. All-LSP records
|
|
350
|
+
// with a hash baseline force content confirmation because a same-size rewrite
|
|
351
|
+
// can land at-or-before the baseline.
|
|
352
|
+
const freshness = freshnessFromMtime({
|
|
353
|
+
mtimeMs: stat.mtimeMs,
|
|
354
|
+
referenceMs: args.recordedAtMs,
|
|
355
|
+
});
|
|
356
|
+
if (!args.forceContent && freshness.verdict !== "stale")
|
|
357
|
+
return { verdict: "unchanged" };
|
|
358
|
+
// Same length AND mtime moved. Only the hash can separate a one-character
|
|
359
|
+
// edit from a `touch`, and a same-length edit is the common shape, not an
|
|
360
|
+
// exotic one. Forced all-LSP confirmation reaches this tier even when mtime
|
|
361
|
+
// did not move.
|
|
362
|
+
if (args.recordedHash === undefined)
|
|
363
|
+
return { verdict: "unverifiable", unverifiableReason: "missing-baseline" };
|
|
364
|
+
// Defect shape 9: each read is individually bounded, but N blockers in one
|
|
365
|
+
// turn is an unbounded AGGREGATE read on a hook path. The per-sweep byte
|
|
366
|
+
// budget bounds the other axis; past it the record is unverifiable, not
|
|
367
|
+
// drifted.
|
|
368
|
+
if (stat.size > args.budget.bytesLeft) {
|
|
369
|
+
args.budget.exhausted = true;
|
|
370
|
+
return {
|
|
371
|
+
verdict: "unverifiable",
|
|
372
|
+
unverifiableReason: "hash-budget-exhausted",
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
const content = await bounded(fs.promises.readFile(args.filePath), boundOptions("selfDriftHash"));
|
|
376
|
+
if (content === undefined)
|
|
377
|
+
return { verdict: "unverifiable", unverifiableReason: "hash-unavailable" };
|
|
378
|
+
args.budget.bytesLeft -= content.byteLength;
|
|
379
|
+
const hash = createHash("sha256").update(content).digest("hex");
|
|
380
|
+
return { verdict: hash === args.recordedHash ? "unchanged" : "drift" };
|
|
381
|
+
}
|
|
222
382
|
/**
|
|
223
383
|
* Resolve `filePath`'s forward imports and stat each, returning the imports that
|
|
224
384
|
* exist together with their mtime. Shared by the inline-blocker sweep and the
|
|
@@ -268,14 +428,18 @@ export async function collectForwardImportMtimes(cwd, filePath, resolveForwardIm
|
|
|
268
428
|
// re-export preserves every existing importer's path.
|
|
269
429
|
export { MTIME_DRIFT_TOLERANCE_MS } from "./freshness.js";
|
|
270
430
|
import { freshnessFromMtime } from "./freshness.js";
|
|
271
|
-
async function detectDrift(cwd, filePath, recordedAtMs, resolveForwardImports, turnIndex
|
|
431
|
+
async function detectDrift(cwd, filePath, recordedAtMs, resolveForwardImports, turnIndex,
|
|
432
|
+
/** Skip the own-file mtime tier after content confirms it is unchanged. */
|
|
433
|
+
skipOwnFile = false) {
|
|
272
434
|
const drifted = [];
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
435
|
+
if (!skipOwnFile) {
|
|
436
|
+
const ownFreshness = freshnessFromMtime({
|
|
437
|
+
mtimeMs: await statMtimeMs(filePath),
|
|
438
|
+
referenceMs: recordedAtMs,
|
|
439
|
+
});
|
|
440
|
+
if (ownFreshness.verdict === "stale")
|
|
441
|
+
drifted.push(filePath);
|
|
442
|
+
}
|
|
279
443
|
const { mtimes, truncated } = await collectForwardImportMtimes(cwd, filePath, resolveForwardImports, turnIndex);
|
|
280
444
|
for (const { path: depPath, mtimeMs } of mtimes) {
|
|
281
445
|
const verdict = freshnessFromMtime({
|
|
@@ -290,19 +454,53 @@ async function detectDrift(cwd, filePath, recordedAtMs, resolveForwardImports, t
|
|
|
290
454
|
/**
|
|
291
455
|
* Whether a population entry is eligible for the drift check at all — the same
|
|
292
456
|
* three gates the main sweep loop applies (not already stale, has a timestamp
|
|
293
|
-
* baseline,
|
|
457
|
+
* baseline, HAS recorded provenance). Factored out so the #1790 review F5 dedup
|
|
294
458
|
* decision below (chain vs. separate row) asks the IDENTICAL question the main
|
|
295
459
|
* loop will ask, rather than a second hand-written approximation of it that
|
|
296
460
|
* could drift from the real gates.
|
|
461
|
+
*
|
|
462
|
+
* The third gate reads "has provenance", not "is all-`lsp`": a non-LSP record is
|
|
463
|
+
* now eligible for the self-drift axis (own file only, no import walk). Widening
|
|
464
|
+
* it here and in the loop together is the point of the shared predicate — the
|
|
465
|
+
* loop still decides WHICH axis each eligible entry gets.
|
|
297
466
|
*/
|
|
298
467
|
function isEligibleForDriftCheck(entry) {
|
|
299
468
|
if (entry.stale)
|
|
300
469
|
return false;
|
|
301
470
|
if (entry.recordedAtMs === undefined)
|
|
302
471
|
return false;
|
|
303
|
-
return
|
|
304
|
-
|
|
305
|
-
|
|
472
|
+
return entry.sources !== undefined && entry.sources.length > 0;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* All recorded sources are `"lsp"` — the record gets the full own-file PLUS
|
|
476
|
+
* forward-import walk. Anything else (tree-sitter, ast-grep, mixed, `"unknown"`)
|
|
477
|
+
* gets the self-only axis. One spelling, shared by the sweep loop and the
|
|
478
|
+
* chain decision below so the two can never disagree about which axis an entry
|
|
479
|
+
* is on.
|
|
480
|
+
*/
|
|
481
|
+
function isAllLspSourced(sources) {
|
|
482
|
+
return (sources !== undefined &&
|
|
483
|
+
sources.length > 0 &&
|
|
484
|
+
sources.every((source) => source === "lsp"));
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Whether a duplicated widget row may be CHAINED onto this inline entry's single
|
|
488
|
+
* drift check instead of being given its own population row (#1790 review F5).
|
|
489
|
+
*
|
|
490
|
+
* Strictly stronger than {@link isEligibleForDriftCheck} since the self-drift
|
|
491
|
+
* axis landed, and deliberately so. A widget row from
|
|
492
|
+
* `getWidgetBlockingFilesForSweep` is pure-LSP by construction, so the check that
|
|
493
|
+
* speaks for it must walk forward imports. An inline entry on the self-only axis
|
|
494
|
+
* never consults imports, so chaining a widget row onto it would silently drop
|
|
495
|
+
* that row's import axis — the #1790 ghost, in a new disguise: the inline entry
|
|
496
|
+
* reports `kept`, the widget row is never independently checked, and its
|
|
497
|
+
* `isBlocking` stays true for a dependency that drifted.
|
|
498
|
+
*
|
|
499
|
+
* Such an inline entry is still drift-checked on its own axis. It just cannot
|
|
500
|
+
* answer another store's question.
|
|
501
|
+
*/
|
|
502
|
+
function canSubsumeLspWidgetRow(entry) {
|
|
503
|
+
return isEligibleForDriftCheck(entry) && isAllLspSourced(entry.sources);
|
|
306
504
|
}
|
|
307
505
|
/**
|
|
308
506
|
* Freshness sweep over every blocking row the widget currently serves — the cached
|
|
@@ -347,6 +545,9 @@ export async function sweepInlineBlockerFreshness(runtime, cwd, options) {
|
|
|
347
545
|
revalidated: 0,
|
|
348
546
|
alreadyStale: 0,
|
|
349
547
|
truncatedImports: 0,
|
|
548
|
+
selfHealed: 0,
|
|
549
|
+
selfUnverifiable: 0,
|
|
550
|
+
hashBudgetExhausted: 0,
|
|
350
551
|
};
|
|
351
552
|
const resolveForwardImports = options?.resolveForwardImports ?? extractForwardImportPaths;
|
|
352
553
|
let turnIndex;
|
|
@@ -368,7 +569,11 @@ export async function sweepInlineBlockerFreshness(runtime, cwd, options) {
|
|
|
368
569
|
stale: entry.stale ?? false,
|
|
369
570
|
recordedAtMs: entry.recordedAtMs,
|
|
370
571
|
sources: entry.sources,
|
|
572
|
+
staleReason: entry.staleReason,
|
|
573
|
+
recordedSize: entry.recordedSize,
|
|
574
|
+
recordedHash: entry.recordedHash,
|
|
371
575
|
demote: () => runtime.markInlineBlockerStale(entry.filePath, "dependency-drift"),
|
|
576
|
+
setSelfDrift: (isSelfDrift) => runtime.setInlineBlockerSelfDriftStale(entry.filePath, isSelfDrift),
|
|
372
577
|
}));
|
|
373
578
|
// #1790 review F2: dedup key is `normalizeEphemeralMapKey`, not
|
|
374
579
|
// `normalizeMapKey` — the latter realpaths a live file and walks up the
|
|
@@ -390,16 +595,22 @@ export async function sweepInlineBlockerFreshness(runtime, cwd, options) {
|
|
|
390
595
|
// drop, not a merge. The main loop below short-circuits BEFORE ever
|
|
391
596
|
// calling `demote()` for an already-stale entry (the one-way
|
|
392
597
|
// dependency-drift latch — a forever-ghost once it fires once), an
|
|
393
|
-
// unstamped legacy record, or a
|
|
394
|
-
//
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
//
|
|
400
|
-
//
|
|
401
|
-
//
|
|
402
|
-
|
|
598
|
+
// unstamped legacy record, or a record with no provenance at all. Any of
|
|
599
|
+
// those swallows a chained widget demote even though the widget row is
|
|
600
|
+
// pure-LSP by construction (`getWidgetBlockingFilesForSweep` only emits
|
|
601
|
+
// LSP-sourced rows) and carries its OWN baseline. Eligibility belongs to
|
|
602
|
+
// the STORE the row came from, not the file path two stores happen to
|
|
603
|
+
// share — so only chain when the inline entry would itself reach
|
|
604
|
+
// `demote()`; otherwise give the widget row its own population entry so
|
|
605
|
+
// its own gates and its own drift check decide its own fate.
|
|
606
|
+
//
|
|
607
|
+
// Since the self-drift axis landed, "would reach `demote()`" is no longer
|
|
608
|
+
// enough: a non-LSP inline entry IS drift-checked now, but only against
|
|
609
|
+
// its own file. It never consults imports, so it cannot speak for a
|
|
610
|
+
// pure-LSP widget row whose dependency drifted. `canSubsumeLspWidgetRow`
|
|
611
|
+
// carries that stronger test; the unrelated-ast-grep-finding case in the
|
|
612
|
+
// paragraph above is now excluded by the axis, not by ineligibility.
|
|
613
|
+
if (inlineEntry && canSubsumeLspWidgetRow(inlineEntry)) {
|
|
403
614
|
// #1790 review F1: a duplicated path is counted and drift-checked ONCE
|
|
404
615
|
// (via the inline entry above), but BOTH stores must record the
|
|
405
616
|
// verdict — `markInlineBlockerStale` only ever touches
|
|
@@ -423,12 +634,31 @@ export async function sweepInlineBlockerFreshness(runtime, cwd, options) {
|
|
|
423
634
|
recordedAtMs: extra.recordedAtMs,
|
|
424
635
|
sources: ["lsp"],
|
|
425
636
|
demote: extra.demote,
|
|
637
|
+
// A widget row is pure-LSP by construction, so it takes the import
|
|
638
|
+
// axis and never reaches the self axis. No `setSelfDrift` (#2982):
|
|
639
|
+
// the widget store has no re-arming self-drift setter, and an entry
|
|
640
|
+
// without one is not eligible for that axis.
|
|
641
|
+
staleReason: undefined,
|
|
642
|
+
recordedSize: undefined,
|
|
643
|
+
recordedHash: undefined,
|
|
426
644
|
});
|
|
427
645
|
}
|
|
428
646
|
counts.total = population.length;
|
|
647
|
+
// One budget for the whole sweep (defect shape 9), consumed by the self
|
|
648
|
+
// axis's hash tier as it goes.
|
|
649
|
+
const hashBudget = {
|
|
650
|
+
bytesLeft: SELF_DRIFT_HASH_BUDGET_BYTES,
|
|
651
|
+
exhausted: false,
|
|
652
|
+
};
|
|
429
653
|
for (const entry of population) {
|
|
430
654
|
try {
|
|
431
|
-
|
|
655
|
+
// #2982: a latched demotion belongs to the gate that made it and this
|
|
656
|
+
// loop leaves it alone. Its OWN self-drift demotion is re-armable, so
|
|
657
|
+
// it is re-derived every turn instead: bytes that come back must
|
|
658
|
+
// un-demote the record, which is what lets the self axis skip the
|
|
659
|
+
// #1950 delivery cap rather than retire a security finding for good.
|
|
660
|
+
const selfDriftDemoted = entry.stale && entry.staleReason === "self-drift";
|
|
661
|
+
if (entry.stale && !selfDriftDemoted) {
|
|
432
662
|
counts.alreadyStale += 1;
|
|
433
663
|
continue;
|
|
434
664
|
}
|
|
@@ -439,17 +669,154 @@ export async function sweepInlineBlockerFreshness(runtime, cwd, options) {
|
|
|
439
669
|
counts.kept += 1;
|
|
440
670
|
continue;
|
|
441
671
|
}
|
|
442
|
-
// #1631 review F4
|
|
443
|
-
//
|
|
444
|
-
//
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
672
|
+
// #1631 review F4 kept every non-`"lsp"` record at full authority. Its
|
|
673
|
+
// stated reason is about the IMPORT axis only — "an ast-grep secret match
|
|
674
|
+
// doesn't stop being true because a file it imports was edited" — but the
|
|
675
|
+
// gate discarded the self axis with it. A record with NO provenance at all
|
|
676
|
+
// stays fail-closed here, the same test
|
|
677
|
+
// `retireInlineBlockerOnConfirmedClean` applies before it will retire.
|
|
678
|
+
const recordedSources = entry.sources !== undefined && entry.sources.length > 0
|
|
679
|
+
? entry.sources
|
|
680
|
+
: undefined;
|
|
681
|
+
if (recordedSources === undefined) {
|
|
449
682
|
counts.kept += 1;
|
|
450
683
|
continue;
|
|
451
684
|
}
|
|
452
|
-
|
|
685
|
+
// #2982 remainder: the record's OWN file is checked via the content-
|
|
686
|
+
// confirmed self-drift axis (size → hash, re-arming) for records that
|
|
687
|
+
// have a re-arming setter — shared by the non-LSP self axis below and
|
|
688
|
+
// the all-LSP axis. The mtime-only own-file check in `detectDrift` is
|
|
689
|
+
// blind to the out-of-band-rewrite case (an external write can land at-
|
|
690
|
+
// or-before the `recordedAtMs` baseline), so the content axis owns the
|
|
691
|
+
// own-file verdict when it can decide. One bounded() call site for both
|
|
692
|
+
// axes — the #2523 registry requires a single occurrence, so the two
|
|
693
|
+
// branches must not each wrap the identical call. Demotion, not deletion
|
|
694
|
+
// (#1419): a demoted entry is re-served in the advisory channel marked
|
|
695
|
+
// `[stale — re-run to confirm]` and retires through the existing #1950
|
|
696
|
+
// delivery cap.
|
|
697
|
+
const setSelfDrift = entry.setSelfDrift;
|
|
698
|
+
const isLspSourced = isAllLspSourced(recordedSources);
|
|
699
|
+
let ownVerdict;
|
|
700
|
+
if (setSelfDrift) {
|
|
701
|
+
// The outer bound too: an expired one yields `undefined`, and that
|
|
702
|
+
// means "could not decide", never "changed".
|
|
703
|
+
const hashBudgetWasExhausted = hashBudget.exhausted;
|
|
704
|
+
const selfDrift = (await bounded(detectSelfDrift({
|
|
705
|
+
filePath: entry.filePath,
|
|
706
|
+
recordedAtMs: entry.recordedAtMs,
|
|
707
|
+
recordedSize: entry.recordedSize,
|
|
708
|
+
recordedHash: entry.recordedHash,
|
|
709
|
+
forceContent: isLspSourced &&
|
|
710
|
+
entry.recordedSize !== undefined &&
|
|
711
|
+
entry.recordedHash !== undefined,
|
|
712
|
+
signal: options?.signal,
|
|
713
|
+
budget: hashBudget,
|
|
714
|
+
}), {
|
|
715
|
+
ms: HOOK_WALL_BUDGET_MS.turn_end,
|
|
716
|
+
signal: options?.signal,
|
|
717
|
+
hook: "turn_end",
|
|
718
|
+
label: "detectSelfDrift",
|
|
719
|
+
})) ?? {
|
|
720
|
+
verdict: "unverifiable",
|
|
721
|
+
unverifiableReason: "bound-expired",
|
|
722
|
+
};
|
|
723
|
+
ownVerdict = selfDrift.verdict;
|
|
724
|
+
if (ownVerdict === "unverifiable") {
|
|
725
|
+
// Decide nothing. Leave the record in whatever state it holds.
|
|
726
|
+
counts.selfUnverifiable += 1;
|
|
727
|
+
const reason = selfDrift.unverifiableReason ??
|
|
728
|
+
(hashBudgetWasExhausted || hashBudget.exhausted
|
|
729
|
+
? "hash-budget-exhausted"
|
|
730
|
+
: "hash-unavailable");
|
|
731
|
+
recordDegradationOnce({
|
|
732
|
+
kind: "self-drift-unverifiable",
|
|
733
|
+
subject: `inline-blocker:${toRunnerDisplayPath(cwd, entry.filePath)}#${reason}`,
|
|
734
|
+
reason: `the self-drift check was unverifiable: ${reason}`,
|
|
735
|
+
});
|
|
736
|
+
if (reason === "hash-budget-exhausted") {
|
|
737
|
+
recordDegradationOnce({
|
|
738
|
+
kind: "self-drift-hash-budget-exhausted",
|
|
739
|
+
subject: `inline-blocker:${toRunnerDisplayPath(cwd, entry.filePath)}`,
|
|
740
|
+
reason: "the aggregate self-drift hash budget was exhausted",
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
if (!isLspSourced) {
|
|
746
|
+
// The self axis. Content-confirmed, re-arming, and outside the
|
|
747
|
+
// #1950 delivery cap. A store with no re-arming setter is not
|
|
748
|
+
// eligible for it and stays authoritative (fail-closed).
|
|
749
|
+
if (!setSelfDrift || ownVerdict === undefined) {
|
|
750
|
+
counts.kept += 1;
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
if (ownVerdict === "unverifiable") {
|
|
754
|
+
if (selfDriftDemoted)
|
|
755
|
+
counts.alreadyStale += 1;
|
|
756
|
+
else
|
|
757
|
+
counts.kept += 1;
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
const shouldDemote = ownVerdict === "drift";
|
|
761
|
+
const transitioned = setSelfDrift(shouldDemote);
|
|
762
|
+
if (shouldDemote) {
|
|
763
|
+
if (transitioned)
|
|
764
|
+
counts.revalidated += 1;
|
|
765
|
+
else
|
|
766
|
+
counts.alreadyStale += 1;
|
|
767
|
+
}
|
|
768
|
+
else if (transitioned) {
|
|
769
|
+
counts.selfHealed += 1;
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
counts.kept += 1;
|
|
773
|
+
}
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
// The LSP axis (#1618). The own file is checked by the content axis
|
|
777
|
+
// above when a baseline was captured; without one it falls back to the
|
|
778
|
+
// mtime check (the pre-#2982 behavior, fail-open). The forward-import
|
|
779
|
+
// walk stays on the one-way dependency-drift axis.
|
|
780
|
+
let contentDecidedOwnFile = false;
|
|
781
|
+
if (setSelfDrift !== undefined &&
|
|
782
|
+
ownVerdict !== undefined &&
|
|
783
|
+
entry.recordedSize !== undefined) {
|
|
784
|
+
if (ownVerdict === "drift") {
|
|
785
|
+
const transitioned = setSelfDrift(true);
|
|
786
|
+
if (transitioned)
|
|
787
|
+
counts.revalidated += 1;
|
|
788
|
+
else
|
|
789
|
+
counts.alreadyStale += 1;
|
|
790
|
+
// Own file drifted; the record is out of the authoritative
|
|
791
|
+
// channel, so the import walk is moot this turn.
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
if (ownVerdict === "unchanged") {
|
|
795
|
+
// Content confirms the own file is unchanged: skip the mtime check
|
|
796
|
+
// below (a `touch` must not demote a still-valid record), and
|
|
797
|
+
// un-demote a record self-drifted last turn.
|
|
798
|
+
contentDecidedOwnFile = true;
|
|
799
|
+
const transitioned = setSelfDrift(false);
|
|
800
|
+
if (transitioned)
|
|
801
|
+
counts.selfHealed += 1;
|
|
802
|
+
}
|
|
803
|
+
else {
|
|
804
|
+
// "unverifiable": the content axis could not decide (a stat that
|
|
805
|
+
// failed, or a size that matched but whose hash never landed). Fall
|
|
806
|
+
// back to the mtime check below.
|
|
807
|
+
if (selfDriftDemoted) {
|
|
808
|
+
counts.alreadyStale += 1;
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
// Forward-import walk (one-way dependency-drift). Skip the own file ONLY
|
|
814
|
+
// when the content axis decided it "unchanged" — the one case where the
|
|
815
|
+
// mtime check would wrongly demote on a `touch`. A widget row (no setter)
|
|
816
|
+
// and an inline entry without a baseline never reach the content axis, so
|
|
817
|
+
// they still get the mtime own-file check (fail-open).
|
|
818
|
+
const skipOwnFile = contentDecidedOwnFile;
|
|
819
|
+
const { drifted, truncated } = await detectDrift(cwd, entry.filePath, entry.recordedAtMs, resolveForwardImports, turnIndex, skipOwnFile);
|
|
453
820
|
if (truncated)
|
|
454
821
|
counts.truncatedImports += 1;
|
|
455
822
|
if (drifted.length > 0) {
|
|
@@ -465,5 +832,6 @@ export async function sweepInlineBlockerFreshness(runtime, cwd, options) {
|
|
|
465
832
|
counts.kept += 1;
|
|
466
833
|
}
|
|
467
834
|
}
|
|
835
|
+
counts.hashBudgetExhausted = hashBudget.exhausted ? 1 : 0;
|
|
468
836
|
return counts;
|
|
469
837
|
}
|
|
@@ -41,6 +41,7 @@ import { logLatency } from "./latency-logger.js";
|
|
|
41
41
|
import { emitBounded } from "./bounded-telemetry.js";
|
|
42
42
|
import { recordDegradationOnce } from "./degradation-ledger.js";
|
|
43
43
|
import { createSingleFlight } from "./single-flight.js";
|
|
44
|
+
import { agentBehaviorClient } from "./agent-behavior-client.js";
|
|
44
45
|
/**
|
|
45
46
|
* The clients of a COMPLETED load, or `null` while none has completed.
|
|
46
47
|
*
|
|
@@ -56,6 +57,10 @@ import { createSingleFlight } from "./single-flight.js";
|
|
|
56
57
|
* by the single-flight primitive's identity-guarded release).
|
|
57
58
|
*/
|
|
58
59
|
let residentClients = null;
|
|
60
|
+
/** Behaviour history remains available before analyzer bootstrap completes. */
|
|
61
|
+
export function getAgentBehaviorClient() {
|
|
62
|
+
return agentBehaviorClient;
|
|
63
|
+
}
|
|
59
64
|
/**
|
|
60
65
|
* Set by {@link markAnalyzerBootstrapShutdown} when the PRIMARY session tears
|
|
61
66
|
* down. It refuses NEW loads only — a demand that already joined the in-flight
|
|
@@ -233,7 +238,7 @@ async function buildBootstrapClients() {
|
|
|
233
238
|
load("opengrep", async () => new (await import("./opengrep-client.js")).OpengrepClient()),
|
|
234
239
|
// The process-wide singleton, for the same reason as "go" above.
|
|
235
240
|
load("rust", async () => (await import("./rust-client.js")).rustClient),
|
|
236
|
-
load("agent-behavior", async () =>
|
|
241
|
+
load("agent-behavior", async () => (await import("./agent-behavior-client.js")).agentBehaviorClient),
|
|
237
242
|
loadList("dead-code", async () => (await import("./dead-code-client.js")).getDeadCodeClients()),
|
|
238
243
|
]);
|
|
239
244
|
if (failures.length > 0)
|