@moretea-labs/forge 1.6.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -0
- package/CHANGELOG.md +39 -0
- package/CLAUDE.md +4 -0
- package/README.md +2 -2
- package/README.zh-CN.md +1 -1
- package/SKILL.md +2 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/agents/openai.yaml +1 -1
- package/assets/hooks/hook-input.sh +11 -8
- package/assets/hooks/lib/session-state.sh +10 -7
- package/assets/hooks/lib/workflow-state.sh +13 -177
- package/assets/hooks/post-tool-observer.sh +8 -2
- package/assets/hooks/prompt-guard.sh +5 -81
- package/assets/hooks/session-start-context.sh +56 -1
- package/assets/partials/05-workflow.partial.md +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +2 -0
- package/assets/partials-agents/03-orchestration.partial.md +1 -1
- package/assets/plugin-registry.v1.json +3 -3
- package/assets/reference-configs/document-generation.md +1 -1
- package/assets/reference-configs/harness-overview.md +1 -1
- package/assets/reference-configs/sprint-contracts.md +3 -3
- package/assets/skill-version.json +2 -2
- package/assets/templates/contract.template.md +2 -7
- package/assets/templates/helpers/capture-plan.sh +2 -2
- package/assets/templates/helpers/check-architecture-sync.sh +17 -0
- package/assets/templates/helpers/check-task-workflow.sh +4 -4
- package/assets/templates/helpers/contract-worktree.sh +1 -21
- package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
- package/assets/templates/helpers/inspect-project-state.ts +8 -6
- package/assets/templates/helpers/migrate-project-template.sh +22 -45
- package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
- package/assets/templates/helpers/new-plan.sh +1 -1
- package/assets/templates/helpers/plan-to-todo.sh +12 -37
- package/assets/templates/helpers/ship-worktrees.sh +2 -29
- package/assets/templates/helpers/verify-contract.sh +1 -111
- package/assets/templates/helpers/verify-sprint.sh +3 -180
- package/assets/templates/helpers/workflow-contract.ts +5 -1
- package/assets/templates/plan.template.md +1 -1
- package/assets/templates/review.template.md +9 -34
- package/assets/workflow-contract.v1.json +10 -19
- package/docs/README.md +10 -10
- package/docs/architecture/CURRENT.md +178 -0
- package/docs/architecture/EVOLUTION.md +42 -0
- package/docs/architecture/current/README.md +4 -156
- package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
- package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
- package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
- package/docs/architecture/history/README.md +7 -0
- package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
- package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
- package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
- package/docs/architecture/history.md +3 -34
- package/docs/architecture/index.md +15 -98
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
- package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
- package/docs/architecture/versions/1.6.md +38 -0
- package/docs/architecture/versions/1.7.md +41 -0
- package/docs/forge-chatgpt-browser-engine.md +2 -2
- package/docs/forge-chatgpt-mcp-setup.md +4 -28
- package/docs/forge-chatgpt-supervised-automation.md +3 -3
- package/docs/forge-github-issue-launcher.md +1 -1
- package/docs/forge-plugin-management.md +1 -1
- package/docs/forge-runtime-self-healing-loop.md +1 -1
- package/docs/operations/controller-browser-plugin.md +26 -6
- package/docs/operations/controller-performance-and-502.md +143 -0
- package/docs/operations/controller-reliability-runbook.md +1 -1
- package/docs/operations/features.md +2 -2
- package/docs/operations/github-repository.md +1 -1
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +5 -3
- package/docs/wiki/Architecture.md +6 -6
- package/docs/wiki/Implementation.md +2 -2
- package/docs/wiki/Integrations.md +2 -2
- package/docs/wiki/Work-Lifecycle.md +1 -1
- package/package.json +33 -11
- package/scripts/activate-source-baseline.ts +17 -13
- package/scripts/architecture-event.ts +2 -820
- package/scripts/architecture-queue.sh +2 -645
- package/scripts/archive-architecture-request.sh +2 -242
- package/scripts/archive-workflow.sh +1 -291
- package/scripts/benchmark-background-check-overlap.ts +228 -0
- package/scripts/benchmark-mcp-host.sh +117 -0
- package/scripts/benchmark-mcp-transport.ts +170 -0
- package/scripts/benchmark-quality-harness.ts +146 -0
- package/scripts/benchmark-route-session-concurrency.ts +207 -6
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
- package/scripts/benchmark-typescript-navigation.ts +186 -0
- package/scripts/benchmark-work-efficiency.ts +334 -0
- package/scripts/capability-config.ts +2 -392
- package/scripts/capability-resolver.ts +2 -503
- package/scripts/capture-plan.sh +1 -332
- package/scripts/check-agent-tooling.sh +2 -1558
- package/scripts/check-architecture-sync.sh +2 -371
- package/scripts/check-brain-manifest.sh +2 -253
- package/scripts/check-context-files.sh +2 -47
- package/scripts/check-deploy-sql-order.sh +2 -115
- package/scripts/check-mcp-compatibility.ts +14 -0
- package/scripts/check-runtime-architecture.mjs +349 -79
- package/scripts/check-skill-version.ts +3 -191
- package/scripts/check-source-duplication.mjs +61 -0
- package/scripts/check-stable-baseline.ts +92 -0
- package/scripts/check-task-sync.sh +2 -63
- package/scripts/check-task-workflow.sh +2 -1000
- package/scripts/codex-handoff-resume.sh +2 -267
- package/scripts/context-contract-sync.sh +2 -514
- package/scripts/contract-run.ts +2 -463
- package/scripts/contract-worktree.sh +1 -722
- package/scripts/ensure-task-workflow.sh +1 -1225
- package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
- package/scripts/inspect-project-state.ts +4 -279
- package/scripts/install-standalone-recovery.ts +24 -0
- package/scripts/lib/project-init-lib.sh +22 -46
- package/scripts/lib/run-forge-helper.sh +19 -0
- package/scripts/lib/run-forge-helper.ts +26 -0
- package/scripts/migrate-project-template.sh +1 -1189
- package/scripts/migrate-workflow-docs.ts +4 -412
- package/scripts/new-plan.sh +1 -180
- package/scripts/new-spec.sh +1 -47
- package/scripts/new-sprint.sh +1 -46
- package/scripts/plan-to-todo.sh +1 -796
- package/scripts/prepare-codex-handoff.sh +2 -201
- package/scripts/prepare-handoff.sh +1 -78
- package/scripts/refresh-current-status.sh +2 -467
- package/scripts/run-governed-gate.ts +5 -1
- package/scripts/select-agent-context-blocks.sh +2 -64
- package/scripts/ship-worktrees.sh +1 -558
- package/scripts/smoke-ios-physical-device.ts +4 -2
- package/scripts/smoke-runtime-recovery.ts +1 -35
- package/scripts/smoke-schedule-engine.ts +1 -20
- package/scripts/sprint-backlog.sh +1 -834
- package/scripts/stage-runtime-release.ts +2 -1
- package/scripts/summarize-failures.sh +2 -123
- package/scripts/switch-plan.sh +1 -150
- package/scripts/sync-brain-docs.sh +2 -379
- package/scripts/sync-generated-authority.mjs +175 -0
- package/scripts/verify-contract.sh +2 -703
- package/scripts/verify-forge-runtime.sh +0 -1
- package/scripts/verify-sprint.sh +1 -677
- package/scripts/workflow-contract.ts +4 -205
- package/scripts/workstream-sync.sh +2 -261
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/commands/init-hook.ts +3 -3
- package/src/cli/commands/mcp.ts +9 -5
- package/src/cli/commands/plugin.ts +12 -3
- package/src/cli/commands/recovery.ts +25 -3
- package/src/cli/commands/runtime.ts +55 -3
- package/src/cli/commands/security.ts +12 -1
- package/src/cli/commands/setup-profile.ts +31 -21
- package/src/cli/controller/check-runner.ts +113 -5
- package/src/cli/controller/composite-result.ts +1 -1
- package/src/cli/controller/context/focus.ts +51 -0
- package/src/cli/controller/context/instruction-context.ts +114 -0
- package/src/cli/controller/context/known-paths.ts +76 -0
- package/src/cli/controller/context/query-planning.ts +110 -0
- package/src/cli/controller/context/rule-resolution.ts +532 -0
- package/src/cli/controller/context/source-materializer.ts +278 -0
- package/src/cli/controller/context/structural.ts +16 -0
- package/src/cli/controller/context/types.ts +286 -0
- package/src/cli/controller/context-pack.ts +828 -487
- package/src/cli/controller/launch-agents.ts +77 -2
- package/src/cli/controller/operational-plan.ts +2 -2
- package/src/cli/controller/work-mode.ts +6 -4
- package/src/cli/editing/edit-session.ts +60 -7
- package/src/cli/github/contracts.ts +16 -0
- package/src/cli/github/github.ts +135 -16
- package/src/cli/github/session.ts +2 -1
- package/src/cli/installer/managed-entries.ts +15 -1
- package/src/cli/local-bridge/console-automations.ts +54 -78
- package/src/cli/local-bridge/console-view-models.ts +1 -35
- package/src/cli/local-bridge/facade-api.ts +37 -264
- package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
- package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
- package/src/cli/local-bridge/frontend/src/styles.css +1 -1
- package/src/cli/local-bridge/frontend/src/types.ts +5 -8
- package/src/cli/local-bridge/job-store.ts +1 -490
- package/src/cli/local-bridge/server.ts +2 -515
- package/src/cli/local-bridge/ui-dist/app.css +1 -1
- package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
- package/src/cli/mcp/auth.ts +5 -5
- package/src/cli/mcp/instructions.ts +3 -2
- package/src/cli/mcp/legacy-tool-service.ts +10 -222
- package/src/cli/mcp/multi-repository.ts +16 -5
- package/src/cli/mcp/oauth.ts +175 -26
- package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
- package/src/cli/mcp/repository-tools.ts +282 -146
- package/src/cli/mcp/server.ts +586 -116
- package/src/cli/mcp/tool-capability-cache.ts +1 -1
- package/src/cli/mcp/tool-contract.ts +14 -0
- package/src/cli/mcp/toolset-selection.ts +1 -1
- package/src/cli/mcp/toolset.ts +44 -7
- package/src/cli/mcp/transports/http.ts +124 -14
- package/src/cli/mcp/transports/session-registry.ts +43 -5
- package/src/cli/mcp/types.ts +3 -3
- package/src/cli/repositories/command-classifier.ts +103 -21
- package/src/cli/repositories/command-executor.ts +78 -526
- package/src/cli/repositories/command-process.ts +108 -0
- package/src/cli/repositories/command-scope.ts +99 -41
- package/src/cli/repositories/controller-home.ts +7 -8
- package/src/cli/repositories/git-command-executor.ts +2 -0
- package/src/cli/repositories/locks.ts +53 -16
- package/src/cli/repositories/repository-snapshot.ts +124 -0
- package/src/cli/repositories/selected-path-actions.ts +17 -9
- package/src/cli/repositories/structured-git.ts +118 -6
- package/src/cli/repository/inspector.ts +563 -65
- package/src/cli/repository/session-cache.ts +58 -17
- package/src/cli/v81-entry.ts +1 -4
- package/src/effects/fs-transaction.ts +85 -17
- package/src/runtime/context/codegraph-read-provider.ts +233 -2
- package/src/runtime/context/codegraph-sidecar.cjs +74 -40
- package/src/runtime/context/generic-lsp-provider.ts +355 -0
- package/src/runtime/context/lsp-client.ts +283 -0
- package/src/runtime/context/semantic-navigation-contract.ts +61 -0
- package/src/runtime/context/semantic-navigation.ts +223 -0
- package/src/runtime/context/swift-navigation.ts +565 -0
- package/src/runtime/context/typescript-navigation.ts +226 -0
- package/src/runtime/control-plane/dispatch-priority.ts +28 -0
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +48 -16
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
- package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
- package/src/runtime/control-plane/execution/session-store.ts +18 -0
- package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
- package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
- package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
- package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
- package/src/runtime/control-plane/execution/work-handle-authority.ts +96 -0
- package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
- package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
- package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
- package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
- package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +22 -18
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +156 -11
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
- package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
- package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +90 -9
- package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
- package/src/runtime/control-plane/facade/controller-round-relay.ts +1118 -0
- package/src/runtime/control-plane/facade/controller-session-store.ts +302 -12
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +17 -1
- package/src/runtime/control-plane/facade/goal-workloop.ts +928 -190
- package/src/runtime/control-plane/facade/index.ts +3 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +275 -42
- package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
- package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
- package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
- package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
- package/src/runtime/control-plane/facade/types.ts +90 -10
- package/src/runtime/control-plane/facade/work-contract-store.ts +157 -261
- package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
- package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
- package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
- package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +152 -0
- package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +278 -543
- package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
- package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
- package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
- package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
- package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
- package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
- package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
- package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
- package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
- package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +63 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +365 -117
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +89 -0
- package/src/runtime/control-plane/launcher/thin-launcher.ts +244 -11
- package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
- package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
- package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
- package/src/runtime/control-plane/release-retention.ts +62 -2
- package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
- package/src/runtime/control-plane/routing/route-policy.ts +24 -21
- package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
- package/src/runtime/control-plane/runtime-status-client.ts +0 -2
- package/src/runtime/diagnostics/mcp-timing.ts +13 -0
- package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
- package/src/runtime/diagnostics/performance.ts +24 -1
- package/src/runtime/evidence/event-ledger.ts +1 -1
- package/src/runtime/evidence/process-check-execution.ts +18 -0
- package/src/runtime/execution/jobs/child-reference.ts +6 -2
- package/src/runtime/execution/jobs/store.ts +58 -4
- package/src/runtime/execution/jobs/types.ts +1 -1
- package/src/runtime/execution/managed-workspace.ts +203 -1
- package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
- package/src/runtime/execution/process-runtime/check-result.ts +88 -0
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
- package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +367 -40
- package/src/runtime/execution/process-runtime/gc.ts +38 -6
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +1201 -0
- package/src/runtime/execution/process-runtime/persisted-check.ts +51 -5
- package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
- package/src/runtime/execution/process-runtime/runtime.ts +53 -3
- package/src/runtime/execution/process-runtime/store.ts +1 -0
- package/src/runtime/execution/process-runtime/types.ts +4 -12
- package/src/runtime/execution/thin-harness/async-process.ts +12 -2
- package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
- package/src/runtime/execution/work-activity.ts +21 -0
- package/src/runtime/execution/workers/executor.ts +18 -61
- package/src/runtime/gateway/mcp/execution-tools.ts +35 -1852
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
- package/src/runtime/gateway/mcp/process-tools.ts +83 -37
- package/src/runtime/gateway/mcp/router.ts +186 -84
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +565 -0
- package/src/runtime/gateway/mcp/runtime-tools.ts +2378 -1947
- package/src/runtime/plugins/app-store-connect-adapter.ts +216 -47
- package/src/runtime/plugins/browser-adapter.ts +2662 -829
- package/src/runtime/plugins/browser-automation-service.ts +10 -0
- package/src/runtime/plugins/browser-handoff-host.ts +2 -25
- package/src/runtime/plugins/browser-handoff.ts +110 -5
- package/src/runtime/plugins/browser-macos-bridge.ts +701 -63
- package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
- package/src/runtime/plugins/browser-native-session-service.ts +222 -0
- package/src/runtime/plugins/browser-node-bridge.ts +6 -34
- package/src/runtime/plugins/browser-provider-registry.ts +109 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +154 -0
- package/src/runtime/plugins/browser-runtime.ts +193 -0
- package/src/runtime/plugins/browser-session-authority.ts +332 -0
- package/src/runtime/plugins/browser-session-store.ts +121 -0
- package/src/runtime/plugins/browser-session-types.ts +98 -0
- package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +10 -4
- package/src/runtime/plugins/external-adapter.ts +358 -3
- package/src/runtime/plugins/external-unix-socket.ts +7 -1
- package/src/runtime/plugins/first-party-registry.ts +2 -0
- package/src/runtime/plugins/github-adapter.ts +125 -0
- package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
- package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
- package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
- package/src/runtime/plugins/ios-adapter.ts +106 -16
- package/src/runtime/plugins/ios-agent-device.ts +234 -64
- package/src/runtime/plugins/ios-physical-device.ts +1143 -392
- package/src/runtime/plugins/lightweight-action.ts +153 -0
- package/src/runtime/plugins/local-system-adapter.ts +123 -6
- package/src/runtime/plugins/macos-capability-broker.ts +47 -9
- package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
- package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
- package/src/runtime/plugins/resend-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +330 -33
- package/src/runtime/plugins/types.ts +38 -3
- package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
- package/src/runtime/projections/git-status-sampler.ts +16 -16
- package/src/runtime/projections/materialized-view.ts +28 -10
- package/src/runtime/recovery/maintenance-executor.ts +426 -25
- package/src/runtime/root/mcp-transport.ts +180 -10
- package/src/runtime/root/package-connector-service.ts +230 -10
- package/src/runtime/root/package-runtime-release.ts +143 -17
- package/src/runtime/root/package-runtime-service.ts +461 -22
- package/src/runtime/root/release-manifest.ts +144 -18
- package/src/runtime/root/release-materialize.ts +125 -32
- package/src/runtime/root/release-store.ts +48 -11
- package/src/runtime/root/runtime.ts +23 -5
- package/src/runtime/root/service.ts +100 -29
- package/src/runtime/root/types.ts +12 -0
- package/src/runtime/safe-tooling/ios-development.ts +321 -63
- package/src/runtime/shared/trusted-executable.ts +62 -0
- package/src/runtime/shared/trusted-node-executable.ts +48 -0
- package/src/runtime/standalone-recovery/core.ts +1079 -240
- package/src/runtime/standalone-recovery/entry.ts +100 -24
- package/src/runtime/standalone-recovery/installer.ts +20 -4
- package/src/runtime/standalone-recovery/service-labels.ts +2 -0
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
- package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
- package/src/runtime/workflow/schedules/engine.ts +364 -202
- package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
- package/src/runtime/workflow/schedules/settlement.ts +64 -17
- package/src/runtime/workflow/schedules/store.ts +209 -19
- package/src/runtime/workflow/schedules/types.ts +2 -4
- package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
- package/src/testing/test-governance.ts +8 -40
- package/assets/reference-configs/heartbeat-triage.md +0 -57
- package/assets/templates/helpers/harness-trace-grade.sh +0 -150
- package/assets/templates/helpers/heartbeat-triage.sh +0 -361
- package/assets/templates/helpers/maintenance-triage.sh +0 -167
- package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
- package/docs/architecture/current/architecture-invariants.md +0 -331
- package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
- package/docs/architecture/current/codegraph-context-provider.md +0 -238
- package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
- package/docs/architecture/current/controller-repository-decoupling.md +0 -148
- package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
- package/docs/architecture/current/entity-model.md +0 -612
- package/docs/architecture/current/external-plugin-governance.md +0 -396
- package/docs/architecture/current/failure-recovery.md +0 -447
- package/docs/architecture/current/governance.md +0 -213
- package/docs/architecture/current/human-interaction-plane.md +0 -87
- package/docs/architecture/current/implementation-status.md +0 -128
- package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
- package/docs/architecture/current/legacy-governance.md +0 -58
- package/docs/architecture/current/local-workspace-target-grants.md +0 -70
- package/docs/architecture/current/migration-roadmap.md +0 -105
- package/docs/architecture/current/multi-repository-execution.md +0 -371
- package/docs/architecture/current/operations-runbook.md +0 -45
- package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
- package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
- package/docs/architecture/current/repository-access-modes.md +0 -102
- package/docs/architecture/current/route-policy.md +0 -15
- package/docs/architecture/current/route-session-concurrency.md +0 -85
- package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
- package/docs/architecture/current/runtime-directory-map.md +0 -49
- package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
- package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
- package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
- package/docs/architecture/current/system-overview.md +0 -169
- package/docs/architecture/current/target-requirements-traceability.md +0 -202
- package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
- package/docs/architecture/current/thin-harness-v1.md +0 -321
- package/docs/architecture/current/verification-and-release-gates.md +0 -391
- package/docs/forge-autonomous-goal-loop.md +0 -9
- package/docs/forge-provider-configuration.md +0 -7
- package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
- package/docs/operations/gmail-assistant-routines.md +0 -186
- package/docs/operations/personal-assistant-operator-guide.md +0 -61
- package/docs/operations/provider-configuration.md +0 -31
- package/scripts/harness-trace-grade.sh +0 -150
- package/scripts/heartbeat-triage.sh +0 -361
- package/scripts/maintenance-triage.sh +0 -167
- package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
- package/src/cli/repositories/goal-registry.ts +0 -239
- package/src/runtime/assistant/action-proposals.ts +0 -279
- package/src/runtime/assistant/intent.ts +0 -395
- package/src/runtime/assistant/model-provider.ts +0 -460
- package/src/runtime/assistant/openapi.ts +0 -223
- package/src/runtime/assistant/policy.ts +0 -89
- package/src/runtime/assistant/readiness.ts +0 -258
- package/src/runtime/assistant/routine-runtime.ts +0 -477
- package/src/runtime/assistant/schedule-binding.ts +0 -171
- package/src/runtime/assistant/standing-grants.ts +0 -375
- package/src/runtime/assistant/store.ts +0 -243
- package/src/runtime/assistant/types.ts +0 -135
- package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
- package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
- package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
- package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
- package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
- package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
- package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
- package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
- package/src/runtime/control-plane/goal-loop/index.ts +0 -13
- package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
- package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
- package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
- package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
- package/src/runtime/control-plane/goal-loop/types.ts +0 -378
- package/src/runtime/control-plane/startup-recovery.ts +0 -198
- package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
- package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
- package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
- package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
- package/src/runtime/workflow/findings/store.ts +0 -204
- package/src/runtime/workflow/findings/types.ts +0 -58
- package/src/runtime/workflow/portfolio/engine.ts +0 -20
- package/src/runtime/workflow/portfolio/store.ts +0 -185
- package/src/runtime/workflow/portfolio/types.ts +0 -29
|
@@ -1,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,12 @@ 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;
|
|
75
|
+
/** Internal/test seam: bound dirty-path fingerprint enrichment independently from child execution lifetime. */
|
|
76
|
+
snapshotFingerprintTimeoutMs?: number;
|
|
58
77
|
}
|
|
59
78
|
|
|
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
79
|
|
|
83
80
|
export interface RepositoryCommandExecution {
|
|
84
81
|
status: 'preview' | 'approval_required' | 'executed';
|
|
@@ -103,6 +100,8 @@ export interface RepositoryCommandExecution {
|
|
|
103
100
|
authorizationDecision?: AuthorizationDecision;
|
|
104
101
|
approvalRequestId?: string;
|
|
105
102
|
infrastructureError?: { code: string; message: string };
|
|
103
|
+
/** Child execution completed, but post-execution repository evidence could not be proven. Mutation state is unknown until a later authoritative inspection. */
|
|
104
|
+
evidenceError?: { code: string; message: string };
|
|
106
105
|
externalPathUsages?: RepositoryCommandExternalPathUsage[];
|
|
107
106
|
}
|
|
108
107
|
|
|
@@ -113,21 +112,6 @@ export interface PreparedRepositoryCommandExecution {
|
|
|
113
112
|
execution: RepositoryCommandExecution;
|
|
114
113
|
}
|
|
115
114
|
|
|
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
115
|
|
|
132
116
|
/** Sensible interactive default for ordinary repository commands. */
|
|
133
117
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
@@ -154,460 +138,6 @@ function boundedInteger(value: number | undefined, fallback: number, minimum: nu
|
|
|
154
138
|
return normalized;
|
|
155
139
|
}
|
|
156
140
|
|
|
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
141
|
function approvalToken(
|
|
612
142
|
repository: RepositoryRecord,
|
|
613
143
|
relativeCwd: string,
|
|
@@ -628,20 +158,6 @@ function approvalToken(
|
|
|
628
158
|
})).digest('hex');
|
|
629
159
|
}
|
|
630
160
|
|
|
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
161
|
function finalizePreparedExecution(
|
|
646
162
|
repository: RepositoryRecord,
|
|
647
163
|
input: ExecuteRepositoryCommandInput,
|
|
@@ -670,7 +186,11 @@ function finalizePreparedExecution(
|
|
|
670
186
|
const isGit = command.kind === 'argv'
|
|
671
187
|
? command.executable?.split(/[\\/]/).at(-1)?.toLowerCase() === 'git'
|
|
672
188
|
: /^\s*git\s+/i.test(command.shellCommand!);
|
|
673
|
-
|
|
189
|
+
// Command classification is observability/replay metadata, not execution
|
|
190
|
+
// authority. Raw repository commands follow the host AI permission model once
|
|
191
|
+
// repository/cwd/external-path scope has been resolved. Typed catastrophic
|
|
192
|
+
// Forge operations retain their own explicit confirmation contracts.
|
|
193
|
+
const risk = isGit ? 'local_git' : 'local_command';
|
|
674
194
|
const delegated = input.authorizationDecision ?? (controllerHome ? decideAuthorization({
|
|
675
195
|
controllerHome,
|
|
676
196
|
accessMode: permission?.mode ?? 'request',
|
|
@@ -701,16 +221,16 @@ function finalizePreparedExecution(
|
|
|
701
221
|
approvalToken: token,
|
|
702
222
|
authorization: input.authorization,
|
|
703
223
|
before,
|
|
704
|
-
policyDecision: input.dryRun === true ||
|
|
224
|
+
policyDecision: input.dryRun === true || !controllerHome || effectiveDecision?.decision === 'allow'
|
|
705
225
|
? 'allowed'
|
|
706
|
-
:
|
|
226
|
+
: 'approval_required',
|
|
707
227
|
...(effectiveDecision ? { authorizationDecision: effectiveDecision } : {}),
|
|
708
228
|
...(effectiveDecision?.decision === 'user_confirmation_required' ? { approvalRequestId: effectiveDecision.approvalRequestId } : {}),
|
|
709
229
|
externalPathUsages: externalPathUsages.length > 0 ? externalPathUsages : undefined,
|
|
710
230
|
};
|
|
711
231
|
const confirmed = input.authorization === 'confirmed_plan' && input.approvalToken === token;
|
|
712
|
-
const delegatedAllowed = effectiveDecision?.decision === 'allow';
|
|
713
|
-
const executable = input.dryRun === true ||
|
|
232
|
+
const delegatedAllowed = !controllerHome || effectiveDecision?.decision === 'allow';
|
|
233
|
+
const executable = input.dryRun === true || confirmed || delegatedAllowed;
|
|
714
234
|
execution.policyDecision = executable ? 'allowed' : 'approval_required';
|
|
715
235
|
return {
|
|
716
236
|
root,
|
|
@@ -741,7 +261,9 @@ function prepareRepositoryCommandExecution(
|
|
|
741
261
|
externalPathUsages: RepositoryCommandExternalPathUsage[];
|
|
742
262
|
} {
|
|
743
263
|
const { root, cwd, relativeCwd } = resolveRepositoryCommandCwd(repository, input.cwd);
|
|
744
|
-
const command = assertRepositoryCommandInputAllowed(input.command
|
|
264
|
+
const command = assertRepositoryCommandInputAllowed(input.command, {
|
|
265
|
+
allowOpaqueLocalScript: input.allowOpaqueLocalScript,
|
|
266
|
+
});
|
|
745
267
|
const externalGrants = loadExternalFilesystemGrants(root).grants;
|
|
746
268
|
const externalPathUsages = assertCommandPathOperandsStayInRepository(command, cwd, root, externalGrants);
|
|
747
269
|
const classification = classifyRepositoryCommand(command, repository.defaultBranch);
|
|
@@ -776,11 +298,21 @@ async function prepareRepositoryCommandExecutionAsync(
|
|
|
776
298
|
externalPathUsages: RepositoryCommandExternalPathUsage[];
|
|
777
299
|
}> {
|
|
778
300
|
const { root, cwd, relativeCwd } = resolveRepositoryCommandCwd(repository, input.cwd);
|
|
779
|
-
const command = assertRepositoryCommandInputAllowed(input.command
|
|
301
|
+
const command = assertRepositoryCommandInputAllowed(input.command, {
|
|
302
|
+
allowOpaqueLocalScript: input.allowOpaqueLocalScript,
|
|
303
|
+
});
|
|
780
304
|
const externalGrants = loadExternalFilesystemGrants(root).grants;
|
|
781
305
|
const externalPathUsages = assertCommandPathOperandsStayInRepository(command, cwd, root, externalGrants);
|
|
782
306
|
const classification = classifyRepositoryCommand(command, repository.defaultBranch);
|
|
783
|
-
|
|
307
|
+
// A proven readonly direct/preview lane never compares before/after mutation.
|
|
308
|
+
// Skip per-path fingerprints there so a large dirty worktree cannot block a
|
|
309
|
+
// harmless read; write-capable lanes keep the strict fingerprint cap.
|
|
310
|
+
const before = input.reuseSnapshot ?? (input.allowNonGitWorkspace
|
|
311
|
+
? emptyWorkspaceSnapshot()
|
|
312
|
+
: await repositorySnapshotAsync(root, input.signal, {
|
|
313
|
+
pathFingerprints: !(classification.risk === 'readonly' && controllerHome === undefined),
|
|
314
|
+
fingerprintTimeoutMs: input.snapshotFingerprintTimeoutMs,
|
|
315
|
+
}));
|
|
784
316
|
return finalizePreparedExecution(
|
|
785
317
|
repository,
|
|
786
318
|
input,
|
|
@@ -907,6 +439,7 @@ export function executeRepositoryCommand(
|
|
|
907
439
|
message: error || `repository command failed with exit ${String(result.status ?? 1)}`,
|
|
908
440
|
} : undefined,
|
|
909
441
|
};
|
|
442
|
+
if (execution.repositoryChanged) invalidateRepositoryReadCaches(root);
|
|
910
443
|
auditCommand(controllerHome, repository, execution);
|
|
911
444
|
return execution;
|
|
912
445
|
}
|
|
@@ -956,7 +489,12 @@ export async function executeRepositoryReadOnlyCommandDirect(
|
|
|
956
489
|
}
|
|
957
490
|
|
|
958
491
|
const result = await runCanonicalCommand(command, cwd, timeoutMs, maxOutputBytes, { signal });
|
|
959
|
-
|
|
492
|
+
// This entry point is reachable only after the bounded classifier has proven
|
|
493
|
+
// the command readonly. A second full repository snapshot would start four
|
|
494
|
+
// extra Git processes merely to rediscover the invariant we already used to
|
|
495
|
+
// select this lane. Keep the captured pre-execution snapshot as both sides of
|
|
496
|
+
// the receipt; write-capable lanes still take independent before/after snapshots.
|
|
497
|
+
const after = before;
|
|
960
498
|
return {
|
|
961
499
|
...base,
|
|
962
500
|
status: 'executed',
|
|
@@ -967,8 +505,8 @@ export async function executeRepositoryReadOnlyCommandDirect(
|
|
|
967
505
|
stdout: result.stdout,
|
|
968
506
|
stderr: result.stderr,
|
|
969
507
|
after,
|
|
970
|
-
repositoryChanged:
|
|
971
|
-
changedPaths:
|
|
508
|
+
repositoryChanged: false,
|
|
509
|
+
changedPaths: [],
|
|
972
510
|
policyDecision: 'allowed',
|
|
973
511
|
externalPathUsages: externalPathUsages.length > 0 ? externalPathUsages : undefined,
|
|
974
512
|
infrastructureError: result.timedOut
|
|
@@ -1032,9 +570,19 @@ export async function executeRepositoryCommandAsync(
|
|
|
1032
570
|
...hooks,
|
|
1033
571
|
signal,
|
|
1034
572
|
});
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
573
|
+
let after: RepositoryCommandSnapshot | undefined = input.allowNonGitWorkspace ? before : undefined;
|
|
574
|
+
let evidenceError: RepositoryCommandExecution['evidenceError'];
|
|
575
|
+
if (!input.allowNonGitWorkspace) {
|
|
576
|
+
try {
|
|
577
|
+
after = await repositorySnapshotAsync(root, signal?.aborted ? undefined : signal, {
|
|
578
|
+
fingerprintTimeoutMs: input.snapshotFingerprintTimeoutMs,
|
|
579
|
+
});
|
|
580
|
+
} catch (error) {
|
|
581
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
582
|
+
const code = message.includes(':') ? message.slice(0, message.indexOf(':')) : 'POST_EXECUTION_EVIDENCE_FAILED';
|
|
583
|
+
evidenceError = { code, message };
|
|
584
|
+
}
|
|
585
|
+
}
|
|
1038
586
|
const execution: RepositoryCommandExecution = {
|
|
1039
587
|
...base,
|
|
1040
588
|
status: 'executed',
|
|
@@ -1045,8 +593,9 @@ export async function executeRepositoryCommandAsync(
|
|
|
1045
593
|
stdout: result.stdout,
|
|
1046
594
|
stderr: result.stderr,
|
|
1047
595
|
after,
|
|
1048
|
-
repositoryChanged: snapshotChanged(before, after),
|
|
1049
|
-
changedPaths: changedSnapshotPaths(before, after),
|
|
596
|
+
repositoryChanged: after ? snapshotChanged(before, after) : undefined,
|
|
597
|
+
changedPaths: after ? changedSnapshotPaths(before, after) : undefined,
|
|
598
|
+
evidenceError,
|
|
1050
599
|
policyDecision: 'allowed',
|
|
1051
600
|
externalPathUsages: externalPathUsages.length > 0 ? externalPathUsages : undefined,
|
|
1052
601
|
infrastructureError: result.timedOut
|
|
@@ -1061,6 +610,9 @@ export async function executeRepositoryCommandAsync(
|
|
|
1061
610
|
}
|
|
1062
611
|
: undefined,
|
|
1063
612
|
};
|
|
613
|
+
// Unknown post-execution mutation evidence is conservatively cache-invalidating;
|
|
614
|
+
// later reads must re-observe the repository rather than reuse a false unchanged state.
|
|
615
|
+
if (execution.repositoryChanged === true || execution.evidenceError) invalidateRepositoryReadCaches(root);
|
|
1064
616
|
auditCommand(controllerHome, repository, execution);
|
|
1065
617
|
return execution;
|
|
1066
618
|
}
|