@moretea-labs/forge 1.6.0 → 1.7.0
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/AGENTS.md +2 -0
- package/CHANGELOG.md +26 -0
- package/CLAUDE.md +2 -0
- package/README.md +2 -2
- package/README.zh-CN.md +1 -1
- package/SKILL.md +2 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/agents/openai.yaml +1 -1
- package/assets/hooks/hook-input.sh +11 -8
- package/assets/hooks/lib/session-state.sh +10 -7
- package/assets/hooks/lib/workflow-state.sh +13 -177
- package/assets/hooks/post-tool-observer.sh +8 -2
- package/assets/hooks/prompt-guard.sh +5 -81
- package/assets/hooks/session-start-context.sh +56 -1
- package/assets/partials/05-workflow.partial.md +1 -1
- package/assets/partials-agents/03-orchestration.partial.md +1 -1
- package/assets/plugin-registry.v1.json +1 -1
- package/assets/reference-configs/document-generation.md +1 -1
- package/assets/reference-configs/harness-overview.md +1 -1
- package/assets/reference-configs/sprint-contracts.md +3 -3
- package/assets/skill-version.json +2 -2
- package/assets/templates/contract.template.md +2 -7
- package/assets/templates/helpers/capture-plan.sh +2 -2
- package/assets/templates/helpers/check-task-workflow.sh +4 -4
- package/assets/templates/helpers/contract-worktree.sh +1 -21
- package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
- package/assets/templates/helpers/migrate-project-template.sh +22 -45
- package/assets/templates/helpers/new-plan.sh +1 -1
- package/assets/templates/helpers/plan-to-todo.sh +12 -37
- package/assets/templates/helpers/ship-worktrees.sh +2 -29
- package/assets/templates/helpers/verify-contract.sh +1 -111
- package/assets/templates/helpers/verify-sprint.sh +3 -180
- package/assets/templates/plan.template.md +1 -1
- package/assets/templates/review.template.md +9 -34
- package/assets/workflow-contract.v1.json +10 -19
- package/docs/README.md +10 -10
- package/docs/architecture/CURRENT.md +170 -0
- package/docs/architecture/EVOLUTION.md +42 -0
- package/docs/architecture/current/README.md +4 -156
- package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
- package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
- package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
- package/docs/architecture/history/README.md +7 -0
- package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
- package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
- package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
- package/docs/architecture/history.md +3 -34
- package/docs/architecture/index.md +15 -98
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
- package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
- package/docs/architecture/versions/1.6.md +38 -0
- package/docs/architecture/versions/1.7.md +41 -0
- package/docs/forge-chatgpt-browser-engine.md +2 -2
- package/docs/forge-chatgpt-mcp-setup.md +4 -28
- package/docs/forge-chatgpt-supervised-automation.md +3 -3
- package/docs/forge-github-issue-launcher.md +1 -1
- package/docs/forge-runtime-self-healing-loop.md +1 -1
- package/docs/operations/controller-browser-plugin.md +26 -6
- package/docs/operations/controller-performance-and-502.md +124 -0
- package/docs/operations/controller-reliability-runbook.md +1 -1
- package/docs/operations/features.md +2 -2
- package/docs/operations/github-repository.md +1 -1
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +5 -3
- package/docs/wiki/Architecture.md +6 -6
- package/docs/wiki/Implementation.md +2 -2
- package/docs/wiki/Integrations.md +2 -2
- package/docs/wiki/Work-Lifecycle.md +1 -1
- package/package.json +29 -10
- package/scripts/activate-source-baseline.ts +17 -13
- package/scripts/benchmark-background-check-overlap.ts +228 -0
- package/scripts/benchmark-mcp-host.sh +117 -0
- package/scripts/benchmark-mcp-transport.ts +170 -0
- package/scripts/benchmark-quality-harness.ts +146 -0
- package/scripts/benchmark-route-session-concurrency.ts +207 -6
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
- package/scripts/benchmark-typescript-navigation.ts +186 -0
- package/scripts/benchmark-work-efficiency.ts +334 -0
- package/scripts/capture-plan.sh +3 -3
- package/scripts/check-agent-tooling.sh +8 -35
- package/scripts/check-architecture-sync.sh +17 -86
- package/scripts/check-mcp-compatibility.ts +14 -0
- package/scripts/check-runtime-architecture.mjs +107 -64
- package/scripts/check-stable-baseline.ts +92 -0
- package/scripts/check-task-workflow.sh +4 -4
- package/scripts/contract-worktree.sh +1 -21
- package/scripts/ensure-task-workflow.sh +22 -55
- package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
- package/scripts/install-standalone-recovery.ts +24 -0
- package/scripts/lib/project-init-lib.sh +15 -44
- package/scripts/migrate-project-template.sh +23 -1179
- package/scripts/new-plan.sh +1 -1
- package/scripts/plan-to-todo.sh +11 -36
- package/scripts/run-governed-gate.ts +2 -1
- package/scripts/ship-worktrees.sh +2 -29
- package/scripts/smoke-ios-physical-device.ts +4 -2
- package/scripts/smoke-runtime-recovery.ts +1 -35
- package/scripts/smoke-schedule-engine.ts +1 -20
- package/scripts/verify-contract.sh +1 -111
- package/scripts/verify-forge-runtime.sh +0 -1
- package/scripts/verify-sprint.sh +3 -180
- package/scripts/workflow-contract.ts +1 -5
- package/src/cli/commands/init-hook.ts +3 -3
- package/src/cli/commands/mcp.ts +9 -5
- package/src/cli/commands/plugin.ts +1 -1
- package/src/cli/commands/recovery.ts +25 -3
- package/src/cli/commands/runtime.ts +55 -3
- package/src/cli/commands/security.ts +12 -1
- package/src/cli/commands/setup-profile.ts +31 -21
- package/src/cli/controller/check-runner.ts +106 -3
- package/src/cli/controller/composite-result.ts +1 -1
- package/src/cli/controller/context/focus.ts +51 -0
- package/src/cli/controller/context/instruction-context.ts +114 -0
- package/src/cli/controller/context/known-paths.ts +76 -0
- package/src/cli/controller/context/query-planning.ts +110 -0
- package/src/cli/controller/context/rule-resolution.ts +532 -0
- package/src/cli/controller/context/source-materializer.ts +278 -0
- package/src/cli/controller/context/structural.ts +16 -0
- package/src/cli/controller/context/types.ts +286 -0
- package/src/cli/controller/context-pack.ts +785 -469
- package/src/cli/controller/launch-agents.ts +77 -2
- package/src/cli/controller/operational-plan.ts +2 -2
- package/src/cli/controller/work-mode.ts +6 -4
- package/src/cli/editing/edit-session.ts +60 -7
- package/src/cli/github/github.ts +133 -0
- package/src/cli/installer/managed-entries.ts +15 -1
- package/src/cli/local-bridge/console-automations.ts +54 -78
- package/src/cli/local-bridge/console-view-models.ts +1 -35
- package/src/cli/local-bridge/facade-api.ts +0 -217
- package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
- package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
- package/src/cli/local-bridge/frontend/src/styles.css +1 -1
- package/src/cli/local-bridge/frontend/src/types.ts +5 -8
- package/src/cli/local-bridge/job-store.ts +1 -490
- package/src/cli/local-bridge/server.ts +0 -513
- package/src/cli/local-bridge/ui-dist/app.css +1 -1
- package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
- package/src/cli/mcp/auth.ts +5 -5
- package/src/cli/mcp/instructions.ts +3 -2
- package/src/cli/mcp/legacy-tool-service.ts +0 -189
- package/src/cli/mcp/multi-repository.ts +16 -5
- package/src/cli/mcp/oauth.ts +175 -26
- package/src/cli/mcp/repository-tools.ts +247 -107
- package/src/cli/mcp/server.ts +537 -111
- package/src/cli/mcp/tool-capability-cache.ts +1 -1
- package/src/cli/mcp/toolset-selection.ts +1 -1
- package/src/cli/mcp/toolset.ts +42 -5
- package/src/cli/mcp/transports/http.ts +101 -14
- package/src/cli/mcp/transports/session-registry.ts +43 -5
- package/src/cli/mcp/types.ts +3 -3
- package/src/cli/repositories/command-classifier.ts +101 -19
- package/src/cli/repositories/command-executor.ts +55 -521
- package/src/cli/repositories/command-process.ts +108 -0
- package/src/cli/repositories/command-scope.ts +99 -41
- package/src/cli/repositories/controller-home.ts +7 -8
- package/src/cli/repositories/git-command-executor.ts +2 -0
- package/src/cli/repositories/locks.ts +53 -16
- package/src/cli/repositories/repository-snapshot.ts +121 -0
- package/src/cli/repositories/selected-path-actions.ts +17 -9
- package/src/cli/repositories/structured-git.ts +61 -3
- package/src/cli/repository/inspector.ts +553 -63
- package/src/cli/repository/session-cache.ts +58 -17
- package/src/cli/v81-entry.ts +1 -4
- package/src/effects/fs-transaction.ts +85 -17
- package/src/runtime/context/codegraph-read-provider.ts +233 -2
- package/src/runtime/context/codegraph-sidecar.cjs +74 -40
- package/src/runtime/context/generic-lsp-provider.ts +355 -0
- package/src/runtime/context/lsp-client.ts +283 -0
- package/src/runtime/context/semantic-navigation.ts +275 -0
- package/src/runtime/context/swift-navigation.ts +565 -0
- package/src/runtime/context/typescript-navigation.ts +226 -0
- package/src/runtime/control-plane/dispatch-priority.ts +28 -0
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +47 -8
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
- package/src/runtime/control-plane/execution/session-store.ts +18 -0
- package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
- package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +143 -8
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +85 -8
- package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
- package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
- package/src/runtime/control-plane/facade/controller-session-store.ts +132 -1
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +2 -0
- package/src/runtime/control-plane/facade/goal-workloop.ts +686 -129
- package/src/runtime/control-plane/facade/index.ts +1 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +185 -20
- package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
- package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
- package/src/runtime/control-plane/facade/types.ts +22 -5
- package/src/runtime/control-plane/facade/work-contract-store.ts +175 -73
- package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
- package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
- package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
- package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
- package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
- package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
- package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
- package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
- package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
- package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
- package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
- package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
- package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
- package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +355 -116
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
- package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
- package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
- package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
- package/src/runtime/control-plane/release-retention.ts +62 -2
- package/src/runtime/control-plane/routing/route-policy.ts +7 -16
- package/src/runtime/control-plane/runtime-status-client.ts +0 -2
- package/src/runtime/diagnostics/mcp-timing.ts +13 -0
- package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
- package/src/runtime/diagnostics/performance.ts +24 -1
- package/src/runtime/evidence/event-ledger.ts +1 -1
- package/src/runtime/execution/jobs/store.ts +58 -4
- package/src/runtime/execution/jobs/types.ts +1 -1
- package/src/runtime/execution/managed-workspace.ts +203 -1
- package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
- package/src/runtime/execution/process-runtime/check-result.ts +53 -0
- package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +345 -40
- package/src/runtime/execution/process-runtime/gc.ts +38 -6
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
- package/src/runtime/execution/process-runtime/persisted-check.ts +47 -4
- package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
- package/src/runtime/execution/process-runtime/runtime.ts +43 -3
- package/src/runtime/execution/process-runtime/store.ts +1 -0
- package/src/runtime/execution/process-runtime/types.ts +2 -0
- package/src/runtime/execution/thin-harness/async-process.ts +12 -2
- package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
- package/src/runtime/execution/workers/executor.ts +0 -48
- package/src/runtime/gateway/mcp/execution-tools.ts +1192 -114
- package/src/runtime/gateway/mcp/process-tools.ts +83 -37
- package/src/runtime/gateway/mcp/router.ts +184 -82
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
- package/src/runtime/gateway/mcp/runtime-tools.ts +2224 -1589
- package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
- package/src/runtime/plugins/browser-adapter.ts +2901 -241
- package/src/runtime/plugins/browser-automation-service.ts +10 -0
- package/src/runtime/plugins/browser-handoff.ts +92 -2
- package/src/runtime/plugins/browser-macos-bridge.ts +582 -49
- package/src/runtime/plugins/browser-node-bridge.ts +6 -34
- package/src/runtime/plugins/browser-provider-registry.ts +109 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
- package/src/runtime/plugins/browser-runtime.ts +183 -0
- package/src/runtime/plugins/browser-session-authority.ts +332 -0
- package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +8 -2
- package/src/runtime/plugins/external-adapter.ts +324 -3
- package/src/runtime/plugins/external-unix-socket.ts +7 -1
- package/src/runtime/plugins/first-party-registry.ts +2 -0
- package/src/runtime/plugins/github-adapter.ts +125 -0
- package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
- package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
- package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
- package/src/runtime/plugins/ios-adapter.ts +106 -16
- package/src/runtime/plugins/ios-agent-device.ts +234 -64
- package/src/runtime/plugins/ios-physical-device.ts +1143 -392
- package/src/runtime/plugins/lightweight-action.ts +153 -0
- package/src/runtime/plugins/local-system-adapter.ts +123 -6
- package/src/runtime/plugins/macos-capability-broker.ts +55 -9
- package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
- package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
- package/src/runtime/plugins/resend-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +269 -29
- package/src/runtime/plugins/types.ts +35 -2
- package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
- package/src/runtime/projections/git-status-sampler.ts +16 -16
- package/src/runtime/projections/materialized-view.ts +28 -10
- package/src/runtime/recovery/maintenance-executor.ts +393 -28
- package/src/runtime/root/mcp-transport.ts +180 -10
- package/src/runtime/root/package-connector-service.ts +186 -8
- package/src/runtime/root/package-runtime-release.ts +143 -17
- package/src/runtime/root/package-runtime-service.ts +430 -22
- package/src/runtime/root/release-manifest.ts +7 -2
- package/src/runtime/root/release-materialize.ts +19 -0
- package/src/runtime/root/release-store.ts +48 -11
- package/src/runtime/root/runtime.ts +23 -5
- package/src/runtime/root/service.ts +100 -29
- package/src/runtime/root/types.ts +2 -0
- package/src/runtime/safe-tooling/ios-development.ts +321 -63
- package/src/runtime/shared/trusted-executable.ts +62 -0
- package/src/runtime/shared/trusted-node-executable.ts +48 -0
- package/src/runtime/standalone-recovery/core.ts +700 -92
- package/src/runtime/standalone-recovery/entry.ts +99 -23
- package/src/runtime/standalone-recovery/installer.ts +19 -3
- package/src/runtime/standalone-recovery/service-labels.ts +2 -0
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
- package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
- package/src/runtime/workflow/schedules/engine.ts +287 -151
- package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
- package/src/runtime/workflow/schedules/settlement.ts +48 -4
- package/src/runtime/workflow/schedules/store.ts +131 -10
- package/src/runtime/workflow/schedules/types.ts +2 -4
- package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
- package/src/testing/test-governance.ts +8 -40
- package/assets/reference-configs/heartbeat-triage.md +0 -57
- package/assets/templates/helpers/harness-trace-grade.sh +0 -150
- package/assets/templates/helpers/heartbeat-triage.sh +0 -361
- package/assets/templates/helpers/maintenance-triage.sh +0 -167
- package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
- package/docs/architecture/current/architecture-invariants.md +0 -331
- package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
- package/docs/architecture/current/codegraph-context-provider.md +0 -238
- package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
- package/docs/architecture/current/controller-repository-decoupling.md +0 -148
- package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
- package/docs/architecture/current/entity-model.md +0 -612
- package/docs/architecture/current/external-plugin-governance.md +0 -396
- package/docs/architecture/current/failure-recovery.md +0 -447
- package/docs/architecture/current/governance.md +0 -213
- package/docs/architecture/current/human-interaction-plane.md +0 -87
- package/docs/architecture/current/implementation-status.md +0 -128
- package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
- package/docs/architecture/current/legacy-governance.md +0 -58
- package/docs/architecture/current/local-workspace-target-grants.md +0 -70
- package/docs/architecture/current/migration-roadmap.md +0 -105
- package/docs/architecture/current/multi-repository-execution.md +0 -371
- package/docs/architecture/current/operations-runbook.md +0 -45
- package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
- package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
- package/docs/architecture/current/repository-access-modes.md +0 -102
- package/docs/architecture/current/route-policy.md +0 -15
- package/docs/architecture/current/route-session-concurrency.md +0 -85
- package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
- package/docs/architecture/current/runtime-directory-map.md +0 -49
- package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
- package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
- package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
- package/docs/architecture/current/system-overview.md +0 -169
- package/docs/architecture/current/target-requirements-traceability.md +0 -202
- package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
- package/docs/architecture/current/thin-harness-v1.md +0 -321
- package/docs/architecture/current/verification-and-release-gates.md +0 -391
- package/docs/forge-autonomous-goal-loop.md +0 -9
- package/docs/forge-provider-configuration.md +0 -7
- package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
- package/docs/operations/gmail-assistant-routines.md +0 -186
- package/docs/operations/personal-assistant-operator-guide.md +0 -61
- package/docs/operations/provider-configuration.md +0 -31
- package/scripts/harness-trace-grade.sh +0 -150
- package/scripts/heartbeat-triage.sh +0 -361
- package/scripts/maintenance-triage.sh +0 -167
- package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
- package/src/cli/repositories/goal-registry.ts +0 -239
- package/src/runtime/assistant/action-proposals.ts +0 -279
- package/src/runtime/assistant/intent.ts +0 -395
- package/src/runtime/assistant/model-provider.ts +0 -460
- package/src/runtime/assistant/openapi.ts +0 -223
- package/src/runtime/assistant/policy.ts +0 -89
- package/src/runtime/assistant/readiness.ts +0 -258
- package/src/runtime/assistant/routine-runtime.ts +0 -477
- package/src/runtime/assistant/schedule-binding.ts +0 -171
- package/src/runtime/assistant/standing-grants.ts +0 -375
- package/src/runtime/assistant/store.ts +0 -243
- package/src/runtime/assistant/types.ts +0 -135
- package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
- package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
- package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
- package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
- package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
- package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
- package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
- package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
- package/src/runtime/control-plane/goal-loop/index.ts +0 -13
- package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
- package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
- package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
- package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
- package/src/runtime/control-plane/goal-loop/types.ts +0 -378
- package/src/runtime/control-plane/startup-recovery.ts +0 -198
- package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
- package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
- package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
- package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
- package/src/runtime/workflow/findings/store.ts +0 -204
- package/src/runtime/workflow/findings/types.ts +0 -58
- package/src/runtime/workflow/portfolio/engine.ts +0 -20
- package/src/runtime/workflow/portfolio/store.ts +0 -185
- package/src/runtime/workflow/portfolio/types.ts +0 -29
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { statSync } from 'fs';
|
|
2
|
+
import { dirname, relative, resolve } from 'path';
|
|
3
|
+
import * as ts from 'typescript';
|
|
4
|
+
|
|
5
|
+
export type TypeScriptNavigationKind = 'definition' | 'references' | 'implementations';
|
|
6
|
+
|
|
7
|
+
export interface TypeScriptNavigationRequest {
|
|
8
|
+
navigation: TypeScriptNavigationKind;
|
|
9
|
+
path: string;
|
|
10
|
+
line: number;
|
|
11
|
+
column: number;
|
|
12
|
+
tsconfigPath?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface TypeScriptNavigationLocation {
|
|
16
|
+
path: string;
|
|
17
|
+
line: number;
|
|
18
|
+
column: number;
|
|
19
|
+
name?: string;
|
|
20
|
+
kind?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TypeScriptNavigationResult {
|
|
24
|
+
navigation: TypeScriptNavigationKind;
|
|
25
|
+
target: { path: string; line: number; column: number };
|
|
26
|
+
locations: TypeScriptNavigationLocation[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface TypeScriptNavigationAccess {
|
|
30
|
+
/** Stable identity for one read-policy scope. Restricted and unrestricted projects must never share a Language Service. */
|
|
31
|
+
cacheScope: string;
|
|
32
|
+
/** Source identity observed by the caller. A different identity must not reuse project membership. */
|
|
33
|
+
sourceIdentity?: string;
|
|
34
|
+
/** Return true only for repository-relative paths that this navigation call may read. */
|
|
35
|
+
allowRepositoryPath(relativePath: string): boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface CachedProject {
|
|
39
|
+
repoRoot: string;
|
|
40
|
+
configPath: string;
|
|
41
|
+
service: ts.LanguageService;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const projects = new Map<string, CachedProject>();
|
|
45
|
+
const MAX_CACHED_TYPESCRIPT_PROJECTS = 32;
|
|
46
|
+
|
|
47
|
+
function normalizePath(path: string): string {
|
|
48
|
+
return path.replace(/\\/g, '/');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function scriptVersion(path: string): string {
|
|
52
|
+
try {
|
|
53
|
+
const stat = statSync(path);
|
|
54
|
+
return `${stat.mtimeMs}:${stat.size}`;
|
|
55
|
+
} catch {
|
|
56
|
+
return 'missing';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function repositoryRelativePath(repoRoot: string, fileName: string): string | undefined {
|
|
61
|
+
const normalized = normalizePath(relative(repoRoot, resolve(fileName)));
|
|
62
|
+
if (!normalized || normalized === '.' || normalized === '..' || normalized.startsWith('../')) return undefined;
|
|
63
|
+
return normalized;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function loadProject(repoRoot: string, tsconfigPath = 'tsconfig.json', access?: TypeScriptNavigationAccess): CachedProject {
|
|
67
|
+
const root = resolve(repoRoot);
|
|
68
|
+
const configPath = resolve(root, tsconfigPath);
|
|
69
|
+
const configRelative = repositoryRelativePath(root, configPath);
|
|
70
|
+
if (!configRelative) throw new Error('TypeScript navigation tsconfig must be inside the repository.');
|
|
71
|
+
if (access && !access.allowRepositoryPath(configRelative)) {
|
|
72
|
+
throw new Error(`TypeScript navigation tsconfig is denied by read policy: ${configRelative}.`);
|
|
73
|
+
}
|
|
74
|
+
const cacheKey = `${root}\0${configPath}\0${scriptVersion(configPath)}\0${access?.cacheScope ?? 'unrestricted'}\0${access?.sourceIdentity ?? 'unbound-source'}`;
|
|
75
|
+
const cached = projects.get(cacheKey);
|
|
76
|
+
if (cached) {
|
|
77
|
+
projects.delete(cacheKey);
|
|
78
|
+
projects.set(cacheKey, cached);
|
|
79
|
+
return cached;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const canReadAbsolute = (fileName: string): boolean => {
|
|
83
|
+
if (!access) return true;
|
|
84
|
+
const repoRelative = repositoryRelativePath(root, fileName);
|
|
85
|
+
return repoRelative === undefined || access.allowRepositoryPath(repoRelative);
|
|
86
|
+
};
|
|
87
|
+
const readAllowedFile = (fileName: string): string | undefined => canReadAbsolute(fileName) ? ts.sys.readFile(fileName) : undefined;
|
|
88
|
+
const fileExistsAllowed = (fileName: string): boolean => canReadAbsolute(fileName) && ts.sys.fileExists(fileName);
|
|
89
|
+
const readDirectoryAllowed: typeof ts.sys.readDirectory = (path, extensions, exclude, include, depth) =>
|
|
90
|
+
ts.sys.readDirectory(path, extensions, exclude, include, depth).filter(canReadAbsolute);
|
|
91
|
+
|
|
92
|
+
const config = ts.readConfigFile(configPath, readAllowedFile);
|
|
93
|
+
if (config.error) {
|
|
94
|
+
throw new Error(ts.flattenDiagnosticMessageText(config.error.messageText, '\n'));
|
|
95
|
+
}
|
|
96
|
+
const parseHost: ts.ParseConfigHost = {
|
|
97
|
+
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
|
|
98
|
+
readDirectory: readDirectoryAllowed,
|
|
99
|
+
fileExists: fileExistsAllowed,
|
|
100
|
+
readFile: readAllowedFile,
|
|
101
|
+
};
|
|
102
|
+
const parsed = ts.parseJsonConfigFileContent(config.config, parseHost, dirname(configPath), undefined, configPath);
|
|
103
|
+
if (parsed.errors.length > 0) {
|
|
104
|
+
throw new Error(parsed.errors.map((entry) => ts.flattenDiagnosticMessageText(entry.messageText, '\n')).join('\n'));
|
|
105
|
+
}
|
|
106
|
+
const scriptFileNames = parsed.fileNames.filter(canReadAbsolute);
|
|
107
|
+
|
|
108
|
+
const host: ts.LanguageServiceHost = {
|
|
109
|
+
getCompilationSettings: () => parsed.options,
|
|
110
|
+
getScriptFileNames: () => scriptFileNames,
|
|
111
|
+
getScriptVersion: scriptVersion,
|
|
112
|
+
getScriptSnapshot: (fileName) => {
|
|
113
|
+
if (!fileExistsAllowed(fileName)) return undefined;
|
|
114
|
+
const content = readAllowedFile(fileName);
|
|
115
|
+
return content === undefined ? undefined : ts.ScriptSnapshot.fromString(content);
|
|
116
|
+
},
|
|
117
|
+
getCurrentDirectory: () => root,
|
|
118
|
+
getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options),
|
|
119
|
+
fileExists: fileExistsAllowed,
|
|
120
|
+
readFile: readAllowedFile,
|
|
121
|
+
readDirectory: readDirectoryAllowed,
|
|
122
|
+
directoryExists: ts.sys.directoryExists,
|
|
123
|
+
getDirectories: ts.sys.getDirectories,
|
|
124
|
+
realpath: ts.sys.realpath,
|
|
125
|
+
useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
|
|
126
|
+
getNewLine: () => ts.sys.newLine,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const project = {
|
|
130
|
+
repoRoot: root,
|
|
131
|
+
configPath,
|
|
132
|
+
service: ts.createLanguageService(host, ts.createDocumentRegistry()),
|
|
133
|
+
};
|
|
134
|
+
projects.set(cacheKey, project);
|
|
135
|
+
while (projects.size > MAX_CACHED_TYPESCRIPT_PROJECTS) {
|
|
136
|
+
const oldestKey = projects.keys().next().value as string | undefined;
|
|
137
|
+
if (!oldestKey) break;
|
|
138
|
+
const oldest = projects.get(oldestKey);
|
|
139
|
+
projects.delete(oldestKey);
|
|
140
|
+
oldest?.service.dispose();
|
|
141
|
+
}
|
|
142
|
+
return project;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function sourcePosition(project: CachedProject, program: ts.Program, fileName: string, line: number, column: number): number {
|
|
146
|
+
if (!Number.isInteger(line) || line < 1 || !Number.isInteger(column) || column < 1) {
|
|
147
|
+
throw new Error('TypeScript navigation line and column must be positive 1-based integers.');
|
|
148
|
+
}
|
|
149
|
+
const source = program.getSourceFile(fileName);
|
|
150
|
+
if (!source) throw new Error(`TypeScript project does not include ${normalizePath(relative(project.repoRoot, fileName))}.`);
|
|
151
|
+
if (line > source.getLineAndCharacterOfPosition(source.getEnd()).line + 1) {
|
|
152
|
+
throw new Error(`TypeScript navigation line ${line} is outside ${normalizePath(relative(project.repoRoot, fileName))}.`);
|
|
153
|
+
}
|
|
154
|
+
return source.getPositionOfLineAndCharacter(line - 1, column - 1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function location(
|
|
158
|
+
project: CachedProject,
|
|
159
|
+
program: ts.Program,
|
|
160
|
+
fileName: string,
|
|
161
|
+
textSpan: ts.TextSpan,
|
|
162
|
+
name?: string,
|
|
163
|
+
kind?: ts.ScriptElementKind,
|
|
164
|
+
): TypeScriptNavigationLocation {
|
|
165
|
+
const source = program.getSourceFile(fileName);
|
|
166
|
+
if (!source) {
|
|
167
|
+
return { path: normalizePath(relative(project.repoRoot, fileName)), line: 1, column: 1, ...(name ? { name } : {}), ...(kind ? { kind } : {}) };
|
|
168
|
+
}
|
|
169
|
+
const point = source.getLineAndCharacterOfPosition(textSpan.start);
|
|
170
|
+
return {
|
|
171
|
+
path: normalizePath(relative(project.repoRoot, fileName)),
|
|
172
|
+
line: point.line + 1,
|
|
173
|
+
column: point.character + 1,
|
|
174
|
+
...(name ? { name } : {}),
|
|
175
|
+
...(kind ? { kind } : {}),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function dedupe(locations: TypeScriptNavigationLocation[]): TypeScriptNavigationLocation[] {
|
|
180
|
+
const seen = new Set<string>();
|
|
181
|
+
return locations.filter((entry) => {
|
|
182
|
+
const key = `${entry.path}:${entry.line}:${entry.column}:${entry.name ?? ''}:${entry.kind ?? ''}`;
|
|
183
|
+
if (seen.has(key)) return false;
|
|
184
|
+
seen.add(key);
|
|
185
|
+
return true;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function navigateTypeScriptSymbol(
|
|
190
|
+
repoRoot: string,
|
|
191
|
+
request: TypeScriptNavigationRequest,
|
|
192
|
+
access?: TypeScriptNavigationAccess,
|
|
193
|
+
): TypeScriptNavigationResult {
|
|
194
|
+
const project = loadProject(repoRoot, request.tsconfigPath, access);
|
|
195
|
+
const fileName = resolve(project.repoRoot, request.path);
|
|
196
|
+
// A references response may materialize hundreds of locations. Acquire the
|
|
197
|
+
// Language Service Program once per navigation so that location formatting
|
|
198
|
+
// does not repeatedly re-enter the service while the result is built.
|
|
199
|
+
const program = project.service.getProgram();
|
|
200
|
+
if (!program) throw new Error('TypeScript Language Service did not produce a Program.');
|
|
201
|
+
const position = sourcePosition(project, program, fileName, request.line, request.column);
|
|
202
|
+
let locations: TypeScriptNavigationLocation[] = [];
|
|
203
|
+
|
|
204
|
+
if (request.navigation === 'definition') {
|
|
205
|
+
locations = (project.service.getDefinitionAtPosition(fileName, position) ?? [])
|
|
206
|
+
.map((entry) => location(project, program, entry.fileName, entry.textSpan, entry.name, entry.kind));
|
|
207
|
+
} else if (request.navigation === 'implementations') {
|
|
208
|
+
locations = (project.service.getImplementationAtPosition(fileName, position) ?? [])
|
|
209
|
+
.map((entry) => location(project, program, entry.fileName, entry.textSpan, undefined, entry.kind));
|
|
210
|
+
} else {
|
|
211
|
+
locations = (project.service.findReferences(fileName, position) ?? []).flatMap((group) =>
|
|
212
|
+
group.references.map((entry) => location(project, program, entry.fileName, entry.textSpan, group.definition.name, group.definition.kind)),
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
navigation: request.navigation,
|
|
218
|
+
target: { path: normalizePath(request.path), line: request.line, column: request.column },
|
|
219
|
+
locations: dedupe(locations),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function clearTypeScriptNavigationCache(): void {
|
|
224
|
+
for (const project of projects.values()) project.service.dispose();
|
|
225
|
+
projects.clear();
|
|
226
|
+
}
|
|
@@ -65,3 +65,31 @@ export function compareExecutionJobDispatchRanks(
|
|
|
65
65
|
|| left.queuedAtMs - right.queuedAtMs
|
|
66
66
|
|| left.jobId.localeCompare(right.jobId);
|
|
67
67
|
}
|
|
68
|
+
|
|
69
|
+
export function selectExecutionJobDispatchRepositories(
|
|
70
|
+
activeJobs: readonly ExecutionJob[],
|
|
71
|
+
scheduleNow: number,
|
|
72
|
+
lastRepoDispatch: ReadonlyMap<string, number>,
|
|
73
|
+
): string[] {
|
|
74
|
+
const waiting = activeJobs.filter(isExecutionJobDispatchCandidate);
|
|
75
|
+
const rankByJobId = new Map(
|
|
76
|
+
waiting.map((job) => [job.jobId, rankExecutionJobForDispatch(job, scheduleNow)] as const),
|
|
77
|
+
);
|
|
78
|
+
const compareWaiting = (left: ExecutionJob, right: ExecutionJob): number =>
|
|
79
|
+
compareExecutionJobDispatchRanks(rankByJobId.get(left.jobId)!, rankByJobId.get(right.jobId)!);
|
|
80
|
+
const topByRepo = new Map<string, ExecutionJob>();
|
|
81
|
+
for (const job of waiting.slice().sort(compareWaiting)) {
|
|
82
|
+
if (!topByRepo.has(job.repoId)) topByRepo.set(job.repoId, job);
|
|
83
|
+
}
|
|
84
|
+
return [...topByRepo.keys()].sort((left, right) => {
|
|
85
|
+
const leftRank = rankByJobId.get(topByRepo.get(left)!.jobId)!;
|
|
86
|
+
const rightRank = rankByJobId.get(topByRepo.get(right)!.jobId)!;
|
|
87
|
+
const priority = leftRank.effectivePriority - rightRank.effectivePriority;
|
|
88
|
+
if (priority !== 0) return priority;
|
|
89
|
+
const fairness = (lastRepoDispatch.get(left) ?? 0) - (lastRepoDispatch.get(right) ?? 0);
|
|
90
|
+
return fairness
|
|
91
|
+
|| leftRank.queuedAtMs - rightRank.queuedAtMs
|
|
92
|
+
|| leftRank.jobId.localeCompare(rightRank.jobId)
|
|
93
|
+
|| left.localeCompare(right);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
@@ -3,9 +3,10 @@ import { getEditSession, listEditSessions, type EditSession } from '../../../cli
|
|
|
3
3
|
import { globMatches } from '../../../cli/mcp/paths';
|
|
4
4
|
import { runProcess } from '../../../effects/process-runner';
|
|
5
5
|
import { completeRequirementFromWork } from '../persistence/requirement-store';
|
|
6
|
-
import { getWorkContract, recordWorkCompletionReceipt, updateWorkContract } from '../facade/work-contract-store';
|
|
6
|
+
import { appendWorkEvidence, getWorkContract, recordWorkCompletionReceipt, updateWorkContract } from '../facade/work-contract-store';
|
|
7
7
|
import { isDirectEditWorkCompletionReceipt, isTerminalWorkContractStatus, type DirectEditWorkCompletionReceipt, type WorkReconciliationRecord } from '../facade/types';
|
|
8
|
-
import {
|
|
8
|
+
import { historicalVerificationEvidenceAtRevision } from './verification-evidence';
|
|
9
|
+
import { readWorkHandle } from './work-handle-store';
|
|
9
10
|
|
|
10
11
|
export interface DirectEditWorkCompletionReconciliation {
|
|
11
12
|
completedWorkIds: string[];
|
|
@@ -209,7 +210,16 @@ export function acceptReviewedDirectEditWorkReconciliation(input: ReviewedDirect
|
|
|
209
210
|
}
|
|
210
211
|
throw new Error(`DIRECT_EDIT_WORK_RECONCILIATION_WORK_TERMINAL: ${input.workId}`);
|
|
211
212
|
}
|
|
212
|
-
|
|
213
|
+
const failedHandle = work.status === 'failed'
|
|
214
|
+
? readWorkHandle(input.controllerHome, input.repoId, input.workId)
|
|
215
|
+
: undefined;
|
|
216
|
+
const failedReviewedRecovery = work.workKind === 'repository_change'
|
|
217
|
+
&& work.status === 'failed'
|
|
218
|
+
&& failedHandle?.managedWorktree === false
|
|
219
|
+
&& failedHandle.state === 'failed'
|
|
220
|
+
&& failedHandle.finalization.validation === 'failed'
|
|
221
|
+
&& String(failedHandle.finalization.lastError ?? failedHandle.failureReason ?? '').includes('WORK_HANDLE_HEAD_CHANGED');
|
|
222
|
+
if ((isTerminalWorkContractStatus(work.status) && !failedReviewedRecovery) || work.workKind !== 'repository_change') {
|
|
213
223
|
throw new Error(`DIRECT_EDIT_WORK_RECONCILIATION_WORK_NOT_ELIGIBLE: ${input.workId}`);
|
|
214
224
|
}
|
|
215
225
|
if (!work.baseRevision?.trim()) throw new Error('DIRECT_EDIT_WORK_RECONCILIATION_BASE_REVISION_MISSING');
|
|
@@ -226,7 +236,15 @@ export function acceptReviewedDirectEditWorkReconciliation(input: ReviewedDirect
|
|
|
226
236
|
}
|
|
227
237
|
|
|
228
238
|
const comparedPaths = normalizedComparedPaths(input.comparedPaths);
|
|
229
|
-
|
|
239
|
+
let comparisonBaseRevision = baseRevision;
|
|
240
|
+
if (failedReviewedRecovery) {
|
|
241
|
+
const targetParent = exactCommit(input.repoRoot, `${targetRevision}^`, 'TARGET_PARENT_REVISION');
|
|
242
|
+
if (!git(input.repoRoot, ['merge-base', '--is-ancestor', baseRevision, targetParent]).ok) {
|
|
243
|
+
throw new Error('DIRECT_EDIT_WORK_RECONCILIATION_TARGET_PARENT_NOT_DESCENDANT');
|
|
244
|
+
}
|
|
245
|
+
comparisonBaseRevision = targetParent;
|
|
246
|
+
}
|
|
247
|
+
const actualPaths = comparedRevisionPaths(input.repoRoot, comparisonBaseRevision, targetRevision);
|
|
230
248
|
if (comparedPaths.length !== actualPaths.length || comparedPaths.some((path, index) => path !== actualPaths[index])) {
|
|
231
249
|
throw new Error('DIRECT_EDIT_WORK_RECONCILIATION_PATH_COMPARISON_MISMATCH');
|
|
232
250
|
}
|
|
@@ -239,12 +257,18 @@ export function acceptReviewedDirectEditWorkReconciliation(input: ReviewedDirect
|
|
|
239
257
|
|
|
240
258
|
let verifiedAt = new Date().toISOString();
|
|
241
259
|
for (const checkId of work.checks) {
|
|
242
|
-
const
|
|
260
|
+
const candidates = historicalVerificationEvidenceAtRevision(work.checkRefs, {
|
|
243
261
|
sourceRevision: targetRevision,
|
|
262
|
+
repoId: input.repoId,
|
|
263
|
+
workId: work.workId,
|
|
264
|
+
checkoutId: work.checkoutId,
|
|
244
265
|
checkId,
|
|
245
266
|
requestedChecks: work.checks,
|
|
246
|
-
}).
|
|
247
|
-
|
|
267
|
+
}).filter((entry) => entry.current && entry.record.outcome === 'valid_pass');
|
|
268
|
+
const distinctReceipts = new Map(candidates.map((entry) => [entry.record.receipt!.receiptId, entry]));
|
|
269
|
+
if (distinctReceipts.size === 0) throw new Error(`DIRECT_EDIT_WORK_RECONCILIATION_CHECK_EVIDENCE_STALE: ${checkId}`);
|
|
270
|
+
if (distinctReceipts.size > 1) throw new Error(`DIRECT_EDIT_WORK_RECONCILIATION_CHECK_EVIDENCE_AMBIGUOUS: ${checkId}`);
|
|
271
|
+
const current = [...distinctReceipts.values()][0]!;
|
|
248
272
|
verifiedAt = current.record.completedAt ?? current.record.recordedAt ?? verifiedAt;
|
|
249
273
|
}
|
|
250
274
|
|
|
@@ -301,6 +325,21 @@ export function acceptReviewedDirectEditWorkReconciliation(input: ReviewedDirect
|
|
|
301
325
|
'completed_changed',
|
|
302
326
|
'repository_change',
|
|
303
327
|
);
|
|
304
|
-
if (recorded.requirementId)
|
|
328
|
+
if (recorded.requirementId) {
|
|
329
|
+
try {
|
|
330
|
+
completeRequirementFromWork({ controllerHome: input.controllerHome }, { requirementId: recorded.requirementId, work: recorded });
|
|
331
|
+
} catch (error) {
|
|
332
|
+
try {
|
|
333
|
+
appendWorkEvidence({ controllerHome: input.controllerHome, repoId: input.repoId }, input.workId, {
|
|
334
|
+
title: 'requirement completion projection pending',
|
|
335
|
+
summary: `Work completion remains authoritative; Requirement projection could not be applied: ${error instanceof Error ? error.message : String(error)}`.slice(0, 2_000),
|
|
336
|
+
detailLevel: 'summary',
|
|
337
|
+
});
|
|
338
|
+
} catch {
|
|
339
|
+
// Reconciliation already recorded the terminal receipt. A diagnostic
|
|
340
|
+
// write failure cannot make that completed Work non-terminal again.
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
305
344
|
return { workId: input.workId, reconciliation, receipt };
|
|
306
345
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
listControllerChecks,
|
|
10
10
|
} from '../../../cli/controller/check-runner';
|
|
11
11
|
import type { RepositoryRecord } from '../../../cli/repositories/types';
|
|
12
|
+
import { selectRepositoryCheckout } from '../../../cli/repositories/registry';
|
|
12
13
|
import { executionIdentityForRepository } from './execution-identity';
|
|
13
14
|
import {
|
|
14
15
|
claimsForCheck,
|
|
@@ -266,6 +267,57 @@ function failedRun(
|
|
|
266
267
|
return coordinatorResult(controllerHome, failed);
|
|
267
268
|
}
|
|
268
269
|
|
|
270
|
+
export function resolveEditValidationRepository(
|
|
271
|
+
repository: RepositoryRecord,
|
|
272
|
+
editSessionId: string,
|
|
273
|
+
): { repository: RepositoryRecord; session: EditSession } {
|
|
274
|
+
const checkoutIds = [...new Set([
|
|
275
|
+
repository.activeCheckoutId,
|
|
276
|
+
...repository.checkouts.map((checkout) => checkout.checkoutId),
|
|
277
|
+
].filter(Boolean))];
|
|
278
|
+
for (const checkoutId of checkoutIds) {
|
|
279
|
+
let candidate: RepositoryRecord;
|
|
280
|
+
try {
|
|
281
|
+
candidate = selectRepositoryCheckout(repository, checkoutId);
|
|
282
|
+
} catch {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
let session: EditSession;
|
|
286
|
+
try {
|
|
287
|
+
session = getEditSession(candidate.canonicalRoot, editSessionId);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
if (error instanceof Error && error.message === `edit session not found: ${editSessionId}`) continue;
|
|
290
|
+
throw error;
|
|
291
|
+
}
|
|
292
|
+
if (session.repoId && session.repoId !== candidate.repoId) {
|
|
293
|
+
throw new Error(`EDIT_VALIDATION_SESSION_IDENTITY_MISMATCH: repoId ${session.repoId} != ${candidate.repoId}`);
|
|
294
|
+
}
|
|
295
|
+
if (session.checkoutId && session.checkoutId !== candidate.activeCheckoutId) {
|
|
296
|
+
throw new Error(`EDIT_VALIDATION_SESSION_IDENTITY_MISMATCH: checkoutId ${session.checkoutId} != ${candidate.activeCheckoutId}`);
|
|
297
|
+
}
|
|
298
|
+
return { repository: candidate, session };
|
|
299
|
+
}
|
|
300
|
+
throw new Error(`edit session not found: ${editSessionId}`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function validationRunRepository(
|
|
304
|
+
repository: RepositoryRecord,
|
|
305
|
+
run: EditValidationRunState,
|
|
306
|
+
): RepositoryRecord {
|
|
307
|
+
if (run.repositoryId !== repository.repoId) {
|
|
308
|
+
throw new Error(`EDIT_VALIDATION_REPOSITORY_MISMATCH: ${run.repositoryId} != ${repository.repoId}`);
|
|
309
|
+
}
|
|
310
|
+
const candidate = selectRepositoryCheckout(repository, run.checkoutId);
|
|
311
|
+
const session = getEditSession(candidate.canonicalRoot, run.editSessionId);
|
|
312
|
+
if (session.repoId && session.repoId !== run.repositoryId) {
|
|
313
|
+
throw new Error(`EDIT_VALIDATION_SESSION_IDENTITY_MISMATCH: repoId ${session.repoId} != ${run.repositoryId}`);
|
|
314
|
+
}
|
|
315
|
+
if (session.checkoutId && session.checkoutId !== run.checkoutId) {
|
|
316
|
+
throw new Error(`EDIT_VALIDATION_SESSION_IDENTITY_MISMATCH: checkoutId ${session.checkoutId} != ${run.checkoutId}`);
|
|
317
|
+
}
|
|
318
|
+
return candidate;
|
|
319
|
+
}
|
|
320
|
+
|
|
269
321
|
function receiptFor(
|
|
270
322
|
controllerHome: string,
|
|
271
323
|
repository: RepositoryRecord,
|
|
@@ -312,10 +364,18 @@ export async function reconcileEditValidationRun(
|
|
|
312
364
|
): Promise<EditValidationCoordinatorResult> {
|
|
313
365
|
let run = readRun(controllerHome, repository.repoId, runInput.validationId) ?? runInput;
|
|
314
366
|
if (run.status !== 'running') return coordinatorResult(controllerHome, run);
|
|
315
|
-
|
|
316
|
-
|
|
367
|
+
let validationRepository: RepositoryRecord;
|
|
368
|
+
try {
|
|
369
|
+
validationRepository = validationRunRepository(repository, run);
|
|
370
|
+
} catch (error) {
|
|
371
|
+
return failedRun(
|
|
372
|
+
controllerHome,
|
|
373
|
+
run,
|
|
374
|
+
'EDIT_VALIDATION_CHECKOUT_UNAVAILABLE',
|
|
375
|
+
error instanceof Error ? error.message : String(error),
|
|
376
|
+
);
|
|
317
377
|
}
|
|
318
|
-
const session = getEditSession(
|
|
378
|
+
const session = getEditSession(validationRepository.canonicalRoot, run.editSessionId);
|
|
319
379
|
if (session.currentRevision !== run.editRevision) {
|
|
320
380
|
return failedRun(controllerHome, run, 'EDIT_VALIDATION_STALE_REVISION', `Validation targets revision ${run.editRevision}, current edit revision is ${session.currentRevision}.`);
|
|
321
381
|
}
|
|
@@ -334,10 +394,10 @@ export async function reconcileEditValidationRun(
|
|
|
334
394
|
const checkRequestId = `${run.requestId}:check:${index + 1}:${createHash('sha256').update(checkId).digest('hex').slice(0, 12)}`;
|
|
335
395
|
const facade = await runPersistedCheckViaProcessRuntime({
|
|
336
396
|
controllerHome,
|
|
337
|
-
repoId:
|
|
338
|
-
checkoutId:
|
|
339
|
-
repoRoot:
|
|
340
|
-
executionIdentity: executionIdentityForRepository(
|
|
397
|
+
repoId: validationRepository.repoId,
|
|
398
|
+
checkoutId: validationRepository.activeCheckoutId,
|
|
399
|
+
repoRoot: validationRepository.canonicalRoot,
|
|
400
|
+
executionIdentity: executionIdentityForRepository(validationRepository),
|
|
341
401
|
checkId,
|
|
342
402
|
timeoutMs: run.timeoutMs,
|
|
343
403
|
interactiveWaitMs: 0,
|
|
@@ -384,10 +444,10 @@ export async function reconcileEditValidationRun(
|
|
|
384
444
|
}
|
|
385
445
|
|
|
386
446
|
try {
|
|
387
|
-
const receipts = run.checkIds.map((checkId) => receiptFor(controllerHome,
|
|
388
|
-
const checked = recordEditSessionProcessCheckReceipts(
|
|
389
|
-
repoId:
|
|
390
|
-
checkoutId:
|
|
447
|
+
const receipts = run.checkIds.map((checkId) => receiptFor(controllerHome, validationRepository, run, checkId));
|
|
448
|
+
const checked = recordEditSessionProcessCheckReceipts(validationRepository.canonicalRoot, run.editSessionId, {
|
|
449
|
+
repoId: validationRepository.repoId,
|
|
450
|
+
checkoutId: validationRepository.activeCheckoutId,
|
|
391
451
|
receipts,
|
|
392
452
|
reviewer: run.reviewer,
|
|
393
453
|
note: run.note,
|
|
@@ -415,7 +475,9 @@ export async function startOrJoinEditValidation(
|
|
|
415
475
|
repository: RepositoryRecord,
|
|
416
476
|
input: EditValidationStartInput,
|
|
417
477
|
): Promise<EditValidationCoordinatorResult> {
|
|
418
|
-
const
|
|
478
|
+
const resolved = resolveEditValidationRepository(repository, input.editSessionId);
|
|
479
|
+
const validationRepository = resolved.repository;
|
|
480
|
+
const session = resolved.session;
|
|
419
481
|
const checkIds = normalized(input.checkIds ?? session.requestedChecks);
|
|
420
482
|
const requestId = input.validationRequestId?.trim()
|
|
421
483
|
|| input.requestId?.trim()
|
|
@@ -426,14 +488,14 @@ export async function startOrJoinEditValidation(
|
|
|
426
488
|
if (existing.checkIds.join('\n') !== checkIds.join('\n')) {
|
|
427
489
|
return failedRun(controllerHome, existing, 'EDIT_VALIDATION_REQUEST_CONFLICT', 'The validation request id is already bound to a different check set.');
|
|
428
490
|
}
|
|
429
|
-
return reconcileEditValidationRun(controllerHome,
|
|
491
|
+
return reconcileEditValidationRun(controllerHome, validationRepository, existing);
|
|
430
492
|
}
|
|
431
493
|
|
|
432
494
|
if (checkIds.length === 0) {
|
|
433
495
|
try {
|
|
434
|
-
const checked = recordEditSessionProcessCheckReceipts(
|
|
435
|
-
repoId:
|
|
436
|
-
checkoutId:
|
|
496
|
+
const checked = recordEditSessionProcessCheckReceipts(validationRepository.canonicalRoot, session.sessionId, {
|
|
497
|
+
repoId: validationRepository.repoId,
|
|
498
|
+
checkoutId: validationRepository.activeCheckoutId,
|
|
437
499
|
receipts: [],
|
|
438
500
|
reviewer: input.reviewer,
|
|
439
501
|
note: input.note,
|
|
@@ -441,8 +503,8 @@ export async function startOrJoinEditValidation(
|
|
|
441
503
|
const completed = saveRun(controllerHome, {
|
|
442
504
|
schemaVersion: 1,
|
|
443
505
|
validationId: id,
|
|
444
|
-
repositoryId:
|
|
445
|
-
checkoutId:
|
|
506
|
+
repositoryId: validationRepository.repoId,
|
|
507
|
+
checkoutId: validationRepository.activeCheckoutId,
|
|
446
508
|
editSessionId: session.sessionId,
|
|
447
509
|
editRevision: session.currentRevision,
|
|
448
510
|
requestId,
|
|
@@ -464,8 +526,8 @@ export async function startOrJoinEditValidation(
|
|
|
464
526
|
const run: EditValidationRunState = {
|
|
465
527
|
schemaVersion: 1,
|
|
466
528
|
validationId: id,
|
|
467
|
-
repositoryId:
|
|
468
|
-
checkoutId:
|
|
529
|
+
repositoryId: validationRepository.repoId,
|
|
530
|
+
checkoutId: validationRepository.activeCheckoutId,
|
|
469
531
|
editSessionId: session.sessionId,
|
|
470
532
|
editRevision: session.currentRevision,
|
|
471
533
|
requestId,
|
|
@@ -487,13 +549,13 @@ export async function startOrJoinEditValidation(
|
|
|
487
549
|
const run = saveRun(controllerHome, {
|
|
488
550
|
schemaVersion: 1,
|
|
489
551
|
validationId: id,
|
|
490
|
-
repositoryId:
|
|
491
|
-
checkoutId:
|
|
552
|
+
repositoryId: validationRepository.repoId,
|
|
553
|
+
checkoutId: validationRepository.activeCheckoutId,
|
|
492
554
|
editSessionId: session.sessionId,
|
|
493
555
|
editRevision: session.currentRevision,
|
|
494
556
|
requestId,
|
|
495
557
|
checkIds,
|
|
496
|
-
lanes: checkLanes(
|
|
558
|
+
lanes: checkLanes(validationRepository, checkIds),
|
|
497
559
|
processes: {},
|
|
498
560
|
reviewer: input.reviewer,
|
|
499
561
|
note: input.note,
|
|
@@ -505,7 +567,7 @@ export async function startOrJoinEditValidation(
|
|
|
505
567
|
createdAt: now(),
|
|
506
568
|
updatedAt: now(),
|
|
507
569
|
});
|
|
508
|
-
return reconcileEditValidationRun(controllerHome,
|
|
570
|
+
return reconcileEditValidationRun(controllerHome, validationRepository, run);
|
|
509
571
|
}
|
|
510
572
|
|
|
511
573
|
export interface PendingEditValidationSummary {
|
|
@@ -163,6 +163,24 @@ export function updateExecutionSession(
|
|
|
163
163
|
return updated;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
export function invalidateExecutionSession(
|
|
167
|
+
controllerHome: string,
|
|
168
|
+
sessionId: string,
|
|
169
|
+
reason: string,
|
|
170
|
+
): ExecutionSessionContext | undefined {
|
|
171
|
+
const current = peekExecutionSession(controllerHome, sessionId);
|
|
172
|
+
if (!current || current.invalidatedAt) return current;
|
|
173
|
+
const at = now();
|
|
174
|
+
const invalidated: ExecutionSessionContext = {
|
|
175
|
+
...current,
|
|
176
|
+
updatedAt: at,
|
|
177
|
+
invalidatedAt: at,
|
|
178
|
+
invalidationReason: reason.trim().slice(0, 512) || 'transport session closed',
|
|
179
|
+
};
|
|
180
|
+
writeExecutionSession(controllerHome, invalidated, 'session_invalidated');
|
|
181
|
+
return invalidated;
|
|
182
|
+
}
|
|
183
|
+
|
|
166
184
|
function writeExecutionSession(controllerHome: string, session: ExecutionSessionContext, action: string): void {
|
|
167
185
|
writeControlPlaneRecord(controllerHome, {
|
|
168
186
|
namespace: 'execution_session',
|
|
@@ -14,7 +14,6 @@ export interface VerificationEvidenceIdentity {
|
|
|
14
14
|
workspaceFingerprint?: string;
|
|
15
15
|
checkId: string;
|
|
16
16
|
requestedChecks: string[];
|
|
17
|
-
commandId?: string;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
export interface EffectiveVerificationEvidence {
|
|
@@ -23,6 +22,12 @@ export interface EffectiveVerificationEvidence {
|
|
|
23
22
|
staleReason?: string;
|
|
24
23
|
}
|
|
25
24
|
|
|
25
|
+
export interface HistoricalVerificationEvidenceIdentity extends Omit<VerificationEvidenceIdentity, 'workspaceFingerprint'> {
|
|
26
|
+
repoId: string;
|
|
27
|
+
workId: string;
|
|
28
|
+
checkoutId?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
function decodeGitQuotedPath(rawPath: string): string {
|
|
27
32
|
if (!rawPath.startsWith('"') && !rawPath.endsWith('"')) return rawPath;
|
|
28
33
|
if (!rawPath.startsWith('"') || !rawPath.endsWith('"')) {
|
|
@@ -146,14 +151,13 @@ export function workValidationInputFingerprint(
|
|
|
146
151
|
})).digest('hex');
|
|
147
152
|
}
|
|
148
153
|
|
|
149
|
-
/** Stable identity for inputs that make a check result reusable. */
|
|
154
|
+
/** Stable identity for semantic inputs that make a check result reusable. Invocation ids are audited separately. */
|
|
150
155
|
export function verificationInputFingerprint(input: VerificationEvidenceIdentity): string {
|
|
151
156
|
return createHash('sha256').update(JSON.stringify({
|
|
152
157
|
sourceRevision: input.sourceRevision,
|
|
153
158
|
workspaceFingerprint: input.workspaceFingerprint ?? null,
|
|
154
159
|
checkId: input.checkId,
|
|
155
160
|
requestedChecks: [...input.requestedChecks].sort(),
|
|
156
|
-
commandId: input.commandId ?? null,
|
|
157
161
|
})).digest('hex');
|
|
158
162
|
}
|
|
159
163
|
|
|
@@ -161,6 +165,57 @@ export function commandFingerprint(checkId: string, commandId: string | undefine
|
|
|
161
165
|
return createHash('sha256').update(JSON.stringify({ checkId, commandId: commandId ?? null })).digest('hex');
|
|
162
166
|
}
|
|
163
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Selects immutable Work-bound verification evidence for an explicit historical
|
|
170
|
+
* revision. Unlike current-workspace reuse, the historical workspace identity
|
|
171
|
+
* comes from each durable receipt record and is verified against the record's
|
|
172
|
+
* persisted semantic and command fingerprints.
|
|
173
|
+
*/
|
|
174
|
+
export function historicalVerificationEvidenceAtRevision(
|
|
175
|
+
records: VerificationRecord[],
|
|
176
|
+
expected: HistoricalVerificationEvidenceIdentity,
|
|
177
|
+
): EffectiveVerificationEvidence[] {
|
|
178
|
+
return records
|
|
179
|
+
.filter((record) => record.checkId === expected.checkId)
|
|
180
|
+
.map((record) => {
|
|
181
|
+
if (record.supersedes || record.outcome === 'superseded') {
|
|
182
|
+
return { record, current: false, staleReason: 'superseded' };
|
|
183
|
+
}
|
|
184
|
+
if (!record.sourceRevision || !record.workspaceFingerprint || !record.verificationInputFingerprint || !record.commandFingerprint) {
|
|
185
|
+
return { record, current: false, staleReason: 'historical evidence has no exact input identity' };
|
|
186
|
+
}
|
|
187
|
+
if (record.sourceRevision !== expected.sourceRevision) {
|
|
188
|
+
return { record, current: false, staleReason: `source revision changed: ${record.sourceRevision} -> ${expected.sourceRevision}` };
|
|
189
|
+
}
|
|
190
|
+
const receipt = record.receipt;
|
|
191
|
+
if (
|
|
192
|
+
!receipt
|
|
193
|
+
|| receipt.repoId !== expected.repoId
|
|
194
|
+
|| receipt.workId !== expected.workId
|
|
195
|
+
|| receipt.checkId !== expected.checkId
|
|
196
|
+
|| (expected.checkoutId !== undefined && receipt.checkoutId !== expected.checkoutId)
|
|
197
|
+
) {
|
|
198
|
+
return { record, current: false, staleReason: 'historical receipt identity changed' };
|
|
199
|
+
}
|
|
200
|
+
if (receipt.status !== 'passed' || receipt.runtimeStatus !== 'succeeded' || !receipt.ok || receipt.timedOut || receipt.cancelled) {
|
|
201
|
+
return { record, current: false, staleReason: 'historical receipt is not a valid pass' };
|
|
202
|
+
}
|
|
203
|
+
const expectedFingerprint = verificationInputFingerprint({
|
|
204
|
+
sourceRevision: expected.sourceRevision,
|
|
205
|
+
workspaceFingerprint: record.workspaceFingerprint,
|
|
206
|
+
checkId: expected.checkId,
|
|
207
|
+
requestedChecks: expected.requestedChecks,
|
|
208
|
+
});
|
|
209
|
+
if (record.verificationInputFingerprint !== expectedFingerprint) {
|
|
210
|
+
return { record, current: false, staleReason: 'verification inputs changed' };
|
|
211
|
+
}
|
|
212
|
+
if (record.commandFingerprint !== commandFingerprint(record.checkId, receipt.commandId)) {
|
|
213
|
+
return { record, current: false, staleReason: 'check command identity changed' };
|
|
214
|
+
}
|
|
215
|
+
return { record, current: true };
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
164
219
|
/**
|
|
165
220
|
* Keeps historical evidence auditable while preventing a previous revision or
|
|
166
221
|
* changed check inputs from being selected as proof for the current Work.
|