@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,10 +1,8 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
|
-
import {
|
|
3
|
-
import { appendFileSync,
|
|
4
|
-
import { dirname, join
|
|
2
|
+
import { spawnSync } from 'child_process';
|
|
3
|
+
import { appendFileSync, mkdirSync } from 'fs';
|
|
4
|
+
import { dirname, join } from 'path';
|
|
5
5
|
import { capProcessOutput, redactProcessOutput } from '../../effects/process-runner';
|
|
6
|
-
import { repositoryChildProcessEnvironment } from '../../runtime/shared/process-environment';
|
|
7
|
-
import { terminateProcessTree } from '../../runtime/shared/process-tree';
|
|
8
6
|
import { MAX_AGENT_TIMEOUT_MS, MIN_AGENT_TIMEOUT_MS } from '../controller/runtime-config';
|
|
9
7
|
import { repositoryControllerRoot } from './controller-home';
|
|
10
8
|
import {
|
|
@@ -22,8 +20,25 @@ import { commandValue, type CanonicalRepositoryCommand, type RepositoryCommandVa
|
|
|
22
20
|
import { loadExternalFilesystemGrants } from '../../runtime/safe-tooling/external-filesystem';
|
|
23
21
|
import type { RepositoryRecord } from './types';
|
|
24
22
|
import { readRepositoryAccessPolicy } from '../../runtime/control-plane/governance/access-policy';
|
|
23
|
+
import { invalidateRepositoryReadCaches } from '../repository/inspector';
|
|
25
24
|
import { assertResolvedAuthorization, decideAuthorization, type AuthorizationDecision } from '../../runtime/control-plane/governance/authorization';
|
|
26
25
|
import { assertRuntimeMayWriteOrThrow } from '../../runtime/root/write-fence';
|
|
26
|
+
import { commandEnvironment, runCanonicalCommand, type RepositoryCommandAsyncHooks, type SpawnCommandResult } from './command-process';
|
|
27
|
+
import {
|
|
28
|
+
changedSnapshotPaths,
|
|
29
|
+
emptyWorkspaceSnapshot,
|
|
30
|
+
repositorySnapshot,
|
|
31
|
+
repositorySnapshotAsync,
|
|
32
|
+
snapshotChanged,
|
|
33
|
+
type RepositoryCommandSnapshot,
|
|
34
|
+
} from './repository-snapshot';
|
|
35
|
+
|
|
36
|
+
export type { RepositoryCommandSnapshot } from './repository-snapshot';
|
|
37
|
+
export type { RepositoryCommandAsyncHooks, SpawnCommandResult } from './command-process';
|
|
38
|
+
// Compatibility exports for supported callers; implementations live in their
|
|
39
|
+
// responsibility-owned modules.
|
|
40
|
+
export { runCanonicalCommand } from './command-process';
|
|
41
|
+
export { repositorySnapshotAsync } from './repository-snapshot';
|
|
27
42
|
|
|
28
43
|
export { classifyRepositoryCommand } from './command-classifier';
|
|
29
44
|
export type {
|
|
@@ -55,30 +70,10 @@ export interface ExecuteRepositoryCommandInput {
|
|
|
55
70
|
reuseSnapshot?: RepositoryCommandSnapshot;
|
|
56
71
|
/** Internal: explicit ephemeral workspace authority may operate without a Git repository. */
|
|
57
72
|
allowNonGitWorkspace?: boolean;
|
|
73
|
+
/** Internal: command-facade already classified this as a bounded local script; retain policy/snapshot/audit without durable Process state. */
|
|
74
|
+
allowOpaqueLocalScript?: boolean;
|
|
58
75
|
}
|
|
59
76
|
|
|
60
|
-
export interface RepositoryCommandSnapshot {
|
|
61
|
-
head: string | null;
|
|
62
|
-
branch: string | null;
|
|
63
|
-
/** User-owned workspace state. Harness runtime storage is intentionally excluded. */
|
|
64
|
-
status: string;
|
|
65
|
-
dirty: boolean;
|
|
66
|
-
refsHash: string;
|
|
67
|
-
paths: string[];
|
|
68
|
-
pathFingerprints: Record<string, string>;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function emptyWorkspaceSnapshot(): RepositoryCommandSnapshot {
|
|
72
|
-
return {
|
|
73
|
-
head: null,
|
|
74
|
-
branch: null,
|
|
75
|
-
status: '',
|
|
76
|
-
dirty: false,
|
|
77
|
-
refsHash: createHash('sha256').update('').digest('hex'),
|
|
78
|
-
paths: [],
|
|
79
|
-
pathFingerprints: {},
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
77
|
|
|
83
78
|
export interface RepositoryCommandExecution {
|
|
84
79
|
status: 'preview' | 'approval_required' | 'executed';
|
|
@@ -113,21 +108,6 @@ export interface PreparedRepositoryCommandExecution {
|
|
|
113
108
|
execution: RepositoryCommandExecution;
|
|
114
109
|
}
|
|
115
110
|
|
|
116
|
-
export interface SpawnCommandResult {
|
|
117
|
-
ok: boolean;
|
|
118
|
-
exitCode: number;
|
|
119
|
-
timedOut: boolean;
|
|
120
|
-
cancelled: boolean;
|
|
121
|
-
stdout: string;
|
|
122
|
-
stderr: string;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export interface RepositoryCommandAsyncHooks {
|
|
126
|
-
onSpawn?: (pid: number) => void;
|
|
127
|
-
onStdout?: (chunk: string) => void;
|
|
128
|
-
onStderr?: (chunk: string) => void;
|
|
129
|
-
signal?: AbortSignal;
|
|
130
|
-
}
|
|
131
111
|
|
|
132
112
|
/** Sensible interactive default for ordinary repository commands. */
|
|
133
113
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
@@ -154,460 +134,6 @@ function boundedInteger(value: number | undefined, fallback: number, minimum: nu
|
|
|
154
134
|
return normalized;
|
|
155
135
|
}
|
|
156
136
|
|
|
157
|
-
function commandEnvironment(): NodeJS.ProcessEnv {
|
|
158
|
-
const allowed = [
|
|
159
|
-
'PATH', 'HOME', 'USER', 'LOGNAME', 'TMPDIR', 'TMP', 'TEMP', 'SHELL',
|
|
160
|
-
'LANG', 'LC_ALL', 'TERM', 'SSH_AUTH_SOCK', 'GPG_TTY', 'XDG_CONFIG_HOME',
|
|
161
|
-
];
|
|
162
|
-
const env: NodeJS.ProcessEnv = {};
|
|
163
|
-
for (const key of allowed) if (process.env[key] !== undefined) env[key] = process.env[key];
|
|
164
|
-
env.GIT_TERMINAL_PROMPT = '0';
|
|
165
|
-
env.CI = '1';
|
|
166
|
-
return repositoryChildProcessEnvironment(env);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function truncatedOutput(chunks: Buffer[], truncated: boolean, maxOutputBytes: number): string {
|
|
170
|
-
const text = Buffer.concat(chunks).toString('utf8');
|
|
171
|
-
const redacted = redactProcessOutput(text);
|
|
172
|
-
return truncated ? capProcessOutput(redacted, maxOutputBytes) : redacted;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function collectOutput(maxOutputBytes: number): {
|
|
176
|
-
write(chunk: string | Buffer): void;
|
|
177
|
-
complete(): string;
|
|
178
|
-
} {
|
|
179
|
-
const chunks: Buffer[] = [];
|
|
180
|
-
let totalBytes = 0;
|
|
181
|
-
let truncated = false;
|
|
182
|
-
return {
|
|
183
|
-
write(chunk: string | Buffer) {
|
|
184
|
-
const buffer = typeof chunk === 'string' ? Buffer.from(chunk, 'utf8') : chunk;
|
|
185
|
-
if (buffer.length === 0) return;
|
|
186
|
-
if (totalBytes >= maxOutputBytes) {
|
|
187
|
-
truncated = true;
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
const remaining = maxOutputBytes - totalBytes;
|
|
191
|
-
if (buffer.length <= remaining) {
|
|
192
|
-
chunks.push(buffer);
|
|
193
|
-
totalBytes += buffer.length;
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
chunks.push(buffer.subarray(0, remaining));
|
|
197
|
-
totalBytes += remaining;
|
|
198
|
-
truncated = true;
|
|
199
|
-
},
|
|
200
|
-
complete() {
|
|
201
|
-
return truncatedOutput(chunks, truncated, maxOutputBytes);
|
|
202
|
-
},
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
async function killCommandTree(child: ChildProcess): Promise<void> {
|
|
207
|
-
const pid = child.pid;
|
|
208
|
-
if (!pid) return;
|
|
209
|
-
try {
|
|
210
|
-
if (process.platform !== 'win32') {
|
|
211
|
-
try {
|
|
212
|
-
process.kill(-pid, 'SIGTERM');
|
|
213
|
-
} catch {
|
|
214
|
-
child.kill('SIGTERM');
|
|
215
|
-
}
|
|
216
|
-
} else {
|
|
217
|
-
child.kill();
|
|
218
|
-
}
|
|
219
|
-
} catch {
|
|
220
|
-
/* already exited */
|
|
221
|
-
}
|
|
222
|
-
await terminateProcessTree(pid, { gracePeriodMs: 200, killAfterMs: 1_000, pollIntervalMs: 25 });
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export async function runCanonicalCommand(
|
|
226
|
-
command: CanonicalRepositoryCommand,
|
|
227
|
-
cwd: string,
|
|
228
|
-
timeoutMs: number,
|
|
229
|
-
maxOutputBytes: number,
|
|
230
|
-
hooks: RepositoryCommandAsyncHooks = {},
|
|
231
|
-
): Promise<SpawnCommandResult> {
|
|
232
|
-
if (hooks.signal?.aborted) {
|
|
233
|
-
return {
|
|
234
|
-
ok: false,
|
|
235
|
-
exitCode: 1,
|
|
236
|
-
timedOut: false,
|
|
237
|
-
cancelled: true,
|
|
238
|
-
stdout: '',
|
|
239
|
-
stderr: 'cancelled before spawn',
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const executable = command.kind === 'argv'
|
|
244
|
-
? command.executable!
|
|
245
|
-
: process.platform === 'win32' ? 'cmd.exe' : '/bin/sh';
|
|
246
|
-
const args = command.kind === 'argv'
|
|
247
|
-
? [...(command.args ?? [])]
|
|
248
|
-
: process.platform === 'win32' ? ['/d', '/s', '/c', command.shellCommand!] : ['-c', command.shellCommand!];
|
|
249
|
-
const display = typeof command.value === 'string' ? command.value : JSON.stringify(command.value);
|
|
250
|
-
const stdoutCollector = collectOutput(maxOutputBytes);
|
|
251
|
-
const stderrCollector = collectOutput(maxOutputBytes);
|
|
252
|
-
const useProcessGroup = process.platform !== 'win32';
|
|
253
|
-
|
|
254
|
-
return await new Promise<SpawnCommandResult>((resolve) => {
|
|
255
|
-
const child = spawn(executable, args, {
|
|
256
|
-
cwd,
|
|
257
|
-
env: commandEnvironment(),
|
|
258
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
259
|
-
detached: useProcessGroup,
|
|
260
|
-
});
|
|
261
|
-
if (child.pid) hooks.onSpawn?.(child.pid);
|
|
262
|
-
let settled = false;
|
|
263
|
-
let timedOut = false;
|
|
264
|
-
let cancelled = false;
|
|
265
|
-
let spawnError = '';
|
|
266
|
-
let timeoutHandle: NodeJS.Timeout | undefined;
|
|
267
|
-
|
|
268
|
-
const finish = (exitCode: number) => {
|
|
269
|
-
if (settled) return;
|
|
270
|
-
settled = true;
|
|
271
|
-
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
272
|
-
hooks.signal?.removeEventListener('abort', onAbort);
|
|
273
|
-
const stderrParts = [stderrCollector.complete()];
|
|
274
|
-
if (timedOut) stderrParts.push(`process timed out after ${timeoutMs}ms: ${redactProcessOutput(display)}`);
|
|
275
|
-
if (cancelled) stderrParts.push('process cancelled');
|
|
276
|
-
if (spawnError) stderrParts.push(redactProcessOutput(spawnError));
|
|
277
|
-
resolve({
|
|
278
|
-
ok: exitCode === 0 && !timedOut && !cancelled && !spawnError,
|
|
279
|
-
exitCode,
|
|
280
|
-
timedOut,
|
|
281
|
-
cancelled,
|
|
282
|
-
stdout: stdoutCollector.complete(),
|
|
283
|
-
stderr: capProcessOutput(stderrParts.filter(Boolean).join('\n'), maxOutputBytes),
|
|
284
|
-
});
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
const onAbort = () => {
|
|
288
|
-
cancelled = true;
|
|
289
|
-
void killCommandTree(child).finally(() => finish(1));
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
timeoutHandle = setTimeout(() => {
|
|
293
|
-
timedOut = true;
|
|
294
|
-
void killCommandTree(child).finally(() => finish(1));
|
|
295
|
-
}, timeoutMs);
|
|
296
|
-
timeoutHandle.unref();
|
|
297
|
-
|
|
298
|
-
hooks.signal?.addEventListener('abort', onAbort, { once: true });
|
|
299
|
-
|
|
300
|
-
child.stdout?.on('data', (chunk) => {
|
|
301
|
-
stdoutCollector.write(chunk);
|
|
302
|
-
hooks.onStdout?.(chunk.toString());
|
|
303
|
-
});
|
|
304
|
-
child.stderr?.on('data', (chunk) => {
|
|
305
|
-
stderrCollector.write(chunk);
|
|
306
|
-
hooks.onStderr?.(chunk.toString());
|
|
307
|
-
});
|
|
308
|
-
child.on('error', (error) => {
|
|
309
|
-
spawnError = error.message;
|
|
310
|
-
});
|
|
311
|
-
child.on('close', (code) => {
|
|
312
|
-
finish(code ?? 1);
|
|
313
|
-
});
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function commandOutput(command: string, args: string[], cwd: string, maxOutputBytes: number): {
|
|
318
|
-
ok: boolean;
|
|
319
|
-
stdout: string;
|
|
320
|
-
} {
|
|
321
|
-
const result = spawnSync(command, args, {
|
|
322
|
-
cwd,
|
|
323
|
-
env: commandEnvironment(),
|
|
324
|
-
encoding: 'utf-8',
|
|
325
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
326
|
-
timeout: 10_000,
|
|
327
|
-
maxBuffer: Math.max(maxOutputBytes, 1024 * 1024),
|
|
328
|
-
});
|
|
329
|
-
return {
|
|
330
|
-
ok: result.status === 0 && !result.error,
|
|
331
|
-
stdout: capProcessOutput(
|
|
332
|
-
redactProcessOutput(typeof result.stdout === 'string' ? result.stdout : ''),
|
|
333
|
-
maxOutputBytes,
|
|
334
|
-
),
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
function gitText(root: string, args: string[]): string {
|
|
339
|
-
const output = commandOutput('git', ['-C', root, ...args], root, 256 * 1024);
|
|
340
|
-
return output.ok ? output.stdout.trim() : '';
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const REPOSITORY_SNAPSHOT_PATHS = [
|
|
344
|
-
'.',
|
|
345
|
-
':(exclude).ai/harness/**',
|
|
346
|
-
':(exclude)_ops/controller-home/**',
|
|
347
|
-
];
|
|
348
|
-
|
|
349
|
-
function statusPath(line: string): string | undefined {
|
|
350
|
-
if (!line.trim() || line.startsWith('##')) return undefined;
|
|
351
|
-
const raw = line.length > 3 ? line.slice(3) : '';
|
|
352
|
-
const path = raw.includes(' -> ') ? raw.split(' -> ').at(-1) : raw;
|
|
353
|
-
return path?.replace(/^"|"$/g, '');
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function repositoryPathFingerprint(root: string, relativePath: string, statusLines: string[]): string {
|
|
357
|
-
const hash = createHash('sha256').update(statusLines.join('\n'));
|
|
358
|
-
const absolute = resolve(root, relativePath);
|
|
359
|
-
if (!existsSync(absolute)) return hash.update('\nmissing').digest('hex');
|
|
360
|
-
try {
|
|
361
|
-
const stat = lstatSync(absolute);
|
|
362
|
-
if (stat.isSymbolicLink()) hash.update(`\nsymlink:${readlinkSync(absolute)}`);
|
|
363
|
-
else if (stat.isFile()) hash.update('\nfile:').update(readFileSync(absolute));
|
|
364
|
-
else hash.update(`\nmode:${stat.mode}:size:${stat.size}`);
|
|
365
|
-
} catch (error) {
|
|
366
|
-
hash.update(`\nunreadable:${error instanceof Error ? error.message : String(error)}`);
|
|
367
|
-
}
|
|
368
|
-
return hash.digest('hex');
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function buildSnapshotFromGitTexts(
|
|
372
|
-
root: string,
|
|
373
|
-
head: string | null,
|
|
374
|
-
branch: string | null,
|
|
375
|
-
status: string,
|
|
376
|
-
refs: string,
|
|
377
|
-
): RepositoryCommandSnapshot {
|
|
378
|
-
const lines = status.split(/\r?\n/).filter((line) => line && !line.startsWith('##'));
|
|
379
|
-
const statusByPath = new Map<string, string[]>();
|
|
380
|
-
for (const line of lines) {
|
|
381
|
-
const path = statusPath(line);
|
|
382
|
-
if (!path) continue;
|
|
383
|
-
const entries = statusByPath.get(path) ?? [];
|
|
384
|
-
entries.push(line);
|
|
385
|
-
statusByPath.set(path, entries);
|
|
386
|
-
}
|
|
387
|
-
const paths = [...statusByPath.keys()].sort();
|
|
388
|
-
const pathFingerprints = Object.fromEntries(paths.map((path) => [
|
|
389
|
-
path,
|
|
390
|
-
repositoryPathFingerprint(root, path, statusByPath.get(path) ?? []),
|
|
391
|
-
]));
|
|
392
|
-
return {
|
|
393
|
-
head,
|
|
394
|
-
branch,
|
|
395
|
-
status,
|
|
396
|
-
dirty: paths.length > 0,
|
|
397
|
-
refsHash: createHash('sha256').update(refs).digest('hex'),
|
|
398
|
-
paths,
|
|
399
|
-
pathFingerprints,
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function repositorySnapshot(root: string): RepositoryCommandSnapshot {
|
|
404
|
-
const head = gitText(root, ['rev-parse', '--verify', 'HEAD']) || null;
|
|
405
|
-
const branch = gitText(root, ['branch', '--show-current']) || null;
|
|
406
|
-
const status = gitText(root, ['status', '--porcelain=v1', '--branch', '--untracked-files=all', '--', ...REPOSITORY_SNAPSHOT_PATHS]);
|
|
407
|
-
const refs = gitText(root, ['show-ref']);
|
|
408
|
-
return buildSnapshotFromGitTexts(root, head, branch, status, refs);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
interface GitTextAsyncResult {
|
|
412
|
-
ok: boolean;
|
|
413
|
-
exitCode: number;
|
|
414
|
-
stdout: string;
|
|
415
|
-
stderr: string;
|
|
416
|
-
timedOut: boolean;
|
|
417
|
-
cancelled: boolean;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
async function gitTextAsync(root: string, args: string[], signal?: AbortSignal): Promise<GitTextAsyncResult> {
|
|
421
|
-
if (signal?.aborted) {
|
|
422
|
-
return { ok: false, exitCode: 1, stdout: '', stderr: 'cancelled', timedOut: false, cancelled: true };
|
|
423
|
-
}
|
|
424
|
-
return await new Promise<GitTextAsyncResult>((resolve) => {
|
|
425
|
-
const child = spawn('git', ['-C', root, ...args], {
|
|
426
|
-
cwd: root,
|
|
427
|
-
env: commandEnvironment(),
|
|
428
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
429
|
-
detached: process.platform !== 'win32',
|
|
430
|
-
});
|
|
431
|
-
const stdout = collectOutput(256 * 1024);
|
|
432
|
-
const stderr = collectOutput(64 * 1024);
|
|
433
|
-
let settled = false;
|
|
434
|
-
let timedOut = false;
|
|
435
|
-
let cancelled = false;
|
|
436
|
-
const timeout = setTimeout(() => {
|
|
437
|
-
timedOut = true;
|
|
438
|
-
void killCommandTree(child).finally(() => {
|
|
439
|
-
if (!settled) {
|
|
440
|
-
settled = true;
|
|
441
|
-
resolve({
|
|
442
|
-
ok: false,
|
|
443
|
-
exitCode: 1,
|
|
444
|
-
stdout: stdout.complete().trim(),
|
|
445
|
-
stderr: `git ${args.join(' ')} timed out`,
|
|
446
|
-
timedOut: true,
|
|
447
|
-
cancelled: false,
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
});
|
|
451
|
-
}, 10_000);
|
|
452
|
-
timeout.unref();
|
|
453
|
-
const onAbort = () => {
|
|
454
|
-
cancelled = true;
|
|
455
|
-
void killCommandTree(child).finally(() => {
|
|
456
|
-
if (!settled) {
|
|
457
|
-
settled = true;
|
|
458
|
-
clearTimeout(timeout);
|
|
459
|
-
resolve({
|
|
460
|
-
ok: false,
|
|
461
|
-
exitCode: 1,
|
|
462
|
-
stdout: '',
|
|
463
|
-
stderr: 'cancelled',
|
|
464
|
-
timedOut: false,
|
|
465
|
-
cancelled: true,
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
});
|
|
469
|
-
};
|
|
470
|
-
signal?.addEventListener('abort', onAbort, { once: true });
|
|
471
|
-
child.stdout?.on('data', (chunk) => stdout.write(chunk));
|
|
472
|
-
child.stderr?.on('data', (chunk) => stderr.write(chunk));
|
|
473
|
-
child.on('error', (error) => {
|
|
474
|
-
if (!settled) {
|
|
475
|
-
settled = true;
|
|
476
|
-
clearTimeout(timeout);
|
|
477
|
-
signal?.removeEventListener('abort', onAbort);
|
|
478
|
-
resolve({
|
|
479
|
-
ok: false,
|
|
480
|
-
exitCode: 1,
|
|
481
|
-
stdout: '',
|
|
482
|
-
stderr: error.message,
|
|
483
|
-
timedOut: false,
|
|
484
|
-
cancelled: false,
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
});
|
|
488
|
-
child.on('close', (code) => {
|
|
489
|
-
if (settled) return;
|
|
490
|
-
settled = true;
|
|
491
|
-
clearTimeout(timeout);
|
|
492
|
-
signal?.removeEventListener('abort', onAbort);
|
|
493
|
-
const exitCode = code ?? 1;
|
|
494
|
-
resolve({
|
|
495
|
-
ok: exitCode === 0 && !timedOut && !cancelled,
|
|
496
|
-
exitCode,
|
|
497
|
-
stdout: stdout.complete().trim(),
|
|
498
|
-
stderr: stderr.complete(),
|
|
499
|
-
timedOut,
|
|
500
|
-
cancelled,
|
|
501
|
-
});
|
|
502
|
-
});
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
const MAX_DIRTY_PATHS_FOR_FINGERPRINT = 200;
|
|
507
|
-
const MAX_FINGERPRINT_FILE_BYTES = 256 * 1024;
|
|
508
|
-
const MAX_FINGERPRINT_TOTAL_BYTES = 8 * 1024 * 1024;
|
|
509
|
-
const MAX_FINGERPRINT_WORKER_MS = 5_000;
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Async repository snapshot for Fast Path — Git via async spawn, fail-closed.
|
|
513
|
-
* Dirty path fingerprints run in a Worker Thread with hard budgets.
|
|
514
|
-
* Over-budget / git errors → throw (caller may escalate to Durable).
|
|
515
|
-
* Only unborn HEAD and empty refs are allowed non-zero exceptions.
|
|
516
|
-
*/
|
|
517
|
-
export async function repositorySnapshotAsync(
|
|
518
|
-
root: string,
|
|
519
|
-
signal?: AbortSignal,
|
|
520
|
-
): Promise<RepositoryCommandSnapshot> {
|
|
521
|
-
const [headResult, branchResult, statusResult, refsResult] = await Promise.all([
|
|
522
|
-
gitTextAsync(root, ['rev-parse', '--verify', 'HEAD'], signal),
|
|
523
|
-
gitTextAsync(root, ['branch', '--show-current'], signal),
|
|
524
|
-
gitTextAsync(root, ['status', '--porcelain=v1', '--branch', '--untracked-files=all', '--', ...REPOSITORY_SNAPSHOT_PATHS], signal),
|
|
525
|
-
gitTextAsync(root, ['show-ref'], signal),
|
|
526
|
-
]);
|
|
527
|
-
|
|
528
|
-
if (statusResult.cancelled || headResult.cancelled || branchResult.cancelled || refsResult.cancelled) {
|
|
529
|
-
throw new Error('CANCELLED: repository snapshot aborted');
|
|
530
|
-
}
|
|
531
|
-
if (statusResult.timedOut || headResult.timedOut) {
|
|
532
|
-
throw new Error('SNAPSHOT_TIMEOUT: git snapshot timed out');
|
|
533
|
-
}
|
|
534
|
-
// status is required — fail closed on any non-ok (empty porcelain is ok only when ok=true)
|
|
535
|
-
if (!statusResult.ok) {
|
|
536
|
-
throw new Error(`SNAPSHOT_FAILED: git status exit ${statusResult.exitCode}: ${statusResult.stderr}`);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
// unborn HEAD: rev-parse exit 128 / known messages → null; other failures fail-closed
|
|
540
|
-
let head: string | null = null;
|
|
541
|
-
if (headResult.ok) {
|
|
542
|
-
head = headResult.stdout || null;
|
|
543
|
-
} else {
|
|
544
|
-
const unborn = headResult.exitCode === 128
|
|
545
|
-
|| /unknown revision|bad revision|Needed a single revision|ambiguous argument|not a valid object name/i.test(headResult.stderr);
|
|
546
|
-
if (!unborn) {
|
|
547
|
-
throw new Error(`SNAPSHOT_FAILED: git rev-parse HEAD exit ${headResult.exitCode}: ${headResult.stderr}`);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// show-ref: empty repo has exit 1 with empty stdout — allowed; other failures fail-closed
|
|
552
|
-
let refs = '';
|
|
553
|
-
if (refsResult.ok) {
|
|
554
|
-
refs = refsResult.stdout;
|
|
555
|
-
} else if (refsResult.exitCode === 1 && !refsResult.stderr.trim()) {
|
|
556
|
-
refs = refsResult.stdout || '';
|
|
557
|
-
} else if (refsResult.exitCode === 1 && /expected|no match|no references/i.test(refsResult.stderr)) {
|
|
558
|
-
refs = refsResult.stdout || '';
|
|
559
|
-
} else {
|
|
560
|
-
throw new Error(`SNAPSHOT_FAILED: git show-ref exit ${refsResult.exitCode}: ${refsResult.stderr}`);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// branch --show-current may fail on detached/unborn — null is fine when status worked
|
|
564
|
-
const branch = branchResult.ok ? (branchResult.stdout || null) : null;
|
|
565
|
-
const status = statusResult.stdout;
|
|
566
|
-
|
|
567
|
-
if (signal?.aborted) throw new Error('CANCELLED: repository snapshot aborted');
|
|
568
|
-
|
|
569
|
-
const lines = status.split(/\r?\n/).filter((line) => line && !line.startsWith('##'));
|
|
570
|
-
if (lines.length > MAX_DIRTY_PATHS_FOR_FINGERPRINT) {
|
|
571
|
-
throw new Error(`SNAPSHOT_TOO_DIRTY: ${lines.length} dirty paths exceeds Fast Path cap ${MAX_DIRTY_PATHS_FOR_FINGERPRINT}`);
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
const statusByPath = new Map<string, string[]>();
|
|
575
|
-
for (const line of lines) {
|
|
576
|
-
const path = statusPath(line);
|
|
577
|
-
if (!path) continue;
|
|
578
|
-
const entries = statusByPath.get(path) ?? [];
|
|
579
|
-
entries.push(line);
|
|
580
|
-
statusByPath.set(path, entries);
|
|
581
|
-
}
|
|
582
|
-
const paths = [...statusByPath.keys()].sort();
|
|
583
|
-
|
|
584
|
-
// Offload fingerprint I/O to Worker Thread (or bounded sync for tiny sets).
|
|
585
|
-
const { computePathFingerprintsAsync } = await import(
|
|
586
|
-
'../../runtime/execution/thin-harness/fingerprint-worker'
|
|
587
|
-
);
|
|
588
|
-
const fingerprintResult = await computePathFingerprintsAsync(
|
|
589
|
-
{
|
|
590
|
-
root,
|
|
591
|
-
paths,
|
|
592
|
-
statusByPath: Object.fromEntries(statusByPath),
|
|
593
|
-
maxFileBytes: MAX_FINGERPRINT_FILE_BYTES,
|
|
594
|
-
maxTotalBytes: MAX_FINGERPRINT_TOTAL_BYTES,
|
|
595
|
-
maxPaths: MAX_DIRTY_PATHS_FOR_FINGERPRINT,
|
|
596
|
-
},
|
|
597
|
-
{ signal, timeoutMs: MAX_FINGERPRINT_WORKER_MS },
|
|
598
|
-
);
|
|
599
|
-
|
|
600
|
-
return {
|
|
601
|
-
head,
|
|
602
|
-
branch,
|
|
603
|
-
status,
|
|
604
|
-
dirty: paths.length > 0,
|
|
605
|
-
refsHash: createHash('sha256').update(refs).digest('hex'),
|
|
606
|
-
paths,
|
|
607
|
-
pathFingerprints: fingerprintResult.pathFingerprints,
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
|
|
611
137
|
function approvalToken(
|
|
612
138
|
repository: RepositoryRecord,
|
|
613
139
|
relativeCwd: string,
|
|
@@ -628,20 +154,6 @@ function approvalToken(
|
|
|
628
154
|
})).digest('hex');
|
|
629
155
|
}
|
|
630
156
|
|
|
631
|
-
function changedSnapshotPaths(before: RepositoryCommandSnapshot, after: RepositoryCommandSnapshot): string[] {
|
|
632
|
-
const paths = new Set([...before.paths, ...after.paths]);
|
|
633
|
-
return [...paths].filter((path) =>
|
|
634
|
-
before.pathFingerprints[path] !== after.pathFingerprints[path]
|
|
635
|
-
).sort();
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
function snapshotChanged(before: RepositoryCommandSnapshot, after: RepositoryCommandSnapshot): boolean {
|
|
639
|
-
return before.head !== after.head
|
|
640
|
-
|| before.branch !== after.branch
|
|
641
|
-
|| before.refsHash !== after.refsHash
|
|
642
|
-
|| changedSnapshotPaths(before, after).length > 0;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
157
|
function finalizePreparedExecution(
|
|
646
158
|
repository: RepositoryRecord,
|
|
647
159
|
input: ExecuteRepositoryCommandInput,
|
|
@@ -670,7 +182,11 @@ function finalizePreparedExecution(
|
|
|
670
182
|
const isGit = command.kind === 'argv'
|
|
671
183
|
? command.executable?.split(/[\\/]/).at(-1)?.toLowerCase() === 'git'
|
|
672
184
|
: /^\s*git\s+/i.test(command.shellCommand!);
|
|
673
|
-
|
|
185
|
+
// Command classification is observability/replay metadata, not execution
|
|
186
|
+
// authority. Raw repository commands follow the host AI permission model once
|
|
187
|
+
// repository/cwd/external-path scope has been resolved. Typed catastrophic
|
|
188
|
+
// Forge operations retain their own explicit confirmation contracts.
|
|
189
|
+
const risk = isGit ? 'local_git' : 'local_command';
|
|
674
190
|
const delegated = input.authorizationDecision ?? (controllerHome ? decideAuthorization({
|
|
675
191
|
controllerHome,
|
|
676
192
|
accessMode: permission?.mode ?? 'request',
|
|
@@ -701,16 +217,16 @@ function finalizePreparedExecution(
|
|
|
701
217
|
approvalToken: token,
|
|
702
218
|
authorization: input.authorization,
|
|
703
219
|
before,
|
|
704
|
-
policyDecision: input.dryRun === true ||
|
|
220
|
+
policyDecision: input.dryRun === true || !controllerHome || effectiveDecision?.decision === 'allow'
|
|
705
221
|
? 'allowed'
|
|
706
|
-
:
|
|
222
|
+
: 'approval_required',
|
|
707
223
|
...(effectiveDecision ? { authorizationDecision: effectiveDecision } : {}),
|
|
708
224
|
...(effectiveDecision?.decision === 'user_confirmation_required' ? { approvalRequestId: effectiveDecision.approvalRequestId } : {}),
|
|
709
225
|
externalPathUsages: externalPathUsages.length > 0 ? externalPathUsages : undefined,
|
|
710
226
|
};
|
|
711
227
|
const confirmed = input.authorization === 'confirmed_plan' && input.approvalToken === token;
|
|
712
|
-
const delegatedAllowed = effectiveDecision?.decision === 'allow';
|
|
713
|
-
const executable = input.dryRun === true ||
|
|
228
|
+
const delegatedAllowed = !controllerHome || effectiveDecision?.decision === 'allow';
|
|
229
|
+
const executable = input.dryRun === true || confirmed || delegatedAllowed;
|
|
714
230
|
execution.policyDecision = executable ? 'allowed' : 'approval_required';
|
|
715
231
|
return {
|
|
716
232
|
root,
|
|
@@ -741,7 +257,9 @@ function prepareRepositoryCommandExecution(
|
|
|
741
257
|
externalPathUsages: RepositoryCommandExternalPathUsage[];
|
|
742
258
|
} {
|
|
743
259
|
const { root, cwd, relativeCwd } = resolveRepositoryCommandCwd(repository, input.cwd);
|
|
744
|
-
const command = assertRepositoryCommandInputAllowed(input.command
|
|
260
|
+
const command = assertRepositoryCommandInputAllowed(input.command, {
|
|
261
|
+
allowOpaqueLocalScript: input.allowOpaqueLocalScript,
|
|
262
|
+
});
|
|
745
263
|
const externalGrants = loadExternalFilesystemGrants(root).grants;
|
|
746
264
|
const externalPathUsages = assertCommandPathOperandsStayInRepository(command, cwd, root, externalGrants);
|
|
747
265
|
const classification = classifyRepositoryCommand(command, repository.defaultBranch);
|
|
@@ -776,11 +294,20 @@ async function prepareRepositoryCommandExecutionAsync(
|
|
|
776
294
|
externalPathUsages: RepositoryCommandExternalPathUsage[];
|
|
777
295
|
}> {
|
|
778
296
|
const { root, cwd, relativeCwd } = resolveRepositoryCommandCwd(repository, input.cwd);
|
|
779
|
-
const command = assertRepositoryCommandInputAllowed(input.command
|
|
297
|
+
const command = assertRepositoryCommandInputAllowed(input.command, {
|
|
298
|
+
allowOpaqueLocalScript: input.allowOpaqueLocalScript,
|
|
299
|
+
});
|
|
780
300
|
const externalGrants = loadExternalFilesystemGrants(root).grants;
|
|
781
301
|
const externalPathUsages = assertCommandPathOperandsStayInRepository(command, cwd, root, externalGrants);
|
|
782
302
|
const classification = classifyRepositoryCommand(command, repository.defaultBranch);
|
|
783
|
-
|
|
303
|
+
// A proven readonly direct/preview lane never compares before/after mutation.
|
|
304
|
+
// Skip per-path fingerprints there so a large dirty worktree cannot block a
|
|
305
|
+
// harmless read; write-capable lanes keep the strict fingerprint cap.
|
|
306
|
+
const before = input.reuseSnapshot ?? (input.allowNonGitWorkspace
|
|
307
|
+
? emptyWorkspaceSnapshot()
|
|
308
|
+
: await repositorySnapshotAsync(root, input.signal, {
|
|
309
|
+
pathFingerprints: !(classification.risk === 'readonly' && controllerHome === undefined),
|
|
310
|
+
}));
|
|
784
311
|
return finalizePreparedExecution(
|
|
785
312
|
repository,
|
|
786
313
|
input,
|
|
@@ -907,6 +434,7 @@ export function executeRepositoryCommand(
|
|
|
907
434
|
message: error || `repository command failed with exit ${String(result.status ?? 1)}`,
|
|
908
435
|
} : undefined,
|
|
909
436
|
};
|
|
437
|
+
if (execution.repositoryChanged) invalidateRepositoryReadCaches(root);
|
|
910
438
|
auditCommand(controllerHome, repository, execution);
|
|
911
439
|
return execution;
|
|
912
440
|
}
|
|
@@ -956,7 +484,12 @@ export async function executeRepositoryReadOnlyCommandDirect(
|
|
|
956
484
|
}
|
|
957
485
|
|
|
958
486
|
const result = await runCanonicalCommand(command, cwd, timeoutMs, maxOutputBytes, { signal });
|
|
959
|
-
|
|
487
|
+
// This entry point is reachable only after the bounded classifier has proven
|
|
488
|
+
// the command readonly. A second full repository snapshot would start four
|
|
489
|
+
// extra Git processes merely to rediscover the invariant we already used to
|
|
490
|
+
// select this lane. Keep the captured pre-execution snapshot as both sides of
|
|
491
|
+
// the receipt; write-capable lanes still take independent before/after snapshots.
|
|
492
|
+
const after = before;
|
|
960
493
|
return {
|
|
961
494
|
...base,
|
|
962
495
|
status: 'executed',
|
|
@@ -967,8 +500,8 @@ export async function executeRepositoryReadOnlyCommandDirect(
|
|
|
967
500
|
stdout: result.stdout,
|
|
968
501
|
stderr: result.stderr,
|
|
969
502
|
after,
|
|
970
|
-
repositoryChanged:
|
|
971
|
-
changedPaths:
|
|
503
|
+
repositoryChanged: false,
|
|
504
|
+
changedPaths: [],
|
|
972
505
|
policyDecision: 'allowed',
|
|
973
506
|
externalPathUsages: externalPathUsages.length > 0 ? externalPathUsages : undefined,
|
|
974
507
|
infrastructureError: result.timedOut
|
|
@@ -1061,6 +594,7 @@ export async function executeRepositoryCommandAsync(
|
|
|
1061
594
|
}
|
|
1062
595
|
: undefined,
|
|
1063
596
|
};
|
|
597
|
+
if (execution.repositoryChanged) invalidateRepositoryReadCaches(root);
|
|
1064
598
|
auditCommand(controllerHome, repository, execution);
|
|
1065
599
|
return execution;
|
|
1066
600
|
}
|