@moretea-labs/forge 1.5.1 → 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 +46 -0
- package/CLAUDE.md +2 -0
- package/DESIGN.md +7 -2
- package/README.md +4 -4
- package/README.zh-CN.md +2 -2
- 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/pre-edit-guard.sh +8 -39
- package/assets/hooks/prompt-guard.sh +7 -83
- 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 +54 -3
- package/assets/reference-configs/document-generation.md +1 -1
- package/assets/reference-configs/harness-overview.md +1 -1
- package/assets/reference-configs/hook-operations.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 +13 -38
- 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/sprint-backlog.sh +6 -6
- 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 +38 -22
- package/docs/operations/controller-performance-and-502.md +124 -0
- package/docs/operations/controller-reliability-runbook.md +1 -1
- package/docs/operations/features.md +3 -3
- 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/tutorials/01-install-and-start.md +1 -1
- package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
- package/docs/wiki/Architecture.md +6 -6
- package/docs/wiki/Implementation.md +2 -2
- package/docs/wiki/Installation.md +1 -1
- package/docs/wiki/Integrations.md +2 -2
- package/docs/wiki/Quick-Start.md +1 -1
- 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-public-docs.sh +14 -1
- 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-chatgpt-automation-schedules.ts +64 -0
- 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/sprint-backlog.sh +1 -1
- 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/chatgpt.ts +31 -2
- package/src/cli/commands/init-hook.ts +3 -3
- package/src/cli/commands/mcp.ts +9 -5
- package/src/cli/commands/plugin.ts +37 -10
- 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 +898 -430
- 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/hook/prompt-guard-decision.ts +17 -18
- package/src/cli/hook/prompt-intents.ts +1 -1
- package/src/cli/installer/managed-entries.ts +15 -1
- package/src/cli/local-bridge/console-automations.ts +225 -67
- package/src/cli/local-bridge/console-view-models.ts +3 -35
- package/src/cli/local-bridge/facade-api.ts +2 -217
- package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +153 -3
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +183 -3
- package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +1 -1
- package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +4 -3
- package/src/cli/local-bridge/frontend/src/lib/api.ts +18 -11
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
- package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +1 -1
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +4 -3
- package/src/cli/local-bridge/frontend/src/styles.css +27 -11
- package/src/cli/local-bridge/frontend/src/types.ts +8 -7
- package/src/cli/local-bridge/job-store.ts +1 -490
- package/src/cli/local-bridge/server.ts +29 -532
- package/src/cli/local-bridge/ui-dist/app.css +1 -1
- package/src/cli/local-bridge/ui-dist/app.js +5101 -4967
- 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/paths.ts +8 -2
- package/src/cli/mcp/repository-tools.ts +356 -101
- package/src/cli/mcp/server.ts +625 -54
- 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 +125 -15
- package/src/cli/mcp/transports/session-registry.ts +67 -5
- package/src/cli/mcp/types.ts +3 -3
- package/src/cli/repositories/command-classifier.ts +209 -14
- 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/safe-patch.ts +6 -4
- package/src/cli/repositories/selected-path-actions.ts +17 -9
- package/src/cli/repositories/structured-git.ts +110 -10
- package/src/cli/repository/inspector.ts +565 -62
- 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 +196 -2
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
- package/src/runtime/control-plane/execution/execution-identity.ts +65 -14
- package/src/runtime/control-plane/execution/session-store.ts +18 -0
- package/src/runtime/control-plane/execution/validation.ts +15 -2
- 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 +175 -8
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -21
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +87 -9
- 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 +144 -14
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +9 -2
- package/src/runtime/control-plane/facade/goal-workloop.ts +885 -105
- package/src/runtime/control-plane/facade/index.ts +1 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +211 -26
- package/src/runtime/control-plane/facade/requirement-board.ts +24 -8
- 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 +55 -11
- package/src/runtime/control-plane/facade/work-contract-store.ts +204 -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 +642 -51
- 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/requirement-store.ts +15 -11
- 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 +19 -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 +52 -14
- package/src/runtime/execution/process-runtime/check-result.ts +63 -2
- package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +346 -40
- package/src/runtime/execution/process-runtime/gc.ts +98 -12
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
- package/src/runtime/execution/process-runtime/persisted-check.ts +57 -6
- package/src/runtime/execution/process-runtime/resource-claims.ts +97 -8
- package/src/runtime/execution/process-runtime/runtime.ts +59 -4
- package/src/runtime/execution/process-runtime/store.ts +7 -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 +1219 -126
- package/src/runtime/gateway/mcp/process-tools.ts +83 -37
- package/src/runtime/gateway/mcp/router.ts +185 -83
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
- package/src/runtime/gateway/mcp/runtime-tools.ts +2555 -1616
- package/src/runtime/model-clients/deepseek-adapter.ts +0 -32
- package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
- package/src/runtime/plugins/browser-adapter.ts +2925 -318
- package/src/runtime/plugins/browser-automation-service.ts +10 -0
- package/src/runtime/plugins/browser-handoff-host.ts +6 -31
- package/src/runtime/plugins/browser-handoff.ts +92 -5
- package/src/runtime/plugins/browser-macos-bridge.ts +586 -45
- 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 +15 -12
- package/src/runtime/plugins/external-adapter.ts +324 -3
- package/src/runtime/plugins/external-registration.ts +84 -5
- package/src/runtime/plugins/external-unix-socket.ts +7 -1
- package/src/runtime/plugins/first-party-registry.ts +4 -2
- package/src/runtime/plugins/github-adapter.ts +125 -0
- package/src/runtime/plugins/gmail-adapter.ts +28 -2
- 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 +125 -8
- 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 +181 -0
- package/src/runtime/plugins/resend-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +366 -24
- package/src/runtime/plugins/types.ts +39 -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/actions.ts +0 -10
- package/src/runtime/recovery/classifier.ts +0 -9
- package/src/runtime/recovery/maintenance-executor.ts +415 -18
- package/src/runtime/recovery/probe.ts +0 -1
- package/src/runtime/recovery/types.ts +0 -1
- 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 +162 -19
- package/src/runtime/root/release-store.ts +48 -11
- package/src/runtime/root/runtime.ts +23 -5
- package/src/runtime/root/service.ts +100 -27
- package/src/runtime/root/types.ts +2 -0
- package/src/runtime/safe-tooling/index.ts +0 -1
- package/src/runtime/safe-tooling/ios-development.ts +321 -63
- package/src/runtime/safe-tooling/types.ts +0 -30
- 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 +702 -94
- 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 +258 -0
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +40 -0
- package/src/runtime/workflow/schedules/engine.ts +509 -80
- 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 +6 -3
- package/src/runtime/workflow/schedules/work-continuation.ts +120 -34
- 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/bin/forge-desktop-helper.mjs +0 -204
- 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 -453
- package/docs/architecture/current/codegraph-context-provider.md +0 -226
- 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 -354
- package/docs/architecture/current/entity-model.md +0 -592
- 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 -144
- 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 -311
- 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 -83
- package/docs/operations/controller-desktop-plugin.md +0 -112
- 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/plugins/desktop-adapter.ts +0 -374
- package/src/runtime/safe-tooling/browser-safe.ts +0 -133
- 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');
|
package/src/cli/github/github.ts
CHANGED
|
@@ -32,6 +32,41 @@ export interface PublishIssueOptions {
|
|
|
32
32
|
projectNumber?: number;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export interface GitHubWorkflowInfo {
|
|
36
|
+
id: number;
|
|
37
|
+
name: string;
|
|
38
|
+
path: string;
|
|
39
|
+
state: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface GitHubWorkflowRunInfo {
|
|
43
|
+
databaseId: number;
|
|
44
|
+
workflowName: string;
|
|
45
|
+
displayTitle: string;
|
|
46
|
+
status: string;
|
|
47
|
+
conclusion?: string;
|
|
48
|
+
event: string;
|
|
49
|
+
headBranch: string;
|
|
50
|
+
headSha: string;
|
|
51
|
+
url: string;
|
|
52
|
+
createdAt: string;
|
|
53
|
+
updatedAt: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface GitHubWorkflowRunQuery {
|
|
57
|
+
workflow?: string;
|
|
58
|
+
branch?: string;
|
|
59
|
+
status?: string;
|
|
60
|
+
limit?: number;
|
|
61
|
+
includeDisabled?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface GitHubWorkflowDispatchInput {
|
|
65
|
+
workflow: string;
|
|
66
|
+
ref?: string;
|
|
67
|
+
inputs?: Record<string, string | number | boolean>;
|
|
68
|
+
}
|
|
69
|
+
|
|
35
70
|
function parseJson<T>(value: string, label: string): T {
|
|
36
71
|
try { return JSON.parse(value) as T; } catch (error) { throw new Error(`${label} returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`); }
|
|
37
72
|
}
|
|
@@ -43,6 +78,63 @@ function parseRepo(value: string): { owner: string; repo: string } {
|
|
|
43
78
|
return { owner, repo };
|
|
44
79
|
}
|
|
45
80
|
|
|
81
|
+
function repositoryName(value: string): string {
|
|
82
|
+
const parsed = parseRepo(value);
|
|
83
|
+
return `${parsed.owner}/${parsed.repo}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function boundedWorkflowLimit(value: number | undefined, fallback: number): number {
|
|
87
|
+
if (value === undefined) return fallback;
|
|
88
|
+
if (!Number.isInteger(value) || value < 1 || value > 100) throw new Error('GITHUB_WORKFLOW_LIMIT_INVALID: limit must be an integer between 1 and 100');
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function boundedSingleLine(value: unknown, label: string, maxLength = 256): string | undefined {
|
|
93
|
+
if (value === undefined || value === null || String(value).trim() === '') return undefined;
|
|
94
|
+
const normalized = String(value).trim();
|
|
95
|
+
if (normalized.length > maxLength || /[\r\n\0]/.test(normalized)) throw new Error(`${label} must be a bounded single-line value`);
|
|
96
|
+
return normalized;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function buildGitHubWorkflowListArgs(repository: string, input: { limit?: number; includeDisabled?: boolean } = {}): string[] {
|
|
100
|
+
const args = ['workflow', 'list', '--repo', repositoryName(repository), '--json', 'id,name,path,state', '--limit', String(boundedWorkflowLimit(input.limit, 50))];
|
|
101
|
+
if (input.includeDisabled === true) args.push('--all');
|
|
102
|
+
return args;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function buildGitHubWorkflowRunListArgs(repository: string, input: GitHubWorkflowRunQuery = {}): string[] {
|
|
106
|
+
const args = [
|
|
107
|
+
'run', 'list', '--repo', repositoryName(repository),
|
|
108
|
+
'--json', 'databaseId,workflowName,displayTitle,status,conclusion,event,headBranch,headSha,url,createdAt,updatedAt',
|
|
109
|
+
'--limit', String(boundedWorkflowLimit(input.limit, 20)),
|
|
110
|
+
];
|
|
111
|
+
const workflow = boundedSingleLine(input.workflow, 'workflow');
|
|
112
|
+
const branch = boundedSingleLine(input.branch, 'branch');
|
|
113
|
+
const status = boundedSingleLine(input.status, 'status');
|
|
114
|
+
if (workflow) args.push('--workflow', workflow);
|
|
115
|
+
if (branch) args.push('--branch', branch);
|
|
116
|
+
if (status) args.push('--status', status);
|
|
117
|
+
if (input.includeDisabled === true) args.push('--all');
|
|
118
|
+
return args;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function buildGitHubWorkflowDispatchArgs(repository: string, input: GitHubWorkflowDispatchInput): string[] {
|
|
122
|
+
const workflow = boundedSingleLine(input.workflow, 'workflow');
|
|
123
|
+
if (!workflow) throw new Error('GITHUB_WORKFLOW_REQUIRED: workflow is required');
|
|
124
|
+
const args = ['workflow', 'run', workflow, '--repo', repositoryName(repository)];
|
|
125
|
+
const ref = boundedSingleLine(input.ref, 'ref');
|
|
126
|
+
if (ref) args.push('--ref', ref);
|
|
127
|
+
const entries = Object.entries(input.inputs ?? {}).sort(([left], [right]) => left.localeCompare(right));
|
|
128
|
+
if (entries.length > 20) throw new Error('GITHUB_WORKFLOW_INPUTS_INVALID: at most 20 inputs are allowed');
|
|
129
|
+
for (const [key, rawValue] of entries) {
|
|
130
|
+
if (!/^[A-Za-z0-9_.-]{1,80}$/.test(key)) throw new Error(`GITHUB_WORKFLOW_INPUT_KEY_INVALID: ${key}`);
|
|
131
|
+
const value = boundedSingleLine(rawValue, `workflow input ${key}`, 8192);
|
|
132
|
+
if (value === undefined) throw new Error(`GITHUB_WORKFLOW_INPUT_VALUE_INVALID: ${key}`);
|
|
133
|
+
args.push('--raw-field', `${key}=${value}`);
|
|
134
|
+
}
|
|
135
|
+
return args;
|
|
136
|
+
}
|
|
137
|
+
|
|
46
138
|
function gh(repoRoot: string, args: string[], input?: string, maxOutputBytes = 512 * 1024) {
|
|
47
139
|
return runProcess('gh', args, {
|
|
48
140
|
cwd: repoRoot,
|
|
@@ -81,6 +173,47 @@ function versionAtLeast(version: string, minimum: [number, number, number]): boo
|
|
|
81
173
|
return true;
|
|
82
174
|
}
|
|
83
175
|
|
|
176
|
+
export function listGitHubWorkflows(
|
|
177
|
+
repoRoot: string,
|
|
178
|
+
explicitRepo?: string,
|
|
179
|
+
input: { limit?: number; includeDisabled?: boolean } = {},
|
|
180
|
+
): GitHubWorkflowInfo[] {
|
|
181
|
+
const repository = explicitRepo?.trim() ? repositoryName(explicitRepo) : resolveGitHubRepository(repoRoot).nameWithOwner;
|
|
182
|
+
const result = gh(repoRoot, buildGitHubWorkflowListArgs(repository, input));
|
|
183
|
+
if (!result.ok) throw new Error(`failed to list GitHub workflows: ${result.error || result.stderr}`);
|
|
184
|
+
return parseJson<GitHubWorkflowInfo[]>(result.stdout, 'gh workflow list');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function listGitHubWorkflowRuns(
|
|
188
|
+
repoRoot: string,
|
|
189
|
+
explicitRepo?: string,
|
|
190
|
+
input: GitHubWorkflowRunQuery = {},
|
|
191
|
+
): GitHubWorkflowRunInfo[] {
|
|
192
|
+
const repository = explicitRepo?.trim() ? repositoryName(explicitRepo) : resolveGitHubRepository(repoRoot).nameWithOwner;
|
|
193
|
+
const result = gh(repoRoot, buildGitHubWorkflowRunListArgs(repository, input));
|
|
194
|
+
if (!result.ok) throw new Error(`failed to list GitHub workflow runs: ${result.error || result.stderr}`);
|
|
195
|
+
return parseJson<GitHubWorkflowRunInfo[]>(result.stdout, 'gh run list');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function dispatchGitHubWorkflow(
|
|
199
|
+
repoRoot: string,
|
|
200
|
+
explicitRepo: string | undefined,
|
|
201
|
+
input: GitHubWorkflowDispatchInput,
|
|
202
|
+
): Record<string, unknown> {
|
|
203
|
+
const repository = explicitRepo?.trim() ? repositoryName(explicitRepo) : resolveGitHubRepository(repoRoot).nameWithOwner;
|
|
204
|
+
const result = gh(repoRoot, buildGitHubWorkflowDispatchArgs(repository, input), undefined, 128 * 1024);
|
|
205
|
+
if (!result.ok) throw new Error(`failed to dispatch GitHub workflow: ${result.error || result.stderr}`);
|
|
206
|
+
const output = result.stdout.trim();
|
|
207
|
+
return {
|
|
208
|
+
repository,
|
|
209
|
+
workflow: input.workflow,
|
|
210
|
+
...(input.ref?.trim() ? { ref: input.ref.trim() } : {}),
|
|
211
|
+
inputs: input.inputs ?? {},
|
|
212
|
+
...(output ? { output } : {}),
|
|
213
|
+
...(/^https:\/\//i.test(output) ? { runUrl: output } : {}),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
84
217
|
export function getGitHubStatus(repoRoot: string, explicitRepo?: string): GitHubStatus {
|
|
85
218
|
const errors: string[] = [];
|
|
86
219
|
const versionResult = gh(repoRoot, ['--version'], undefined, 16 * 1024);
|
|
@@ -112,40 +112,38 @@ function isExecutionIntent(
|
|
|
112
112
|
);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
function explicitPlanExecution(intent: PromptGuardExecutionIntent): boolean {
|
|
116
|
+
return intent === 'embedded_approved_plan' || intent === 'plan_execution_projection';
|
|
117
|
+
}
|
|
118
|
+
|
|
115
119
|
function decideNoActivePlanAction(
|
|
116
120
|
intent: PromptGuardExecutionIntent,
|
|
117
121
|
state: PromptGuardState,
|
|
118
122
|
): PromptGuardAction {
|
|
119
|
-
|
|
120
|
-
|
|
123
|
+
// Host hooks never decide that ordinary implementation requires a Plan.
|
|
124
|
+
// ChatGPT may inspect first and then explicitly commit to Plan execution.
|
|
125
|
+
if (!explicitPlanExecution(intent)) {
|
|
126
|
+
return state.worktree === 'linked_target' ? 'worktree_execution_advice' : 'allow';
|
|
121
127
|
}
|
|
128
|
+
if (state.pending === 'fresh') return 'plan_capture_pending_advice';
|
|
122
129
|
if (state.worktree === 'linked_target') return 'worktree_execution_advice';
|
|
123
|
-
|
|
124
|
-
return 'plan_capture_missing_active_advice';
|
|
125
|
-
}
|
|
126
|
-
return 'plan_status_no_active_block';
|
|
130
|
+
return 'plan_capture_missing_active_advice';
|
|
127
131
|
}
|
|
128
132
|
|
|
129
133
|
function decideDraftPlanAction(
|
|
130
134
|
intent: PromptGuardExecutionIntent,
|
|
131
135
|
): PromptGuardAction {
|
|
132
|
-
|
|
133
|
-
return 'plan_status_not_approved_block';
|
|
136
|
+
return explicitPlanExecution(intent) ? 'plan_capture_draft_advice' : 'allow';
|
|
134
137
|
}
|
|
135
138
|
|
|
136
139
|
function decideApprovedPlanAction(
|
|
137
140
|
intent: PromptGuardExecutionIntent,
|
|
138
141
|
state: PromptGuardState,
|
|
139
142
|
): PromptGuardAction {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
state.contract !== 'present'
|
|
145
|
-
) {
|
|
146
|
-
return 'plan_execution_scaffold_advice';
|
|
147
|
-
}
|
|
148
|
-
if (state.contract !== 'present') return 'contract_missing_block';
|
|
143
|
+
// Plan contracts may guide an explicitly selected Plan execution, but their
|
|
144
|
+
// presence/evidence is not a semantic permission gate for ordinary edits.
|
|
145
|
+
if (!explicitPlanExecution(intent)) return 'allow';
|
|
146
|
+
if (state.contract !== 'present') return 'plan_execution_scaffold_advice';
|
|
149
147
|
return 'allow';
|
|
150
148
|
}
|
|
151
149
|
|
|
@@ -233,6 +231,7 @@ export function decidePromptGuardAction(
|
|
|
233
231
|
): PromptGuardAction {
|
|
234
232
|
if (intent === 'done') return decideDoneAction(state);
|
|
235
233
|
if (!isExecutionIntent(intent)) return 'allow';
|
|
236
|
-
|
|
234
|
+
// Product/spec/Plan state is context for the Controller, not execution
|
|
235
|
+
// authorization. Hard path, permission and resource safety live elsewhere.
|
|
237
236
|
return PROMPT_GUARD_EXECUTION_TABLE[state.plan][intent](state);
|
|
238
237
|
}
|
|
@@ -401,7 +401,7 @@ export function isThinkPlanStartIntent(ctx: PromptIntentContext): boolean {
|
|
|
401
401
|
if (isBugOrHuntIntent(ctx)) return false;
|
|
402
402
|
if (THINK_COMMAND.test(ctx.text)) return true;
|
|
403
403
|
if (isPlanConsultationIntent(ctx)) return false;
|
|
404
|
-
return THINK_PLAN_PHRASE.test(ctx.text)
|
|
404
|
+
return THINK_PLAN_PHRASE.test(ctx.text);
|
|
405
405
|
}
|
|
406
406
|
|
|
407
407
|
export function normalizePlanSlug(value: string): 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;
|