@moretea-labs/forge 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -0
- package/CHANGELOG.md +26 -0
- package/CLAUDE.md +2 -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/03-orchestration.partial.md +1 -1
- package/assets/plugin-registry.v1.json +1 -1
- 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-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/migrate-project-template.sh +22 -45
- package/assets/templates/helpers/new-plan.sh +1 -1
- package/assets/templates/helpers/plan-to-todo.sh +12 -37
- package/assets/templates/helpers/ship-worktrees.sh +2 -29
- package/assets/templates/helpers/verify-contract.sh +1 -111
- package/assets/templates/helpers/verify-sprint.sh +3 -180
- package/assets/templates/plan.template.md +1 -1
- package/assets/templates/review.template.md +9 -34
- package/assets/workflow-contract.v1.json +10 -19
- package/docs/README.md +10 -10
- package/docs/architecture/CURRENT.md +170 -0
- package/docs/architecture/EVOLUTION.md +42 -0
- package/docs/architecture/current/README.md +4 -156
- package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
- package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
- package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
- package/docs/architecture/history/README.md +7 -0
- package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
- package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
- package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
- package/docs/architecture/history.md +3 -34
- package/docs/architecture/index.md +15 -98
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
- package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
- package/docs/architecture/versions/1.6.md +38 -0
- package/docs/architecture/versions/1.7.md +41 -0
- package/docs/forge-chatgpt-browser-engine.md +2 -2
- package/docs/forge-chatgpt-mcp-setup.md +4 -28
- package/docs/forge-chatgpt-supervised-automation.md +3 -3
- package/docs/forge-github-issue-launcher.md +1 -1
- package/docs/forge-runtime-self-healing-loop.md +1 -1
- package/docs/operations/controller-browser-plugin.md +26 -6
- package/docs/operations/controller-performance-and-502.md +124 -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 +29 -10
- package/scripts/activate-source-baseline.ts +17 -13
- package/scripts/benchmark-background-check-overlap.ts +228 -0
- package/scripts/benchmark-mcp-host.sh +117 -0
- package/scripts/benchmark-mcp-transport.ts +170 -0
- package/scripts/benchmark-quality-harness.ts +146 -0
- package/scripts/benchmark-route-session-concurrency.ts +207 -6
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
- package/scripts/benchmark-typescript-navigation.ts +186 -0
- package/scripts/benchmark-work-efficiency.ts +334 -0
- package/scripts/capture-plan.sh +3 -3
- package/scripts/check-agent-tooling.sh +8 -35
- package/scripts/check-architecture-sync.sh +17 -86
- package/scripts/check-mcp-compatibility.ts +14 -0
- package/scripts/check-runtime-architecture.mjs +107 -64
- package/scripts/check-stable-baseline.ts +92 -0
- package/scripts/check-task-workflow.sh +4 -4
- package/scripts/contract-worktree.sh +1 -21
- package/scripts/ensure-task-workflow.sh +22 -55
- package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
- package/scripts/install-standalone-recovery.ts +24 -0
- package/scripts/lib/project-init-lib.sh +15 -44
- package/scripts/migrate-project-template.sh +23 -1179
- package/scripts/new-plan.sh +1 -1
- package/scripts/plan-to-todo.sh +11 -36
- package/scripts/run-governed-gate.ts +2 -1
- package/scripts/ship-worktrees.sh +2 -29
- package/scripts/smoke-ios-physical-device.ts +4 -2
- package/scripts/smoke-runtime-recovery.ts +1 -35
- package/scripts/smoke-schedule-engine.ts +1 -20
- package/scripts/verify-contract.sh +1 -111
- package/scripts/verify-forge-runtime.sh +0 -1
- package/scripts/verify-sprint.sh +3 -180
- package/scripts/workflow-contract.ts +1 -5
- package/src/cli/commands/init-hook.ts +3 -3
- package/src/cli/commands/mcp.ts +9 -5
- package/src/cli/commands/plugin.ts +1 -1
- package/src/cli/commands/recovery.ts +25 -3
- package/src/cli/commands/runtime.ts +55 -3
- package/src/cli/commands/security.ts +12 -1
- package/src/cli/commands/setup-profile.ts +31 -21
- package/src/cli/controller/check-runner.ts +106 -3
- package/src/cli/controller/composite-result.ts +1 -1
- package/src/cli/controller/context/focus.ts +51 -0
- package/src/cli/controller/context/instruction-context.ts +114 -0
- package/src/cli/controller/context/known-paths.ts +76 -0
- package/src/cli/controller/context/query-planning.ts +110 -0
- package/src/cli/controller/context/rule-resolution.ts +532 -0
- package/src/cli/controller/context/source-materializer.ts +278 -0
- package/src/cli/controller/context/structural.ts +16 -0
- package/src/cli/controller/context/types.ts +286 -0
- package/src/cli/controller/context-pack.ts +785 -469
- package/src/cli/controller/launch-agents.ts +77 -2
- package/src/cli/controller/operational-plan.ts +2 -2
- package/src/cli/controller/work-mode.ts +6 -4
- package/src/cli/editing/edit-session.ts +60 -7
- package/src/cli/github/github.ts +133 -0
- package/src/cli/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 +0 -217
- 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 +0 -513
- 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 +0 -189
- package/src/cli/mcp/multi-repository.ts +16 -5
- package/src/cli/mcp/oauth.ts +175 -26
- package/src/cli/mcp/repository-tools.ts +247 -107
- package/src/cli/mcp/server.ts +537 -111
- package/src/cli/mcp/tool-capability-cache.ts +1 -1
- package/src/cli/mcp/toolset-selection.ts +1 -1
- package/src/cli/mcp/toolset.ts +42 -5
- package/src/cli/mcp/transports/http.ts +101 -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 +101 -19
- package/src/cli/repositories/command-executor.ts +55 -521
- package/src/cli/repositories/command-process.ts +108 -0
- package/src/cli/repositories/command-scope.ts +99 -41
- package/src/cli/repositories/controller-home.ts +7 -8
- package/src/cli/repositories/git-command-executor.ts +2 -0
- package/src/cli/repositories/locks.ts +53 -16
- package/src/cli/repositories/repository-snapshot.ts +121 -0
- package/src/cli/repositories/selected-path-actions.ts +17 -9
- package/src/cli/repositories/structured-git.ts +61 -3
- package/src/cli/repository/inspector.ts +553 -63
- package/src/cli/repository/session-cache.ts +58 -17
- package/src/cli/v81-entry.ts +1 -4
- package/src/effects/fs-transaction.ts +85 -17
- package/src/runtime/context/codegraph-read-provider.ts +233 -2
- package/src/runtime/context/codegraph-sidecar.cjs +74 -40
- package/src/runtime/context/generic-lsp-provider.ts +355 -0
- package/src/runtime/context/lsp-client.ts +283 -0
- package/src/runtime/context/semantic-navigation.ts +275 -0
- package/src/runtime/context/swift-navigation.ts +565 -0
- package/src/runtime/context/typescript-navigation.ts +226 -0
- package/src/runtime/control-plane/dispatch-priority.ts +28 -0
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +47 -8
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
- 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-handle-store.ts +27 -2
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +143 -8
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +85 -8
- package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
- package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
- package/src/runtime/control-plane/facade/controller-session-store.ts +132 -1
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +2 -0
- package/src/runtime/control-plane/facade/goal-workloop.ts +686 -129
- package/src/runtime/control-plane/facade/index.ts +1 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +185 -20
- 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 +22 -5
- package/src/runtime/control-plane/facade/work-contract-store.ts +175 -73
- package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
- package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
- package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
- package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
- package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
- package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
- package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
- package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
- package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
- package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
- package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
- package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
- package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
- package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +355 -116
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
- package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
- package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
- package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
- package/src/runtime/control-plane/release-retention.ts +62 -2
- package/src/runtime/control-plane/routing/route-policy.ts +7 -16
- package/src/runtime/control-plane/runtime-status-client.ts +0 -2
- package/src/runtime/diagnostics/mcp-timing.ts +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/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 +53 -0
- package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +345 -40
- package/src/runtime/execution/process-runtime/gc.ts +38 -6
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
- package/src/runtime/execution/process-runtime/persisted-check.ts +47 -4
- package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
- package/src/runtime/execution/process-runtime/runtime.ts +43 -3
- package/src/runtime/execution/process-runtime/store.ts +1 -0
- package/src/runtime/execution/process-runtime/types.ts +2 -0
- package/src/runtime/execution/thin-harness/async-process.ts +12 -2
- package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
- package/src/runtime/execution/workers/executor.ts +0 -48
- package/src/runtime/gateway/mcp/execution-tools.ts +1192 -114
- package/src/runtime/gateway/mcp/process-tools.ts +83 -37
- package/src/runtime/gateway/mcp/router.ts +184 -82
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
- package/src/runtime/gateway/mcp/runtime-tools.ts +2224 -1589
- package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
- package/src/runtime/plugins/browser-adapter.ts +2901 -241
- package/src/runtime/plugins/browser-automation-service.ts +10 -0
- package/src/runtime/plugins/browser-handoff.ts +92 -2
- package/src/runtime/plugins/browser-macos-bridge.ts +582 -49
- package/src/runtime/plugins/browser-node-bridge.ts +6 -34
- package/src/runtime/plugins/browser-provider-registry.ts +109 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
- package/src/runtime/plugins/browser-runtime.ts +183 -0
- package/src/runtime/plugins/browser-session-authority.ts +332 -0
- package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +8 -2
- package/src/runtime/plugins/external-adapter.ts +324 -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 +55 -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 +269 -29
- package/src/runtime/plugins/types.ts +35 -2
- package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
- package/src/runtime/projections/git-status-sampler.ts +16 -16
- package/src/runtime/projections/materialized-view.ts +28 -10
- package/src/runtime/recovery/maintenance-executor.ts +393 -28
- package/src/runtime/root/mcp-transport.ts +180 -10
- package/src/runtime/root/package-connector-service.ts +186 -8
- package/src/runtime/root/package-runtime-release.ts +143 -17
- package/src/runtime/root/package-runtime-service.ts +430 -22
- package/src/runtime/root/release-manifest.ts +7 -2
- package/src/runtime/root/release-materialize.ts +19 -0
- 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 +2 -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 +700 -92
- package/src/runtime/standalone-recovery/entry.ts +99 -23
- package/src/runtime/standalone-recovery/installer.ts +19 -3
- package/src/runtime/standalone-recovery/service-labels.ts +2 -0
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
- package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
- package/src/runtime/workflow/schedules/engine.ts +287 -151
- package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
- package/src/runtime/workflow/schedules/settlement.ts +48 -4
- package/src/runtime/workflow/schedules/store.ts +131 -10
- package/src/runtime/workflow/schedules/types.ts +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,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, writeFileSync } from 'fs';
|
|
2
3
|
import { basename, dirname, join, relative, resolve, sep } from 'path';
|
|
3
4
|
import { spawnSync } from 'child_process';
|
|
4
5
|
import type { RepositoryRecord } from '../../cli/repositories/types';
|
|
@@ -19,32 +20,40 @@ export interface IosDevelopmentHooks {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
let hooks: IosDevelopmentHooks = {};
|
|
23
|
+
let artifactSequence = 0;
|
|
22
24
|
|
|
23
25
|
/** Host Xcode/simctl probes are expensive (spawnSync). Cache them for hot MCP reads. */
|
|
24
26
|
const XCODE_STATUS_TTL_MS = 60_000;
|
|
25
27
|
const XCODE_STATUS_DEGRADED_TTL_MS = 10_000;
|
|
26
28
|
const PROJECT_DISCOVER_TTL_MS = 30_000;
|
|
29
|
+
const SCHEME_LIST_TTL_MS = 30_000;
|
|
27
30
|
let xcodeStatusCache:
|
|
28
31
|
| { expiresAt: number; value: ReturnType<typeof probeIosXcodeStatus> }
|
|
29
32
|
| undefined;
|
|
30
33
|
const projectDiscoverCache = new Map<string, { expiresAt: number; value: ReturnType<typeof probeIosProjectDiscover> }>();
|
|
34
|
+
const schemeListCache = new Map<string, { expiresAt: number; value: ReturnType<typeof probeIosSchemesList> }>();
|
|
31
35
|
|
|
32
36
|
export function setIosDevelopmentHooksForTest(next: IosDevelopmentHooks): void {
|
|
33
37
|
hooks = next;
|
|
38
|
+
artifactSequence = 0;
|
|
34
39
|
xcodeStatusCache = undefined;
|
|
35
40
|
projectDiscoverCache.clear();
|
|
41
|
+
schemeListCache.clear();
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
export function resetIosDevelopmentHooksForTest(): void {
|
|
39
45
|
hooks = {};
|
|
46
|
+
artifactSequence = 0;
|
|
40
47
|
xcodeStatusCache = undefined;
|
|
41
48
|
projectDiscoverCache.clear();
|
|
49
|
+
schemeListCache.clear();
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
/** Test helper: drop cached Xcode probe results without clearing hooks. */
|
|
45
53
|
export function clearIosXcodeStatusCacheForTest(): void {
|
|
46
54
|
xcodeStatusCache = undefined;
|
|
47
55
|
projectDiscoverCache.clear();
|
|
56
|
+
schemeListCache.clear();
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
export function iosDevelopmentPlatform(): NodeJS.Platform {
|
|
@@ -113,7 +122,7 @@ function safeRepoRelativePath(repoRoot: string, value: string | undefined): stri
|
|
|
113
122
|
|
|
114
123
|
function safeArtifactDir(
|
|
115
124
|
repository: RepositoryRecord,
|
|
116
|
-
leaf: 'screenshots' | 'logs' | 'build-reports' | 'DerivedData',
|
|
125
|
+
leaf: 'screenshots' | 'logs' | 'build-reports' | 'test-results' | 'DerivedData',
|
|
117
126
|
overrideRoot?: string,
|
|
118
127
|
): string {
|
|
119
128
|
const root = overrideRoot
|
|
@@ -135,10 +144,24 @@ function timestamp(): string {
|
|
|
135
144
|
return now().toISOString().replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z');
|
|
136
145
|
}
|
|
137
146
|
|
|
147
|
+
function artifactStamp(): string {
|
|
148
|
+
artifactSequence = (artifactSequence + 1) % Number.MAX_SAFE_INTEGER;
|
|
149
|
+
return `${timestamp()}-${process.pid}-${artifactSequence}`;
|
|
150
|
+
}
|
|
151
|
+
|
|
138
152
|
function sanitize(value: string): string {
|
|
139
153
|
return value.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'ios';
|
|
140
154
|
}
|
|
141
155
|
|
|
156
|
+
function stableBuildDerivedDataPath(
|
|
157
|
+
repository: RepositoryRecord,
|
|
158
|
+
input: { workspace?: string; project?: string; scheme: string; configuration: string; destination: string },
|
|
159
|
+
): string {
|
|
160
|
+
const identity = JSON.stringify([input.workspace ?? '', input.project ?? '', input.scheme, input.configuration, input.destination]);
|
|
161
|
+
const digest = createHash('sha256').update(identity).digest('hex').slice(0, 16);
|
|
162
|
+
return join(safeArtifactDir(repository, 'DerivedData'), `build-${sanitize(input.scheme)}-${digest}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
142
165
|
function boundedText(value: string, maxBytes = 20_000): { content: string; truncated: boolean } {
|
|
143
166
|
const buffer = Buffer.from(value, 'utf-8');
|
|
144
167
|
if (buffer.byteLength <= maxBytes) return { content: value, truncated: false };
|
|
@@ -172,6 +195,28 @@ function probeIosXcodeStatus() {
|
|
|
172
195
|
};
|
|
173
196
|
}
|
|
174
197
|
|
|
198
|
+
export function iosXcodeStatusSnapshot() {
|
|
199
|
+
const unsupported = assertDarwin();
|
|
200
|
+
if (unsupported) return { status: unsupported, probed: true, cached: false };
|
|
201
|
+
const currentMs = currentTimeMs();
|
|
202
|
+
if (xcodeStatusCache && xcodeStatusCache.expiresAt > currentMs) {
|
|
203
|
+
return { status: xcodeStatusCache.value, probed: true, cached: true };
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
status: {
|
|
207
|
+
ready: false,
|
|
208
|
+
platform: platform(),
|
|
209
|
+
simctlAvailable: false,
|
|
210
|
+
problems: [{
|
|
211
|
+
code: 'XCODE_STATUS_NOT_PROBED',
|
|
212
|
+
message: 'Xcode readiness has not been probed asynchronously or explicitly in this runtime yet.',
|
|
213
|
+
}],
|
|
214
|
+
},
|
|
215
|
+
probed: false,
|
|
216
|
+
cached: false,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
175
220
|
export function iosXcodeStatus(options: { forceRefresh?: boolean } = {}) {
|
|
176
221
|
const currentMs = currentTimeMs();
|
|
177
222
|
if (!options.forceRefresh && xcodeStatusCache && xcodeStatusCache.expiresAt > currentMs) {
|
|
@@ -205,15 +250,16 @@ export function iosSimulatorsList(input: { runtime?: string; name?: string } = {
|
|
|
205
250
|
return { ready: true, devices };
|
|
206
251
|
}
|
|
207
252
|
|
|
208
|
-
function findFiles(root: string, suffix: string, maxDepth =
|
|
253
|
+
function findFiles(root: string, suffix: string, maxDepth = 6): string[] {
|
|
209
254
|
const results: string[] = [];
|
|
255
|
+
const skippedDirectories = new Set(['node_modules', '.git', '.forge', '.ai', 'DerivedData', 'Pods', '.build', 'build', 'dist', '.cache', 'Carthage']);
|
|
210
256
|
function visit(dir: string, depth: number) {
|
|
211
257
|
if (depth > maxDepth || results.length > 50) return;
|
|
212
258
|
let entries;
|
|
213
259
|
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
214
260
|
for (const entry of entries) {
|
|
215
261
|
const entryName = String(entry.name);
|
|
216
|
-
if (
|
|
262
|
+
if (skippedDirectories.has(entryName)) continue;
|
|
217
263
|
const path = join(dir, entryName);
|
|
218
264
|
if (entry.isDirectory() && entryName.endsWith(suffix)) results.push(path);
|
|
219
265
|
else if (entry.isDirectory()) visit(path, depth + 1);
|
|
@@ -253,12 +299,11 @@ export function iosProjectDiscover(repository: RepositoryRecord, options: { forc
|
|
|
253
299
|
return value;
|
|
254
300
|
}
|
|
255
301
|
|
|
256
|
-
|
|
302
|
+
function probeIosSchemesList(repository: RepositoryRecord, input: { workspace?: string; project?: string } = {}) {
|
|
257
303
|
const unsupported = assertDarwin();
|
|
258
304
|
if (unsupported) return unsupported;
|
|
259
|
-
const
|
|
260
|
-
const
|
|
261
|
-
const project = safeRepoRelativePath(repository.canonicalRoot, input.project ?? discovered.project);
|
|
305
|
+
const workspace = safeRepoRelativePath(repository.canonicalRoot, input.workspace);
|
|
306
|
+
const project = safeRepoRelativePath(repository.canonicalRoot, input.project);
|
|
262
307
|
const args = workspace
|
|
263
308
|
? ['-list', '-json', '-workspace', workspace]
|
|
264
309
|
: project
|
|
@@ -274,6 +319,21 @@ export function iosSchemesList(repository: RepositoryRecord, input: { workspace?
|
|
|
274
319
|
}
|
|
275
320
|
}
|
|
276
321
|
|
|
322
|
+
export function iosSchemesList(repository: RepositoryRecord, input: { workspace?: string; project?: string } = {}) {
|
|
323
|
+
const unsupported = assertDarwin();
|
|
324
|
+
if (unsupported) return unsupported;
|
|
325
|
+
const discovered = iosProjectDiscover(repository);
|
|
326
|
+
const workspace = safeRepoRelativePath(repository.canonicalRoot, input.workspace ?? discovered.workspace);
|
|
327
|
+
const project = safeRepoRelativePath(repository.canonicalRoot, input.project ?? discovered.project);
|
|
328
|
+
const cacheKey = JSON.stringify([resolve(repository.canonicalRoot), workspace ?? '', project ?? '']);
|
|
329
|
+
const currentMs = currentTimeMs();
|
|
330
|
+
const cached = schemeListCache.get(cacheKey);
|
|
331
|
+
if (cached && cached.expiresAt > currentMs) return cached.value;
|
|
332
|
+
const value = probeIosSchemesList(repository, { workspace, project });
|
|
333
|
+
if (value.ready) schemeListCache.set(cacheKey, { expiresAt: currentMs + SCHEME_LIST_TTL_MS, value });
|
|
334
|
+
return value;
|
|
335
|
+
}
|
|
336
|
+
|
|
277
337
|
export function iosSimulatorBoot(input: { udid: string; openSimulator?: boolean; timeoutMs?: number }) {
|
|
278
338
|
const unsupported = assertDarwin();
|
|
279
339
|
if (unsupported) return unsupported;
|
|
@@ -281,15 +341,23 @@ export function iosSimulatorBoot(input: { udid: string; openSimulator?: boolean;
|
|
|
281
341
|
if (!udid) throw new Error('IOS_SIMULATOR_UDID_REQUIRED');
|
|
282
342
|
const timeoutMs = input.timeoutMs ?? 60_000;
|
|
283
343
|
const boot = runCommand('xcrun', ['simctl', 'boot', udid], { timeoutMs });
|
|
284
|
-
const alreadyBooted = /
|
|
344
|
+
const alreadyBooted = /current state:\s*Booted|already booted/i.test(boot.stderr + boot.stdout);
|
|
285
345
|
if (!boot.ok && !alreadyBooted) {
|
|
286
|
-
return {
|
|
346
|
+
return {
|
|
347
|
+
ready: false,
|
|
348
|
+
booted: false,
|
|
349
|
+
udid,
|
|
350
|
+
alreadyBooted: false,
|
|
351
|
+
error: { code: 'SIMULATOR_BOOT_FAILED', message: boot.stderr || boot.stdout },
|
|
352
|
+
command: boot.command,
|
|
353
|
+
};
|
|
287
354
|
}
|
|
288
355
|
const readiness = runCommand('xcrun', ['simctl', 'bootstatus', udid, '-b'], { timeoutMs });
|
|
289
356
|
if (!readiness.ok) {
|
|
290
357
|
return {
|
|
291
358
|
ready: false,
|
|
292
|
-
booted:
|
|
359
|
+
booted: false,
|
|
360
|
+
bootRequested: boot.ok || alreadyBooted,
|
|
293
361
|
udid,
|
|
294
362
|
alreadyBooted,
|
|
295
363
|
ownership: alreadyBooted ? 'reused' : 'started_by_work',
|
|
@@ -316,7 +384,7 @@ export function iosSimulatorShutdown(input: { udid: string; timeoutMs?: number }
|
|
|
316
384
|
const udid = String(input.udid ?? '').trim();
|
|
317
385
|
if (!udid) throw new Error('IOS_SIMULATOR_UDID_REQUIRED');
|
|
318
386
|
const result = runCommand('xcrun', ['simctl', 'shutdown', udid], { timeoutMs: input.timeoutMs ?? 60_000 });
|
|
319
|
-
const alreadyShutdown = /current state
|
|
387
|
+
const alreadyShutdown = /current state:\s*Shutdown|already shutdown/i.test(result.stderr + result.stdout);
|
|
320
388
|
return {
|
|
321
389
|
ready: result.ok || alreadyShutdown,
|
|
322
390
|
shutdown: result.ok || alreadyShutdown,
|
|
@@ -327,7 +395,20 @@ export function iosSimulatorShutdown(input: { udid: string; timeoutMs?: number }
|
|
|
327
395
|
};
|
|
328
396
|
}
|
|
329
397
|
|
|
330
|
-
export function iosAppBuild(repository: RepositoryRecord, input: {
|
|
398
|
+
export function iosAppBuild(repository: RepositoryRecord, input: {
|
|
399
|
+
scheme?: string;
|
|
400
|
+
udid?: string;
|
|
401
|
+
workspace?: string;
|
|
402
|
+
project?: string;
|
|
403
|
+
configuration?: string;
|
|
404
|
+
timeoutMs?: number;
|
|
405
|
+
simulatorName?: string;
|
|
406
|
+
destinationPlatform?: 'ios_simulator' | 'ios';
|
|
407
|
+
provisioning?: {
|
|
408
|
+
allowUpdates: boolean;
|
|
409
|
+
authentication?: { privateKeyPath: string; keyId: string; issuerId: string };
|
|
410
|
+
};
|
|
411
|
+
}) {
|
|
331
412
|
const unsupported = assertDarwin();
|
|
332
413
|
if (unsupported) return unsupported;
|
|
333
414
|
const listed = iosSchemesList(repository, { workspace: input.workspace, project: input.project });
|
|
@@ -337,34 +418,136 @@ export function iosAppBuild(repository: RepositoryRecord, input: { scheme?: stri
|
|
|
337
418
|
if (!scheme) throw new Error('IOS_SCHEME_REQUIRED: provide scheme or share at least one Xcode scheme');
|
|
338
419
|
const workspace = safeRepoRelativePath(repository.canonicalRoot, input.workspace ?? listedReady.workspace);
|
|
339
420
|
const project = safeRepoRelativePath(repository.canonicalRoot, input.project ?? listedReady.project);
|
|
340
|
-
const
|
|
341
|
-
const
|
|
421
|
+
const configuration = input.configuration ?? 'Debug';
|
|
422
|
+
const destinationPlatform = input.destinationPlatform ?? 'ios_simulator';
|
|
423
|
+
const udid = String(input.udid ?? '').trim();
|
|
424
|
+
if (destinationPlatform === 'ios' && !udid) throw new Error('IOS_PHYSICAL_DEVICE_UDID_REQUIRED');
|
|
425
|
+
const destination = destinationPlatform === 'ios'
|
|
426
|
+
? `platform=iOS,id=${udid}`
|
|
427
|
+
: udid
|
|
428
|
+
? `platform=iOS Simulator,id=${udid}`
|
|
429
|
+
: `platform=iOS Simulator,name=${input.simulatorName ?? 'iPhone 16 Pro'}`;
|
|
430
|
+
const derivedDataPath = stableBuildDerivedDataPath(repository, { workspace, project, scheme, configuration, destination });
|
|
431
|
+
const derivedDataReused = existsSync(derivedDataPath);
|
|
342
432
|
mkdirSync(derivedDataPath, { recursive: true });
|
|
433
|
+
const priorBuiltApps = derivedDataReused ? findBuiltApps(repository, derivedDataPath) : [];
|
|
343
434
|
const args = ['build'];
|
|
344
435
|
if (workspace) args.push('-workspace', workspace);
|
|
345
436
|
else if (project) args.push('-project', project);
|
|
346
|
-
args.push('-scheme', scheme, '-configuration',
|
|
347
|
-
if (input.udid) args.push('-destination', `platform=iOS Simulator,id=${String(input.udid).trim()}`);
|
|
348
|
-
else args.push('-destination', 'platform=iOS Simulator,name=' + (input.simulatorName ?? 'iPhone 16 Pro'));
|
|
437
|
+
args.push('-scheme', scheme, '-configuration', configuration, '-destination', destination);
|
|
349
438
|
args.push('-derivedDataPath', derivedDataPath);
|
|
439
|
+
const authentication = input.provisioning?.authentication;
|
|
440
|
+
if (input.provisioning?.allowUpdates) args.push('-allowProvisioningUpdates');
|
|
441
|
+
if (authentication) {
|
|
442
|
+
args.push(
|
|
443
|
+
'-authenticationKeyPath', authentication.privateKeyPath,
|
|
444
|
+
'-authenticationKeyID', authentication.keyId,
|
|
445
|
+
'-authenticationKeyIssuerID', authentication.issuerId,
|
|
446
|
+
);
|
|
447
|
+
}
|
|
350
448
|
const result = runCommand('xcodebuild', args, { cwd: repository.canonicalRoot, timeoutMs: input.timeoutMs ?? 10 * 60_000 });
|
|
449
|
+
const sensitiveValues = authentication
|
|
450
|
+
? [authentication.privateKeyPath, authentication.keyId, authentication.issuerId]
|
|
451
|
+
: [];
|
|
452
|
+
const redactSensitiveText = (value: string): string => sensitiveValues.reduce(
|
|
453
|
+
(current, secret) => secret ? current.split(secret).join('[REDACTED]') : current,
|
|
454
|
+
value,
|
|
455
|
+
);
|
|
456
|
+
const redactBoundedText = (value: string) => {
|
|
457
|
+
const bounded = boundedText(value);
|
|
458
|
+
return { ...bounded, content: redactSensitiveText(bounded.content) };
|
|
459
|
+
};
|
|
460
|
+
const sensitiveFlags = new Set(['-authenticationKeyPath', '-authenticationKeyID', '-authenticationKeyIssuerID']);
|
|
461
|
+
const safeCommand = result.command.map((entry, index, command) => (
|
|
462
|
+
index > 0 && sensitiveFlags.has(command[index - 1] ?? '') ? '[REDACTED]' : entry
|
|
463
|
+
));
|
|
464
|
+
const safeStdout = redactBoundedText(result.stdout);
|
|
465
|
+
const safeStderr = redactBoundedText(result.stderr);
|
|
351
466
|
const builtApps = findBuiltApps(repository, derivedDataPath);
|
|
352
|
-
const
|
|
467
|
+
const priorSet = new Set(priorBuiltApps);
|
|
468
|
+
const newBuiltApps = builtApps.filter((candidate) => !priorSet.has(candidate));
|
|
469
|
+
const selected = selectBuiltAppProduct(scheme, newBuiltApps.length > 0 ? newBuiltApps : builtApps);
|
|
353
470
|
const productError = result.ok ? selected.error : undefined;
|
|
354
471
|
return {
|
|
355
472
|
ready: result.ok && Boolean(selected.appPath),
|
|
356
473
|
ok: result.ok && Boolean(selected.appPath),
|
|
357
474
|
scheme,
|
|
358
|
-
configuration
|
|
475
|
+
configuration,
|
|
476
|
+
destination,
|
|
359
477
|
derivedDataPath: relative(repository.canonicalRoot, derivedDataPath),
|
|
478
|
+
derivedDataReused,
|
|
360
479
|
appPath: selected.appPath,
|
|
361
480
|
builtApps,
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
481
|
+
newBuiltApps,
|
|
482
|
+
command: safeCommand,
|
|
483
|
+
stdout: safeStdout,
|
|
484
|
+
stderr: safeStderr,
|
|
365
485
|
error: result.ok
|
|
366
486
|
? productError
|
|
367
|
-
: { code: 'IOS_BUILD_FAILED', message: (result.stderr || result.stdout).slice(0, 2000) },
|
|
487
|
+
: { code: 'IOS_BUILD_FAILED', message: redactSensitiveText(result.stderr || result.stdout).slice(0, 2000) },
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export function iosXcodeTest(repository: RepositoryRecord, input: {
|
|
492
|
+
scheme?: string;
|
|
493
|
+
udid?: string;
|
|
494
|
+
simulatorName?: string;
|
|
495
|
+
workspace?: string;
|
|
496
|
+
project?: string;
|
|
497
|
+
configuration?: string;
|
|
498
|
+
onlyTesting?: string[];
|
|
499
|
+
parallelTesting?: boolean;
|
|
500
|
+
maxParallelTestingWorkers?: number;
|
|
501
|
+
timeoutMs?: number;
|
|
502
|
+
}) {
|
|
503
|
+
const unsupported = assertDarwin();
|
|
504
|
+
if (unsupported) return unsupported;
|
|
505
|
+
const listed = iosSchemesList(repository, { workspace: input.workspace, project: input.project });
|
|
506
|
+
if (!listed.ready) return listed;
|
|
507
|
+
const listedReady = listed as { ready: true; workspace?: string; project?: string; schemes: string[] };
|
|
508
|
+
const scheme = String(input.scheme ?? listedReady.schemes?.[0] ?? '').trim();
|
|
509
|
+
if (!scheme) throw new Error('IOS_SCHEME_REQUIRED: provide scheme or share at least one Xcode scheme');
|
|
510
|
+
const workspace = safeRepoRelativePath(repository.canonicalRoot, input.workspace ?? listedReady.workspace);
|
|
511
|
+
const project = safeRepoRelativePath(repository.canonicalRoot, input.project ?? listedReady.project);
|
|
512
|
+
const derivedDataPath = join(safeArtifactDir(repository, 'DerivedData'), `tests-${sanitize(scheme)}`);
|
|
513
|
+
const resultBundlePath = join(safeArtifactDir(repository, 'test-results'), `${timestamp()}-${sanitize(scheme)}.xcresult`);
|
|
514
|
+
mkdirSync(derivedDataPath, { recursive: true });
|
|
515
|
+
const args = ['test'];
|
|
516
|
+
if (workspace) args.push('-workspace', workspace);
|
|
517
|
+
else if (project) args.push('-project', project);
|
|
518
|
+
args.push('-scheme', scheme, '-configuration', input.configuration ?? 'Debug');
|
|
519
|
+
if (input.udid) args.push('-destination', `platform=iOS Simulator,id=${String(input.udid).trim()}`);
|
|
520
|
+
else args.push('-destination', 'platform=iOS Simulator,name=' + (input.simulatorName ?? 'iPhone 16 Pro'));
|
|
521
|
+
const parallelTesting = input.parallelTesting === true;
|
|
522
|
+
const maxParallelTestingWorkers = parallelTesting
|
|
523
|
+
? Math.max(1, Math.min(8, Math.trunc(input.maxParallelTestingWorkers ?? 2)))
|
|
524
|
+
: 1;
|
|
525
|
+
args.push(
|
|
526
|
+
'-parallel-testing-enabled', parallelTesting ? 'YES' : 'NO',
|
|
527
|
+
'-maximum-parallel-testing-workers', String(maxParallelTestingWorkers),
|
|
528
|
+
);
|
|
529
|
+
for (const selector of (input.onlyTesting ?? []).slice(0, 50)) {
|
|
530
|
+
const normalized = String(selector).trim();
|
|
531
|
+
if (normalized) args.push(`-only-testing:${normalized}`);
|
|
532
|
+
}
|
|
533
|
+
args.push('-derivedDataPath', derivedDataPath, '-resultBundlePath', resultBundlePath);
|
|
534
|
+
const result = runCommand('xcodebuild', args, { cwd: repository.canonicalRoot, timeoutMs: input.timeoutMs ?? 15 * 60_000 });
|
|
535
|
+
return {
|
|
536
|
+
ready: result.ok,
|
|
537
|
+
ok: result.ok,
|
|
538
|
+
scheme,
|
|
539
|
+
configuration: input.configuration ?? 'Debug',
|
|
540
|
+
onlyTesting: (input.onlyTesting ?? []).map((entry) => String(entry).trim()).filter(Boolean).slice(0, 50),
|
|
541
|
+
parallelTesting,
|
|
542
|
+
maxParallelTestingWorkers,
|
|
543
|
+
derivedDataPath: artifactRelativePath(repository, derivedDataPath),
|
|
544
|
+
resultBundlePath: artifactRelativePath(repository, resultBundlePath),
|
|
545
|
+
command: result.command,
|
|
546
|
+
stdout: boundedText(result.stdout, 64 * 1024),
|
|
547
|
+
stderr: boundedText(result.stderr, 32 * 1024),
|
|
548
|
+
error: result.ok
|
|
549
|
+
? undefined
|
|
550
|
+
: { code: 'IOS_TEST_FAILED', message: (result.stderr || result.stdout).slice(0, 4000) },
|
|
368
551
|
};
|
|
369
552
|
}
|
|
370
553
|
|
|
@@ -398,7 +581,7 @@ function selectBuiltAppProduct(scheme: string, builtApps: string[]): {
|
|
|
398
581
|
return !/(?:tests?|uitests?|xctrunner|runner|testhost)\.app$/i.test(name);
|
|
399
582
|
});
|
|
400
583
|
if (productionCandidates.length === 0) {
|
|
401
|
-
return { error: { code: 'IOS_BUILD_PRODUCT_MISSING', message: 'Build succeeded but no application product was found in this
|
|
584
|
+
return { error: { code: 'IOS_BUILD_PRODUCT_MISSING', message: 'Build succeeded but no application product was found in this DerivedData cache bucket.' } };
|
|
402
585
|
}
|
|
403
586
|
const normalizedScheme = normalizedProductName(scheme);
|
|
404
587
|
const exact = productionCandidates.filter((candidate) => normalizedProductName(basename(candidate)) === normalizedScheme);
|
|
@@ -413,46 +596,96 @@ function selectBuiltAppProduct(scheme: string, builtApps: string[]): {
|
|
|
413
596
|
};
|
|
414
597
|
}
|
|
415
598
|
|
|
416
|
-
function
|
|
417
|
-
const appRoot =
|
|
599
|
+
function readBuiltAppPlistValue(repository: RepositoryRecord, appPath: string, key: string, artifactRoot?: string): string | undefined {
|
|
600
|
+
const appRoot = assertBoundedAppPath(repository, appPath, artifactRoot);
|
|
418
601
|
const plist = join(appRoot, 'Info.plist');
|
|
419
602
|
if (!existsSync(plist)) return undefined;
|
|
420
|
-
const plutil = runCommand('plutil', ['-extract',
|
|
603
|
+
const plutil = runCommand('plutil', ['-extract', key, 'raw', '-o', '-', plist]);
|
|
421
604
|
return plutil.ok && plutil.stdout.trim() ? plutil.stdout.trim() : undefined;
|
|
422
605
|
}
|
|
423
606
|
|
|
607
|
+
function readBuiltAppBundleId(repository: RepositoryRecord, appPath: string, artifactRoot?: string): string | undefined {
|
|
608
|
+
return readBuiltAppPlistValue(repository, appPath, 'CFBundleIdentifier', artifactRoot);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function readBuiltAppExecutable(repository: RepositoryRecord, appPath: string, artifactRoot?: string): string | undefined {
|
|
612
|
+
return readBuiltAppPlistValue(repository, appPath, 'CFBundleExecutable', artifactRoot);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function compareSimulatorCandidates(
|
|
616
|
+
left: { runtime: string; name: string; state: string; udid: string },
|
|
617
|
+
right: { runtime: string; name: string; state: string; udid: string },
|
|
618
|
+
): number {
|
|
619
|
+
const leftBooted = left.state === 'Booted' ? 1 : 0;
|
|
620
|
+
const rightBooted = right.state === 'Booted' ? 1 : 0;
|
|
621
|
+
if (leftBooted !== rightBooted) return rightBooted - leftBooted;
|
|
622
|
+
const runtimeOrder = right.runtime.localeCompare(left.runtime, undefined, { numeric: true, sensitivity: 'base' });
|
|
623
|
+
if (runtimeOrder !== 0) return runtimeOrder;
|
|
624
|
+
return left.name.localeCompare(right.name, undefined, { numeric: true, sensitivity: 'base' });
|
|
625
|
+
}
|
|
626
|
+
|
|
424
627
|
function chooseSimulatorUdid(input: { udid?: string; simulatorName?: string }): string {
|
|
425
628
|
const requested = String(input.udid ?? '').trim();
|
|
426
629
|
if (requested) return requested;
|
|
427
|
-
const
|
|
428
|
-
if (
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
630
|
+
const inventory = iosSimulatorsList();
|
|
631
|
+
if (!inventory.ready) throw new Error('IOS_SIMULATOR_UNAVAILABLE: simulator inventory could not be read');
|
|
632
|
+
const devices = (inventory as { ready: true; devices: Array<{ runtime: string; name: string; state: string; udid: string }> }).devices
|
|
633
|
+
.filter((device) => /SimRuntime\.iOS-/i.test(device.runtime));
|
|
634
|
+
const requestedName = String(input.simulatorName ?? '').trim();
|
|
635
|
+
const matches = requestedName
|
|
636
|
+
? devices.filter((device) => device.name.includes(requestedName)).sort(compareSimulatorCandidates)
|
|
637
|
+
: [];
|
|
638
|
+
if (matches[0]?.udid) return matches[0].udid;
|
|
639
|
+
if (!requestedName) {
|
|
640
|
+
const booted = devices.filter((device) => device.state === 'Booted').sort(compareSimulatorCandidates);
|
|
641
|
+
if (booted[0]?.udid) return booted[0].udid;
|
|
642
|
+
const preferred = devices.filter((device) => device.name.includes('iPhone 16 Pro')).sort(compareSimulatorCandidates);
|
|
643
|
+
if (preferred[0]?.udid) return preferred[0].udid;
|
|
436
644
|
}
|
|
645
|
+
const fallback = [...devices].sort(compareSimulatorCandidates);
|
|
646
|
+
if (fallback[0]?.udid) return fallback[0].udid;
|
|
437
647
|
throw new Error('IOS_SIMULATOR_UNAVAILABLE: no available simulator was found');
|
|
438
648
|
}
|
|
439
649
|
|
|
440
|
-
function
|
|
441
|
-
const
|
|
442
|
-
const
|
|
443
|
-
const rel = relative(
|
|
444
|
-
|
|
650
|
+
function pathIsWithin(root: string, target: string): boolean {
|
|
651
|
+
const canonicalRoot = existsSync(root) ? realpathSync(root) : resolve(root);
|
|
652
|
+
const canonicalTarget = existsSync(target) ? realpathSync(target) : resolve(target);
|
|
653
|
+
const rel = relative(canonicalRoot, canonicalTarget);
|
|
654
|
+
return Boolean(rel) && rel !== '..' && !rel.startsWith(`..${sep}`);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function assertBoundedAppPath(repository: RepositoryRecord, appPath: string, artifactRoot?: string): string {
|
|
658
|
+
const controllerPrefix = 'controller-artifacts/ios/';
|
|
659
|
+
const resolved = artifactRoot && appPath.startsWith(controllerPrefix)
|
|
660
|
+
? resolve(artifactRoot, appPath.slice(controllerPrefix.length))
|
|
661
|
+
: resolve(repository.canonicalRoot, appPath);
|
|
445
662
|
if (!resolved.endsWith('.app')) throw new Error('IOS_APP_PATH_INVALID: app_path must end with .app');
|
|
663
|
+
const derivedDataRoot = resolve(repository.canonicalRoot, '.forge', 'ios', 'DerivedData');
|
|
664
|
+
const controllerArtifactRoot = artifactRoot ? resolve(artifactRoot) : undefined;
|
|
665
|
+
if (!pathIsWithin(derivedDataRoot, resolved) && !(controllerArtifactRoot && pathIsWithin(controllerArtifactRoot, resolved))) {
|
|
666
|
+
throw new Error(
|
|
667
|
+
controllerArtifactRoot
|
|
668
|
+
? 'IOS_APP_PATH_NOT_BOUNDED: app_path must be under .forge/ios/DerivedData or the Controller iOS artifact root'
|
|
669
|
+
: 'IOS_APP_PATH_NOT_BOUNDED: app_path must be under .forge/ios/DerivedData',
|
|
670
|
+
);
|
|
671
|
+
}
|
|
446
672
|
return resolved;
|
|
447
673
|
}
|
|
448
674
|
|
|
449
|
-
export function iosAppInstall(repository: RepositoryRecord, input: { udid: string; appPath: string }) {
|
|
675
|
+
export function iosAppInstall(repository: RepositoryRecord, input: { udid: string; appPath: string; artifactRoot?: string }) {
|
|
450
676
|
const unsupported = assertDarwin();
|
|
451
677
|
if (unsupported) return unsupported;
|
|
452
678
|
const udid = String(input.udid ?? '').trim();
|
|
453
|
-
const appPath =
|
|
679
|
+
const appPath = assertBoundedAppPath(repository, String(input.appPath ?? ''), input.artifactRoot);
|
|
454
680
|
const result = runCommand('xcrun', ['simctl', 'install', udid, appPath], { timeoutMs: 120_000 });
|
|
455
|
-
return {
|
|
681
|
+
return {
|
|
682
|
+
ready: result.ok,
|
|
683
|
+
installed: result.ok,
|
|
684
|
+
udid,
|
|
685
|
+
appPath: artifactRelativePath(repository, appPath, input.artifactRoot),
|
|
686
|
+
absolutePath: appPath,
|
|
687
|
+
error: result.ok ? undefined : { code: 'IOS_INSTALL_FAILED', message: result.stderr || result.stdout },
|
|
688
|
+
};
|
|
456
689
|
}
|
|
457
690
|
|
|
458
691
|
export function iosAppLaunch(input: { udid: string; bundleId: string; arguments?: string[]; waitMs?: number }) {
|
|
@@ -465,14 +698,32 @@ export function iosAppLaunch(input: { udid: string; bundleId: string; arguments?
|
|
|
465
698
|
const result = runCommand('xcrun', ['simctl', 'launch', udid, bundleId, ...launchArgs], { timeoutMs: 60_000 });
|
|
466
699
|
const waitMs = Math.max(0, Math.min(Math.trunc(input.waitMs ?? 1_500), 15_000));
|
|
467
700
|
if (result.ok) sleep(waitMs);
|
|
701
|
+
const pidMatch = result.ok ? result.stdout.match(/:\s*(\d+)\s*$/m) : undefined;
|
|
702
|
+
const pid = pidMatch?.[1] ? Number(pidMatch[1]) : undefined;
|
|
703
|
+
const processCheck = result.ok && Number.isFinite(pid)
|
|
704
|
+
? runCommand('xcrun', ['simctl', 'spawn', udid, 'launchctl', 'procinfo', String(pid)], { timeoutMs: 10_000 })
|
|
705
|
+
: undefined;
|
|
706
|
+
const processCheckText = processCheck ? `${processCheck.stdout}\n${processCheck.stderr}` : '';
|
|
707
|
+
const alive = processCheck
|
|
708
|
+
? processCheck.ok && !/No such process|could not resolve path/i.test(processCheckText)
|
|
709
|
+
: undefined;
|
|
710
|
+
const ready = result.ok && alive !== false;
|
|
468
711
|
return {
|
|
469
|
-
ready
|
|
712
|
+
ready,
|
|
470
713
|
launched: result.ok,
|
|
714
|
+
alive,
|
|
715
|
+
pid,
|
|
471
716
|
udid,
|
|
472
717
|
bundleId,
|
|
473
718
|
waitMs,
|
|
474
719
|
stdout: result.stdout.trim(),
|
|
475
|
-
|
|
720
|
+
command: result.command,
|
|
721
|
+
processCheckCommand: processCheck?.command,
|
|
722
|
+
error: !result.ok
|
|
723
|
+
? { code: 'IOS_LAUNCH_FAILED', message: result.stderr || result.stdout }
|
|
724
|
+
: alive === false
|
|
725
|
+
? { code: 'IOS_LAUNCH_PROCESS_EXITED', message: processCheck?.stderr || processCheck?.stdout || `Process ${pid} exited during post-launch verification.` }
|
|
726
|
+
: undefined,
|
|
476
727
|
};
|
|
477
728
|
}
|
|
478
729
|
|
|
@@ -482,7 +733,7 @@ export function iosSimulatorScreenshot(repository: RepositoryRecord, input: { ud
|
|
|
482
733
|
const udid = String(input.udid ?? '').trim();
|
|
483
734
|
if (!udid) throw new Error('IOS_SIMULATOR_UDID_REQUIRED');
|
|
484
735
|
const dir = safeArtifactDir(repository, 'screenshots', input.artifactRoot);
|
|
485
|
-
const file = join(dir, `${
|
|
736
|
+
const file = join(dir, `${artifactStamp()}-${sanitize(input.label ?? udid)}.png`);
|
|
486
737
|
const result = runCommand('xcrun', ['simctl', 'io', udid, 'screenshot', file], { timeoutMs: 60_000 });
|
|
487
738
|
return {
|
|
488
739
|
ready: result.ok,
|
|
@@ -514,23 +765,27 @@ export function iosSimulatorLogTail(repository: RepositoryRecord, input: { udid:
|
|
|
514
765
|
['show', '--style', 'compact', '--last', last, '--predicate', predicate],
|
|
515
766
|
{ timeoutMs: 30_000 },
|
|
516
767
|
);
|
|
517
|
-
const
|
|
518
|
-
const text = boundedText(
|
|
768
|
+
const diagnosticResult = simulatorResult.ok ? simulatorResult : hostResult?.ok ? hostResult : simulatorResult;
|
|
769
|
+
const text = boundedText(diagnosticResult.stdout || diagnosticResult.stderr, input.maxBytes ?? 20_000);
|
|
519
770
|
const dir = safeArtifactDir(repository, 'logs', input.artifactRoot);
|
|
520
|
-
const file = join(dir, `${
|
|
771
|
+
const file = join(dir, `${artifactStamp()}-${sanitize(input.process ?? udid)}.log`);
|
|
521
772
|
writeFileSync(file, text.content, 'utf-8');
|
|
522
773
|
return {
|
|
523
|
-
ready:
|
|
774
|
+
ready: simulatorResult.ok,
|
|
524
775
|
path: artifactRelativePath(repository, file, input.artifactRoot),
|
|
525
776
|
absolutePath: file,
|
|
526
777
|
content: text.content,
|
|
527
778
|
truncated: text.truncated,
|
|
528
|
-
command:
|
|
779
|
+
command: simulatorResult.command,
|
|
529
780
|
attempts: [simulatorResult.command, ...(hostResult ? [hostResult.command] : [])],
|
|
530
|
-
source: hostResult?.ok ? '
|
|
531
|
-
|
|
781
|
+
source: simulatorResult.ok ? 'simulator_unified_log' : hostResult?.ok ? 'host_unified_log_diagnostic_fallback' : 'simulator_unified_log',
|
|
782
|
+
targetProcess: input.process,
|
|
783
|
+
error: simulatorResult.ok ? undefined : {
|
|
532
784
|
code: 'IOS_LOG_TAIL_FAILED',
|
|
533
|
-
message: [
|
|
785
|
+
message: [
|
|
786
|
+
simulatorResult.stderr || simulatorResult.stdout,
|
|
787
|
+
hostResult?.ok ? 'Host unified log fallback was captured for diagnostics only.' : hostResult?.stderr || hostResult?.stdout,
|
|
788
|
+
]
|
|
534
789
|
.filter(Boolean)
|
|
535
790
|
.join('\n--- fallback ---\n'),
|
|
536
791
|
},
|
|
@@ -860,7 +1115,7 @@ export function iosSmokeReview(repository: RepositoryRecord, input: IosSmokeRevi
|
|
|
860
1115
|
}));
|
|
861
1116
|
|
|
862
1117
|
// 5. install
|
|
863
|
-
const install = iosAppInstall(repository, { udid, appPath });
|
|
1118
|
+
const install = iosAppInstall(repository, { udid, appPath, artifactRoot: input.artifactRoot });
|
|
864
1119
|
if (!install.ready) {
|
|
865
1120
|
overallStatus = 'failed';
|
|
866
1121
|
blockedStage = 'install';
|
|
@@ -883,7 +1138,7 @@ export function iosSmokeReview(repository: RepositoryRecord, input: IosSmokeRevi
|
|
|
883
1138
|
}));
|
|
884
1139
|
|
|
885
1140
|
// 6. launch
|
|
886
|
-
const bundleId = String(input.bundleId ?? readBuiltAppBundleId(repository, appPath) ?? '').trim();
|
|
1141
|
+
const bundleId = String(input.bundleId ?? readBuiltAppBundleId(repository, appPath, input.artifactRoot) ?? '').trim();
|
|
887
1142
|
if (!bundleId) {
|
|
888
1143
|
overallStatus = 'failed';
|
|
889
1144
|
blockedStage = 'launch';
|
|
@@ -901,6 +1156,7 @@ export function iosSmokeReview(repository: RepositoryRecord, input: IosSmokeRevi
|
|
|
901
1156
|
simulatorOwnership, cleanupPolicy, simulatorCleanup,
|
|
902
1157
|
};
|
|
903
1158
|
}
|
|
1159
|
+
const appExecutable = readBuiltAppExecutable(repository, appPath, input.artifactRoot);
|
|
904
1160
|
const launch = iosAppLaunch({ udid, bundleId, waitMs: input.launchWaitMs });
|
|
905
1161
|
if (!launch.ready) {
|
|
906
1162
|
overallStatus = 'failed';
|
|
@@ -946,8 +1202,7 @@ export function iosSmokeReview(repository: RepositoryRecord, input: IosSmokeRevi
|
|
|
946
1202
|
'Ensure the simulator is booted and simctl io screenshot works for the selected udid.',
|
|
947
1203
|
{ command: screenshot.command, evidence: screenshot as unknown as Record<string, unknown> },
|
|
948
1204
|
));
|
|
949
|
-
|
|
950
|
-
// still attempt logs for diagnosis
|
|
1205
|
+
// Continue to logs for diagnosis; do not add a skipped logs stage before the real attempt.
|
|
951
1206
|
} else {
|
|
952
1207
|
stages.push(stagePassed('screenshot', `Captured screenshot ${screenshot.screenshot}.`, {
|
|
953
1208
|
command: screenshot.command,
|
|
@@ -959,7 +1214,7 @@ export function iosSmokeReview(repository: RepositoryRecord, input: IosSmokeRevi
|
|
|
959
1214
|
// 8. logs
|
|
960
1215
|
const logs = iosSimulatorLogTail(repository, {
|
|
961
1216
|
udid,
|
|
962
|
-
process:
|
|
1217
|
+
process: appExecutable,
|
|
963
1218
|
maxBytes: 12_000,
|
|
964
1219
|
artifactRoot: input.artifactRoot,
|
|
965
1220
|
}) as {
|
|
@@ -969,6 +1224,8 @@ export function iosSmokeReview(repository: RepositoryRecord, input: IosSmokeRevi
|
|
|
969
1224
|
truncated?: boolean;
|
|
970
1225
|
command?: string[];
|
|
971
1226
|
error?: { message?: string };
|
|
1227
|
+
source?: string;
|
|
1228
|
+
targetProcess?: string;
|
|
972
1229
|
};
|
|
973
1230
|
if (Array.isArray(logs.command)) commands.push(logs.command);
|
|
974
1231
|
if (logs.path) artifacts.push(logs.path);
|
|
@@ -981,12 +1238,12 @@ export function iosSmokeReview(repository: RepositoryRecord, input: IosSmokeRevi
|
|
|
981
1238
|
'logs',
|
|
982
1239
|
logs.error?.message ?? 'Log collection failed',
|
|
983
1240
|
'Simulator may still be booting; retry log_tail after a few seconds.',
|
|
984
|
-
{ command: logs.command, evidence: { path: logs.path, truncated: logs.truncated }, artifacts: logs.path ? [logs.path] : undefined },
|
|
1241
|
+
{ command: logs.command, evidence: { path: logs.path, truncated: logs.truncated, process: appExecutable, source: logs.source }, artifacts: logs.path ? [logs.path] : undefined },
|
|
985
1242
|
));
|
|
986
1243
|
} else {
|
|
987
1244
|
stages.push(stagePassed('logs', `Collected logs at ${logs.path}.`, {
|
|
988
1245
|
command: logs.command,
|
|
989
|
-
evidence: { path: logs.path, truncated: logs.truncated, preview: logs.content?.slice(0, 500) },
|
|
1246
|
+
evidence: { path: logs.path, truncated: logs.truncated, process: appExecutable, source: logs.source, preview: logs.content?.slice(0, 500) },
|
|
990
1247
|
artifacts: logs.path ? [logs.path] : undefined,
|
|
991
1248
|
}));
|
|
992
1249
|
}
|
|
@@ -1005,6 +1262,7 @@ export function iosSmokeReview(repository: RepositoryRecord, input: IosSmokeRevi
|
|
|
1005
1262
|
schemes: schemesList,
|
|
1006
1263
|
scheme: selectedScheme,
|
|
1007
1264
|
bundleId,
|
|
1265
|
+
appExecutable,
|
|
1008
1266
|
udid,
|
|
1009
1267
|
build: buildResult,
|
|
1010
1268
|
boot,
|