@herbertgao/pi-extensions 2026.9.6 → 2026.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -8
- package/THIRD_PARTY_NOTICES.md +0 -4
- package/node_modules/@narumitw/pi-btw/README.md +2 -0
- package/node_modules/@narumitw/pi-btw/dist/index.ts +284 -365
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
- package/node_modules/@narumitw/pi-btw/package.json +52 -53
- package/node_modules/@narumitw/pi-btw/src/bring-to-main.ts +494 -547
- package/node_modules/@narumitw/pi-btw/src/btw.ts +814 -856
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +673 -674
- package/node_modules/@narumitw/pi-btw/src/keybindings.ts +228 -270
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +309 -318
- package/node_modules/@narumitw/pi-btw/src/menu.ts +416 -454
- package/node_modules/@narumitw/pi-btw/src/settings.ts +203 -219
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +173 -201
- package/node_modules/@narumitw/pi-btw/src/text.ts +21 -23
- package/node_modules/@narumitw/pi-btw/src/transcript-markdown.ts +65 -0
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +611 -662
- package/node_modules/pi-lens/CHANGELOG.md +239 -0
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +30 -4
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +2 -0
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +26 -0
- package/node_modules/pi-lens/dist/clients/biome-client.js +1 -1
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +403 -35
- package/node_modules/pi-lens/dist/clients/bootstrap.js +6 -1
- package/node_modules/pi-lens/dist/clients/cascade-format.js +200 -4
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +10 -2
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +16 -1
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +12 -1
- package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +65 -0
- package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +28 -0
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +9 -2
- package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +222 -0
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +389 -13
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +175 -25
- package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +62 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +21 -41
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +6 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +7 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -12
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +2 -21
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/types.js +4 -1
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +1 -0
- package/node_modules/pi-lens/dist/clients/effective-config.js +1 -1
- package/node_modules/pi-lens/dist/clients/file-kinds.js +26 -1
- package/node_modules/pi-lens/dist/clients/file-role.js +21 -21
- package/node_modules/pi-lens/dist/clients/file-utils.js +156 -10
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +56 -9
- package/node_modules/pi-lens/dist/clients/format-service.js +39 -21
- package/node_modules/pi-lens/dist/clients/formatters.js +60 -13
- package/node_modules/pi-lens/dist/clients/generation-guard.js +2 -2
- package/node_modules/pi-lens/dist/clients/git-guard.js +11 -3
- package/node_modules/pi-lens/dist/clients/indent-retarget.js +78 -8
- package/node_modules/pi-lens/dist/clients/installer/index.js +333 -114
- package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +7 -0
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +20 -10
- package/node_modules/pi-lens/dist/clients/language-profile.js +23 -1
- package/node_modules/pi-lens/dist/clients/language-registry.js +5 -1
- package/node_modules/pi-lens/dist/clients/latency-logger.js +4 -1
- package/node_modules/pi-lens/dist/clients/lens-engine.js +12 -3
- package/node_modules/pi-lens/dist/clients/lens-events.js +2 -1
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +79 -0
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/client.js +25 -21
- package/node_modules/pi-lens/dist/clients/lsp/config.js +2 -2
- package/node_modules/pi-lens/dist/clients/lsp/index.js +7 -0
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +16 -4
- package/node_modules/pi-lens/dist/clients/lsp/server.js +19 -2
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +5 -3
- package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +12 -8
- package/node_modules/pi-lens/dist/clients/mcp/session.js +36 -4
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +24 -7
- package/node_modules/pi-lens/dist/clients/observed-mutation.js +40 -2
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +23 -3
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +110 -15
- package/node_modules/pi-lens/dist/clients/path-utils.js +123 -3
- package/node_modules/pi-lens/dist/clients/performance-report.js +5 -1
- package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +1 -1
- package/node_modules/pi-lens/dist/clients/pipeline.js +74 -5
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +37 -5
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +145 -18
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +4 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +59 -3
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +45 -2
- package/node_modules/pi-lens/dist/clients/read-guard.js +70 -11
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +147 -9
- package/node_modules/pi-lens/dist/clients/ruff-client.js +1 -1
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +33 -5
- package/node_modules/pi-lens/dist/clients/runtime-context.js +6 -2
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +77 -7
- package/node_modules/pi-lens/dist/clients/runtime-session.js +21 -2
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +43 -12
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +330 -85
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +147 -17
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +258 -5
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +6 -0
- package/node_modules/pi-lens/dist/clients/session-event-guard.js +50 -5
- package/node_modules/pi-lens/dist/clients/sg-runner.js +23 -11
- package/node_modules/pi-lens/dist/clients/sgconfig.js +79 -2
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +6 -41
- package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +43 -0
- package/node_modules/pi-lens/dist/clients/tool-agreement.js +423 -0
- package/node_modules/pi-lens/dist/clients/tool-config.js +50 -0
- package/node_modules/pi-lens/dist/clients/tool-cwd.js +47 -76
- package/node_modules/pi-lens/dist/clients/tool-policy.js +163 -7
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +120 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +127 -189
- package/node_modules/pi-lens/dist/clients/widget-state.js +98 -0
- package/node_modules/pi-lens/dist/clients/word-index.js +5 -2
- package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +7 -0
- package/node_modules/pi-lens/dist/index.js +27416 -24153
- package/node_modules/pi-lens/dist/mcp/server.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +7 -1
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +168 -53
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +244 -72
- package/node_modules/pi-lens/docs/audit1.md +5 -5
- package/node_modules/pi-lens/docs/dependencies.md +11 -0
- package/node_modules/pi-lens/docs/dispositions.md +52 -0
- package/node_modules/pi-lens/docs/environment-variables.md +2 -2
- package/node_modules/pi-lens/docs/features.md +4 -4
- package/node_modules/pi-lens/docs/globalconfig.md +3 -1
- package/node_modules/pi-lens/docs/language-coverage.md +1 -0
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +53 -94
- package/node_modules/pi-lens/docs/pi-lens-investigator.md +43 -38
- package/node_modules/pi-lens/docs/pi-lens-retro.md +97 -0
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +53 -66
- package/node_modules/pi-lens/docs/release-qa-baseline.md +22 -0
- package/node_modules/pi-lens/docs/settings.md +7 -4
- package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
- package/node_modules/pi-lens/docs/usage.md +2 -0
- package/node_modules/pi-lens/package.json +1 -1
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +24 -12
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +40 -0
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +15 -0
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +7 -0
- package/package.json +5 -11
- package/node_modules/@luxusai/pi-hindsight/CHANGELOG.md +0 -376
- package/node_modules/@luxusai/pi-hindsight/README.md +0 -92
- package/node_modules/@luxusai/pi-hindsight/docs/adr/001-memory-lifecycle-and-scope.md +0 -56
- package/node_modules/@luxusai/pi-hindsight/docs/adr/002-explicit-routing-strategy-seam.md +0 -169
- package/node_modules/@luxusai/pi-hindsight/docs/adr/003-tui-memory-mode-vocabulary.md +0 -107
- package/node_modules/@luxusai/pi-hindsight/docs/adr/004-lifeos-dual-bank-design.md +0 -89
- package/node_modules/@luxusai/pi-hindsight/docs/adr/005-domain-banks-and-agent-first-surface.md +0 -189
- package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark.webp +0 -0
- package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark@2x.webp +0 -0
- package/node_modules/@luxusai/pi-hindsight/docs/coding-memory-evaluation.md +0 -54
- package/node_modules/@luxusai/pi-hindsight/docs/compatibility.md +0 -75
- package/node_modules/@luxusai/pi-hindsight/docs/hindsight-core-functions.md +0 -300
- package/node_modules/@luxusai/pi-hindsight/docs/mission-and-mental-model-quality.md +0 -158
- package/node_modules/@luxusai/pi-hindsight/docs/next-opt-out-design.md +0 -164
- package/node_modules/@luxusai/pi-hindsight/docs/risky-memory-modes.md +0 -139
- package/node_modules/@luxusai/pi-hindsight/docs/starter-mental-model-suggestions.md +0 -74
- package/node_modules/@luxusai/pi-hindsight/docs/surface-reference.md +0 -261
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-operations.ts +0 -151
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-selection.ts +0 -18
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-settings-presenter.ts +0 -46
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-templates.ts +0 -383
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/banking.ts +0 -240
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/knowledge-page-seed.ts +0 -176
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/retain-strategies.ts +0 -178
- package/node_modules/@luxusai/pi-hindsight/extensions/client/client-retry.ts +0 -41
- package/node_modules/@luxusai/pi-hindsight/extensions/client/client.ts +0 -386
- package/node_modules/@luxusai/pi-hindsight/extensions/client/fetch-compat.ts +0 -39
- package/node_modules/@luxusai/pi-hindsight/extensions/client/timeout.ts +0 -34
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-defaults.ts +0 -140
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-model.ts +0 -61
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-registry.ts +0 -925
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-field-paths.ts +0 -247
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-normalize.ts +0 -547
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-writer.ts +0 -480
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config.ts +0 -182
- package/node_modules/@luxusai/pi-hindsight/extensions/config/setup-gate.ts +0 -86
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-execute.ts +0 -1010
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-parse.ts +0 -175
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-plan.ts +0 -425
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-presentation.ts +0 -210
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-sessions.ts +0 -817
- package/node_modules/@luxusai/pi-hindsight/extensions/index.ts +0 -25
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/git-seed.ts +0 -319
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-recall.ts +0 -196
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-retain.ts +0 -189
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-runtime.ts +0 -65
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle.ts +0 -260
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/mental-models.ts +0 -244
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/observation-scopes.ts +0 -59
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-cleanup.ts +0 -82
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-visibility.ts +0 -45
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall.ts +0 -331
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-cursor.ts +0 -354
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-job-builder.ts +0 -66
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-receipts.ts +0 -132
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain.ts +0 -228
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-bank-template-operations.ts +0 -74
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-config-operations.ts +0 -16
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-control-operations.ts +0 -648
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-diagnostics-operations.ts +0 -105
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-identity.ts +0 -68
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-service.ts +0 -71
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-types.ts +0 -13
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-recall-operations.ts +0 -162
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-retain-operations.ts +0 -131
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-scope.ts +0 -104
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-session-operations.ts +0 -37
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/operation-catalog.ts +0 -893
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/reflect-presenter.ts +0 -35
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/scope-migrate.ts +0 -177
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/tools.ts +0 -7
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/flush-presenter.ts +0 -21
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/jsonl-queue-store.ts +0 -112
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-delivery.ts +0 -111
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-lock.ts +0 -214
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-operations.ts +0 -70
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue.ts +0 -392
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/bank-template-presentation.ts +0 -45
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/commands.ts +0 -9
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/guided-setup.ts +0 -860
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/prefill-input.ts +0 -105
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-flow.ts +0 -189
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-server-probe.ts +0 -299
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-actions.ts +0 -201
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-facts.ts +0 -47
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-render.ts +0 -239
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-types.ts +0 -50
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui.ts +0 -259
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/tool-presenters.ts +0 -77
- package/node_modules/@luxusai/pi-hindsight/extensions/types.ts +0 -534
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/diagnostics.ts +0 -319
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/messages.ts +0 -325
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/sanitize.ts +0 -42
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-memory-meta.ts +0 -244
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-operations.ts +0 -56
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/session.ts +0 -50
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-fields.ts +0 -167
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-health.ts +0 -207
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/status.ts +0 -120
- package/node_modules/@luxusai/pi-hindsight/extensions/version.ts +0 -8
- package/node_modules/@luxusai/pi-hindsight/package.json +0 -118
- package/node_modules/@luxusai/pi-hindsight/skills/hindsight-memory-doctor/SKILL.md +0 -75
|
@@ -2,8 +2,9 @@ import * as nodeCrypto from "node:crypto";
|
|
|
2
2
|
import * as nodeFs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { noteAuthoritativeContentAttachment } from "./agent-nudge.js";
|
|
5
|
-
import { captureFileStats,
|
|
5
|
+
import { captureFileStats, diffFileContent, getOpaqueBaselineStore, recoverOpaqueChangesViaGit, } from "./opaque-mutation-scan.js";
|
|
6
6
|
import { normalizeMapKey } from "./path-utils.js";
|
|
7
|
+
import { detectFileKind } from "./file-kinds.js";
|
|
7
8
|
import { extractReadPathsFromCommand, extractDeletedPathsFromCommand, extractGrepSearchReadsFromOutput, extractWrittenPathsFromCommand, tokenizeShellCommand, } from "./bash-file-access.js";
|
|
8
9
|
import { registerSearchReads, } from "./search-read-registration.js";
|
|
9
10
|
import { createFileTime } from "./file-time.js";
|
|
@@ -21,6 +22,7 @@ import { replayThroughMutationBridge, storedLineHashesFor, } from "./observed-mu
|
|
|
21
22
|
import { getAmbientAbortSignal } from "./safe-spawn.js";
|
|
22
23
|
import { resolveToolCallCorrelationId } from "./tool-event.js";
|
|
23
24
|
import { boundedIndexesForCount, createReadGuardEditBatchSummary, getReadGuardCorrelationId, logReadGuardEvent, } from "./read-guard-logger.js";
|
|
25
|
+
import { countFileLines } from "./read-guard-tool-lines.js";
|
|
24
26
|
import { notifyExternalFileChange, resyncGitChangedFiles, } from "./lsp/index.js";
|
|
25
27
|
import { runPipeline } from "./pipeline.js";
|
|
26
28
|
import { renderPostAutofixNotice, } from "./post-autofix-notice.js";
|
|
@@ -28,7 +30,14 @@ import { syncGitGuardRecord } from "./git-guard.js";
|
|
|
28
30
|
import { scheduleWordIndexPersist } from "./word-index.js";
|
|
29
31
|
import { RUNTIME_CONFIG } from "./runtime-config.js";
|
|
30
32
|
import { getActiveSessionId } from "./session-lifecycle.js";
|
|
33
|
+
import { requestBootstrapClients } from "./bootstrap.js";
|
|
34
|
+
import { bounded } from "./deadline-utils.js";
|
|
35
|
+
import { HOOK_WALL_BUDGET_MS } from "./hook-budgets.js";
|
|
36
|
+
import { incrementDegradationCount, recordDegradationOnce, } from "./degradation-ledger.js";
|
|
31
37
|
const AUTHORITATIVE_CONTENT_MAX_BYTES = RUNTIME_CONFIG.pipeline.lspMaxFileBytes;
|
|
38
|
+
// Keep same-turn analysis below the observed-directory capture bound so one
|
|
39
|
+
// opaque codemod cannot monopolize the hook or analyzer fleet.
|
|
40
|
+
const OBSERVED_DISPATCH_MAX_PATHS = 32;
|
|
32
41
|
/**
|
|
33
42
|
* Git subcommands that import ANOTHER commit's content into the index. The
|
|
34
43
|
* whole family, with a verdict for each (#2060):
|
|
@@ -93,6 +102,29 @@ export function isFailedGitIntegrationCommand(command, isError) {
|
|
|
93
102
|
return false;
|
|
94
103
|
});
|
|
95
104
|
}
|
|
105
|
+
function ensureToolResultClients(deps) {
|
|
106
|
+
if (deps.biomeClient && deps.ruffClient && deps.metricsClient)
|
|
107
|
+
return true;
|
|
108
|
+
const request = {
|
|
109
|
+
reason: "tool-result-analysis",
|
|
110
|
+
hook: "tool_result_edit",
|
|
111
|
+
timeoutMs: HOOK_WALL_BUDGET_MS.tool_result_edit,
|
|
112
|
+
...(deps.signal === undefined ? {} : { signal: deps.signal }),
|
|
113
|
+
};
|
|
114
|
+
return bounded(requestBootstrapClients(request), {
|
|
115
|
+
ms: HOOK_WALL_BUDGET_MS.tool_result_edit,
|
|
116
|
+
signal: deps.signal,
|
|
117
|
+
hook: "tool_result_edit",
|
|
118
|
+
label: "tool-result-bootstrap-demand",
|
|
119
|
+
}).then((clients) => {
|
|
120
|
+
if (!clients)
|
|
121
|
+
return false;
|
|
122
|
+
deps.biomeClient = clients.biomeClient;
|
|
123
|
+
deps.ruffClient = clients.ruffClient;
|
|
124
|
+
deps.metricsClient = clients.metricsClient;
|
|
125
|
+
return true;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
96
128
|
function parseDiffRanges(diff) {
|
|
97
129
|
const changedLines = [];
|
|
98
130
|
for (const line of diff.split("\n")) {
|
|
@@ -144,13 +176,11 @@ export function clearLastAnalyzedStateCache() {
|
|
|
144
176
|
* with one owner (#2464 review round 3, F1) rather than two open-coded blocks
|
|
145
177
|
* ~80 lines apart whose invariants only line up if a reader checks both.
|
|
146
178
|
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* identity guard in the release has no reachable production trigger left, so
|
|
151
|
-
* the only honest way to prove it is to drive the seam directly.
|
|
179
|
+
* Kept private because both dispatch call sites now use the shared claim before
|
|
180
|
+
* registration; publishing this seam without the claim obligation would make
|
|
181
|
+
* the identity guard's precondition easy to violate again.
|
|
152
182
|
*/
|
|
153
|
-
|
|
183
|
+
function registerInFlightPipeline(filePath, stateHash, pipeline) {
|
|
154
184
|
let filePipelines = inFlightPipelines.get(filePath);
|
|
155
185
|
if (!filePipelines) {
|
|
156
186
|
filePipelines = new Map();
|
|
@@ -172,7 +202,7 @@ export function registerInFlightPipeline(filePath, stateHash, pipeline) {
|
|
|
172
202
|
* deduping and its `participantIds`/`participantTotal` under-count. Delete only
|
|
173
203
|
* when the outer map still points at the very map this registration went into.
|
|
174
204
|
*/
|
|
175
|
-
|
|
205
|
+
function releaseInFlightPipeline(filePath, stateHash, registered) {
|
|
176
206
|
registered.delete(stateHash);
|
|
177
207
|
if (registered.size === 0 && inFlightPipelines.get(filePath) === registered) {
|
|
178
208
|
inFlightPipelines.delete(filePath);
|
|
@@ -208,7 +238,7 @@ export function releaseInFlightPipeline(filePath, stateHash, registered) {
|
|
|
208
238
|
* already analysed this turn — the duplicate-recording inversion #2464 exists
|
|
209
239
|
* to remove.
|
|
210
240
|
*/
|
|
211
|
-
|
|
241
|
+
function claimPipelineDispatch(args) {
|
|
212
242
|
const { filePath, stateHash, turnIndex, participantId, dbg } = args;
|
|
213
243
|
// Deduplicate concurrent calls for the same final file state (pi can fire one
|
|
214
244
|
// tool_result per edit hunk). Do not dedupe by file alone: a distinct later
|
|
@@ -454,9 +484,10 @@ function refreshCachedExports(runtime, filePath) {
|
|
|
454
484
|
* pipeline AND record" block could not express.
|
|
455
485
|
*/
|
|
456
486
|
async function dispatchPipelineAnalysis(args) {
|
|
457
|
-
const { deps, runtime, filePath, dispatchCwd, turnStateCwd, autofixMode, modifiedRanges, writeIndex, initialStateHash, readGuardCorrelationId, requestedEditIndexes, requestedEditTotal, isPartialApplyResult, toolResultStart, nativeAppliedPairs, } = args;
|
|
487
|
+
const { deps, runtime, filePath, dispatchCwd, turnStateCwd, autofixMode, modifiedRanges, writeIndex, initialStateHash, readGuardCorrelationId, requestedEditIndexes, requestedEditTotal, isPartialApplyResult, toolResultStart, nativeAppliedPairs, allowReadGuardWrites, } = args;
|
|
458
488
|
const { event, getFlag, getFlagSource, dbg, biomeClient, ruffClient, metricsClient, resetLSPService, } = deps;
|
|
459
489
|
const pipelinePromise = runPipeline({
|
|
490
|
+
signal: deps.signal,
|
|
460
491
|
filePath,
|
|
461
492
|
cwd: dispatchCwd,
|
|
462
493
|
projectRoot: turnStateCwd,
|
|
@@ -497,9 +528,9 @@ async function dispatchPipelineAnalysis(args) {
|
|
|
497
528
|
scheduleWordIndexPersist(dispatchCwd, index, dbg);
|
|
498
529
|
},
|
|
499
530
|
}, {
|
|
500
|
-
biomeClient,
|
|
501
|
-
ruffClient,
|
|
502
|
-
metricsClient,
|
|
531
|
+
biomeClient: biomeClient,
|
|
532
|
+
ruffClient: ruffClient,
|
|
533
|
+
metricsClient: metricsClient,
|
|
503
534
|
getFormatService,
|
|
504
535
|
fixedThisTurn: runtime.fixedThisTurn,
|
|
505
536
|
});
|
|
@@ -632,11 +663,20 @@ async function dispatchPipelineAnalysis(args) {
|
|
|
632
663
|
// ladder (the #2402 after-write hash was never stamped), and a duplicate
|
|
633
664
|
// tool_result for the same bytes analysed the file a second time (the
|
|
634
665
|
// already-analysed latch was never set).
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
666
|
+
// The latch identifies the bytes this pipeline actually analysed. A pipeline
|
|
667
|
+
// that reports no write analysed its input state, even if another writer
|
|
668
|
+
// changed disk while the await was parked. A pipeline-reported write selects
|
|
669
|
+
// only the identity captured by runPipeline before analysis awaits (#2499).
|
|
670
|
+
const pipelineOwnedWriteHash = result.fileModified
|
|
671
|
+
? result.postWriteStateHash
|
|
672
|
+
: undefined;
|
|
673
|
+
const finalStateHash = pipelineOwnedWriteHash ?? initialStateHash;
|
|
674
|
+
if (!result.fileModified || pipelineOwnedWriteHash !== undefined) {
|
|
675
|
+
lastAnalyzedStateByFile.set(filePath, {
|
|
676
|
+
turnIndex: runtime.turnIndex,
|
|
677
|
+
stateHash: finalStateHash,
|
|
678
|
+
});
|
|
679
|
+
}
|
|
640
680
|
// #2402: pi-lens' own immediate format/autofix may have rewritten the file
|
|
641
681
|
// after the native edit was recorded by the caller. Stamp the post-pipeline
|
|
642
682
|
// state so an identical retry against the formatted bytes is still
|
|
@@ -645,7 +685,7 @@ async function dispatchPipelineAnalysis(args) {
|
|
|
645
685
|
// `initialStateHash` IS the post-write hash at both call sites — the
|
|
646
686
|
// classified chain passes `postWriteStateHash` verbatim, the observed path
|
|
647
687
|
// passes the same pre-settle digest.
|
|
648
|
-
if (
|
|
688
|
+
if (pipelineOwnedWriteHash !== undefined) {
|
|
649
689
|
for (const pair of nativeAppliedPairs) {
|
|
650
690
|
runtime.partialApplyRecords.noteAfterWriteHash(filePath, pair.oldText, pair.newText, finalStateHash);
|
|
651
691
|
}
|
|
@@ -653,7 +693,7 @@ async function dispatchPipelineAnalysis(args) {
|
|
|
653
693
|
// The model's write/edit and pi-lens' own immediate format/autofix are now
|
|
654
694
|
// reflected on disk. Refresh read-guard staleness stamps so a follow-up edit
|
|
655
695
|
// is judged by read-range coverage, not by our own previous write.
|
|
656
|
-
if (!getFlag("no-read-guard")) {
|
|
696
|
+
if (!getFlag("no-read-guard") && allowReadGuardWrites) {
|
|
657
697
|
const changedForReadGuard = new Set([
|
|
658
698
|
path.resolve(filePath),
|
|
659
699
|
...(result.changedFiles ?? []).map((changedFile) => path.resolve(changedFile)),
|
|
@@ -670,6 +710,7 @@ export async function handleToolResult(deps) {
|
|
|
670
710
|
const { event, getFlag, dbg, runtime, cacheManager, agentBehaviorRecord, formatBehaviorWarnings, } = deps;
|
|
671
711
|
const rawFilePath = event.input.path;
|
|
672
712
|
const workspaceRoot = runtime.projectRoot || process.cwd();
|
|
713
|
+
let bashAuthorshipConfirmed = deps._readGuardAuthorship ?? event.toolName !== "bash";
|
|
673
714
|
// #1642: a gitignored worktree edit got re-attributed onto a
|
|
674
715
|
// same-relative-path file in the parent checkout because this handler
|
|
675
716
|
// used to always resolve a relative path against `workspaceRoot`
|
|
@@ -791,10 +832,16 @@ export async function handleToolResult(deps) {
|
|
|
791
832
|
// THIS set, not raw recognized - otherwise a redirect target dropped
|
|
792
833
|
// by the isError filter would be subtracted from recovery AND
|
|
793
834
|
// excluded here, attributed nowhere.
|
|
794
|
-
|
|
835
|
+
let recognizedWritten = event.isError !== true
|
|
795
836
|
? recognized.filter((wp) => !isExternalOrVendorFile(wp, workspaceRoot) &&
|
|
796
837
|
!isPathIgnoredByProject(wp, workspaceRoot, false))
|
|
797
838
|
: [];
|
|
839
|
+
// Fence the mtime-based session-authored fallback before any async
|
|
840
|
+
// observation. Only later content evidence may clear this fence.
|
|
841
|
+
if (!getFlag("no-read-guard")) {
|
|
842
|
+
for (const recognizedPath of recognizedWritten)
|
|
843
|
+
deps.readGuard?.recordUnchanged?.(recognizedPath);
|
|
844
|
+
}
|
|
798
845
|
// #2000 phase 2: when the extractor recognizes NOTHING, the command is
|
|
799
846
|
// opaque-candidate — recover its actual changed set by diffing the pre
|
|
800
847
|
// snapshot taken at tool_call. Partial writes that landed before a
|
|
@@ -802,6 +849,8 @@ export async function handleToolResult(deps) {
|
|
|
802
849
|
// them) — a deliberate divergence from the isError filter above, which
|
|
803
850
|
// exists for restore semantics where attribution would lie.
|
|
804
851
|
let opaquePaths = [];
|
|
852
|
+
let observedChangedKeys;
|
|
853
|
+
let recognizedAuthored = [];
|
|
805
854
|
// Recovery runs for EVERY bash command with a pending baseline - not
|
|
806
855
|
// only recognized-empty ones. A mixed command (`python x.py > out.ts`
|
|
807
856
|
// plus script-internal writes) previously skipped observation entirely
|
|
@@ -829,6 +878,7 @@ export async function handleToolResult(deps) {
|
|
|
829
878
|
if (recovery.verdict === "recovered") {
|
|
830
879
|
opaquePaths = recovery.paths.filter((p) => !isExternalOrVendorFile(p, scanRoot) &&
|
|
831
880
|
!isPathIgnoredByProject(p, scanRoot, false));
|
|
881
|
+
observedChangedKeys = new Set(opaquePaths);
|
|
832
882
|
}
|
|
833
883
|
else if (recovery.verdict === "unknown") {
|
|
834
884
|
// #2060: deliberately WIDER than the old `recognized.length > 0`
|
|
@@ -837,6 +887,12 @@ export async function handleToolResult(deps) {
|
|
|
837
887
|
// nothing at all.
|
|
838
888
|
unknownReason = recovery.unknownReason;
|
|
839
889
|
}
|
|
890
|
+
else {
|
|
891
|
+
// A clean git verdict is complete evidence that no path
|
|
892
|
+
// changed; it is not missing evidence. Keep recognized writes
|
|
893
|
+
// out of authorship because git found no changed bytes.
|
|
894
|
+
observedChangedKeys = new Set();
|
|
895
|
+
}
|
|
840
896
|
// #2060: both counts are bounded (one record per tool_result, no
|
|
841
897
|
// per-path logging) and exist because filtering is invisible in
|
|
842
898
|
// production otherwise - the dropped paths simply never appear.
|
|
@@ -862,9 +918,11 @@ export async function handleToolResult(deps) {
|
|
|
862
918
|
else if (pending.stats) {
|
|
863
919
|
const outcome = await captureFileStats(scanRoot, {
|
|
864
920
|
withHashes: true,
|
|
921
|
+
...deps._opaqueCaptureOptions,
|
|
865
922
|
});
|
|
866
923
|
if (outcome.snapshot && !outcome.unknownReason) {
|
|
867
|
-
opaquePaths =
|
|
924
|
+
opaquePaths = diffFileContent(pending.stats, outcome.snapshot);
|
|
925
|
+
observedChangedKeys = new Set(opaquePaths);
|
|
868
926
|
}
|
|
869
927
|
else {
|
|
870
928
|
unknownReason =
|
|
@@ -880,6 +938,16 @@ export async function handleToolResult(deps) {
|
|
|
880
938
|
const survivingKeys = new Set(recognizedWritten.map((p) => normalizeMapKey(path.resolve(p))));
|
|
881
939
|
opaquePaths = opaquePaths.filter((p) => !survivingKeys.has(p));
|
|
882
940
|
}
|
|
941
|
+
if (observedChangedKeys) {
|
|
942
|
+
recognizedAuthored = recognizedWritten.filter((file) => observedChangedKeys.has(normalizeMapKey(path.resolve(file))));
|
|
943
|
+
if (recognizedAuthored.length === 0)
|
|
944
|
+
recognizedWritten = [];
|
|
945
|
+
}
|
|
946
|
+
else if (unknownReason) {
|
|
947
|
+
recognizedAuthored = [];
|
|
948
|
+
// The bounded opaque_mutation_coverage_unknown record below remains
|
|
949
|
+
// the production proof that authorship evidence was unavailable.
|
|
950
|
+
}
|
|
883
951
|
if (unknownReason) {
|
|
884
952
|
logLatency({
|
|
885
953
|
type: "phase",
|
|
@@ -906,9 +974,15 @@ export async function handleToolResult(deps) {
|
|
|
906
974
|
// set must hold those exact strings - no re-resolution.
|
|
907
975
|
const opaqueSet = new Set(opaquePaths);
|
|
908
976
|
const written = [...recognizedWritten, ...opaquePaths];
|
|
977
|
+
const recognizedAuthoredSet = new Set(recognizedAuthored);
|
|
978
|
+
bashAuthorshipConfirmed =
|
|
979
|
+
recognizedAuthored.length > 0 || opaquePaths.length > 0;
|
|
909
980
|
for (const wp of written) {
|
|
910
|
-
if (!getFlag("no-read-guard")
|
|
981
|
+
if (!getFlag("no-read-guard") &&
|
|
982
|
+
(opaqueSet.has(wp) || recognizedAuthoredSet.has(wp)))
|
|
911
983
|
deps.readGuard?.recordWritten(wp);
|
|
984
|
+
else if (!getFlag("no-read-guard") && recognizedWritten.includes(wp))
|
|
985
|
+
deps.readGuard?.recordUnchanged?.(wp);
|
|
912
986
|
const receipt = runtime
|
|
913
987
|
.recordMutationToolReceipt;
|
|
914
988
|
const autofixMode = receipt
|
|
@@ -939,6 +1013,7 @@ export async function handleToolResult(deps) {
|
|
|
939
1013
|
_autofixMode: autofixMode,
|
|
940
1014
|
_attachmentBudget: syntheticAttachmentBudget,
|
|
941
1015
|
_mutationSourceOverride: isOpaque ? "opaque-script" : undefined,
|
|
1016
|
+
_readGuardAuthorship: opaqueSet.has(wp) || recognizedAuthoredSet.has(wp),
|
|
942
1017
|
});
|
|
943
1018
|
if (syntheticResult) {
|
|
944
1019
|
// #1590: forward verbatim. The synthetic call already charged the
|
|
@@ -949,7 +1024,57 @@ export async function handleToolResult(deps) {
|
|
|
949
1024
|
}
|
|
950
1025
|
}
|
|
951
1026
|
if (event.isError !== true && !getFlag("no-read-guard")) {
|
|
952
|
-
|
|
1027
|
+
const truncation = event.details?.truncation;
|
|
1028
|
+
const parsedSpans = extractReadPathsFromCommand(command, workspaceRoot);
|
|
1029
|
+
let spans = parsedSpans;
|
|
1030
|
+
if (truncation?.truncated === true &&
|
|
1031
|
+
typeof truncation.outputLines === "number") {
|
|
1032
|
+
if (parsedSpans.length === 1) {
|
|
1033
|
+
const span = parsedSpans[0];
|
|
1034
|
+
if (span) {
|
|
1035
|
+
// countFileLines intentionally preserves the split-based guard
|
|
1036
|
+
// convention, where a trailing newline contributes an empty final
|
|
1037
|
+
// element. The host's output line count does not include that
|
|
1038
|
+
// element, so remove it from the span basis before taking the tail.
|
|
1039
|
+
const hasTrailingNewline = nodeFs
|
|
1040
|
+
.readFileSync(span.filePath, "utf8")
|
|
1041
|
+
.endsWith("\n");
|
|
1042
|
+
const spanLineCount = hasTrailingNewline
|
|
1043
|
+
? Math.max(1, span.limit - 1)
|
|
1044
|
+
: span.limit;
|
|
1045
|
+
const shown = Math.min(truncation.outputLines, spanLineCount);
|
|
1046
|
+
spans =
|
|
1047
|
+
shown > 0
|
|
1048
|
+
? [
|
|
1049
|
+
{
|
|
1050
|
+
...span,
|
|
1051
|
+
offset: span.offset + spanLineCount - shown,
|
|
1052
|
+
limit: shown,
|
|
1053
|
+
},
|
|
1054
|
+
]
|
|
1055
|
+
: [];
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
else if (parsedSpans.length > 1) {
|
|
1059
|
+
recordDegradationOnce({
|
|
1060
|
+
kind: "bash-view-clipped",
|
|
1061
|
+
subject: "multi-span",
|
|
1062
|
+
reason: "truncated bash view contained multiple spans",
|
|
1063
|
+
});
|
|
1064
|
+
spans = [];
|
|
1065
|
+
}
|
|
1066
|
+
if (parsedSpans.length === 1 &&
|
|
1067
|
+
spans.length === 1 &&
|
|
1068
|
+
(spans[0]?.offset !== parsedSpans[0]?.offset ||
|
|
1069
|
+
spans[0]?.limit !== parsedSpans[0]?.limit)) {
|
|
1070
|
+
recordDegradationOnce({
|
|
1071
|
+
kind: "bash-view-clipped",
|
|
1072
|
+
subject: parsedSpans[0]?.filePath ?? "unknown",
|
|
1073
|
+
reason: "truncated bash view narrowed a single span",
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
for (const span of spans) {
|
|
953
1078
|
if (isExternalOrVendorFile(span.filePath, workspaceRoot))
|
|
954
1079
|
continue;
|
|
955
1080
|
if (isPathIgnoredByProject(span.filePath, workspaceRoot, false))
|
|
@@ -1029,6 +1154,74 @@ export async function handleToolResult(deps) {
|
|
|
1029
1154
|
});
|
|
1030
1155
|
}
|
|
1031
1156
|
}
|
|
1157
|
+
// Native read results are the authoritative read boundary. The tool_call
|
|
1158
|
+
// input can request past EOF, and the host can cap bytes or lines before the
|
|
1159
|
+
// result reaches the model. Register only the delivered range (#2802).
|
|
1160
|
+
if (deps.readGuard &&
|
|
1161
|
+
event.toolName === "read" &&
|
|
1162
|
+
event.isError !== true &&
|
|
1163
|
+
filePath &&
|
|
1164
|
+
!getFlag("no-read-guard") &&
|
|
1165
|
+
!isExternalOrVendorFile(filePath, workspaceRoot)) {
|
|
1166
|
+
const deliveredFilePath = attribution?.resolvedPath && nodeFs.existsSync(attribution.resolvedPath)
|
|
1167
|
+
? attribution.resolvedPath
|
|
1168
|
+
: filePath;
|
|
1169
|
+
if (nodeFs.existsSync(deliveredFilePath)) {
|
|
1170
|
+
const nativeReadToolCallId = resolveToolCallCorrelationId(event);
|
|
1171
|
+
const input = event.input;
|
|
1172
|
+
const requestedOffset = input.offset ?? 1;
|
|
1173
|
+
const requestedLimit = input.limit;
|
|
1174
|
+
const truncation = event.details?.truncation;
|
|
1175
|
+
const available = Math.max(0, countFileLines(deliveredFilePath) - requestedOffset + 1);
|
|
1176
|
+
const deliveredLimit = Math.min(available, truncation?.outputLines ?? requestedLimit ?? available);
|
|
1177
|
+
if (deliveredLimit > 0) {
|
|
1178
|
+
if (truncation?.truncated === true && deliveredLimit < available) {
|
|
1179
|
+
recordDegradationOnce({
|
|
1180
|
+
kind: "native-read-clipped",
|
|
1181
|
+
subject: deliveredFilePath,
|
|
1182
|
+
reason: "native read result was clipped before delivery",
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
logReadGuardEvent({
|
|
1186
|
+
event: "read_pattern",
|
|
1187
|
+
sessionId: runtime.telemetrySessionId,
|
|
1188
|
+
filePath: deliveredFilePath,
|
|
1189
|
+
requestedOffset,
|
|
1190
|
+
requestedLimit: requestedLimit ?? deliveredLimit,
|
|
1191
|
+
effectiveOffset: requestedOffset,
|
|
1192
|
+
effectiveLimit: deliveredLimit,
|
|
1193
|
+
metadata: {
|
|
1194
|
+
totalLines: countFileLines(deliveredFilePath),
|
|
1195
|
+
isPartial: deliveredLimit < available,
|
|
1196
|
+
fileKind: detectFileKind(deliveredFilePath) ?? "unknown",
|
|
1197
|
+
fractionRead: available > 0
|
|
1198
|
+
? Math.round((deliveredLimit / available) * 100) / 100
|
|
1199
|
+
: 1,
|
|
1200
|
+
expandedByTs: false,
|
|
1201
|
+
},
|
|
1202
|
+
});
|
|
1203
|
+
const deliveredRecord = {
|
|
1204
|
+
filePath: deliveredFilePath,
|
|
1205
|
+
requestedOffset,
|
|
1206
|
+
requestedLimit: requestedLimit ?? deliveredLimit,
|
|
1207
|
+
effectiveOffset: requestedOffset,
|
|
1208
|
+
effectiveLimit: deliveredLimit,
|
|
1209
|
+
expandedByLsp: false,
|
|
1210
|
+
turnIndex: runtime.turnIndex,
|
|
1211
|
+
writeIndex: runtime.peekWriteIndex(),
|
|
1212
|
+
timestamp: Date.now(),
|
|
1213
|
+
};
|
|
1214
|
+
if (nativeReadToolCallId) {
|
|
1215
|
+
deps.readGuard.recordRead(deliveredRecord, {
|
|
1216
|
+
supersedes: { toolCallId: nativeReadToolCallId },
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
else {
|
|
1220
|
+
deps.readGuard.recordRead(deliveredRecord);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1032
1225
|
// #2423: THE inbound gate. Everything below this line is the mutation
|
|
1033
1226
|
// bookkeeping chain — read-guard staleness stamp, turn state, change-log
|
|
1034
1227
|
// receipt, deferred autofix and format. Before the seam this compared
|
|
@@ -1198,14 +1391,36 @@ export async function handleToolResult(deps) {
|
|
|
1198
1391
|
// "edit", so the un-receipted default is "deferred" either way.
|
|
1199
1392
|
const observedAutofixMode = receiptOutcome?.autofixMode ??
|
|
1200
1393
|
(observedKind === "edit" ? "deferred" : "immediate");
|
|
1201
|
-
// Analyse the
|
|
1202
|
-
//
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1394
|
+
// Analyse the files the observation actually RECORDED, not merely the
|
|
1395
|
+
// path the tool named. A directory is never a pipeline target.
|
|
1396
|
+
const observedDispatchPaths = observedChangedPaths
|
|
1397
|
+
.filter((candidate) => {
|
|
1398
|
+
try {
|
|
1399
|
+
return !nodeFs.statSync(candidate).isDirectory();
|
|
1400
|
+
}
|
|
1401
|
+
catch {
|
|
1402
|
+
return false;
|
|
1403
|
+
}
|
|
1404
|
+
})
|
|
1405
|
+
.slice(0, OBSERVED_DISPATCH_MAX_PATHS);
|
|
1406
|
+
const observedDispatchDropped = observedChangedPaths.length - observedDispatchPaths.length;
|
|
1407
|
+
if (observedDispatchDropped > 0) {
|
|
1408
|
+
incrementDegradationCount({
|
|
1409
|
+
kind: "observed-mutation-dispatch-cap",
|
|
1410
|
+
subject: event.toolName,
|
|
1411
|
+
reason: `observed mutation dispatch capped at ${OBSERVED_DISPATCH_MAX_PATHS}; ${observedDispatchDropped} path(s) not dispatched`,
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
if (observedDispatchPaths.length > 0) {
|
|
1415
|
+
const observedClients = ensureToolResultClients(deps);
|
|
1416
|
+
if (observedClients !== true &&
|
|
1417
|
+
!(await bounded(Promise.resolve(observedClients), {
|
|
1418
|
+
ms: HOOK_WALL_BUDGET_MS.tool_result_edit,
|
|
1419
|
+
signal: deps.signal ?? undefined,
|
|
1420
|
+
hook: "tool_result_edit",
|
|
1421
|
+
label: "observed-tool-result-analysis",
|
|
1422
|
+
})))
|
|
1423
|
+
return;
|
|
1209
1424
|
const observedReadGuardCorrelationId = getReadGuardCorrelationId(event);
|
|
1210
1425
|
// #2464 review round 3 (F1): the SAME two pre-conditions the
|
|
1211
1426
|
// classified site consults, through the same shared claim. Round 2
|
|
@@ -1215,60 +1430,72 @@ export async function handleToolResult(deps) {
|
|
|
1215
1430
|
// second overwrote the first's registry entry, and the first's
|
|
1216
1431
|
// release then evicted the outer entry a live, unrelated classified
|
|
1217
1432
|
// pipeline had since re-created under it.
|
|
1218
|
-
const
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1433
|
+
for (const observedPath of observedDispatchPaths) {
|
|
1434
|
+
const observedDispatchSignal = deps.signal;
|
|
1435
|
+
const observedJoinSignal = deps.signal;
|
|
1436
|
+
const observedStateHashForPath = getFileStateHash(observedPath);
|
|
1437
|
+
const observedClaim = claimPipelineDispatch({
|
|
1438
|
+
filePath: observedPath,
|
|
1439
|
+
stateHash: observedStateHashForPath,
|
|
1440
|
+
turnIndex: runtime.turnIndex,
|
|
1441
|
+
participantId: observedReadGuardCorrelationId,
|
|
1442
|
+
dbg,
|
|
1443
|
+
});
|
|
1444
|
+
if (!observedClaim.proceed) {
|
|
1445
|
+
if (observedClaim.joined) {
|
|
1446
|
+
await bounded(observedClaim.joined, {
|
|
1447
|
+
ms: HOOK_WALL_BUDGET_MS.tool_result_edit,
|
|
1448
|
+
signal: observedJoinSignal,
|
|
1449
|
+
hook: "tool_result_edit",
|
|
1450
|
+
label: "observed-tool-result-join",
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
continue;
|
|
1454
|
+
}
|
|
1455
|
+
const observedDispatchOutcome = await bounded(dispatchPipelineAnalysis({
|
|
1456
|
+
deps,
|
|
1457
|
+
runtime,
|
|
1458
|
+
filePath: observedPath,
|
|
1459
|
+
dispatchCwd: resolveLanguageRootForFile(observedPath, workspaceRoot),
|
|
1460
|
+
turnStateCwd: path.resolve(workspaceRoot),
|
|
1461
|
+
autofixMode: observedAutofixMode,
|
|
1462
|
+
modifiedRanges: undefined,
|
|
1463
|
+
writeIndex: runtime.nextWriteIndex(),
|
|
1464
|
+
initialStateHash: observedStateHashForPath,
|
|
1465
|
+
readGuardCorrelationId: observedReadGuardCorrelationId,
|
|
1466
|
+
requestedEditIndexes: getRequestedEditIndexes(event, observedKind),
|
|
1467
|
+
requestedEditTotal: getRequestedEditCount(event, observedKind),
|
|
1468
|
+
isPartialApplyResult: (event.details ?? {})
|
|
1469
|
+
.piLensPartialApply === true,
|
|
1470
|
+
participantIds: [observedReadGuardCorrelationId],
|
|
1471
|
+
participantTotal: 1,
|
|
1472
|
+
toolResultStart,
|
|
1473
|
+
nativeAppliedPairs: observedAppliedPairs,
|
|
1474
|
+
// #2802 authorship rule (master): the observational
|
|
1475
|
+
// settle already recorded this edit through the
|
|
1476
|
+
// bridge, so the dispatch's read-guard write refresh
|
|
1477
|
+
// carries evidence and stays enabled on every
|
|
1478
|
+
// per-path dispatch.
|
|
1479
|
+
allowReadGuardWrites: true,
|
|
1480
|
+
}), {
|
|
1481
|
+
ms: HOOK_WALL_BUDGET_MS.tool_result_edit,
|
|
1482
|
+
signal: observedDispatchSignal,
|
|
1483
|
+
hook: "tool_result_edit",
|
|
1484
|
+
label: "observed-tool-result-analysis",
|
|
1485
|
+
});
|
|
1486
|
+
if (observedDispatchOutcome?.crashed) {
|
|
1487
|
+
// #2464 review round 2, S6: parity with the classified chain — a
|
|
1488
|
+
// pipeline crash surfaces its notice to the agent instead of being
|
|
1489
|
+
// swallowed into a dbg line the model never sees. The recorded
|
|
1490
|
+
// edit stands either way; only the analysis was lost.
|
|
1491
|
+
dbg(`tool_result: pipeline analysis crashed for the observed mutation on ${observedPath}; the recorded edit stands, analysis did not run this turn`);
|
|
1492
|
+
return observedDispatchOutcome.response;
|
|
1228
1493
|
}
|
|
1229
|
-
// Same terminal value as the block's own exit below: the bridge
|
|
1230
|
-
// already recorded this edit, and the analysis it would have asked
|
|
1231
|
-
// for is either running or already done.
|
|
1232
|
-
return syntheticWriteContent.length > 0
|
|
1233
|
-
? { content: [...event.content, ...syntheticWriteContent] }
|
|
1234
|
-
: undefined;
|
|
1235
|
-
}
|
|
1236
|
-
const observedDispatchOutcome = await dispatchPipelineAnalysis({
|
|
1237
|
-
deps,
|
|
1238
|
-
runtime,
|
|
1239
|
-
filePath,
|
|
1240
|
-
dispatchCwd: resolveLanguageRootForFile(filePath, workspaceRoot),
|
|
1241
|
-
turnStateCwd: path.resolve(workspaceRoot),
|
|
1242
|
-
autofixMode: observedAutofixMode,
|
|
1243
|
-
// #2423: no adapter/diff ranges exist for a "learned" (unnamed)
|
|
1244
|
-
// tool — the classified chain below hits the same gap for this
|
|
1245
|
-
// provenance and also leaves `modifiedRanges` undefined, so the
|
|
1246
|
-
// dispatch runs unscoped (whole-file) exactly as it would there.
|
|
1247
|
-
modifiedRanges: undefined,
|
|
1248
|
-
writeIndex: runtime.nextWriteIndex(),
|
|
1249
|
-
initialStateHash: observedStateHash,
|
|
1250
|
-
readGuardCorrelationId: observedReadGuardCorrelationId,
|
|
1251
|
-
requestedEditIndexes: getRequestedEditIndexes(event, observedKind),
|
|
1252
|
-
requestedEditTotal: getRequestedEditCount(event, observedKind),
|
|
1253
|
-
isPartialApplyResult: (event.details ?? {})
|
|
1254
|
-
.piLensPartialApply === true,
|
|
1255
|
-
participantIds: [observedReadGuardCorrelationId],
|
|
1256
|
-
participantTotal: 1,
|
|
1257
|
-
toolResultStart,
|
|
1258
|
-
nativeAppliedPairs: observedAppliedPairs,
|
|
1259
|
-
});
|
|
1260
|
-
if (observedDispatchOutcome.crashed) {
|
|
1261
|
-
// #2464 review round 2, S6: parity with the classified chain — a
|
|
1262
|
-
// pipeline crash surfaces its notice to the agent instead of being
|
|
1263
|
-
// swallowed into a dbg line the model never sees. The recorded
|
|
1264
|
-
// edit stands either way; only the analysis was lost.
|
|
1265
|
-
dbg(`tool_result: pipeline analysis crashed for the observed mutation on ${filePath}; the recorded edit stands, analysis did not run this turn`);
|
|
1266
|
-
return observedDispatchOutcome.response;
|
|
1267
1494
|
}
|
|
1268
1495
|
dbg(`tool_result: the observational settle already recorded ${observedReplayed} mutation(s) for "${event.toolName}"; kept the applied-edit records, mutation receipt, cachedExports refresh, and ran the pipeline dispatch (#2464); skipped the bridge-covered staleness stamp / turn-state ranges / change-log receipt / deferred autofix+format`);
|
|
1269
1496
|
}
|
|
1270
1497
|
else {
|
|
1271
|
-
dbg(`tool_result: the observational settle recorded ${observedReplayed} mutation(s) for "${event.toolName}",
|
|
1498
|
+
dbg(`tool_result: the observational settle recorded ${observedReplayed} mutation(s) for "${event.toolName}", but no dispatchable changed paths remained`);
|
|
1272
1499
|
}
|
|
1273
1500
|
}
|
|
1274
1501
|
return syntheticWriteContent.length > 0
|
|
@@ -1379,7 +1606,8 @@ export async function handleToolResult(deps) {
|
|
|
1379
1606
|
}
|
|
1380
1607
|
// Refresh the read-guard's FileTime stamp so that the model's own write
|
|
1381
1608
|
// doesn't trigger a spurious "file_modified" block on the next edit.
|
|
1382
|
-
|
|
1609
|
+
if (bashAuthorshipConfirmed)
|
|
1610
|
+
deps.readGuard?.recordWritten(filePath);
|
|
1383
1611
|
// Keep cachedExports in sync after each write/edit so the pre-write STOP
|
|
1384
1612
|
// check doesn't fire on names that were removed from this file this session.
|
|
1385
1613
|
refreshCachedExports(runtime, filePath);
|
|
@@ -1411,7 +1639,7 @@ export async function handleToolResult(deps) {
|
|
|
1411
1639
|
// tool_result is emitted after write/edit has already been applied.
|
|
1412
1640
|
// Asserting pre-write stamps here produces false positives on rapid edits.
|
|
1413
1641
|
sessionFileTime.read(filePath);
|
|
1414
|
-
if (!getFlag("no-read-guard")) {
|
|
1642
|
+
if (!getFlag("no-read-guard") && bashAuthorshipConfirmed) {
|
|
1415
1643
|
const readGuard = runtime.readGuard;
|
|
1416
1644
|
readGuard?.recordWritten?.(filePath);
|
|
1417
1645
|
}
|
|
@@ -1532,7 +1760,16 @@ export async function handleToolResult(deps) {
|
|
|
1532
1760
|
// (defined above `handleToolResult`), shared with the observed-mutation
|
|
1533
1761
|
// early return. This call site is otherwise unchanged — same arguments, same
|
|
1534
1762
|
// crash-then-return / success-then-continue shape as before the split.
|
|
1535
|
-
const
|
|
1763
|
+
const classifiedClients = ensureToolResultClients(deps);
|
|
1764
|
+
if (classifiedClients !== true &&
|
|
1765
|
+
!(await bounded(Promise.resolve(classifiedClients), {
|
|
1766
|
+
ms: HOOK_WALL_BUDGET_MS.tool_result_edit,
|
|
1767
|
+
signal: deps.signal,
|
|
1768
|
+
hook: "tool_result_edit",
|
|
1769
|
+
label: "classified-tool-result-analysis",
|
|
1770
|
+
})))
|
|
1771
|
+
return;
|
|
1772
|
+
const dispatchOutcome = await bounded(dispatchPipelineAnalysis({
|
|
1536
1773
|
deps,
|
|
1537
1774
|
runtime,
|
|
1538
1775
|
filePath,
|
|
@@ -1550,7 +1787,15 @@ export async function handleToolResult(deps) {
|
|
|
1550
1787
|
participantTotal,
|
|
1551
1788
|
toolResultStart,
|
|
1552
1789
|
nativeAppliedPairs,
|
|
1790
|
+
allowReadGuardWrites: bashAuthorshipConfirmed,
|
|
1791
|
+
}), {
|
|
1792
|
+
ms: HOOK_WALL_BUDGET_MS.tool_result_edit,
|
|
1793
|
+
signal: deps.signal || undefined,
|
|
1794
|
+
hook: "tool_result_edit",
|
|
1795
|
+
label: "pipeline-analysis",
|
|
1553
1796
|
});
|
|
1797
|
+
if (!dispatchOutcome)
|
|
1798
|
+
return;
|
|
1554
1799
|
if (dispatchOutcome.crashed) {
|
|
1555
1800
|
return dispatchOutcome.response;
|
|
1556
1801
|
}
|
|
@@ -1684,7 +1929,7 @@ export async function handleToolResult(deps) {
|
|
|
1684
1929
|
// #1561: stamp the verdict with THIS dispatch's write token — the same
|
|
1685
1930
|
// counter `lsp_diagnostics`' reconciliation seam draws from — so a later
|
|
1686
1931
|
// confirmed-clean result can be ordered against it instead of racing it.
|
|
1687
|
-
runtime.recordInlineBlockers(filePath, result.inlineBlockerSummary, writeIndex, result.inlineBlockerSources, result.inlineBlockerLines);
|
|
1932
|
+
runtime.recordInlineBlockers(filePath, result.inlineBlockerSummary, writeIndex, result.inlineBlockerSources, result.inlineBlockerLines, result.inlineBlockerFileContent);
|
|
1688
1933
|
}
|
|
1689
1934
|
else {
|
|
1690
1935
|
runtime.clearInlineBlockers(filePath);
|