@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
|
@@ -13,8 +13,14 @@ import { observeRuntimeStatus } from '../../runtime/root/status';
|
|
|
13
13
|
import { readMcpServiceBearerToken } from '../mcp/auth';
|
|
14
14
|
import { publishRuntimeRelease } from '../../runtime/root/release-store';
|
|
15
15
|
import { installForgeRuntimeService } from '../../runtime/root/service';
|
|
16
|
-
import { installPackageRuntimeService } from '../../runtime/root/package-runtime-service';
|
|
16
|
+
import { activateScheduledPackageRuntimeServiceFromPath, installPackageRuntimeService, readPackageRuntimeActivationReceipt } from '../../runtime/root/package-runtime-service';
|
|
17
17
|
import { assertRuntimeReleaseFiles, stageRuntimeReleaseFromCandidateSource } from '../../runtime/root/release-materialize';
|
|
18
|
+
import {
|
|
19
|
+
applyControllerHomeMigration,
|
|
20
|
+
previewControllerHomeMigration,
|
|
21
|
+
rollbackControllerHomeMigration,
|
|
22
|
+
archiveUnroutableMigratedWork,
|
|
23
|
+
} from '../../runtime/control-plane/persistence/controller-home-migration';
|
|
18
24
|
|
|
19
25
|
function output(value: unknown, json = true): void {
|
|
20
26
|
console.log(json ? JSON.stringify(value, null, 2) : String(value));
|
|
@@ -30,6 +36,22 @@ export function buildRuntimeCommand(): Command {
|
|
|
30
36
|
const service = command.command('service')
|
|
31
37
|
.description('Install or inspect the user-level Forge Runtime owner');
|
|
32
38
|
|
|
39
|
+
service.command('activate-package', { hidden: true })
|
|
40
|
+
.description('Internal one-shot activation helper for a staged package Runtime release')
|
|
41
|
+
.requiredOption('--request <path>', 'Activation request path')
|
|
42
|
+
.action(async (opts: { request: string }) => {
|
|
43
|
+
output(await activateScheduledPackageRuntimeServiceFromPath(opts.request));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
service.command('activation-status')
|
|
47
|
+
.description('Read a durable package Runtime activation receipt')
|
|
48
|
+
.requiredOption('--receipt <path>', 'Activation receipt path returned by install-package')
|
|
49
|
+
.action((opts: { receipt: string }) => {
|
|
50
|
+
const receipt = readPackageRuntimeActivationReceipt(resolve(opts.receipt));
|
|
51
|
+
if (!receipt) throw new Error('RUNTIME_PACKAGE_ACTIVATION_RECEIPT_NOT_FOUND');
|
|
52
|
+
output(receipt);
|
|
53
|
+
});
|
|
54
|
+
|
|
33
55
|
service.command('install-package')
|
|
34
56
|
.description('Install the current packaged Forge Runtime without requiring a Git checkout, Bun compilation, CodeGraph, or Standalone Recovery')
|
|
35
57
|
.option('--controller-home <path>', 'Controller Home; defaults to the user-level Forge Controller Home')
|
|
@@ -37,7 +59,8 @@ export function buildRuntimeCommand(): Command {
|
|
|
37
59
|
.option('--port <port>', 'MCP listener port', '8765')
|
|
38
60
|
.option('--auth-token-file <path>', 'Raw bearer token file (created from user-level MCP config when missing)')
|
|
39
61
|
.option('--portable', 'Force a detached session process instead of launchd/systemd user persistence')
|
|
40
|
-
.
|
|
62
|
+
.option('--refresh-connector', 'Explicitly replace/restart the persistent public MCP Gateway instead of reusing a healthy existing Gateway')
|
|
63
|
+
.action(async (opts: { controllerHome?: string; host: string; port: string; authTokenFile?: string; portable?: boolean; refreshConnector?: boolean }) => {
|
|
41
64
|
const home = resolveControllerHome(opts.controllerHome);
|
|
42
65
|
const port = Number(opts.port);
|
|
43
66
|
if (!Number.isInteger(port) || port < 1 || port > 65_535) throw new Error('RUNTIME_SERVICE_PORT_INVALID');
|
|
@@ -56,6 +79,7 @@ export function buildRuntimeCommand(): Command {
|
|
|
56
79
|
port,
|
|
57
80
|
authTokenFile: tokenPath,
|
|
58
81
|
forcePortable: opts.portable === true,
|
|
82
|
+
refreshConnector: opts.refreshConnector === true,
|
|
59
83
|
});
|
|
60
84
|
output({
|
|
61
85
|
status: result.status,
|
|
@@ -73,8 +97,11 @@ export function buildRuntimeCommand(): Command {
|
|
|
73
97
|
servicePath: result.servicePath,
|
|
74
98
|
pid: result.pid,
|
|
75
99
|
connector: result.connector,
|
|
100
|
+
activation: result.activation,
|
|
76
101
|
warnings: result.warnings,
|
|
77
|
-
next:
|
|
102
|
+
next: result.activation
|
|
103
|
+
? `forge runtime service activation-status --receipt ${result.activation.receiptPath}`
|
|
104
|
+
: `forge runtime status --controller-home ${home}`,
|
|
78
105
|
});
|
|
79
106
|
});
|
|
80
107
|
|
|
@@ -163,6 +190,31 @@ export function buildRuntimeCommand(): Command {
|
|
|
163
190
|
});
|
|
164
191
|
});
|
|
165
192
|
|
|
193
|
+
command.command('migrate-controller-home')
|
|
194
|
+
.description('Preview or transactionally migrate non-terminal business state from an explicitly selected retired Controller Home')
|
|
195
|
+
.requiredOption('--source <path>', 'Retired Controller Home to inspect; repository presence never selects this implicitly')
|
|
196
|
+
.option('--destination <path>', 'Canonical Controller Home; defaults to the installed user-level authority')
|
|
197
|
+
.option('--apply', 'Apply the reviewed migration and freeze the source SQLite as a read-only archive')
|
|
198
|
+
.option('--rollback <migration-id>', 'Rollback one migration only when none of its imported records changed')
|
|
199
|
+
.option('--archive-unroutable <migration-id>', 'Archive imported nonterminal Work whose repository is not registered in the canonical Home')
|
|
200
|
+
.action(async (opts: { source: string; destination?: string; apply?: boolean; rollback?: string; archiveUnroutable?: string }) => {
|
|
201
|
+
const destination = resolveControllerHome(opts.destination);
|
|
202
|
+
if (opts.archiveUnroutable?.trim()) {
|
|
203
|
+
output(archiveUnroutableMigratedWork({ destinationHome: destination, migrationId: opts.archiveUnroutable.trim() }));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (opts.rollback?.trim()) {
|
|
207
|
+
output(rollbackControllerHomeMigration({ destinationHome: destination, migrationId: opts.rollback.trim() }));
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const input = { sourceHome: resolve(opts.source), destinationHome: destination };
|
|
211
|
+
if (opts.apply === true) {
|
|
212
|
+
output(await applyControllerHomeMigration(input));
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
output(await previewControllerHomeMigration(input));
|
|
216
|
+
});
|
|
217
|
+
|
|
166
218
|
command.command('status')
|
|
167
219
|
.description('Read the Runtime Root status projection, active durable Jobs, and materialized repository projections')
|
|
168
220
|
.requiredOption('--controller-home <path>', 'Explicit Controller Home')
|
|
@@ -8,7 +8,7 @@ import * as fs from 'fs';
|
|
|
8
8
|
import * as os from 'os';
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import { execFileSync } from 'child_process';
|
|
11
|
-
import { MANAGED_TAG, type HookEntry } from '../installer/managed-entries';
|
|
11
|
+
import { MANAGED_TAG, isLegacyRepoHarnessCommand, type HookEntry } from '../installer/managed-entries';
|
|
12
12
|
|
|
13
13
|
export type SecurityStatus = 'ok' | 'warn' | 'fail';
|
|
14
14
|
export type SecuritySeverity = 'warn' | 'high' | 'fail';
|
|
@@ -273,6 +273,17 @@ function scanHookConfig(
|
|
|
273
273
|
if (!command) continue;
|
|
274
274
|
const suspicious = suspiciousMatch(command);
|
|
275
275
|
if (command.includes(MANAGED_TAG) && suspicious === null) continue;
|
|
276
|
+
if (isLegacyRepoHarnessCommand(command)) {
|
|
277
|
+
findings.push({
|
|
278
|
+
filePath,
|
|
279
|
+
ruleId: 'legacy-repo-harness-hook-adapter',
|
|
280
|
+
severity: 'warn',
|
|
281
|
+
summary: `${hostLabel} ${event} hook uses the retired repo-harness adapter`,
|
|
282
|
+
recommendation: 'Run forge install --target codex --location global to replace retired adapters with forge-hook.',
|
|
283
|
+
command,
|
|
284
|
+
});
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
276
287
|
findings.push({
|
|
277
288
|
filePath,
|
|
278
289
|
ruleId: suspicious?.ruleId ?? 'unmanaged-hook-command',
|
|
@@ -206,7 +206,7 @@ export function detectSetupPlatform(options: { platform?: NodeJS.Platform; arch?
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
|
|
209
|
-
function isLoopbackMcpEndpoint(value: string | undefined):
|
|
209
|
+
function isLoopbackMcpEndpoint(value: string | undefined): value is string {
|
|
210
210
|
if (!value) return false;
|
|
211
211
|
try {
|
|
212
212
|
const parsed = new URL(value);
|
|
@@ -220,6 +220,11 @@ function isLoopbackMcpEndpoint(value: string | undefined): boolean {
|
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
function tunnelRuntimeProfileTargetsEndpoint(profilePath: string | undefined, endpoint: string): boolean {
|
|
224
|
+
if (!profilePath) return false;
|
|
225
|
+
try { return readFileSync(profilePath, 'utf8').includes(endpoint); } catch { return false; }
|
|
226
|
+
}
|
|
227
|
+
|
|
223
228
|
export interface ControllerGuidance {
|
|
224
229
|
controller: SetupControllerKind;
|
|
225
230
|
ready: boolean;
|
|
@@ -266,8 +271,8 @@ export function resolveControllerGuidance(
|
|
|
266
271
|
controller,
|
|
267
272
|
ready: false,
|
|
268
273
|
title: controller === 'chatgpt' ? 'Configure the ChatGPT controller endpoint' : 'Configure the remote MCP controller endpoint',
|
|
269
|
-
detail: 'Create the
|
|
270
|
-
command: `forge mcp setup chatgpt --user-level${endpoint}`,
|
|
274
|
+
detail: 'Create or refresh the Forge OAuth/MCP configuration in the selected Controller Home. This does not install Codex or Claude and does not require a repository.',
|
|
275
|
+
command: `forge mcp setup chatgpt --user-level --controller-home ${JSON.stringify(controllerHome)}${endpoint}`,
|
|
271
276
|
};
|
|
272
277
|
}
|
|
273
278
|
continue;
|
|
@@ -327,7 +332,7 @@ export function resolveRuntimeGuidance(
|
|
|
327
332
|
ready: false,
|
|
328
333
|
title: 'Install the user-level Forge Runtime',
|
|
329
334
|
detail: 'Use the packaged Runtime path. It does not require a Git checkout, Bun compilation, CodeGraph, or Standalone Recovery.',
|
|
330
|
-
command:
|
|
335
|
+
command: `forge runtime service install-package --controller-home ${JSON.stringify(controllerHome)}`,
|
|
331
336
|
};
|
|
332
337
|
}
|
|
333
338
|
|
|
@@ -364,48 +369,53 @@ export function resolveTunnelGuidance(
|
|
|
364
369
|
detail: 'Install the supported tunnel-client binary from OpenAI Platform Tunnels or the official openai/tunnel-client release. The runtime API key remains an environment/file reference owned by tunnel-client; Forge does not store it.',
|
|
365
370
|
};
|
|
366
371
|
}
|
|
372
|
+
const localConfig = loadMcpServiceLocalConfig(resolveControllerHome(options.controllerHome));
|
|
373
|
+
const localEndpoint = localConfig?.chatgpt?.localEndpoint;
|
|
374
|
+
if (!isLoopbackMcpEndpoint(localEndpoint)) {
|
|
375
|
+
return {
|
|
376
|
+
provider, ready: false, title: 'Prepare the local ChatGPT OAuth endpoint',
|
|
377
|
+
detail: 'Secure Tunnel must terminate at Forge’s loopback OAuth Gateway, not the bearer-only Canonical Runtime. Refresh the user-level ChatGPT MCP configuration first.',
|
|
378
|
+
command: `forge mcp setup chatgpt --user-level --controller-home ${JSON.stringify(resolveControllerHome(options.controllerHome))}`,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
367
381
|
const preferredAlias = 'forge';
|
|
368
|
-
const
|
|
382
|
+
const configuredAliases: string[] = [];
|
|
369
383
|
const listed = spawnSync('tunnel-client', ['runtimes', 'list', '--json'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 15_000, env: options.env ?? process.env });
|
|
370
384
|
if (listed.status === 0) {
|
|
371
385
|
try {
|
|
372
386
|
const value = JSON.parse(listed.stdout || '{}') as { aliases?: Array<{ alias?: string; tunnel_id?: string }> };
|
|
373
387
|
for (const entry of value.aliases ?? []) {
|
|
374
|
-
if (entry.tunnel_id === profile.tunnel.tunnelId && entry.alias)
|
|
388
|
+
if (entry.tunnel_id === profile.tunnel.tunnelId && entry.alias) configuredAliases.push(entry.alias);
|
|
375
389
|
}
|
|
376
390
|
} catch {
|
|
377
391
|
// Older clients may not expose local aliases as JSON. Keep the preferred alias fallback.
|
|
378
392
|
}
|
|
379
393
|
}
|
|
380
|
-
|
|
394
|
+
const aliases = Array.from(new Set([...configuredAliases, preferredAlias]));
|
|
395
|
+
for (const alias of aliases) {
|
|
381
396
|
const status = spawnSync('tunnel-client', ['runtimes', 'status', alias, '--json'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 15_000, env: options.env ?? process.env });
|
|
382
397
|
if (status.status !== 0) continue;
|
|
383
398
|
try {
|
|
384
|
-
const value = JSON.parse(status.stdout || '{}') as { process_running?: boolean; healthy?: boolean; ready?: boolean; tunnel_id?: string };
|
|
399
|
+
const value = JSON.parse(status.stdout || '{}') as { process_running?: boolean; healthy?: boolean; ready?: boolean; tunnel_id?: string; profile_path?: string };
|
|
385
400
|
if (value.process_running === true && value.healthy === true && value.ready === true
|
|
386
|
-
&&
|
|
401
|
+
&& value.tunnel_id === profile.tunnel.tunnelId
|
|
402
|
+
&& tunnelRuntimeProfileTargetsEndpoint(value.profile_path, localEndpoint)) {
|
|
387
403
|
return {
|
|
388
404
|
provider, ready: true, title: 'OpenAI Secure MCP Tunnel',
|
|
389
|
-
detail: `Managed runtime ${alias} is running, healthy, and
|
|
405
|
+
detail: `Managed runtime ${alias} is running, healthy, and bound to ${profile.tunnel.tunnelId} -> ${localEndpoint}.`,
|
|
390
406
|
};
|
|
391
407
|
}
|
|
392
408
|
} catch {
|
|
393
409
|
// Treat malformed or older status output as not-ready instead of claiming success.
|
|
394
410
|
}
|
|
395
411
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
return {
|
|
401
|
-
provider, ready: false, title: 'Prepare the local ChatGPT OAuth endpoint',
|
|
402
|
-
detail: 'Secure Tunnel must terminate at Forge’s loopback OAuth Gateway, not the bearer-only Canonical Runtime. Refresh the user-level ChatGPT MCP configuration first.',
|
|
403
|
-
command: 'forge mcp setup chatgpt --user-level',
|
|
404
|
-
};
|
|
405
|
-
}
|
|
412
|
+
// A stopped alias still owns its recorded tunnel identity. Reconnect that
|
|
413
|
+
// alias instead of repointing the conventional `forge` alias, which may
|
|
414
|
+
// legitimately refer to a different tunnel from an earlier setup.
|
|
415
|
+
const alias = configuredAliases[0] ?? preferredAlias;
|
|
406
416
|
return {
|
|
407
417
|
provider, ready: false, title: 'Connect OpenAI Secure MCP Tunnel',
|
|
408
|
-
detail: 'Create a supervised tunnel-client runtime and verify
|
|
418
|
+
detail: 'Create a supervised tunnel-client runtime and verify its tunnel id and MCP target with runtimes status. Keep the runtime API key in an environment or file reference owned by tunnel-client; Forge never reads or stores the key.',
|
|
409
419
|
command: `tunnel-client runtimes connect --alias ${alias} --tunnel-id ${profile.tunnel.tunnelId} --runtime-api-key env:CONTROL_PLANE_API_KEY --mcp-server-url ${localEndpoint}`,
|
|
410
420
|
};
|
|
411
421
|
}
|
|
@@ -11,6 +11,7 @@ import { atomicWriteFileSync } from '../installer/shared';
|
|
|
11
11
|
import { runBoundedChild } from '../../runtime/shared/bounded-child-supervisor';
|
|
12
12
|
import { signalProcessTree } from '../../runtime/shared/process-tree';
|
|
13
13
|
import { repositoryChildProcessEnvironment, resolveBunExecutable } from '../../runtime/shared/process-environment';
|
|
14
|
+
import { materializeManagedWorkspaceCheckDependencies } from '../../runtime/execution/managed-workspace';
|
|
14
15
|
|
|
15
16
|
export interface ControllerCheckEffects {
|
|
16
17
|
/** Repository-relative read scopes. Use [\".\"] for the whole checkout. */
|
|
@@ -25,6 +26,18 @@ export interface ControllerCheckEffects {
|
|
|
25
26
|
hostServices?: string[];
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
export type ControllerCheckCostClass = 'L0' | 'L1' | 'L2' | 'L3' | 'L4';
|
|
30
|
+
export type ControllerCheckRiskFloor = 'readonly' | 'low' | 'medium' | 'high' | 'destructive';
|
|
31
|
+
export type ControllerCheckPhase = 'pre_edit' | 'post_edit' | 'pre_finalize' | 'release';
|
|
32
|
+
|
|
33
|
+
export interface ControllerCheckSelection {
|
|
34
|
+
/** Execution cost only; it does not lower severity or automatically run the check. */
|
|
35
|
+
costClass: ControllerCheckCostClass;
|
|
36
|
+
/** Lowest change-risk band where this check is normally useful when selected mechanically. */
|
|
37
|
+
riskFloor: ControllerCheckRiskFloor;
|
|
38
|
+
phases: ControllerCheckPhase[];
|
|
39
|
+
}
|
|
40
|
+
|
|
28
41
|
export interface ControllerCheck {
|
|
29
42
|
id: string;
|
|
30
43
|
description: string;
|
|
@@ -33,6 +46,8 @@ export interface ControllerCheck {
|
|
|
33
46
|
timeoutMs: number;
|
|
34
47
|
source: 'repo-config' | 'package-script';
|
|
35
48
|
effects?: ControllerCheckEffects;
|
|
49
|
+
/** Selection/preflight metadata; intentionally excluded from physical execution identity. */
|
|
50
|
+
selection?: ControllerCheckSelection;
|
|
36
51
|
}
|
|
37
52
|
|
|
38
53
|
export interface ControllerCheckSnapshot extends ControllerCheck {
|
|
@@ -68,10 +83,12 @@ interface CheckConfig {
|
|
|
68
83
|
cwd?: string;
|
|
69
84
|
timeoutMs?: number;
|
|
70
85
|
effects?: unknown;
|
|
86
|
+
selection?: unknown;
|
|
71
87
|
}>;
|
|
72
88
|
}
|
|
73
89
|
|
|
74
90
|
const CHECK_CONFIG = '.forge/checks.json';
|
|
91
|
+
const LEGACY_TRACKED_CHECK_CONFIG = '.repo-harness/checks.json';
|
|
75
92
|
const CHECK_EVIDENCE_ROOT = '.ai/harness/checks/controller';
|
|
76
93
|
const HEAVY_CHECK_LOCK = '.ai/harness/controller/heavy-check.lock';
|
|
77
94
|
const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
@@ -180,15 +197,87 @@ function normalizeCheckEffects(repoRoot: string, value: unknown): ControllerChec
|
|
|
180
197
|
};
|
|
181
198
|
}
|
|
182
199
|
|
|
200
|
+
const CHECK_COST_CLASSES = ['L0', 'L1', 'L2', 'L3', 'L4'] as const;
|
|
201
|
+
const CHECK_RISK_FLOORS = ['readonly', 'low', 'medium', 'high', 'destructive'] as const;
|
|
202
|
+
const CHECK_PHASES = ['pre_edit', 'post_edit', 'pre_finalize', 'release'] as const;
|
|
203
|
+
|
|
204
|
+
function inferredCheckSelection(name: string): ControllerCheckSelection {
|
|
205
|
+
const normalized = name.trim().toLowerCase().replace(/^package:/, '');
|
|
206
|
+
if (/(?:^|:)(?:check:)?release$|full[-:]?regression/.test(normalized)) {
|
|
207
|
+
return { costClass: 'L4', riskFloor: 'high', phases: ['release'] };
|
|
208
|
+
}
|
|
209
|
+
if (/(?:^|:)(?:check:)?main$|(?:^|:)ci(?::|$)/.test(normalized)) {
|
|
210
|
+
return { costClass: 'L4', riskFloor: 'medium', phases: ['pre_finalize'] };
|
|
211
|
+
}
|
|
212
|
+
if (/(?:^|:)(?:browser-live|performance|journey)(?::|$)/.test(normalized)) {
|
|
213
|
+
return { costClass: 'L3', riskFloor: 'medium', phases: ['pre_finalize'] };
|
|
214
|
+
}
|
|
215
|
+
if (/(?:^|:)(?:integration|build)(?::|$)/.test(normalized) || normalized === 'test') {
|
|
216
|
+
return { costClass: 'L2', riskFloor: 'medium', phases: ['post_edit', 'pre_finalize'] };
|
|
217
|
+
}
|
|
218
|
+
if (/(?:^|:)(?:architecture-sync|check-scheduling|context-files|bootstrap-files|background-check-overlap)$/.test(normalized)) {
|
|
219
|
+
return { costClass: 'L0', riskFloor: 'low', phases: ['post_edit'] };
|
|
220
|
+
}
|
|
221
|
+
return { costClass: 'L1', riskFloor: 'low', phases: ['post_edit'] };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function normalizeCheckSelection(value: unknown, id: string): ControllerCheckSelection {
|
|
225
|
+
const fallback = inferredCheckSelection(id);
|
|
226
|
+
if (value === undefined) return fallback;
|
|
227
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('check selection must be an object');
|
|
228
|
+
const raw = value as Record<string, unknown>;
|
|
229
|
+
const unsupported = Object.keys(raw).filter((key) => !['costClass', 'cost_class', 'riskFloor', 'risk_floor', 'phases'].includes(key));
|
|
230
|
+
if (unsupported.length > 0) throw new Error(`unsupported check selection field(s): ${unsupported.join(', ')}`);
|
|
231
|
+
const cost = raw.costClass ?? raw.cost_class;
|
|
232
|
+
const risk = raw.riskFloor ?? raw.risk_floor;
|
|
233
|
+
const phases = raw.phases;
|
|
234
|
+
if (cost !== undefined && (typeof cost !== 'string' || !CHECK_COST_CLASSES.includes(cost as ControllerCheckCostClass))) {
|
|
235
|
+
throw new Error(`check selection.costClass must be one of: ${CHECK_COST_CLASSES.join(', ')}`);
|
|
236
|
+
}
|
|
237
|
+
if (risk !== undefined && (typeof risk !== 'string' || !CHECK_RISK_FLOORS.includes(risk as ControllerCheckRiskFloor))) {
|
|
238
|
+
throw new Error(`check selection.riskFloor must be one of: ${CHECK_RISK_FLOORS.join(', ')}`);
|
|
239
|
+
}
|
|
240
|
+
if (phases !== undefined && (!Array.isArray(phases) || phases.length === 0 || phases.some((entry) => typeof entry !== 'string' || !CHECK_PHASES.includes(entry as ControllerCheckPhase)))) {
|
|
241
|
+
throw new Error(`check selection.phases must contain one or more of: ${CHECK_PHASES.join(', ')}`);
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
costClass: (cost as ControllerCheckCostClass | undefined) ?? fallback.costClass,
|
|
245
|
+
riskFloor: (risk as ControllerCheckRiskFloor | undefined) ?? fallback.riskFloor,
|
|
246
|
+
phases: phases ? [...new Set(phases as ControllerCheckPhase[])] : fallback.phases,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function controllerCheckSelection(check: Pick<ControllerCheck, 'id' | 'selection'>): ControllerCheckSelection {
|
|
251
|
+
return check.selection ?? inferredCheckSelection(check.id);
|
|
252
|
+
}
|
|
253
|
+
|
|
183
254
|
function inferredPackageCheckEffects(name: string): ControllerCheckEffects | undefined {
|
|
184
255
|
const normalized = name.trim().toLowerCase();
|
|
185
256
|
const staticAnalysis = /(?:^|:)(?:type|typecheck|lint|format:check|runtime-architecture|mcp-compatibility|forge-runtime)$/.test(normalized);
|
|
186
|
-
|
|
257
|
+
if (staticAnalysis) return { reads: ['.'], cache: 'write' };
|
|
258
|
+
const isolatedReadOnlyCheck = /(?:^|:)(?:quality-harness|evaluation-framework|background-check-overlap|typescript-navigation|check-scheduling|bootstrap-files)$/.test(normalized);
|
|
259
|
+
if (isolatedReadOnlyCheck) return { reads: ['.'], temp: 'isolated', git: 'read' };
|
|
260
|
+
const browserLive = /(?:^|:)browser-live$/.test(normalized);
|
|
261
|
+
if (browserLive) {
|
|
262
|
+
return {
|
|
263
|
+
reads: ['.'],
|
|
264
|
+
temp: 'isolated',
|
|
265
|
+
hostServices: ['browser-live'],
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
return undefined;
|
|
187
269
|
}
|
|
188
270
|
|
|
189
271
|
function configuredChecks(repoRoot: string): ControllerCheck[] {
|
|
190
|
-
|
|
191
|
-
|
|
272
|
+
// `.forge/` may intentionally be machine-local/ignored. Preserve it as the
|
|
273
|
+
// authoritative current location, but inherit the tracked legacy registry in
|
|
274
|
+
// isolated Git checkouts when the current file is absent. Never merge both:
|
|
275
|
+
// deterministic precedence prevents checkout-local definitions from being
|
|
276
|
+
// silently combined with portable repository policy.
|
|
277
|
+
const currentPath = join(repoRoot, CHECK_CONFIG);
|
|
278
|
+
const legacyPath = join(repoRoot, LEGACY_TRACKED_CHECK_CONFIG);
|
|
279
|
+
const path = existsSync(currentPath) ? currentPath : existsSync(legacyPath) ? legacyPath : undefined;
|
|
280
|
+
if (!path) return [];
|
|
192
281
|
const config = JSON.parse(readFileSync(path, 'utf-8')) as CheckConfig;
|
|
193
282
|
return Object.entries(config.checks ?? {}).flatMap(([id, value]) => {
|
|
194
283
|
if (!Array.isArray(value.command) || value.command.length === 0 || value.command.some((part) => typeof part !== 'string' || part.length === 0)) return [];
|
|
@@ -200,6 +289,7 @@ function configuredChecks(repoRoot: string): ControllerCheck[] {
|
|
|
200
289
|
timeoutMs: boundedTimeout(value.timeoutMs),
|
|
201
290
|
source: 'repo-config' as const,
|
|
202
291
|
effects: normalizeCheckEffects(repoRoot, value.effects),
|
|
292
|
+
selection: normalizeCheckSelection(value.selection, id),
|
|
203
293
|
}];
|
|
204
294
|
});
|
|
205
295
|
}
|
|
@@ -223,6 +313,7 @@ function packageChecks(repoRoot: string): ControllerCheck[] {
|
|
|
223
313
|
timeoutMs: packageScriptTimeoutMs(name),
|
|
224
314
|
source: 'package-script' as const,
|
|
225
315
|
effects: inferredPackageCheckEffects(name),
|
|
316
|
+
selection: inferredCheckSelection(name),
|
|
226
317
|
}];
|
|
227
318
|
});
|
|
228
319
|
}
|
|
@@ -360,6 +451,7 @@ function validateControllerCheckSnapshot(repoRoot: string, snapshot: ControllerC
|
|
|
360
451
|
timeoutMs: boundedTimeout(snapshot.timeoutMs),
|
|
361
452
|
source: snapshot.source,
|
|
362
453
|
effects: normalizeCheckEffects(repoRoot, snapshot.effects),
|
|
454
|
+
selection: normalizeCheckSelection(snapshot.selection, snapshot.id),
|
|
363
455
|
};
|
|
364
456
|
}
|
|
365
457
|
|
|
@@ -541,9 +633,15 @@ function readControllerCheckEvidenceByKey(repoRoot: string, id: string, cacheKey
|
|
|
541
633
|
}
|
|
542
634
|
}
|
|
543
635
|
|
|
636
|
+
function prepareControllerCheckDependencies(repoRoot: string, check: ControllerCheck): void {
|
|
637
|
+
if (check.source !== 'package-script' || existsSync(join(repoRoot, 'node_modules'))) return;
|
|
638
|
+
materializeManagedWorkspaceCheckDependencies(repoRoot);
|
|
639
|
+
}
|
|
640
|
+
|
|
544
641
|
export function runControllerCheck(repoRoot: string, id: string, requestedTimeoutMs?: number, snapshot?: ControllerCheckSnapshot): ControllerCheckResult {
|
|
545
642
|
const check = snapshot ? validateControllerCheckSnapshot(repoRoot, snapshot) : listControllerChecks(repoRoot).find((entry) => entry.id === id);
|
|
546
643
|
if (!check || check.id !== id) throw new Error(`check not found: ${id}`);
|
|
644
|
+
prepareControllerCheckDependencies(repoRoot, check);
|
|
547
645
|
const identity = controllerCheckExecutionIdentity(repoRoot, id, requestedTimeoutMs, snapshot);
|
|
548
646
|
const timeoutMs = identity.timeoutMs;
|
|
549
647
|
const revision = identity.revision;
|
|
@@ -675,6 +773,8 @@ export function runControllerCheck(repoRoot: string, id: string, requestedTimeou
|
|
|
675
773
|
|
|
676
774
|
export interface AsyncControllerCheckOptions {
|
|
677
775
|
snapshot?: ControllerCheckSnapshot;
|
|
776
|
+
/** Explicit isolated authority for Candidate verification child processes. */
|
|
777
|
+
isolatedControllerHome?: string;
|
|
678
778
|
requestedTimeoutMs?: number;
|
|
679
779
|
onSpawn?: (pid: number) => void;
|
|
680
780
|
subscriberId?: string;
|
|
@@ -814,12 +914,15 @@ async function executeControllerCheckAsync(
|
|
|
814
914
|
check: ControllerCheck,
|
|
815
915
|
timeoutMs: number,
|
|
816
916
|
onSpawn?: (pid: number) => void,
|
|
917
|
+
isolatedControllerHome?: string,
|
|
817
918
|
): Promise<ControllerCheckResult> {
|
|
818
919
|
const maxOutputBytes = 256 * 1024;
|
|
819
920
|
const command = [check.command[0], ...check.command.slice(1)];
|
|
921
|
+
const childEnvironment = repositoryChildProcessEnvironment();
|
|
922
|
+
if (isolatedControllerHome?.trim()) childEnvironment.FORGE_CONTROLLER_HOME = resolve(isolatedControllerHome);
|
|
820
923
|
const supervised = await runBoundedChild(check.command[0], check.command.slice(1), {
|
|
821
924
|
cwd: resolve(repoRoot, check.cwd),
|
|
822
|
-
env:
|
|
925
|
+
env: childEnvironment,
|
|
823
926
|
timeoutMs,
|
|
824
927
|
maxOutputBytes,
|
|
825
928
|
stdio: 'capture',
|
|
@@ -877,6 +980,11 @@ export function runControllerCheckAsync(
|
|
|
877
980
|
return Promise.reject(error);
|
|
878
981
|
}
|
|
879
982
|
if (!check || check.id !== id) return Promise.reject(new Error(`check not found: ${id}`));
|
|
983
|
+
try {
|
|
984
|
+
prepareControllerCheckDependencies(repoRoot, check);
|
|
985
|
+
} catch (error) {
|
|
986
|
+
return Promise.reject(error);
|
|
987
|
+
}
|
|
880
988
|
const identity = controllerCheckExecutionIdentity(repoRoot, id, options.requestedTimeoutMs, options.snapshot);
|
|
881
989
|
const timeoutMs = identity.timeoutMs;
|
|
882
990
|
const revision = identity.revision;
|
|
@@ -947,7 +1055,7 @@ export function runControllerCheckAsync(
|
|
|
947
1055
|
const result = await executeControllerCheckAsync(repoRoot, check, timeoutMs, (pid) => {
|
|
948
1056
|
lease?.setChildPid(pid);
|
|
949
1057
|
notifySpawn(pid);
|
|
950
|
-
});
|
|
1058
|
+
}, options.isolatedControllerHome);
|
|
951
1059
|
const completedRevision = currentControllerCheckRevision(repoRoot);
|
|
952
1060
|
const stale = completedRevision !== revision;
|
|
953
1061
|
const finalized = {
|
|
@@ -124,7 +124,7 @@ export function compositeRunning(input: {
|
|
|
124
124
|
keyOutput: boundKeyOutput(input.keyOutput ?? input.summary),
|
|
125
125
|
evidenceRefs: input.evidenceRefs ?? [],
|
|
126
126
|
retryable: false,
|
|
127
|
-
nextAction: input.nextAction ?? '
|
|
127
|
+
nextAction: input.nextAction ?? 'Continue independent work; inspect this request only when its outcome can change the next decision. Do not resubmit or periodically poll.',
|
|
128
128
|
details: input.details,
|
|
129
129
|
};
|
|
130
130
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { TaskLedgerTaskProjection } from '../task-ledger';
|
|
2
|
+
import type { projectBoard } from '../issue-store';
|
|
3
|
+
import type { buildControllerTaskLedgerProjection } from '../task-ledger';
|
|
4
|
+
|
|
5
|
+
export interface ContextPackIssueFocus { id?: string; title?: string; summary?: string; tasks: ContextPackTaskFocus[] }
|
|
6
|
+
export interface ContextPackTaskFocus { id?: string; title?: string; objective?: string; status?: string }
|
|
7
|
+
|
|
8
|
+
function record(value: unknown): Record<string, unknown> {
|
|
9
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
10
|
+
}
|
|
11
|
+
function string(value: unknown): string | undefined {
|
|
12
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
13
|
+
}
|
|
14
|
+
function task(value: unknown): ContextPackTaskFocus {
|
|
15
|
+
const item = record(value);
|
|
16
|
+
return { id: string(item.id), title: string(item.title), objective: string(item.objective), status: string(item.status) };
|
|
17
|
+
}
|
|
18
|
+
function issue(value: unknown): ContextPackIssueFocus {
|
|
19
|
+
const item = record(value);
|
|
20
|
+
return {
|
|
21
|
+
id: string(item.id), title: string(item.title), summary: string(item.summary),
|
|
22
|
+
tasks: Array.isArray(item.tasks) ? item.tasks.map(task).filter((entry) => Object.keys(entry).length > 0) : [],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function issueTaskFocus(
|
|
27
|
+
board: ReturnType<typeof projectBoard>,
|
|
28
|
+
issueId?: string,
|
|
29
|
+
taskId?: string,
|
|
30
|
+
): { issue?: ContextPackIssueFocus; task?: ContextPackTaskFocus } {
|
|
31
|
+
const issues = board.issues.map(issue);
|
|
32
|
+
const resolvedIssue = issueId
|
|
33
|
+
? issues.find((entry) => entry.id === issueId)
|
|
34
|
+
: board.currentIssueId ? issues.find((entry) => entry.id === board.currentIssueId) : undefined;
|
|
35
|
+
return { issue: resolvedIssue, task: resolvedIssue?.tasks.find((entry) => entry.id === taskId) ?? resolvedIssue?.tasks[0] };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function ledgerTask(
|
|
39
|
+
ledger: ReturnType<typeof buildControllerTaskLedgerProjection>,
|
|
40
|
+
issueId?: string,
|
|
41
|
+
taskId?: string,
|
|
42
|
+
): TaskLedgerTaskProjection | undefined {
|
|
43
|
+
const tasks = ledger.issues.flatMap((entry) => entry.tasks);
|
|
44
|
+
const find = (candidateIssueId?: string, candidateTaskId?: string) => tasks
|
|
45
|
+
.find((entry) => (!candidateIssueId || entry.issueId === candidateIssueId) && (!candidateTaskId || entry.taskId === candidateTaskId));
|
|
46
|
+
if (!issueId && !taskId) {
|
|
47
|
+
const ready = ledger.readyTasks[0];
|
|
48
|
+
return ledger.attention[0] ?? find(ready?.issueId, ready?.taskId) ?? tasks.find((entry) => entry.dispatchable || entry.queueable);
|
|
49
|
+
}
|
|
50
|
+
return find(issueId, taskId);
|
|
51
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { posix, resolve } from 'path';
|
|
3
|
+
import type { McpPolicy } from '../../mcp/types';
|
|
4
|
+
import { readableFile } from './known-paths';
|
|
5
|
+
|
|
6
|
+
const CONTRACT_NAMES = ['AGENTS.md', 'CLAUDE.md'] as const;
|
|
7
|
+
const DEFAULT_MAX_TARGETS = 12;
|
|
8
|
+
const DEFAULT_MAX_CONTRACTS = 12;
|
|
9
|
+
const DEFAULT_MAX_CHARS_PER_CONTRACT = 12_000;
|
|
10
|
+
const DEFAULT_MAX_TOTAL_CHARS = 48_000;
|
|
11
|
+
|
|
12
|
+
export interface RepositoryInstructionContract {
|
|
13
|
+
path: string;
|
|
14
|
+
appliesTo: string[];
|
|
15
|
+
content: string;
|
|
16
|
+
truncated: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface RepositoryInstructionContext {
|
|
20
|
+
status: 'none' | 'ready' | 'degraded';
|
|
21
|
+
authority: 'guidance_only';
|
|
22
|
+
targetPaths: string[];
|
|
23
|
+
contracts: RepositoryInstructionContract[];
|
|
24
|
+
coverageGaps: string[];
|
|
25
|
+
truncated: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isInstructionContract(path: string): boolean {
|
|
29
|
+
const name = posix.basename(path);
|
|
30
|
+
return name === 'AGENTS.md' || name === 'CLAUDE.md';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function directoryChain(path: string): string[] {
|
|
34
|
+
const directory = posix.dirname(path);
|
|
35
|
+
if (directory === '.' || directory === '/') return [''];
|
|
36
|
+
const segments = directory.split('/').filter(Boolean);
|
|
37
|
+
const chain = [''];
|
|
38
|
+
for (let index = 1; index <= segments.length; index += 1) chain.push(segments.slice(0, index).join('/'));
|
|
39
|
+
return chain;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function buildRepositoryInstructionContext(
|
|
43
|
+
repoRoot: string,
|
|
44
|
+
policy: McpPolicy,
|
|
45
|
+
targetPaths: string[],
|
|
46
|
+
options: { maxTargets?: number; maxContracts?: number; maxCharsPerContract?: number; maxTotalChars?: number } = {},
|
|
47
|
+
): RepositoryInstructionContext {
|
|
48
|
+
const maxTargets = Math.max(1, Math.min(30, Math.trunc(options.maxTargets ?? DEFAULT_MAX_TARGETS)));
|
|
49
|
+
const maxContracts = Math.max(1, Math.min(30, Math.trunc(options.maxContracts ?? DEFAULT_MAX_CONTRACTS)));
|
|
50
|
+
const maxCharsPerContract = Math.max(500, Math.min(50_000, Math.trunc(options.maxCharsPerContract ?? DEFAULT_MAX_CHARS_PER_CONTRACT)));
|
|
51
|
+
const maxTotalChars = Math.max(2_000, Math.min(100_000, Math.trunc(options.maxTotalChars ?? DEFAULT_MAX_TOTAL_CHARS)));
|
|
52
|
+
const allTargets = [...new Set(targetPaths.map((path) => path.trim()).filter(Boolean).filter((path) => !isInstructionContract(path)))];
|
|
53
|
+
const targets = allTargets.slice(0, maxTargets);
|
|
54
|
+
const coverageGaps: string[] = [];
|
|
55
|
+
if (allTargets.length > targets.length) coverageGaps.push(`instruction_targets_truncated:${allTargets.length - targets.length}`);
|
|
56
|
+
|
|
57
|
+
const contractTargets = new Map<string, Set<string>>();
|
|
58
|
+
const denied = new Set<string>();
|
|
59
|
+
for (const target of targets) {
|
|
60
|
+
for (const directory of directoryChain(target)) {
|
|
61
|
+
for (const name of CONTRACT_NAMES) {
|
|
62
|
+
const candidate = directory ? `${directory}/${name}` : name;
|
|
63
|
+
const readable = readableFile(repoRoot, policy, candidate);
|
|
64
|
+
if (!readable.ok) {
|
|
65
|
+
if (!readable.reason.startsWith('path does not exist')) denied.add(`${candidate}:${readable.reason}`);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const appliesTo = contractTargets.get(readable.path) ?? new Set<string>();
|
|
69
|
+
appliesTo.add(target);
|
|
70
|
+
contractTargets.set(readable.path, appliesTo);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (denied.size > 0) coverageGaps.push(...[...denied].sort().map((entry) => `instruction_contract_unreadable:${entry}`));
|
|
75
|
+
|
|
76
|
+
const orderedPaths = [...contractTargets.keys()].sort((left, right) => {
|
|
77
|
+
const leftDepth = left.split('/').length;
|
|
78
|
+
const rightDepth = right.split('/').length;
|
|
79
|
+
if (leftDepth !== rightDepth) return leftDepth - rightDepth;
|
|
80
|
+
const leftName = posix.basename(left);
|
|
81
|
+
const rightName = posix.basename(right);
|
|
82
|
+
if (leftName !== rightName) return CONTRACT_NAMES.indexOf(leftName as typeof CONTRACT_NAMES[number]) - CONTRACT_NAMES.indexOf(rightName as typeof CONTRACT_NAMES[number]);
|
|
83
|
+
return left.localeCompare(right);
|
|
84
|
+
});
|
|
85
|
+
const selectedPaths = orderedPaths.slice(0, maxContracts);
|
|
86
|
+
if (orderedPaths.length > selectedPaths.length) coverageGaps.push(`instruction_contracts_truncated:${orderedPaths.length - selectedPaths.length}`);
|
|
87
|
+
|
|
88
|
+
const contracts: RepositoryInstructionContract[] = [];
|
|
89
|
+
const fairContractBudget = Math.max(500, Math.min(maxCharsPerContract, Math.floor(maxTotalChars / Math.max(1, selectedPaths.length))));
|
|
90
|
+
for (const path of selectedPaths) {
|
|
91
|
+
try {
|
|
92
|
+
const raw = readFileSync(resolve(repoRoot, path), 'utf8');
|
|
93
|
+
const truncated = raw.length > fairContractBudget;
|
|
94
|
+
contracts.push({
|
|
95
|
+
path,
|
|
96
|
+
appliesTo: [...(contractTargets.get(path) ?? new Set<string>())].sort(),
|
|
97
|
+
content: truncated ? raw.slice(0, fairContractBudget) : raw,
|
|
98
|
+
truncated,
|
|
99
|
+
});
|
|
100
|
+
if (truncated) coverageGaps.push(`instruction_contract_content_truncated:${path}`);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
coverageGaps.push(`instruction_contract_read_failed:${path}:${error instanceof Error ? error.message : String(error)}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
status: coverageGaps.length > 0 ? 'degraded' : contracts.length > 0 ? 'ready' : 'none',
|
|
108
|
+
authority: 'guidance_only',
|
|
109
|
+
targetPaths: targets,
|
|
110
|
+
contracts,
|
|
111
|
+
coverageGaps: coverageGaps.slice(0, 30),
|
|
112
|
+
truncated: allTargets.length > targets.length || orderedPaths.length > selectedPaths.length || contracts.some((entry) => entry.truncated),
|
|
113
|
+
};
|
|
114
|
+
}
|