@moretea-labs/forge 1.6.0 → 1.7.1
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 +4 -0
- package/CHANGELOG.md +39 -0
- package/CLAUDE.md +4 -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/02-operating-mode.partial.md +2 -0
- package/assets/partials-agents/03-orchestration.partial.md +1 -1
- package/assets/plugin-registry.v1.json +3 -3
- 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-architecture-sync.sh +17 -0
- 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/inspect-project-state.ts +8 -6
- package/assets/templates/helpers/migrate-project-template.sh +22 -45
- package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
- 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/helpers/workflow-contract.ts +5 -1
- 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 +178 -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-plugin-management.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 +143 -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 +33 -11
- package/scripts/activate-source-baseline.ts +17 -13
- package/scripts/architecture-event.ts +2 -820
- package/scripts/architecture-queue.sh +2 -645
- package/scripts/archive-architecture-request.sh +2 -242
- package/scripts/archive-workflow.sh +1 -291
- 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/capability-config.ts +2 -392
- package/scripts/capability-resolver.ts +2 -503
- package/scripts/capture-plan.sh +1 -332
- package/scripts/check-agent-tooling.sh +2 -1558
- package/scripts/check-architecture-sync.sh +2 -371
- package/scripts/check-brain-manifest.sh +2 -253
- package/scripts/check-context-files.sh +2 -47
- package/scripts/check-deploy-sql-order.sh +2 -115
- package/scripts/check-mcp-compatibility.ts +14 -0
- package/scripts/check-runtime-architecture.mjs +349 -79
- package/scripts/check-skill-version.ts +3 -191
- package/scripts/check-source-duplication.mjs +61 -0
- package/scripts/check-stable-baseline.ts +92 -0
- package/scripts/check-task-sync.sh +2 -63
- package/scripts/check-task-workflow.sh +2 -1000
- package/scripts/codex-handoff-resume.sh +2 -267
- package/scripts/context-contract-sync.sh +2 -514
- package/scripts/contract-run.ts +2 -463
- package/scripts/contract-worktree.sh +1 -722
- package/scripts/ensure-task-workflow.sh +1 -1225
- package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
- package/scripts/inspect-project-state.ts +4 -279
- package/scripts/install-standalone-recovery.ts +24 -0
- package/scripts/lib/project-init-lib.sh +22 -46
- package/scripts/lib/run-forge-helper.sh +19 -0
- package/scripts/lib/run-forge-helper.ts +26 -0
- package/scripts/migrate-project-template.sh +1 -1189
- package/scripts/migrate-workflow-docs.ts +4 -412
- package/scripts/new-plan.sh +1 -180
- package/scripts/new-spec.sh +1 -47
- package/scripts/new-sprint.sh +1 -46
- package/scripts/plan-to-todo.sh +1 -796
- package/scripts/prepare-codex-handoff.sh +2 -201
- package/scripts/prepare-handoff.sh +1 -78
- package/scripts/refresh-current-status.sh +2 -467
- package/scripts/run-governed-gate.ts +5 -1
- package/scripts/select-agent-context-blocks.sh +2 -64
- package/scripts/ship-worktrees.sh +1 -558
- 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/sprint-backlog.sh +1 -834
- package/scripts/stage-runtime-release.ts +2 -1
- package/scripts/summarize-failures.sh +2 -123
- package/scripts/switch-plan.sh +1 -150
- package/scripts/sync-brain-docs.sh +2 -379
- package/scripts/sync-generated-authority.mjs +175 -0
- package/scripts/verify-contract.sh +2 -703
- package/scripts/verify-forge-runtime.sh +0 -1
- package/scripts/verify-sprint.sh +1 -677
- package/scripts/workflow-contract.ts +4 -205
- package/scripts/workstream-sync.sh +2 -261
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/commands/init-hook.ts +3 -3
- package/src/cli/commands/mcp.ts +9 -5
- package/src/cli/commands/plugin.ts +12 -3
- 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 +113 -5
- 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 +828 -487
- 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/contracts.ts +16 -0
- package/src/cli/github/github.ts +135 -16
- package/src/cli/github/session.ts +2 -1
- 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 +37 -264
- 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 +2 -515
- 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 +10 -222
- package/src/cli/mcp/multi-repository.ts +16 -5
- package/src/cli/mcp/oauth.ts +175 -26
- package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
- package/src/cli/mcp/repository-tools.ts +282 -146
- package/src/cli/mcp/server.ts +586 -116
- package/src/cli/mcp/tool-capability-cache.ts +1 -1
- package/src/cli/mcp/tool-contract.ts +14 -0
- package/src/cli/mcp/toolset-selection.ts +1 -1
- package/src/cli/mcp/toolset.ts +44 -7
- package/src/cli/mcp/transports/http.ts +124 -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 +103 -21
- package/src/cli/repositories/command-executor.ts +78 -526
- 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 +124 -0
- package/src/cli/repositories/selected-path-actions.ts +17 -9
- package/src/cli/repositories/structured-git.ts +118 -6
- package/src/cli/repository/inspector.ts +563 -65
- 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-contract.ts +61 -0
- package/src/runtime/context/semantic-navigation.ts +223 -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 +48 -16
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
- package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
- 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-completion-authority.ts +58 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
- package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
- package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
- package/src/runtime/control-plane/execution/work-handle-authority.ts +96 -0
- 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-operation-service.ts +468 -0
- package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
- package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
- package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +22 -18
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +156 -11
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
- package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
- package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +90 -9
- package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
- package/src/runtime/control-plane/facade/controller-round-relay.ts +1118 -0
- package/src/runtime/control-plane/facade/controller-session-store.ts +302 -12
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +17 -1
- package/src/runtime/control-plane/facade/goal-workloop.ts +928 -190
- package/src/runtime/control-plane/facade/index.ts +3 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +275 -42
- package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
- package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
- 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 +90 -10
- package/src/runtime/control-plane/facade/work-contract-store.ts +157 -261
- package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
- 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 +152 -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 +278 -543
- 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 +63 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +365 -117
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +89 -0
- package/src/runtime/control-plane/launcher/thin-launcher.ts +244 -11
- package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
- package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
- 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/repo-actor/registry.ts +4 -2
- package/src/runtime/control-plane/routing/route-policy.ts +24 -21
- package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
- 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/evidence/process-check-execution.ts +18 -0
- package/src/runtime/execution/jobs/child-reference.ts +6 -2
- 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 +88 -0
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
- package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +367 -40
- package/src/runtime/execution/process-runtime/gc.ts +38 -6
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +1201 -0
- package/src/runtime/execution/process-runtime/persisted-check.ts +51 -5
- package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
- package/src/runtime/execution/process-runtime/runtime.ts +53 -3
- package/src/runtime/execution/process-runtime/store.ts +1 -0
- package/src/runtime/execution/process-runtime/types.ts +4 -12
- 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/work-activity.ts +21 -0
- package/src/runtime/execution/workers/executor.ts +18 -61
- package/src/runtime/gateway/mcp/execution-tools.ts +35 -1852
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
- package/src/runtime/gateway/mcp/process-tools.ts +83 -37
- package/src/runtime/gateway/mcp/router.ts +186 -84
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +565 -0
- package/src/runtime/gateway/mcp/runtime-tools.ts +2378 -1947
- package/src/runtime/plugins/app-store-connect-adapter.ts +216 -47
- package/src/runtime/plugins/browser-adapter.ts +2662 -829
- package/src/runtime/plugins/browser-automation-service.ts +10 -0
- package/src/runtime/plugins/browser-handoff-host.ts +2 -25
- package/src/runtime/plugins/browser-handoff.ts +110 -5
- package/src/runtime/plugins/browser-macos-bridge.ts +701 -63
- package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
- package/src/runtime/plugins/browser-native-session-service.ts +222 -0
- 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 +154 -0
- package/src/runtime/plugins/browser-runtime.ts +193 -0
- package/src/runtime/plugins/browser-session-authority.ts +332 -0
- package/src/runtime/plugins/browser-session-store.ts +121 -0
- package/src/runtime/plugins/browser-session-types.ts +98 -0
- package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +10 -4
- package/src/runtime/plugins/external-adapter.ts +358 -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 +47 -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 +330 -33
- package/src/runtime/plugins/types.ts +38 -3
- 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 +426 -25
- package/src/runtime/root/mcp-transport.ts +180 -10
- package/src/runtime/root/package-connector-service.ts +230 -10
- package/src/runtime/root/package-runtime-release.ts +143 -17
- package/src/runtime/root/package-runtime-service.ts +461 -22
- package/src/runtime/root/release-manifest.ts +144 -18
- package/src/runtime/root/release-materialize.ts +125 -32
- 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 +12 -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 +1079 -240
- package/src/runtime/standalone-recovery/entry.ts +100 -24
- package/src/runtime/standalone-recovery/installer.ts +20 -4
- 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 +364 -202
- package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
- package/src/runtime/workflow/schedules/settlement.ts +64 -17
- package/src/runtime/workflow/schedules/store.ts +209 -19
- 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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, rmSync } from 'fs';
|
|
2
|
-
import { dirname, join } from 'path';
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync, rmSync } from 'fs';
|
|
2
|
+
import { basename, dirname, join } from 'path';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
import { runProcess } from '../../effects/process-runner';
|
|
5
5
|
import { isProcessAlive } from '../../runtime/shared/process-tree';
|
|
@@ -341,6 +341,81 @@ export interface LaunchAgentLifecycleResult {
|
|
|
341
341
|
diagnostics: string[];
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
+
export interface RetiredConflictingLaunchAgent {
|
|
345
|
+
label: string;
|
|
346
|
+
plistPath: string;
|
|
347
|
+
backupPath: string;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function decodePlistString(value: string): string {
|
|
351
|
+
return value
|
|
352
|
+
.replaceAll('<', '<')
|
|
353
|
+
.replaceAll('>', '>')
|
|
354
|
+
.replaceAll('"', '"')
|
|
355
|
+
.replaceAll(''', "'")
|
|
356
|
+
.replaceAll('&', '&');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function readLaunchAgentIdentity(path: string): { label: string; arguments: string[] } | undefined {
|
|
360
|
+
let content: string;
|
|
361
|
+
try { content = readFileSync(path, 'utf8'); } catch { return undefined; }
|
|
362
|
+
const label = content.match(/<key>\s*Label\s*<\/key>\s*<string>([\s\S]*?)<\/string>/)?.[1];
|
|
363
|
+
const argumentsBlock = content.match(/<key>\s*ProgramArguments\s*<\/key>\s*<array>([\s\S]*?)<\/array>/)?.[1];
|
|
364
|
+
if (!label || !argumentsBlock) return undefined;
|
|
365
|
+
const args = Array.from(argumentsBlock.matchAll(/<string>([\s\S]*?)<\/string>/g), (match) => decodePlistString(match[1] ?? ''));
|
|
366
|
+
return { label: decodePlistString(label), arguments: args };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Retire Forge-owned launchd entries that would compete for the same listener.
|
|
371
|
+
* A Controller Home hash makes labels stable, but it must not make two local
|
|
372
|
+
* lifecycle owners valid for one port after a migration or reboot.
|
|
373
|
+
*/
|
|
374
|
+
export async function retireConflictingForgeLaunchAgents(
|
|
375
|
+
input: {
|
|
376
|
+
accountHome?: string;
|
|
377
|
+
desiredLabel: string;
|
|
378
|
+
labelPrefix: string;
|
|
379
|
+
port: number;
|
|
380
|
+
requiredArguments: string[];
|
|
381
|
+
},
|
|
382
|
+
dependencies: {
|
|
383
|
+
bootout?: typeof bootoutLaunchAgentWithRetryV2;
|
|
384
|
+
now?: () => number;
|
|
385
|
+
} = {},
|
|
386
|
+
): Promise<RetiredConflictingLaunchAgent[]> {
|
|
387
|
+
const accountHome = input.accountHome?.trim() || process.env.HOME?.trim() || homedir();
|
|
388
|
+
const launchAgentsRoot = join(accountHome, 'Library', 'LaunchAgents');
|
|
389
|
+
if (!existsSync(launchAgentsRoot)) return [];
|
|
390
|
+
const retiredRoot = join(launchAgentsRoot, '.forge-retired');
|
|
391
|
+
const bootout = dependencies.bootout ?? bootoutLaunchAgentWithRetryV2;
|
|
392
|
+
const retired: RetiredConflictingLaunchAgent[] = [];
|
|
393
|
+
const expectedPort = String(input.port);
|
|
394
|
+
const normalizedLabelPrefix = input.labelPrefix.replace(/\.+$/, '');
|
|
395
|
+
|
|
396
|
+
for (const entry of readdirSync(launchAgentsRoot).sort()) {
|
|
397
|
+
if (!entry.endsWith('.plist')) continue;
|
|
398
|
+
const plistPath = join(launchAgentsRoot, entry);
|
|
399
|
+
const identity = readLaunchAgentIdentity(plistPath);
|
|
400
|
+
const forgeOwnedLabel = identity?.label === normalizedLabelPrefix || identity?.label.startsWith(`${normalizedLabelPrefix}.`);
|
|
401
|
+
if (!identity || identity.label === input.desiredLabel || !forgeOwnedLabel) continue;
|
|
402
|
+
if (basename(plistPath, '.plist') !== identity.label) continue;
|
|
403
|
+
if (!input.requiredArguments.every((argument) => identity.arguments.includes(argument))) continue;
|
|
404
|
+
const portIndex = identity.arguments.indexOf('--port');
|
|
405
|
+
if (portIndex < 0 || identity.arguments[portIndex + 1] !== expectedPort) continue;
|
|
406
|
+
|
|
407
|
+
const stopped = await bootout({ label: identity.label, plistPath });
|
|
408
|
+
if (!stopped.ok) {
|
|
409
|
+
throw new Error(`FORGE_LAUNCH_AGENT_CONFLICT_RETIRE_FAILED: ${identity.label}: ${stopped.diagnostics.join('; ')}`);
|
|
410
|
+
}
|
|
411
|
+
mkdirSync(retiredRoot, { recursive: true, mode: 0o700 });
|
|
412
|
+
const backupPath = join(retiredRoot, `${identity.label}.${(dependencies.now ?? Date.now)()}.plist`);
|
|
413
|
+
renameSync(plistPath, backupPath);
|
|
414
|
+
retired.push({ label: identity.label, plistPath, backupPath });
|
|
415
|
+
}
|
|
416
|
+
return retired;
|
|
417
|
+
}
|
|
418
|
+
|
|
344
419
|
export function currentUserLaunchdDomain(): string {
|
|
345
420
|
const uid = typeof process.getuid === 'function'
|
|
346
421
|
? process.getuid()
|
|
@@ -195,8 +195,8 @@ export function buildControllerOperationalPlan(repoRoot: string, ledger: TaskLed
|
|
|
195
195
|
},
|
|
196
196
|
mcpToolSchemaConvergence: {
|
|
197
197
|
source: "mcp-tool-schema-convergence",
|
|
198
|
-
readModels: ["controller_context", "
|
|
199
|
-
commandModels: ["
|
|
198
|
+
readModels: ["controller_context", "rh_context", "work_status_digest", "prepare_transfer_artifacts"],
|
|
199
|
+
commandModels: ["rh_work", "run_check", "repository_command_execute", "plugin_action_execute", "repository_safe_patch_apply"],
|
|
200
200
|
compatibilityRules: [
|
|
201
201
|
"compact reads are default",
|
|
202
202
|
"raw code and raw logs require explicit opt-in tools",
|
|
@@ -84,8 +84,8 @@ function behaviorFor(mode: EffectiveTaskMode): WorkModeAssessment['modeBehavior'
|
|
|
84
84
|
workflow: ['verify checks and changelog', 'build immutable complete release', 'run integration and deployment gates', 'activate through Recovery', 'verify rollback evidence'],
|
|
85
85
|
};
|
|
86
86
|
case 'scale': return {
|
|
87
|
-
structuralContext: 'off', mutationPhase: '
|
|
88
|
-
workflow: ['
|
|
87
|
+
structuralContext: 'off', mutationPhase: 'coordinate', issueRequired: false, planRequired: true, worktreeRequired: true,
|
|
88
|
+
workflow: ['decompose independent deliverables in an approved Plan', 'start bounded isolated Work per Plan step', 'run independent units concurrently when resource claims permit', 'verify deterministic conflict handling and publish coordination evidence'],
|
|
89
89
|
};
|
|
90
90
|
default: return {
|
|
91
91
|
structuralContext: 'off', mutationPhase: 'execute', issueRequired: false, planRequired: false, worktreeRequired: false,
|
|
@@ -149,7 +149,9 @@ export function assessWorkMode(input: WorkModeAssessmentInput): WorkModeAssessme
|
|
|
149
149
|
},
|
|
150
150
|
capabilities: { requiresWorker: input.agentRequested },
|
|
151
151
|
recovery: {
|
|
152
|
-
|
|
152
|
+
// Debugging expands evidence through rh_context; it does not itself need
|
|
153
|
+
// durable continuation or command recovery.
|
|
154
|
+
required: input.requiresRecovery || explicitMode === 'release' || explicitMode === 'scale',
|
|
153
155
|
isolationRequired: explicitMode === 'direct' ? false : input.requiresWorkerIsolation || parallelMode,
|
|
154
156
|
},
|
|
155
157
|
});
|
|
@@ -162,7 +164,7 @@ export function assessWorkMode(input: WorkModeAssessmentInput): WorkModeAssessme
|
|
|
162
164
|
confidence: input.requiresInvestigation || (input.knownPaths?.length ?? 0) === 0 ? 'medium' : 'high',
|
|
163
165
|
reasons: routeDecision.reasons.map((reason) => reason.message),
|
|
164
166
|
nextTools: explicitMode === 'plan' || explicitMode === 'debug'
|
|
165
|
-
? ['
|
|
167
|
+
? ['rh_context(search.structural_context=required)', ...modeBehavior.workflow]
|
|
166
168
|
: explicitMode === 'review'
|
|
167
169
|
? ['repository_git_status', 'repository_diff', ...modeBehavior.workflow]
|
|
168
170
|
: explicitMode === 'release'
|
|
@@ -12,6 +12,7 @@ import { tryAppendControllerWorklogEvent } from '../controller/worklog';
|
|
|
12
12
|
import { globMatches, resolveMcpPath } from '../mcp/paths';
|
|
13
13
|
import type { McpPolicy } from '../mcp/types';
|
|
14
14
|
import type { ProcessCheckCompletionReceipt, ProcessCheckReceiptStatus } from '../../runtime/execution/process-runtime/check-receipt';
|
|
15
|
+
import { invalidateRepositoryReadCaches } from '../repository/inspector';
|
|
15
16
|
|
|
16
17
|
export type EditSessionStatus =
|
|
17
18
|
| 'open'
|
|
@@ -257,14 +258,27 @@ function calculateAssurance(session: EditSession, at: string): EditSessionAssura
|
|
|
257
258
|
};
|
|
258
259
|
}
|
|
259
260
|
|
|
260
|
-
function
|
|
261
|
+
export function assertEditSessionDurableBinding(
|
|
262
|
+
session: EditSession,
|
|
263
|
+
binding: EditSessionBinding | undefined,
|
|
264
|
+
options: { requireBoundIdentity?: boolean } = {},
|
|
265
|
+
): void {
|
|
266
|
+
const hasDurableIdentity = Boolean(
|
|
267
|
+
session.workId
|
|
268
|
+
|| session.repoId
|
|
269
|
+
|| session.checkoutId
|
|
270
|
+
|| session.principalId
|
|
271
|
+
|| session.routeDecisionFingerprint,
|
|
272
|
+
);
|
|
273
|
+
if (options.requireBoundIdentity === true && hasDurableIdentity && !binding) {
|
|
274
|
+
throw new Error('EDIT_SESSION_IDENTITY_MISMATCH: binding');
|
|
275
|
+
}
|
|
261
276
|
if (!binding) return;
|
|
262
277
|
const pairs: Array<[keyof EditSessionBinding, string | undefined, string | undefined]> = [
|
|
263
278
|
['workId', session.workId, binding.workId],
|
|
264
279
|
['repoId', session.repoId, binding.repoId],
|
|
265
280
|
['checkoutId', session.checkoutId, binding.checkoutId],
|
|
266
281
|
['principalId', session.principalId, binding.principalId],
|
|
267
|
-
['controllerInstanceId', session.controllerInstanceId, binding.controllerInstanceId],
|
|
268
282
|
['routeDecisionFingerprint', session.routeDecisionFingerprint, binding.routeDecisionFingerprint],
|
|
269
283
|
];
|
|
270
284
|
for (const [field, expected, observed] of pairs) {
|
|
@@ -272,6 +286,33 @@ function assertSessionIdentity(session: EditSession, binding: EditSessionBinding
|
|
|
272
286
|
}
|
|
273
287
|
}
|
|
274
288
|
|
|
289
|
+
function bindSessionControllerInstance(repoRoot: string, session: EditSession, binding: EditSessionBinding | undefined): EditSession {
|
|
290
|
+
assertEditSessionDurableBinding(session, binding);
|
|
291
|
+
const nextInstanceId = binding?.controllerInstanceId?.trim();
|
|
292
|
+
if (!nextInstanceId || session.controllerInstanceId === nextInstanceId) return session;
|
|
293
|
+
const durableIdentity = [
|
|
294
|
+
[session.workId, binding?.workId],
|
|
295
|
+
[session.repoId, binding?.repoId],
|
|
296
|
+
[session.checkoutId, binding?.checkoutId],
|
|
297
|
+
[session.principalId, binding?.principalId],
|
|
298
|
+
];
|
|
299
|
+
if (!durableIdentity.every(([expected, observed]) => Boolean(expected) && expected === observed)) {
|
|
300
|
+
throw new Error('EDIT_SESSION_IDENTITY_MISMATCH: controllerInstanceId');
|
|
301
|
+
}
|
|
302
|
+
const rebound = { ...session, controllerInstanceId: nextInstanceId, updatedAt: now() };
|
|
303
|
+
writeSession(repoRoot, rebound);
|
|
304
|
+
tryAppendControllerWorklogEvent(repoRoot, {
|
|
305
|
+
category: 'edit',
|
|
306
|
+
action: 'edit_session_controller_instance_rebound',
|
|
307
|
+
summary: `${session.purpose}: resumed after Controller Runtime instance change`,
|
|
308
|
+
issueId: session.issueId,
|
|
309
|
+
taskId: session.taskId,
|
|
310
|
+
editSessionId: session.sessionId,
|
|
311
|
+
details: { workId: session.workId, checkoutId: session.checkoutId },
|
|
312
|
+
});
|
|
313
|
+
return rebound;
|
|
314
|
+
}
|
|
315
|
+
|
|
275
316
|
function now(): string {
|
|
276
317
|
return new Date().toISOString();
|
|
277
318
|
}
|
|
@@ -1102,7 +1143,7 @@ export function applyEditOperations(repoRoot: string, policy: McpPolicy, session
|
|
|
1102
1143
|
maxBatchOperations?: number;
|
|
1103
1144
|
binding?: EditSessionBinding;
|
|
1104
1145
|
} = {}): EditSession {
|
|
1105
|
-
|
|
1146
|
+
let session = getEditSession(repoRoot, sessionId);
|
|
1106
1147
|
if (['finalized', 'rolled_back'].includes(session.status)) throw new Error(`edit session is closed: ${session.status}`);
|
|
1107
1148
|
if (operations.length === 0) throw new Error('at least one edit operation is required');
|
|
1108
1149
|
const maxBatchOperations = Math.max(1, Math.trunc(options.maxBatchOperations ?? MAX_EDIT_PATCH_BATCH_OPERATIONS));
|
|
@@ -1136,7 +1177,7 @@ export function applyEditOperations(repoRoot: string, policy: McpPolicy, session
|
|
|
1136
1177
|
}
|
|
1137
1178
|
const uniquePaths = new Set(operations.map((operation) => operation.path));
|
|
1138
1179
|
if (uniquePaths.size !== operations.length) throw new Error('each path may appear only once per patch batch');
|
|
1139
|
-
|
|
1180
|
+
session = bindSessionControllerInstance(repoRoot, session, options.binding);
|
|
1140
1181
|
if (session.baseRevision && gitRevision(repoRoot) !== session.baseRevision) throw new Error('EDIT_SESSION_BASE_REVISION_CHANGED');
|
|
1141
1182
|
if (session.workspaceFingerprint && workspaceFingerprint(repoRoot) !== session.workspaceFingerprint) throw new Error('EDIT_SESSION_WORKSPACE_FINGERPRINT_CHANGED');
|
|
1142
1183
|
try {
|
|
@@ -1303,6 +1344,7 @@ export function applyEditOperations(repoRoot: string, policy: McpPolicy, session
|
|
|
1303
1344
|
const firstRevision = session.operations.length === 0;
|
|
1304
1345
|
const records: EditSessionOperationRecord[] = [];
|
|
1305
1346
|
let appliedOperationCount = 0;
|
|
1347
|
+
let mutationAttempted = false;
|
|
1306
1348
|
try {
|
|
1307
1349
|
prepared.forEach((item, index) => {
|
|
1308
1350
|
const beforeMode = item.beforeMode;
|
|
@@ -1312,6 +1354,7 @@ export function applyEditOperations(repoRoot: string, policy: McpPolicy, session
|
|
|
1312
1354
|
mkdirSync(dirname(join(repoRoot, backupRelative)), { recursive: true });
|
|
1313
1355
|
writeFileSync(join(repoRoot, backupRelative), item.before, 'utf-8');
|
|
1314
1356
|
}
|
|
1357
|
+
mutationAttempted = true;
|
|
1315
1358
|
if (item.operation.type === 'delete') rmSync(item.absolutePath);
|
|
1316
1359
|
else atomicWriteFile(repoRoot, item.relativePath, item.after, {
|
|
1317
1360
|
backupRoot: `${SESSION_ROOT}/${session.sessionId}/atomic-backups/r${revision}`,
|
|
@@ -1362,6 +1405,8 @@ export function applyEditOperations(repoRoot: string, policy: McpPolicy, session
|
|
|
1362
1405
|
rolledBack: true,
|
|
1363
1406
|
},
|
|
1364
1407
|
);
|
|
1408
|
+
} finally {
|
|
1409
|
+
if (mutationAttempted) invalidateRepositoryReadCaches(repoRoot);
|
|
1365
1410
|
}
|
|
1366
1411
|
|
|
1367
1412
|
const absoluteRevisionPatch = revisionPatchPath(repoRoot, session.sessionId, revision);
|
|
@@ -1546,7 +1591,15 @@ export function rollbackEditSession(repoRoot: string, sessionId: string, input:
|
|
|
1546
1591
|
const target = savepointRevision ?? (input.toRevision === undefined ? 0 : Math.trunc(input.toRevision));
|
|
1547
1592
|
if (target < 0 || target >= session.currentRevision) throw new Error(`rollback target must be between 0 and ${session.currentRevision - 1}`);
|
|
1548
1593
|
const reverted = session.operations.filter((operation) => operation.revision > target).reverse();
|
|
1549
|
-
|
|
1594
|
+
let restoreAttempted = false;
|
|
1595
|
+
try {
|
|
1596
|
+
for (const operation of reverted) {
|
|
1597
|
+
restoreAttempted = true;
|
|
1598
|
+
restoreOperation(repoRoot, operation);
|
|
1599
|
+
}
|
|
1600
|
+
} finally {
|
|
1601
|
+
if (restoreAttempted) invalidateRepositoryReadCaches(repoRoot);
|
|
1602
|
+
}
|
|
1550
1603
|
|
|
1551
1604
|
const at = now();
|
|
1552
1605
|
session.operations = session.operations.filter((operation) => operation.revision <= target);
|
|
@@ -1588,8 +1641,8 @@ export function finalizeEditSession(repoRoot: string, sessionId: string, input:
|
|
|
1588
1641
|
approvalConfirmed?: boolean;
|
|
1589
1642
|
binding?: EditSessionBinding;
|
|
1590
1643
|
} = {}): EditSession {
|
|
1591
|
-
|
|
1592
|
-
|
|
1644
|
+
let session = getEditSession(repoRoot, sessionId);
|
|
1645
|
+
session = bindSessionControllerInstance(repoRoot, session, input.binding);
|
|
1593
1646
|
if (session.baseRevision && gitRevision(repoRoot) !== session.baseRevision) throw new Error('EDIT_SESSION_BASE_REVISION_CHANGED');
|
|
1594
1647
|
if (session.workspaceFingerprint && workspaceFingerprint(repoRoot) !== session.workspaceFingerprint) throw new Error('EDIT_SESSION_WORKSPACE_FINGERPRINT_CHANGED');
|
|
1595
1648
|
if (session.assurance?.approvalRequired && input.approvalConfirmed !== true) throw new Error('EDIT_SESSION_APPROVAL_REQUIRED');
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface GitHubRepositoryInfo {
|
|
2
|
+
nameWithOwner: string;
|
|
3
|
+
owner: string;
|
|
4
|
+
repo: string;
|
|
5
|
+
url: string;
|
|
6
|
+
defaultBranch: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface GitHubStatus {
|
|
10
|
+
available: boolean;
|
|
11
|
+
authenticated: boolean;
|
|
12
|
+
version?: string;
|
|
13
|
+
repository?: GitHubRepositoryInfo;
|
|
14
|
+
agentTaskSupported: boolean;
|
|
15
|
+
errors: string[];
|
|
16
|
+
}
|
package/src/cli/github/github.ts
CHANGED
|
@@ -7,22 +7,8 @@ import type { ControllerIssue, ControllerTask, GitHubIssueLink } from '../contro
|
|
|
7
7
|
import { readTaskRunEvidence } from '../controller/run-evidence';
|
|
8
8
|
import { resolveEffectiveTaskState, resolveIssueLifecycleStatus } from '../controller/task-status-resolver';
|
|
9
9
|
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
owner: string;
|
|
13
|
-
repo: string;
|
|
14
|
-
url: string;
|
|
15
|
-
defaultBranch: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface GitHubStatus {
|
|
19
|
-
available: boolean;
|
|
20
|
-
authenticated: boolean;
|
|
21
|
-
version?: string;
|
|
22
|
-
repository?: GitHubRepositoryInfo;
|
|
23
|
-
agentTaskSupported: boolean;
|
|
24
|
-
errors: string[];
|
|
25
|
-
}
|
|
10
|
+
export type { GitHubRepositoryInfo, GitHubStatus } from './contracts';
|
|
11
|
+
import type { GitHubRepositoryInfo, GitHubStatus } from './contracts';
|
|
26
12
|
|
|
27
13
|
export interface PublishIssueOptions {
|
|
28
14
|
repo?: string;
|
|
@@ -32,6 +18,41 @@ export interface PublishIssueOptions {
|
|
|
32
18
|
projectNumber?: number;
|
|
33
19
|
}
|
|
34
20
|
|
|
21
|
+
export interface GitHubWorkflowInfo {
|
|
22
|
+
id: number;
|
|
23
|
+
name: string;
|
|
24
|
+
path: string;
|
|
25
|
+
state: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface GitHubWorkflowRunInfo {
|
|
29
|
+
databaseId: number;
|
|
30
|
+
workflowName: string;
|
|
31
|
+
displayTitle: string;
|
|
32
|
+
status: string;
|
|
33
|
+
conclusion?: string;
|
|
34
|
+
event: string;
|
|
35
|
+
headBranch: string;
|
|
36
|
+
headSha: string;
|
|
37
|
+
url: string;
|
|
38
|
+
createdAt: string;
|
|
39
|
+
updatedAt: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface GitHubWorkflowRunQuery {
|
|
43
|
+
workflow?: string;
|
|
44
|
+
branch?: string;
|
|
45
|
+
status?: string;
|
|
46
|
+
limit?: number;
|
|
47
|
+
includeDisabled?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface GitHubWorkflowDispatchInput {
|
|
51
|
+
workflow: string;
|
|
52
|
+
ref?: string;
|
|
53
|
+
inputs?: Record<string, string | number | boolean>;
|
|
54
|
+
}
|
|
55
|
+
|
|
35
56
|
function parseJson<T>(value: string, label: string): T {
|
|
36
57
|
try { return JSON.parse(value) as T; } catch (error) { throw new Error(`${label} returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`); }
|
|
37
58
|
}
|
|
@@ -43,6 +64,63 @@ function parseRepo(value: string): { owner: string; repo: string } {
|
|
|
43
64
|
return { owner, repo };
|
|
44
65
|
}
|
|
45
66
|
|
|
67
|
+
function repositoryName(value: string): string {
|
|
68
|
+
const parsed = parseRepo(value);
|
|
69
|
+
return `${parsed.owner}/${parsed.repo}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function boundedWorkflowLimit(value: number | undefined, fallback: number): number {
|
|
73
|
+
if (value === undefined) return fallback;
|
|
74
|
+
if (!Number.isInteger(value) || value < 1 || value > 100) throw new Error('GITHUB_WORKFLOW_LIMIT_INVALID: limit must be an integer between 1 and 100');
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function boundedSingleLine(value: unknown, label: string, maxLength = 256): string | undefined {
|
|
79
|
+
if (value === undefined || value === null || String(value).trim() === '') return undefined;
|
|
80
|
+
const normalized = String(value).trim();
|
|
81
|
+
if (normalized.length > maxLength || /[\r\n\0]/.test(normalized)) throw new Error(`${label} must be a bounded single-line value`);
|
|
82
|
+
return normalized;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function buildGitHubWorkflowListArgs(repository: string, input: { limit?: number; includeDisabled?: boolean } = {}): string[] {
|
|
86
|
+
const args = ['workflow', 'list', '--repo', repositoryName(repository), '--json', 'id,name,path,state', '--limit', String(boundedWorkflowLimit(input.limit, 50))];
|
|
87
|
+
if (input.includeDisabled === true) args.push('--all');
|
|
88
|
+
return args;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function buildGitHubWorkflowRunListArgs(repository: string, input: GitHubWorkflowRunQuery = {}): string[] {
|
|
92
|
+
const args = [
|
|
93
|
+
'run', 'list', '--repo', repositoryName(repository),
|
|
94
|
+
'--json', 'databaseId,workflowName,displayTitle,status,conclusion,event,headBranch,headSha,url,createdAt,updatedAt',
|
|
95
|
+
'--limit', String(boundedWorkflowLimit(input.limit, 20)),
|
|
96
|
+
];
|
|
97
|
+
const workflow = boundedSingleLine(input.workflow, 'workflow');
|
|
98
|
+
const branch = boundedSingleLine(input.branch, 'branch');
|
|
99
|
+
const status = boundedSingleLine(input.status, 'status');
|
|
100
|
+
if (workflow) args.push('--workflow', workflow);
|
|
101
|
+
if (branch) args.push('--branch', branch);
|
|
102
|
+
if (status) args.push('--status', status);
|
|
103
|
+
if (input.includeDisabled === true) args.push('--all');
|
|
104
|
+
return args;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function buildGitHubWorkflowDispatchArgs(repository: string, input: GitHubWorkflowDispatchInput): string[] {
|
|
108
|
+
const workflow = boundedSingleLine(input.workflow, 'workflow');
|
|
109
|
+
if (!workflow) throw new Error('GITHUB_WORKFLOW_REQUIRED: workflow is required');
|
|
110
|
+
const args = ['workflow', 'run', workflow, '--repo', repositoryName(repository)];
|
|
111
|
+
const ref = boundedSingleLine(input.ref, 'ref');
|
|
112
|
+
if (ref) args.push('--ref', ref);
|
|
113
|
+
const entries = Object.entries(input.inputs ?? {}).sort(([left], [right]) => left.localeCompare(right));
|
|
114
|
+
if (entries.length > 20) throw new Error('GITHUB_WORKFLOW_INPUTS_INVALID: at most 20 inputs are allowed');
|
|
115
|
+
for (const [key, rawValue] of entries) {
|
|
116
|
+
if (!/^[A-Za-z0-9_.-]{1,80}$/.test(key)) throw new Error(`GITHUB_WORKFLOW_INPUT_KEY_INVALID: ${key}`);
|
|
117
|
+
const value = boundedSingleLine(rawValue, `workflow input ${key}`, 8192);
|
|
118
|
+
if (value === undefined) throw new Error(`GITHUB_WORKFLOW_INPUT_VALUE_INVALID: ${key}`);
|
|
119
|
+
args.push('--raw-field', `${key}=${value}`);
|
|
120
|
+
}
|
|
121
|
+
return args;
|
|
122
|
+
}
|
|
123
|
+
|
|
46
124
|
function gh(repoRoot: string, args: string[], input?: string, maxOutputBytes = 512 * 1024) {
|
|
47
125
|
return runProcess('gh', args, {
|
|
48
126
|
cwd: repoRoot,
|
|
@@ -81,6 +159,47 @@ function versionAtLeast(version: string, minimum: [number, number, number]): boo
|
|
|
81
159
|
return true;
|
|
82
160
|
}
|
|
83
161
|
|
|
162
|
+
export function listGitHubWorkflows(
|
|
163
|
+
repoRoot: string,
|
|
164
|
+
explicitRepo?: string,
|
|
165
|
+
input: { limit?: number; includeDisabled?: boolean } = {},
|
|
166
|
+
): GitHubWorkflowInfo[] {
|
|
167
|
+
const repository = explicitRepo?.trim() ? repositoryName(explicitRepo) : resolveGitHubRepository(repoRoot).nameWithOwner;
|
|
168
|
+
const result = gh(repoRoot, buildGitHubWorkflowListArgs(repository, input));
|
|
169
|
+
if (!result.ok) throw new Error(`failed to list GitHub workflows: ${result.error || result.stderr}`);
|
|
170
|
+
return parseJson<GitHubWorkflowInfo[]>(result.stdout, 'gh workflow list');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function listGitHubWorkflowRuns(
|
|
174
|
+
repoRoot: string,
|
|
175
|
+
explicitRepo?: string,
|
|
176
|
+
input: GitHubWorkflowRunQuery = {},
|
|
177
|
+
): GitHubWorkflowRunInfo[] {
|
|
178
|
+
const repository = explicitRepo?.trim() ? repositoryName(explicitRepo) : resolveGitHubRepository(repoRoot).nameWithOwner;
|
|
179
|
+
const result = gh(repoRoot, buildGitHubWorkflowRunListArgs(repository, input));
|
|
180
|
+
if (!result.ok) throw new Error(`failed to list GitHub workflow runs: ${result.error || result.stderr}`);
|
|
181
|
+
return parseJson<GitHubWorkflowRunInfo[]>(result.stdout, 'gh run list');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function dispatchGitHubWorkflow(
|
|
185
|
+
repoRoot: string,
|
|
186
|
+
explicitRepo: string | undefined,
|
|
187
|
+
input: GitHubWorkflowDispatchInput,
|
|
188
|
+
): Record<string, unknown> {
|
|
189
|
+
const repository = explicitRepo?.trim() ? repositoryName(explicitRepo) : resolveGitHubRepository(repoRoot).nameWithOwner;
|
|
190
|
+
const result = gh(repoRoot, buildGitHubWorkflowDispatchArgs(repository, input), undefined, 128 * 1024);
|
|
191
|
+
if (!result.ok) throw new Error(`failed to dispatch GitHub workflow: ${result.error || result.stderr}`);
|
|
192
|
+
const output = result.stdout.trim();
|
|
193
|
+
return {
|
|
194
|
+
repository,
|
|
195
|
+
workflow: input.workflow,
|
|
196
|
+
...(input.ref?.trim() ? { ref: input.ref.trim() } : {}),
|
|
197
|
+
inputs: input.inputs ?? {},
|
|
198
|
+
...(output ? { output } : {}),
|
|
199
|
+
...(/^https:\/\//i.test(output) ? { runUrl: output } : {}),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
84
203
|
export function getGitHubStatus(repoRoot: string, explicitRepo?: string): GitHubStatus {
|
|
85
204
|
const errors: string[] = [];
|
|
86
205
|
const versionResult = gh(repoRoot, ['--version'], undefined, 16 * 1024);
|
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
classifyGitHubCopilotPreflight,
|
|
7
7
|
executorHealthCode,
|
|
8
8
|
} from '../agent-jobs/executor-health';
|
|
9
|
-
import { getGitHubStatus, resolveGitHubRepository
|
|
9
|
+
import { getGitHubStatus, resolveGitHubRepository } from './github';
|
|
10
|
+
import type { GitHubRepositoryInfo } from './contracts';
|
|
10
11
|
|
|
11
12
|
export interface GitHubAgentSession {
|
|
12
13
|
id: string;
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { ROUTES, type Route } from '../hook/route-registry';
|
|
19
19
|
|
|
20
20
|
export const MANAGED_TAG = 'forge hook';
|
|
21
|
+
const LEGACY_REPO_HARNESS_TAGS = ['repo-harness-hook', 'repo-harness hook'] as const;
|
|
21
22
|
|
|
22
23
|
export interface HookCommand {
|
|
23
24
|
type: 'command';
|
|
@@ -50,6 +51,19 @@ export function isManagedEntry(entry: HookEntry): boolean {
|
|
|
50
51
|
return entry.hooks.some((h) => typeof h?.command === 'string' && h.command.includes(MANAGED_TAG));
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
/** Legacy adapters are installer-owned migration input, never user hooks. */
|
|
55
|
+
export function isLegacyRepoHarnessEntry(entry: HookEntry): boolean {
|
|
56
|
+
if (!entry || !Array.isArray(entry.hooks)) return false;
|
|
57
|
+
return entry.hooks.some((hook) => (
|
|
58
|
+
typeof hook?.command === 'string'
|
|
59
|
+
&& LEGACY_REPO_HARNESS_TAGS.some((tag) => hook.command.includes(tag))
|
|
60
|
+
));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isLegacyRepoHarnessCommand(command: string): boolean {
|
|
64
|
+
return LEGACY_REPO_HARNESS_TAGS.some((tag) => command.includes(tag));
|
|
65
|
+
}
|
|
66
|
+
|
|
53
67
|
export function buildManagedHooks(host: HookHost): HooksByEvent {
|
|
54
68
|
const out: HooksByEvent = {};
|
|
55
69
|
for (const route of ROUTES) {
|
|
@@ -63,7 +77,7 @@ export function stripManagedEntries(existing: HooksByEvent | undefined): HooksBy
|
|
|
63
77
|
if (!existing) return {};
|
|
64
78
|
const out: HooksByEvent = {};
|
|
65
79
|
for (const [event, entries] of Object.entries(existing)) {
|
|
66
|
-
const kept = (entries ?? []).filter((e) => !isManagedEntry(e));
|
|
80
|
+
const kept = (entries ?? []).filter((e) => !isManagedEntry(e) && !isLegacyRepoHarnessEntry(e));
|
|
67
81
|
if (kept.length > 0) out[event] = kept;
|
|
68
82
|
}
|
|
69
83
|
return out;
|