@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
|
@@ -18,10 +18,10 @@ import {
|
|
|
18
18
|
type DispatchState,
|
|
19
19
|
type EvidenceState,
|
|
20
20
|
type PolicyDecision,
|
|
21
|
-
type SubmittedWorkOperation,
|
|
22
21
|
type SuggestedNextAction,
|
|
23
22
|
type VerificationRecord,
|
|
24
23
|
type WorkReconciliationRecord,
|
|
24
|
+
type SubmittedWorkOperation,
|
|
25
25
|
type WorkContract,
|
|
26
26
|
type WorkContractStatus,
|
|
27
27
|
type WorkRisk,
|
|
@@ -370,11 +370,25 @@ const EVIDENCE_TRANSITIONS: Readonly<Record<EvidenceState, readonly EvidenceStat
|
|
|
370
370
|
failed: ['failed', 'partial', 'valid', 'contradictory'],
|
|
371
371
|
};
|
|
372
372
|
|
|
373
|
-
function validateWorkSemanticTransition(
|
|
373
|
+
function validateWorkSemanticTransition(
|
|
374
|
+
current: WorkContract,
|
|
375
|
+
next: WorkContract,
|
|
376
|
+
options: { allowRetainedCancelledResume?: boolean } = {},
|
|
377
|
+
): WorkContract {
|
|
374
378
|
const retryingFailedWork = current.status === 'failed'
|
|
375
379
|
&& !current.completionOutcome
|
|
376
380
|
&& ['claimed', 'launching', 'running', 'blocked'].includes(next.dispatchState);
|
|
377
|
-
|
|
381
|
+
const resumingRetainedCancelledWork = options.allowRetainedCancelledResume === true
|
|
382
|
+
&& current.status === 'cancelled'
|
|
383
|
+
&& current.dispatchState === 'terminal'
|
|
384
|
+
&& current.phase === 'cleanup'
|
|
385
|
+
&& current.phaseEvidence.cleanup.state === 'skipped'
|
|
386
|
+
&& !current.completionReceipt
|
|
387
|
+
&& !current.completionOutcome
|
|
388
|
+
&& next.status === 'running'
|
|
389
|
+
&& next.dispatchState === 'running'
|
|
390
|
+
&& next.phase === 'implementation';
|
|
391
|
+
if (!retryingFailedWork && !resumingRetainedCancelledWork && !DISPATCH_TRANSITIONS[current.dispatchState].includes(next.dispatchState)) {
|
|
378
392
|
throw new Error(`WORK_SEMANTICS_TRANSITION_INVALID: dispatch ${current.dispatchState} -> ${next.dispatchState}`);
|
|
379
393
|
}
|
|
380
394
|
if (!EVIDENCE_TRANSITIONS[current.evidenceState].includes(next.evidenceState)) {
|
|
@@ -392,6 +406,11 @@ function validateWorkSemanticTransition(current: WorkContract, next: WorkContrac
|
|
|
392
406
|
return next;
|
|
393
407
|
}
|
|
394
408
|
|
|
409
|
+
function suggestedActionsForStatus(status: WorkContractStatus, actions: readonly SuggestedNextAction[]): SuggestedNextAction[] {
|
|
410
|
+
if (status === 'completed' || status === 'cancelled') return [];
|
|
411
|
+
return actions.slice(0, 8);
|
|
412
|
+
}
|
|
413
|
+
|
|
395
414
|
function sqliteBacked(options: WorkContractStoreOptions): options is WorkContractStoreOptions & { controllerHome: string; repoId: string } {
|
|
396
415
|
// A caller-provided root is a test/portable compatibility store. Runtime
|
|
397
416
|
// controller state always carries controllerHome + repoId and is SQLite.
|
|
@@ -429,6 +448,7 @@ function normalizeWorkContractStore(store: WorkContractStore): WorkContractStore
|
|
|
429
448
|
workKind: legacy.workKind ?? 'repository_change',
|
|
430
449
|
dispatchState: legacy.dispatchState ?? inferredDispatchState(status),
|
|
431
450
|
evidenceState: legacy.evidenceState ?? inferredEvidenceState(status),
|
|
451
|
+
suggestedNextActions: suggestedActionsForStatus(status, legacy.suggestedNextActions ?? []),
|
|
432
452
|
reconciliations: legacy.reconciliations ?? [],
|
|
433
453
|
driver: (legacy.driver as unknown as { preferred?: string } | undefined)?.preferred === 'codex_worker'
|
|
434
454
|
? { ...legacy.driver, preferred: 'external_controller', allowWorker: false }
|
|
@@ -541,7 +561,7 @@ export function createWorkContract(options: WorkContractStoreOptions, input: Cre
|
|
|
541
561
|
if (input.status === 'completed' || input.completionReceipt || input.completionOutcome) {
|
|
542
562
|
throw new Error('WORK_COMPLETION_REQUIRES_RECORD_API');
|
|
543
563
|
}
|
|
544
|
-
|
|
564
|
+
const create = (): WorkContract => {
|
|
545
565
|
const at = input.createdAt ?? input.updatedAt ?? nowIso(options);
|
|
546
566
|
const workId = sanitizeFileComponent(input.workId);
|
|
547
567
|
const contract: WorkContract = validateWorkSemantics({
|
|
@@ -585,6 +605,12 @@ export function createWorkContract(options: WorkContractStoreOptions, input: Cre
|
|
|
585
605
|
planStepId: input.planStepId,
|
|
586
606
|
planSourceRevision: input.planSourceRevision,
|
|
587
607
|
scopeSummary: input.scopeSummary?.slice(0, 1_000),
|
|
608
|
+
scopeEvidence: input.scopeEvidence ? {
|
|
609
|
+
initialLikelyPaths: [...new Set(input.scopeEvidence.initialLikelyPaths)].slice(0, 100),
|
|
610
|
+
inspectedPaths: [...new Set(input.scopeEvidence.inspectedPaths)].slice(0, 500),
|
|
611
|
+
actualChangedPaths: [...new Set(input.scopeEvidence.actualChangedPaths)].slice(0, 500),
|
|
612
|
+
recordedAt: input.scopeEvidence.recordedAt,
|
|
613
|
+
} : undefined,
|
|
588
614
|
allowedPaths: (input.allowedPaths ?? []).slice(0, 50),
|
|
589
615
|
forbiddenPaths: (input.forbiddenPaths ?? []).slice(0, 50),
|
|
590
616
|
checks: (input.checks ?? []).slice(0, 30),
|
|
@@ -618,6 +644,23 @@ export function createWorkContract(options: WorkContractStoreOptions, input: Cre
|
|
|
618
644
|
submittedOperation: input.submittedOperation,
|
|
619
645
|
});
|
|
620
646
|
|
|
647
|
+
if (sqliteBacked(options)) {
|
|
648
|
+
withControlPlaneTransaction(options.controllerHome, (database) => {
|
|
649
|
+
if (readControlPlaneRecordWithinTransaction<WorkContract>(database, 'work_contract', options.repoId, contract.workId)) {
|
|
650
|
+
throw new Error(`work contract already exists: ${contract.workId}`);
|
|
651
|
+
}
|
|
652
|
+
writeControlPlaneRecordWithinTransaction(database, {
|
|
653
|
+
namespace: 'work_contract',
|
|
654
|
+
scope: options.repoId,
|
|
655
|
+
key: contract.workId,
|
|
656
|
+
schemaVersion: 2,
|
|
657
|
+
value: contract,
|
|
658
|
+
action: 'work_contract_created',
|
|
659
|
+
expectedRevision: null,
|
|
660
|
+
});
|
|
661
|
+
});
|
|
662
|
+
return contract;
|
|
663
|
+
}
|
|
621
664
|
const store = readWorkContractStore(options);
|
|
622
665
|
if (store.contracts.some((existing) => existing.workId === contract.workId)) {
|
|
623
666
|
throw new Error(`work contract already exists: ${contract.workId}`);
|
|
@@ -629,7 +672,8 @@ export function createWorkContract(options: WorkContractStoreOptions, input: Cre
|
|
|
629
672
|
};
|
|
630
673
|
writeWorkContractStore(options, nextStore);
|
|
631
674
|
return contract;
|
|
632
|
-
}
|
|
675
|
+
};
|
|
676
|
+
return create();
|
|
633
677
|
}
|
|
634
678
|
|
|
635
679
|
interface WorkRequestIndexRecord {
|
|
@@ -647,26 +691,6 @@ function workRequestIndexPath(controllerHome: string, requestId: string): string
|
|
|
647
691
|
return join(root, `${hash}.json`);
|
|
648
692
|
}
|
|
649
693
|
|
|
650
|
-
export interface AcceptSubmittedWorkInput {
|
|
651
|
-
requestId: string;
|
|
652
|
-
repoId: string;
|
|
653
|
-
parentWorkId?: string;
|
|
654
|
-
semanticKey: string;
|
|
655
|
-
operation: SubmittedWorkOperation;
|
|
656
|
-
objective?: string;
|
|
657
|
-
mode?: WorkContract['mode'];
|
|
658
|
-
requestedBy?: WorkContract['requestedBy'];
|
|
659
|
-
principalId?: string;
|
|
660
|
-
controllerInstanceId?: string;
|
|
661
|
-
workKind?: WorkKind;
|
|
662
|
-
risk?: WorkRisk;
|
|
663
|
-
allowedPaths?: string[];
|
|
664
|
-
forbiddenPaths?: string[];
|
|
665
|
-
checks?: string[];
|
|
666
|
-
acceptanceCriteria?: string[];
|
|
667
|
-
constraints?: WorkContract['constraints'];
|
|
668
|
-
}
|
|
669
|
-
|
|
670
694
|
export function getWorkContractByRequestId(
|
|
671
695
|
controllerHome: string,
|
|
672
696
|
requestId: string,
|
|
@@ -687,8 +711,8 @@ export function getWorkContractByRequestId(
|
|
|
687
711
|
}
|
|
688
712
|
|
|
689
713
|
/**
|
|
690
|
-
*
|
|
691
|
-
*
|
|
714
|
+
* Create or reuse the execution-child Work used by typed callers. This is not
|
|
715
|
+
* a generic MCP submission authority and never creates an ExecutionJob.
|
|
692
716
|
*/
|
|
693
717
|
export function acceptSubmittedWorkContract(
|
|
694
718
|
controllerHome: string,
|
|
@@ -696,20 +720,12 @@ export function acceptSubmittedWorkContract(
|
|
|
696
720
|
options: WorkContractStoreOptions = {},
|
|
697
721
|
): { contract: WorkContract; deduplicated: boolean } {
|
|
698
722
|
const home = ensureControllerHome(controllerHome);
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
if (!
|
|
705
|
-
throw new Error('INVALID_ARGUMENT: work_submit requires a semantic operation key');
|
|
706
|
-
}
|
|
707
|
-
if (!input.repoId.trim()) {
|
|
708
|
-
throw new Error('INVALID_ARGUMENT: work_submit is missing required argument(s): repo_id');
|
|
709
|
-
}
|
|
710
|
-
if (!input.operation?.name?.trim()) {
|
|
711
|
-
throw new Error('INVALID_ARGUMENT: work_submit is missing required argument(s): operation');
|
|
712
|
-
}
|
|
723
|
+
const requestId = input.requestId.trim();
|
|
724
|
+
const semanticKey = input.semanticKey.trim();
|
|
725
|
+
if (!requestId) throw new Error('INVALID_ARGUMENT: typed execution is missing request_id');
|
|
726
|
+
if (!semanticKey) throw new Error('INVALID_ARGUMENT: typed execution requires a semantic operation key');
|
|
727
|
+
if (!input.repoId.trim()) throw new Error('INVALID_ARGUMENT: typed execution is missing repo_id');
|
|
728
|
+
if (!input.operation?.name?.trim()) throw new Error('INVALID_ARGUMENT: typed execution is missing operation');
|
|
713
729
|
const parentWorkId = input.parentWorkId?.trim() || undefined;
|
|
714
730
|
if (parentWorkId) {
|
|
715
731
|
const parent = getWorkContract({ controllerHome: home, repoId: input.repoId, now: options.now }, parentWorkId);
|
|
@@ -717,25 +733,17 @@ export function acceptSubmittedWorkContract(
|
|
|
717
733
|
if (isTerminalWorkContractStatus(parent.status)) throw new Error(`PARENT_WORK_TERMINAL: ${parentWorkId}:${parent.status}`);
|
|
718
734
|
if ((parent.lifecycleRole ?? 'primary') !== 'primary') throw new Error(`PARENT_WORK_NOT_PRIMARY: ${parentWorkId}`);
|
|
719
735
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
const recordPath = workRequestIndexPath(home, normalizedRequestId);
|
|
736
|
+
const lockId = createHash('sha256').update(requestId).digest('hex').slice(0, 24);
|
|
737
|
+
return withControllerLock(home, { scope: 'global', resource: `work-request-${lockId}` }, `accept-work:${requestId}`, () => {
|
|
738
|
+
const recordPath = workRequestIndexPath(home, requestId);
|
|
724
739
|
if (existsSync(recordPath)) {
|
|
725
740
|
const record = readJsonFile<WorkRequestIndexRecord>(recordPath);
|
|
726
|
-
if (record.repoId !== input.repoId) {
|
|
727
|
-
|
|
728
|
-
}
|
|
729
|
-
if (record.semanticKey !== normalizedSemanticKey) {
|
|
730
|
-
throw new Error(`REQUEST_ID_CONFLICT: ${normalizedRequestId} already belongs to ${record.semanticKey}`);
|
|
731
|
-
}
|
|
741
|
+
if (record.repoId !== input.repoId) throw new Error(`REQUEST_ID_REPO_CONFLICT: ${requestId} already belongs to repository ${record.repoId}`);
|
|
742
|
+
if (record.semanticKey !== semanticKey) throw new Error(`REQUEST_ID_CONFLICT: ${requestId} already belongs to ${record.semanticKey}`);
|
|
732
743
|
const existing = getWorkContract({ controllerHome: home, repoId: record.repoId, now: options.now }, record.workId);
|
|
733
|
-
if (!existing) {
|
|
734
|
-
throw new Error(`WORK_ACCEPTANCE_LOST: request ${normalizedRequestId} is indexed without a readable WorkContract`);
|
|
735
|
-
}
|
|
744
|
+
if (!existing) throw new Error(`WORK_ACCEPTANCE_LOST: request ${requestId} is indexed without a readable WorkContract`);
|
|
736
745
|
return { contract: existing, deduplicated: true };
|
|
737
746
|
}
|
|
738
|
-
|
|
739
747
|
const contract = createWorkContract({ controllerHome: home, repoId: input.repoId, now: options.now }, {
|
|
740
748
|
workId: `WORK-${Date.now()}-${randomUUID().slice(0, 8)}`,
|
|
741
749
|
repoId: input.repoId,
|
|
@@ -744,7 +752,7 @@ export function acceptSubmittedWorkContract(
|
|
|
744
752
|
mode: input.mode ?? 'direct_control',
|
|
745
753
|
lifecycleRole: 'execution_child',
|
|
746
754
|
parentWorkId,
|
|
747
|
-
objective: (input.objective ?? `
|
|
755
|
+
objective: (input.objective ?? `Typed operation ${input.operation.name}`).slice(0, 2_000),
|
|
748
756
|
acceptanceCriteria: input.acceptanceCriteria ?? [],
|
|
749
757
|
workKind: input.workKind,
|
|
750
758
|
risk: input.risk,
|
|
@@ -754,13 +762,9 @@ export function acceptSubmittedWorkContract(
|
|
|
754
762
|
checks: input.checks ?? [],
|
|
755
763
|
requestedBy: input.requestedBy ?? 'chatgpt',
|
|
756
764
|
status: 'open',
|
|
757
|
-
requestId
|
|
765
|
+
requestId,
|
|
758
766
|
submittedOperation: input.operation,
|
|
759
|
-
driver: {
|
|
760
|
-
preferred: 'external_controller',
|
|
761
|
-
allowWorker: false,
|
|
762
|
-
allowDirectEdit: input.operation.mode === 'readonly' || input.operation.mode === 'mutating',
|
|
763
|
-
},
|
|
767
|
+
driver: { preferred: 'external_controller', allowWorker: false, allowDirectEdit: input.operation.mode === 'readonly' || input.operation.mode === 'mutating' },
|
|
764
768
|
suggestedNextActions: [{
|
|
765
769
|
label: 'Claim controller ownership',
|
|
766
770
|
tool: 'rh_work',
|
|
@@ -770,15 +774,7 @@ export function acceptSubmittedWorkContract(
|
|
|
770
774
|
reason: 'Claim this Work before launching an external SuperController or Process Runtime command.',
|
|
771
775
|
}],
|
|
772
776
|
});
|
|
773
|
-
|
|
774
|
-
writeJsonAtomic(recordPath, {
|
|
775
|
-
requestId: normalizedRequestId,
|
|
776
|
-
repoId: input.repoId,
|
|
777
|
-
workId: contract.workId,
|
|
778
|
-
semanticKey: normalizedSemanticKey,
|
|
779
|
-
createdAt: contract.createdAt,
|
|
780
|
-
} satisfies WorkRequestIndexRecord);
|
|
781
|
-
|
|
777
|
+
writeJsonAtomic(recordPath, { requestId, repoId: input.repoId, workId: contract.workId, semanticKey, createdAt: contract.createdAt } satisfies WorkRequestIndexRecord);
|
|
782
778
|
return { contract, deduplicated: false };
|
|
783
779
|
});
|
|
784
780
|
}
|
|
@@ -965,6 +961,7 @@ function updateWorkContractInternal(
|
|
|
965
961
|
patch: Partial<Omit<WorkContract, 'schemaVersion' | 'workId' | 'repoId' | 'createdAt'>>,
|
|
966
962
|
allowCompletionWrite: boolean,
|
|
967
963
|
allowPhaseWrite = false,
|
|
964
|
+
allowRetainedCancelledResume = false,
|
|
968
965
|
): WorkContract {
|
|
969
966
|
return withWorkContractStoreWrite(options, () => {
|
|
970
967
|
const sanitizedId = sanitizeFileComponent(workId);
|
|
@@ -1022,13 +1019,13 @@ function updateWorkContractInternal(
|
|
|
1022
1019
|
completionOutcome: patch.completionOutcome ?? current.completionOutcome,
|
|
1023
1020
|
evidenceRefs: (patch.evidenceRefs ?? current.evidenceRefs).slice(0, current.evidencePolicy.maxEvidenceRefs),
|
|
1024
1021
|
handoffRefs: (patch.handoffRefs ?? current.handoffRefs).slice(0, 20),
|
|
1025
|
-
suggestedNextActions: (patch.suggestedNextActions ?? current.suggestedNextActions)
|
|
1022
|
+
suggestedNextActions: suggestedActionsForStatus(patch.status ?? current.status, patch.suggestedNextActions ?? current.suggestedNextActions),
|
|
1026
1023
|
policyDecisions: (patch.policyDecisions ?? current.policyDecisions).slice(0, 20),
|
|
1027
1024
|
checkRefs: (patch.checkRefs ?? current.checkRefs).slice(0, 50),
|
|
1028
1025
|
reconciliations: (patch.reconciliations ?? current.reconciliations ?? []).slice(0, 20),
|
|
1029
1026
|
objective: (patch.objective ?? current.objective).slice(0, 2_000),
|
|
1030
1027
|
continuationPrompt: (patch.continuationPrompt ?? current.continuationPrompt)?.slice(0, 2_000),
|
|
1031
|
-
}));
|
|
1028
|
+
}), { allowRetainedCancelledResume });
|
|
1032
1029
|
const contracts = [...store.contracts];
|
|
1033
1030
|
contracts[index] = next;
|
|
1034
1031
|
writeWorkContractStore(options, { schemaVersion: 2, updatedAt: at, contracts });
|
|
@@ -1044,6 +1041,83 @@ export function updateWorkContract(
|
|
|
1044
1041
|
return updateWorkContractInternal(options, workId, patch, false);
|
|
1045
1042
|
}
|
|
1046
1043
|
|
|
1044
|
+
/**
|
|
1045
|
+
* Reopen one explicitly retained cancelled repository Work after the facade has
|
|
1046
|
+
* reauthenticated the same principal and revalidated physical Work ownership.
|
|
1047
|
+
* Generic mutation APIs intentionally cannot perform terminal -> running.
|
|
1048
|
+
*/
|
|
1049
|
+
export function resumeRetainedCancelledWorkContract(
|
|
1050
|
+
options: WorkContractStoreOptions,
|
|
1051
|
+
workId: string,
|
|
1052
|
+
input: {
|
|
1053
|
+
principalId: string;
|
|
1054
|
+
controllerInstanceId: string;
|
|
1055
|
+
summary: string;
|
|
1056
|
+
checkoutId?: string;
|
|
1057
|
+
worktreeRef?: string;
|
|
1058
|
+
},
|
|
1059
|
+
): WorkContract {
|
|
1060
|
+
const current = getWorkContract(options, workId);
|
|
1061
|
+
if (!current) throw new Error(`work contract not found: ${workId}`);
|
|
1062
|
+
if (current.status !== 'cancelled' || current.dispatchState !== 'terminal' || current.phase !== 'cleanup') {
|
|
1063
|
+
throw new Error(`WORK_CANCELLED_RESUME_STATUS_INVALID: ${workId}`);
|
|
1064
|
+
}
|
|
1065
|
+
if (current.workKind !== 'repository_change') throw new Error(`WORK_CANCELLED_RESUME_KIND_INVALID: ${workId}`);
|
|
1066
|
+
if (current.completionReceipt || current.completionOutcome) throw new Error(`WORK_CANCELLED_RESUME_COMPLETION_CONFLICT: ${workId}`);
|
|
1067
|
+
if (current.phaseEvidence.cleanup.state !== 'skipped' || current.phaseEvidence.cleanup.source !== 'recorded') {
|
|
1068
|
+
throw new Error(`WORK_CANCELLED_RESUME_HISTORY_AMBIGUOUS: ${workId}`);
|
|
1069
|
+
}
|
|
1070
|
+
const originalPrincipal = current.principalId?.trim();
|
|
1071
|
+
if (!originalPrincipal || originalPrincipal !== input.principalId.trim()) {
|
|
1072
|
+
throw new Error(`WORK_CANCELLED_RESUME_PRINCIPAL_MISMATCH: ${workId}`);
|
|
1073
|
+
}
|
|
1074
|
+
const at = nowIso(options);
|
|
1075
|
+
const evidenceRefs = [...current.evidenceRefs, {
|
|
1076
|
+
title: 'explicit current-user Work reauthorization',
|
|
1077
|
+
summary: input.summary.trim().slice(0, 1_000),
|
|
1078
|
+
detailLevel: 'summary' as const,
|
|
1079
|
+
}].slice(-current.evidencePolicy.maxEvidenceRefs);
|
|
1080
|
+
const phaseEvidence = transitionPhaseEvidence({ ...current, evidenceRefs }, 'implementation', {
|
|
1081
|
+
status: 'running',
|
|
1082
|
+
summary: input.summary,
|
|
1083
|
+
evidenceRefs,
|
|
1084
|
+
recordedAt: at,
|
|
1085
|
+
source: 'recorded',
|
|
1086
|
+
});
|
|
1087
|
+
return updateWorkContractInternal(options, workId, {
|
|
1088
|
+
status: 'running',
|
|
1089
|
+
phase: 'implementation',
|
|
1090
|
+
phaseEvidence,
|
|
1091
|
+
dispatchState: 'running',
|
|
1092
|
+
evidenceRefs,
|
|
1093
|
+
checkoutId: input.checkoutId ?? current.checkoutId,
|
|
1094
|
+
worktreeRef: input.worktreeRef ?? current.worktreeRef,
|
|
1095
|
+
controllerInstanceId: input.controllerInstanceId,
|
|
1096
|
+
continuationPrompt: input.summary,
|
|
1097
|
+
}, false, true, true);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/** Merge non-authoritative discovery/change evidence without changing policy fences. */
|
|
1101
|
+
export function recordWorkScopeEvidence(
|
|
1102
|
+
options: WorkContractStoreOptions,
|
|
1103
|
+
workId: string,
|
|
1104
|
+
input: { initialLikelyPaths?: string[]; inspectedPaths?: string[]; actualChangedPaths?: string[] },
|
|
1105
|
+
): WorkContract {
|
|
1106
|
+
const current = getWorkContract(options, workId);
|
|
1107
|
+
if (!current) throw new Error(`work contract not found: ${workId}`);
|
|
1108
|
+
const previous = current.scopeEvidence ?? {
|
|
1109
|
+
initialLikelyPaths: [], inspectedPaths: [], actualChangedPaths: [], recordedAt: current.createdAt,
|
|
1110
|
+
};
|
|
1111
|
+
return updateWorkContract(options, workId, {
|
|
1112
|
+
scopeEvidence: {
|
|
1113
|
+
initialLikelyPaths: [...new Set([...previous.initialLikelyPaths, ...(input.initialLikelyPaths ?? [])])].slice(0, 100),
|
|
1114
|
+
inspectedPaths: [...new Set([...previous.inspectedPaths, ...(input.inspectedPaths ?? [])])].slice(0, 500),
|
|
1115
|
+
actualChangedPaths: [...new Set([...previous.actualChangedPaths, ...(input.actualChangedPaths ?? [])])].slice(0, 500),
|
|
1116
|
+
recordedAt: nowIso(options),
|
|
1117
|
+
},
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1047
1121
|
export function transitionWorkContractPhase(
|
|
1048
1122
|
options: WorkContractStoreOptions,
|
|
1049
1123
|
workId: string,
|
|
@@ -1126,6 +1200,9 @@ export function recordWorkCompletionReceipt(
|
|
|
1126
1200
|
return current;
|
|
1127
1201
|
}
|
|
1128
1202
|
const recordedAt = receipt.recordedAt;
|
|
1203
|
+
const receiptChangedPaths = isRepositoryCompletionReceipt(receipt) || isDirectEditWorkCompletionReceipt(receipt)
|
|
1204
|
+
? receipt.changedPaths
|
|
1205
|
+
: [];
|
|
1129
1206
|
const phaseEvidence = Object.fromEntries((['implementation', 'verification', 'delivery', 'cleanup'] as WorkPhase[]).map((phase) => [phase, {
|
|
1130
1207
|
state: 'satisfied',
|
|
1131
1208
|
source: 'recorded',
|
|
@@ -1143,5 +1220,30 @@ export function recordWorkCompletionReceipt(
|
|
|
1143
1220
|
completionOutcome,
|
|
1144
1221
|
...(completionWorkKind ? { workKind: completionWorkKind } : {}),
|
|
1145
1222
|
completionReceipt: receipt,
|
|
1223
|
+
scopeEvidence: {
|
|
1224
|
+
initialLikelyPaths: current.scopeEvidence?.initialLikelyPaths ?? current.allowedPaths,
|
|
1225
|
+
inspectedPaths: current.scopeEvidence?.inspectedPaths ?? [],
|
|
1226
|
+
actualChangedPaths: [...new Set(receiptChangedPaths)].slice(0, 500),
|
|
1227
|
+
recordedAt,
|
|
1228
|
+
},
|
|
1146
1229
|
}, true, true);
|
|
1147
1230
|
}
|
|
1231
|
+
export interface AcceptSubmittedWorkInput {
|
|
1232
|
+
requestId: string;
|
|
1233
|
+
repoId: string;
|
|
1234
|
+
parentWorkId?: string;
|
|
1235
|
+
semanticKey: string;
|
|
1236
|
+
operation: SubmittedWorkOperation;
|
|
1237
|
+
objective?: string;
|
|
1238
|
+
mode?: WorkContract['mode'];
|
|
1239
|
+
requestedBy?: WorkContract['requestedBy'];
|
|
1240
|
+
principalId?: string;
|
|
1241
|
+
controllerInstanceId?: string;
|
|
1242
|
+
workKind?: WorkKind;
|
|
1243
|
+
risk?: WorkRisk;
|
|
1244
|
+
allowedPaths?: string[];
|
|
1245
|
+
forbiddenPaths?: string[];
|
|
1246
|
+
checks?: string[];
|
|
1247
|
+
acceptanceCriteria?: string[];
|
|
1248
|
+
constraints?: WorkContract['constraints'];
|
|
1249
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { execFileSync } from 'child_process';
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
lstatSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
realpathSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
} from 'fs';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { basename, join, relative, resolve, sep } from 'path';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_GENERATED_CACHE_GRACE_MS = 6 * 60 * 60_000;
|
|
13
|
+
const DEFAULT_SCAN_BUDGET = 1_000;
|
|
14
|
+
const DEFAULT_REMOVAL_BUDGET = 8;
|
|
15
|
+
|
|
16
|
+
export interface GeneratedCacheRetentionOptions {
|
|
17
|
+
nowMs?: number;
|
|
18
|
+
graceMs?: number;
|
|
19
|
+
maxEntries?: number;
|
|
20
|
+
maxRemovals?: number;
|
|
21
|
+
processCommands?: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface GeneratedCacheRetentionReport {
|
|
25
|
+
inspected: number;
|
|
26
|
+
eligible: number;
|
|
27
|
+
removedPaths: string[];
|
|
28
|
+
retainedPaths: string[];
|
|
29
|
+
skippedByReason: Record<string, number>;
|
|
30
|
+
errors: string[];
|
|
31
|
+
budgetExhausted: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface XCTestDeviceCleanupReport {
|
|
35
|
+
attempted: boolean;
|
|
36
|
+
removedDevices: number;
|
|
37
|
+
skippedReason?: string;
|
|
38
|
+
error?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface Candidate {
|
|
42
|
+
path: string;
|
|
43
|
+
kind: 'forge_ios_derived_data' | 'harness_derived_data' | 'repo_harness_derived_data' | 'tmp_derived_data';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function increment(counts: Record<string, number>, reason: string): void {
|
|
47
|
+
counts[reason] = (counts[reason] ?? 0) + 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function canonical(path: string): string {
|
|
51
|
+
try { return realpathSync(path); } catch { return resolve(path); }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function pathInside(root: string, path: string): boolean {
|
|
55
|
+
const base = canonical(root);
|
|
56
|
+
const target = canonical(path);
|
|
57
|
+
const rel = relative(base, target);
|
|
58
|
+
return rel !== '' && rel !== '..' && !rel.startsWith(`..${sep}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function collectProcessCommands(): string[] {
|
|
62
|
+
try {
|
|
63
|
+
return execFileSync('ps', ['-axo', 'command='], {
|
|
64
|
+
encoding: 'utf8',
|
|
65
|
+
timeout: 2_000,
|
|
66
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
67
|
+
}).split('\n').map((line) => line.trim()).filter(Boolean);
|
|
68
|
+
} catch {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function commandReferencesPath(commands: readonly string[], repoRoot: string, path: string): boolean {
|
|
74
|
+
const absolute = canonical(path);
|
|
75
|
+
const rel = relative(resolve(repoRoot), absolute).replace(/\\/g, '/');
|
|
76
|
+
return commands.some((command) => command.includes(absolute) || (rel && command.includes(rel)));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function containsTrackedFiles(repoRoot: string, path: string): boolean {
|
|
80
|
+
const rel = relative(resolve(repoRoot), resolve(path));
|
|
81
|
+
if (!rel || rel === '..' || rel.startsWith(`..${sep}`)) return true;
|
|
82
|
+
try {
|
|
83
|
+
const output = execFileSync('git', ['-C', repoRoot, 'ls-files', '--', rel], {
|
|
84
|
+
encoding: 'utf8',
|
|
85
|
+
timeout: 2_000,
|
|
86
|
+
maxBuffer: 256 * 1024,
|
|
87
|
+
});
|
|
88
|
+
return output.trim().length > 0;
|
|
89
|
+
} catch {
|
|
90
|
+
// If repository identity cannot be proven, fail closed.
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function directChildDirectories(root: string, predicate: (name: string) => boolean, budget: { remaining: number }): string[] {
|
|
96
|
+
if (!existsSync(root) || budget.remaining <= 0) return [];
|
|
97
|
+
const found: string[] = [];
|
|
98
|
+
let entries;
|
|
99
|
+
try { entries = readdirSync(root, { withFileTypes: true }); } catch { return found; }
|
|
100
|
+
for (const entry of entries) {
|
|
101
|
+
if (budget.remaining <= 0) break;
|
|
102
|
+
budget.remaining -= 1;
|
|
103
|
+
const name = String(entry.name);
|
|
104
|
+
if (entry.isDirectory() && predicate(name)) found.push(join(root, name));
|
|
105
|
+
}
|
|
106
|
+
return found;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function nestedDerivedDataDirectories(root: string, maxDepth: number, budget: { remaining: number }): string[] {
|
|
110
|
+
if (!existsSync(root) || budget.remaining <= 0) return [];
|
|
111
|
+
const found: string[] = [];
|
|
112
|
+
const visit = (dir: string, depth: number): void => {
|
|
113
|
+
if (depth > maxDepth || budget.remaining <= 0) return;
|
|
114
|
+
let entries;
|
|
115
|
+
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
if (budget.remaining <= 0) return;
|
|
118
|
+
budget.remaining -= 1;
|
|
119
|
+
if (!entry.isDirectory()) continue;
|
|
120
|
+
const path = join(dir, String(entry.name));
|
|
121
|
+
if (String(entry.name) === 'DerivedData') {
|
|
122
|
+
found.push(path);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
visit(path, depth + 1);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
visit(root, 0);
|
|
129
|
+
return found;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function discoverCandidates(repoRoot: string, maxEntries: number): { candidates: Candidate[]; inspected: number; truncated: boolean } {
|
|
133
|
+
const root = resolve(repoRoot);
|
|
134
|
+
const budget = { remaining: Math.max(1, maxEntries) };
|
|
135
|
+
const initial = budget.remaining;
|
|
136
|
+
const candidates: Candidate[] = [];
|
|
137
|
+
|
|
138
|
+
for (const path of directChildDirectories(join(root, '.forge', 'ios', 'DerivedData'), () => true, budget)) {
|
|
139
|
+
candidates.push({ path, kind: 'forge_ios_derived_data' });
|
|
140
|
+
}
|
|
141
|
+
for (const path of directChildDirectories(join(root, '.ai', 'harness'), (name) => name.startsWith('deriveddata-'), budget)) {
|
|
142
|
+
candidates.push({ path, kind: 'harness_derived_data' });
|
|
143
|
+
}
|
|
144
|
+
for (const path of nestedDerivedDataDirectories(join(root, '.repo-harness'), 5, budget)) {
|
|
145
|
+
candidates.push({ path, kind: 'repo_harness_derived_data' });
|
|
146
|
+
}
|
|
147
|
+
for (const path of nestedDerivedDataDirectories(join(root, '.tmp'), 6, budget)) {
|
|
148
|
+
candidates.push({ path, kind: 'tmp_derived_data' });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
candidates: candidates.filter((candidate, index, values) => values.findIndex((item) => resolve(item.path) === resolve(candidate.path)) === index),
|
|
153
|
+
inspected: initial - budget.remaining,
|
|
154
|
+
truncated: budget.remaining <= 0,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Reclaim only rebuildable Xcode DerivedData under Forge/repo-harness-owned
|
|
160
|
+
* namespaces. Result bundles, logs, edit-session metadata, and all tracked files
|
|
161
|
+
* are deliberately outside this cleanup surface.
|
|
162
|
+
*/
|
|
163
|
+
export function cleanupGeneratedRepositoryCaches(
|
|
164
|
+
repoRoot: string,
|
|
165
|
+
options: GeneratedCacheRetentionOptions = {},
|
|
166
|
+
): GeneratedCacheRetentionReport {
|
|
167
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
168
|
+
const graceMs = Math.max(60_000, Math.floor(options.graceMs ?? DEFAULT_GENERATED_CACHE_GRACE_MS));
|
|
169
|
+
const maxEntries = Math.max(1, Math.floor(options.maxEntries ?? DEFAULT_SCAN_BUDGET));
|
|
170
|
+
let remainingRemovals = Math.max(1, Math.floor(options.maxRemovals ?? DEFAULT_REMOVAL_BUDGET));
|
|
171
|
+
const commands = options.processCommands ?? collectProcessCommands();
|
|
172
|
+
const discovered = discoverCandidates(repoRoot, maxEntries);
|
|
173
|
+
const report: GeneratedCacheRetentionReport = {
|
|
174
|
+
inspected: discovered.inspected,
|
|
175
|
+
eligible: 0,
|
|
176
|
+
removedPaths: [],
|
|
177
|
+
retainedPaths: [],
|
|
178
|
+
skippedByReason: {},
|
|
179
|
+
errors: [],
|
|
180
|
+
budgetExhausted: discovered.truncated,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
for (const candidate of discovered.candidates) {
|
|
184
|
+
const ownedRoot = candidate.kind === 'forge_ios_derived_data'
|
|
185
|
+
? join(repoRoot, '.forge', 'ios', 'DerivedData')
|
|
186
|
+
: candidate.kind === 'harness_derived_data'
|
|
187
|
+
? join(repoRoot, '.ai', 'harness')
|
|
188
|
+
: candidate.kind === 'repo_harness_derived_data'
|
|
189
|
+
? join(repoRoot, '.repo-harness')
|
|
190
|
+
: join(repoRoot, '.tmp');
|
|
191
|
+
try {
|
|
192
|
+
const stat = lstatSync(candidate.path);
|
|
193
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || !pathInside(ownedRoot, candidate.path)) {
|
|
194
|
+
increment(report.skippedByReason, 'ownership_unproven');
|
|
195
|
+
report.retainedPaths.push(relative(repoRoot, candidate.path));
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (nowMs - stat.mtimeMs < graceMs) {
|
|
199
|
+
increment(report.skippedByReason, 'retention_grace');
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (commandReferencesPath(commands, repoRoot, candidate.path)) {
|
|
203
|
+
increment(report.skippedByReason, 'active_process');
|
|
204
|
+
report.retainedPaths.push(relative(repoRoot, candidate.path));
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (containsTrackedFiles(repoRoot, candidate.path)) {
|
|
208
|
+
increment(report.skippedByReason, 'tracked_content');
|
|
209
|
+
report.retainedPaths.push(relative(repoRoot, candidate.path));
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
report.eligible += 1;
|
|
213
|
+
if (remainingRemovals <= 0) {
|
|
214
|
+
report.budgetExhausted = true;
|
|
215
|
+
increment(report.skippedByReason, 'cleanup_budget_exhausted');
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
remainingRemovals -= 1;
|
|
219
|
+
rmSync(candidate.path, { recursive: true, force: true });
|
|
220
|
+
report.removedPaths.push(relative(repoRoot, candidate.path).replace(/\\/g, '/'));
|
|
221
|
+
} catch (error) {
|
|
222
|
+
report.errors.push(`${candidate.kind}:${relative(repoRoot, candidate.path)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return report;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* XCTest's private device set is separate from the user's normal CoreSimulator
|
|
231
|
+
* devices. When Forge is idle, delete only that dedicated set through simctl so
|
|
232
|
+
* XCTest clones cannot accumulate indefinitely. Never rm the device set.
|
|
233
|
+
*/
|
|
234
|
+
export function cleanupIdleXCTestDevices(controllerHome: string): XCTestDeviceCleanupReport {
|
|
235
|
+
if (process.platform !== 'darwin') return { attempted: false, removedDevices: 0, skippedReason: 'not_darwin' };
|
|
236
|
+
const normalizedControllerHome = resolve(controllerHome).replace(/\\/g, '/');
|
|
237
|
+
if (!normalizedControllerHome.includes('/.forge/controller')) {
|
|
238
|
+
return { attempted: false, removedDevices: 0, skippedReason: 'nonstandard_controller_home' };
|
|
239
|
+
}
|
|
240
|
+
const deviceSet = join(homedir(), 'Library', 'Developer', 'XCTestDevices');
|
|
241
|
+
if (!existsSync(deviceSet)) return { attempted: false, removedDevices: 0, skippedReason: 'device_set_missing' };
|
|
242
|
+
let deviceDirectories = 0;
|
|
243
|
+
try {
|
|
244
|
+
deviceDirectories = readdirSync(deviceSet, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length;
|
|
245
|
+
} catch (error) {
|
|
246
|
+
return { attempted: false, removedDevices: 0, error: error instanceof Error ? error.message : String(error) };
|
|
247
|
+
}
|
|
248
|
+
if (deviceDirectories <= 4) return { attempted: false, removedDevices: 0, skippedReason: 'below_cleanup_threshold' };
|
|
249
|
+
|
|
250
|
+
const commands = collectProcessCommands();
|
|
251
|
+
if (commands.some((command) => /(?:^|\/)(?:xcodebuild|xctest|swiftc)(?:\s|$)/.test(command))) {
|
|
252
|
+
return { attempted: false, removedDevices: 0, skippedReason: 'active_xcode_process' };
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
const listed = execFileSync('xcrun', ['simctl', '--set', deviceSet, 'list', 'devices', '--json'], {
|
|
256
|
+
encoding: 'utf8',
|
|
257
|
+
timeout: 10_000,
|
|
258
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
259
|
+
});
|
|
260
|
+
const parsed = JSON.parse(listed) as { devices?: Record<string, Array<{ state?: string }>> };
|
|
261
|
+
const devices = Object.values(parsed.devices ?? {}).flat();
|
|
262
|
+
if (devices.some((device) => device.state === 'Booted')) {
|
|
263
|
+
return { attempted: false, removedDevices: 0, skippedReason: 'booted_xctest_device' };
|
|
264
|
+
}
|
|
265
|
+
execFileSync('xcrun', ['simctl', '--set', deviceSet, 'shutdown', 'all'], { timeout: 15_000, stdio: 'ignore' });
|
|
266
|
+
execFileSync('xcrun', ['simctl', '--set', deviceSet, 'delete', 'all'], { timeout: 30_000, stdio: 'ignore' });
|
|
267
|
+
return { attempted: true, removedDevices: devices.length };
|
|
268
|
+
} catch (error) {
|
|
269
|
+
return { attempted: true, removedDevices: 0, error: error instanceof Error ? error.message : String(error) };
|
|
270
|
+
}
|
|
271
|
+
}
|