@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.
Files changed (396) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +26 -0
  3. package/CLAUDE.md +2 -0
  4. package/README.md +2 -2
  5. package/README.zh-CN.md +1 -1
  6. package/SKILL.md +2 -2
  7. package/THIRD_PARTY_NOTICES.md +1 -1
  8. package/agents/openai.yaml +1 -1
  9. package/assets/hooks/hook-input.sh +11 -8
  10. package/assets/hooks/lib/session-state.sh +10 -7
  11. package/assets/hooks/lib/workflow-state.sh +13 -177
  12. package/assets/hooks/post-tool-observer.sh +8 -2
  13. package/assets/hooks/prompt-guard.sh +5 -81
  14. package/assets/hooks/session-start-context.sh +56 -1
  15. package/assets/partials/05-workflow.partial.md +1 -1
  16. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  17. package/assets/plugin-registry.v1.json +1 -1
  18. package/assets/reference-configs/document-generation.md +1 -1
  19. package/assets/reference-configs/harness-overview.md +1 -1
  20. package/assets/reference-configs/sprint-contracts.md +3 -3
  21. package/assets/skill-version.json +2 -2
  22. package/assets/templates/contract.template.md +2 -7
  23. package/assets/templates/helpers/capture-plan.sh +2 -2
  24. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  25. package/assets/templates/helpers/contract-worktree.sh +1 -21
  26. package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
  27. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  28. package/assets/templates/helpers/new-plan.sh +1 -1
  29. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  30. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  31. package/assets/templates/helpers/verify-contract.sh +1 -111
  32. package/assets/templates/helpers/verify-sprint.sh +3 -180
  33. package/assets/templates/plan.template.md +1 -1
  34. package/assets/templates/review.template.md +9 -34
  35. package/assets/workflow-contract.v1.json +10 -19
  36. package/docs/README.md +10 -10
  37. package/docs/architecture/CURRENT.md +170 -0
  38. package/docs/architecture/EVOLUTION.md +42 -0
  39. package/docs/architecture/current/README.md +4 -156
  40. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  41. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  42. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  43. package/docs/architecture/history/README.md +7 -0
  44. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  45. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  46. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  47. package/docs/architecture/history.md +3 -34
  48. package/docs/architecture/index.md +15 -98
  49. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  50. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  51. package/docs/architecture/versions/1.6.md +38 -0
  52. package/docs/architecture/versions/1.7.md +41 -0
  53. package/docs/forge-chatgpt-browser-engine.md +2 -2
  54. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  55. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  56. package/docs/forge-github-issue-launcher.md +1 -1
  57. package/docs/forge-runtime-self-healing-loop.md +1 -1
  58. package/docs/operations/controller-browser-plugin.md +26 -6
  59. package/docs/operations/controller-performance-and-502.md +124 -0
  60. package/docs/operations/controller-reliability-runbook.md +1 -1
  61. package/docs/operations/features.md +2 -2
  62. package/docs/operations/github-repository.md +1 -1
  63. package/docs/operations/homebrew.md +2 -2
  64. package/docs/operations/mcp-tool-exposure.md +5 -3
  65. package/docs/wiki/Architecture.md +6 -6
  66. package/docs/wiki/Implementation.md +2 -2
  67. package/docs/wiki/Integrations.md +2 -2
  68. package/docs/wiki/Work-Lifecycle.md +1 -1
  69. package/package.json +29 -10
  70. package/scripts/activate-source-baseline.ts +17 -13
  71. package/scripts/benchmark-background-check-overlap.ts +228 -0
  72. package/scripts/benchmark-mcp-host.sh +117 -0
  73. package/scripts/benchmark-mcp-transport.ts +170 -0
  74. package/scripts/benchmark-quality-harness.ts +146 -0
  75. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  76. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  77. package/scripts/benchmark-typescript-navigation.ts +186 -0
  78. package/scripts/benchmark-work-efficiency.ts +334 -0
  79. package/scripts/capture-plan.sh +3 -3
  80. package/scripts/check-agent-tooling.sh +8 -35
  81. package/scripts/check-architecture-sync.sh +17 -86
  82. package/scripts/check-mcp-compatibility.ts +14 -0
  83. package/scripts/check-runtime-architecture.mjs +107 -64
  84. package/scripts/check-stable-baseline.ts +92 -0
  85. package/scripts/check-task-workflow.sh +4 -4
  86. package/scripts/contract-worktree.sh +1 -21
  87. package/scripts/ensure-task-workflow.sh +22 -55
  88. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  89. package/scripts/install-standalone-recovery.ts +24 -0
  90. package/scripts/lib/project-init-lib.sh +15 -44
  91. package/scripts/migrate-project-template.sh +23 -1179
  92. package/scripts/new-plan.sh +1 -1
  93. package/scripts/plan-to-todo.sh +11 -36
  94. package/scripts/run-governed-gate.ts +2 -1
  95. package/scripts/ship-worktrees.sh +2 -29
  96. package/scripts/smoke-ios-physical-device.ts +4 -2
  97. package/scripts/smoke-runtime-recovery.ts +1 -35
  98. package/scripts/smoke-schedule-engine.ts +1 -20
  99. package/scripts/verify-contract.sh +1 -111
  100. package/scripts/verify-forge-runtime.sh +0 -1
  101. package/scripts/verify-sprint.sh +3 -180
  102. package/scripts/workflow-contract.ts +1 -5
  103. package/src/cli/commands/init-hook.ts +3 -3
  104. package/src/cli/commands/mcp.ts +9 -5
  105. package/src/cli/commands/plugin.ts +1 -1
  106. package/src/cli/commands/recovery.ts +25 -3
  107. package/src/cli/commands/runtime.ts +55 -3
  108. package/src/cli/commands/security.ts +12 -1
  109. package/src/cli/commands/setup-profile.ts +31 -21
  110. package/src/cli/controller/check-runner.ts +106 -3
  111. package/src/cli/controller/composite-result.ts +1 -1
  112. package/src/cli/controller/context/focus.ts +51 -0
  113. package/src/cli/controller/context/instruction-context.ts +114 -0
  114. package/src/cli/controller/context/known-paths.ts +76 -0
  115. package/src/cli/controller/context/query-planning.ts +110 -0
  116. package/src/cli/controller/context/rule-resolution.ts +532 -0
  117. package/src/cli/controller/context/source-materializer.ts +278 -0
  118. package/src/cli/controller/context/structural.ts +16 -0
  119. package/src/cli/controller/context/types.ts +286 -0
  120. package/src/cli/controller/context-pack.ts +785 -469
  121. package/src/cli/controller/launch-agents.ts +77 -2
  122. package/src/cli/controller/operational-plan.ts +2 -2
  123. package/src/cli/controller/work-mode.ts +6 -4
  124. package/src/cli/editing/edit-session.ts +60 -7
  125. package/src/cli/github/github.ts +133 -0
  126. package/src/cli/installer/managed-entries.ts +15 -1
  127. package/src/cli/local-bridge/console-automations.ts +54 -78
  128. package/src/cli/local-bridge/console-view-models.ts +1 -35
  129. package/src/cli/local-bridge/facade-api.ts +0 -217
  130. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  131. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
  132. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  133. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
  134. package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
  135. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  136. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
  137. package/src/cli/local-bridge/frontend/src/styles.css +1 -1
  138. package/src/cli/local-bridge/frontend/src/types.ts +5 -8
  139. package/src/cli/local-bridge/job-store.ts +1 -490
  140. package/src/cli/local-bridge/server.ts +0 -513
  141. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  142. package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
  143. package/src/cli/mcp/auth.ts +5 -5
  144. package/src/cli/mcp/instructions.ts +3 -2
  145. package/src/cli/mcp/legacy-tool-service.ts +0 -189
  146. package/src/cli/mcp/multi-repository.ts +16 -5
  147. package/src/cli/mcp/oauth.ts +175 -26
  148. package/src/cli/mcp/repository-tools.ts +247 -107
  149. package/src/cli/mcp/server.ts +537 -111
  150. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  151. package/src/cli/mcp/toolset-selection.ts +1 -1
  152. package/src/cli/mcp/toolset.ts +42 -5
  153. package/src/cli/mcp/transports/http.ts +101 -14
  154. package/src/cli/mcp/transports/session-registry.ts +43 -5
  155. package/src/cli/mcp/types.ts +3 -3
  156. package/src/cli/repositories/command-classifier.ts +101 -19
  157. package/src/cli/repositories/command-executor.ts +55 -521
  158. package/src/cli/repositories/command-process.ts +108 -0
  159. package/src/cli/repositories/command-scope.ts +99 -41
  160. package/src/cli/repositories/controller-home.ts +7 -8
  161. package/src/cli/repositories/git-command-executor.ts +2 -0
  162. package/src/cli/repositories/locks.ts +53 -16
  163. package/src/cli/repositories/repository-snapshot.ts +121 -0
  164. package/src/cli/repositories/selected-path-actions.ts +17 -9
  165. package/src/cli/repositories/structured-git.ts +61 -3
  166. package/src/cli/repository/inspector.ts +553 -63
  167. package/src/cli/repository/session-cache.ts +58 -17
  168. package/src/cli/v81-entry.ts +1 -4
  169. package/src/effects/fs-transaction.ts +85 -17
  170. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  171. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  172. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  173. package/src/runtime/context/lsp-client.ts +283 -0
  174. package/src/runtime/context/semantic-navigation.ts +275 -0
  175. package/src/runtime/context/swift-navigation.ts +565 -0
  176. package/src/runtime/context/typescript-navigation.ts +226 -0
  177. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  178. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +47 -8
  179. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  180. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  181. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  182. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  183. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  184. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  185. package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
  186. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +143 -8
  187. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
  188. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +85 -8
  189. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  190. package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
  191. package/src/runtime/control-plane/facade/controller-session-store.ts +132 -1
  192. package/src/runtime/control-plane/facade/goal-workloop-access.ts +2 -0
  193. package/src/runtime/control-plane/facade/goal-workloop.ts +686 -129
  194. package/src/runtime/control-plane/facade/index.ts +1 -0
  195. package/src/runtime/control-plane/facade/plan-contract-store.ts +185 -20
  196. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  197. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  198. package/src/runtime/control-plane/facade/types.ts +22 -5
  199. package/src/runtime/control-plane/facade/work-contract-store.ts +175 -73
  200. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  201. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  202. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  203. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  204. package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
  205. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  206. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  207. package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
  208. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  209. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  210. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  211. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  212. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  213. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  214. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  215. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  216. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  217. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  218. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
  219. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +355 -116
  220. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
  221. package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
  222. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  223. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  224. package/src/runtime/control-plane/release-retention.ts +62 -2
  225. package/src/runtime/control-plane/routing/route-policy.ts +7 -16
  226. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  227. package/src/runtime/diagnostics/mcp-timing.ts +13 -0
  228. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  229. package/src/runtime/diagnostics/performance.ts +24 -1
  230. package/src/runtime/evidence/event-ledger.ts +1 -1
  231. package/src/runtime/execution/jobs/store.ts +58 -4
  232. package/src/runtime/execution/jobs/types.ts +1 -1
  233. package/src/runtime/execution/managed-workspace.ts +203 -1
  234. package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
  235. package/src/runtime/execution/process-runtime/check-result.ts +53 -0
  236. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  237. package/src/runtime/execution/process-runtime/command-facade.ts +345 -40
  238. package/src/runtime/execution/process-runtime/gc.ts +38 -6
  239. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
  240. package/src/runtime/execution/process-runtime/persisted-check.ts +47 -4
  241. package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
  242. package/src/runtime/execution/process-runtime/runtime.ts +43 -3
  243. package/src/runtime/execution/process-runtime/store.ts +1 -0
  244. package/src/runtime/execution/process-runtime/types.ts +2 -0
  245. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  246. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  247. package/src/runtime/execution/workers/executor.ts +0 -48
  248. package/src/runtime/gateway/mcp/execution-tools.ts +1192 -114
  249. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  250. package/src/runtime/gateway/mcp/router.ts +184 -82
  251. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
  252. package/src/runtime/gateway/mcp/runtime-tools.ts +2224 -1589
  253. package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
  254. package/src/runtime/plugins/browser-adapter.ts +2901 -241
  255. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  256. package/src/runtime/plugins/browser-handoff.ts +92 -2
  257. package/src/runtime/plugins/browser-macos-bridge.ts +582 -49
  258. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  259. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  260. package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
  261. package/src/runtime/plugins/browser-runtime.ts +183 -0
  262. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  263. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  264. package/src/runtime/plugins/desktop-operator-registration.ts +8 -2
  265. package/src/runtime/plugins/external-adapter.ts +324 -3
  266. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  267. package/src/runtime/plugins/first-party-registry.ts +2 -0
  268. package/src/runtime/plugins/github-adapter.ts +125 -0
  269. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  270. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  271. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  272. package/src/runtime/plugins/ios-adapter.ts +106 -16
  273. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  274. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  275. package/src/runtime/plugins/lightweight-action.ts +153 -0
  276. package/src/runtime/plugins/local-system-adapter.ts +123 -6
  277. package/src/runtime/plugins/macos-capability-broker.ts +55 -9
  278. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  279. package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
  280. package/src/runtime/plugins/resend-adapter.ts +1 -1
  281. package/src/runtime/plugins/store.ts +269 -29
  282. package/src/runtime/plugins/types.ts +35 -2
  283. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  284. package/src/runtime/projections/git-status-sampler.ts +16 -16
  285. package/src/runtime/projections/materialized-view.ts +28 -10
  286. package/src/runtime/recovery/maintenance-executor.ts +393 -28
  287. package/src/runtime/root/mcp-transport.ts +180 -10
  288. package/src/runtime/root/package-connector-service.ts +186 -8
  289. package/src/runtime/root/package-runtime-release.ts +143 -17
  290. package/src/runtime/root/package-runtime-service.ts +430 -22
  291. package/src/runtime/root/release-manifest.ts +7 -2
  292. package/src/runtime/root/release-materialize.ts +19 -0
  293. package/src/runtime/root/release-store.ts +48 -11
  294. package/src/runtime/root/runtime.ts +23 -5
  295. package/src/runtime/root/service.ts +100 -29
  296. package/src/runtime/root/types.ts +2 -0
  297. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  298. package/src/runtime/shared/trusted-executable.ts +62 -0
  299. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  300. package/src/runtime/standalone-recovery/core.ts +700 -92
  301. package/src/runtime/standalone-recovery/entry.ts +99 -23
  302. package/src/runtime/standalone-recovery/installer.ts +19 -3
  303. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  304. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  305. package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
  306. package/src/runtime/workflow/schedules/engine.ts +287 -151
  307. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  308. package/src/runtime/workflow/schedules/settlement.ts +48 -4
  309. package/src/runtime/workflow/schedules/store.ts +131 -10
  310. package/src/runtime/workflow/schedules/types.ts +2 -4
  311. package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
  312. package/src/testing/test-governance.ts +8 -40
  313. package/assets/reference-configs/heartbeat-triage.md +0 -57
  314. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  315. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  316. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  317. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  318. package/docs/architecture/current/architecture-invariants.md +0 -331
  319. package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
  320. package/docs/architecture/current/codegraph-context-provider.md +0 -238
  321. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  322. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  323. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
  324. package/docs/architecture/current/entity-model.md +0 -612
  325. package/docs/architecture/current/external-plugin-governance.md +0 -396
  326. package/docs/architecture/current/failure-recovery.md +0 -447
  327. package/docs/architecture/current/governance.md +0 -213
  328. package/docs/architecture/current/human-interaction-plane.md +0 -87
  329. package/docs/architecture/current/implementation-status.md +0 -128
  330. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  331. package/docs/architecture/current/legacy-governance.md +0 -58
  332. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  333. package/docs/architecture/current/migration-roadmap.md +0 -105
  334. package/docs/architecture/current/multi-repository-execution.md +0 -371
  335. package/docs/architecture/current/operations-runbook.md +0 -45
  336. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  337. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  338. package/docs/architecture/current/repository-access-modes.md +0 -102
  339. package/docs/architecture/current/route-policy.md +0 -15
  340. package/docs/architecture/current/route-session-concurrency.md +0 -85
  341. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  342. package/docs/architecture/current/runtime-directory-map.md +0 -49
  343. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  344. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  345. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  346. package/docs/architecture/current/system-overview.md +0 -169
  347. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  348. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  349. package/docs/architecture/current/thin-harness-v1.md +0 -321
  350. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  351. package/docs/forge-autonomous-goal-loop.md +0 -9
  352. package/docs/forge-provider-configuration.md +0 -7
  353. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
  354. package/docs/operations/gmail-assistant-routines.md +0 -186
  355. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  356. package/docs/operations/provider-configuration.md +0 -31
  357. package/scripts/harness-trace-grade.sh +0 -150
  358. package/scripts/heartbeat-triage.sh +0 -361
  359. package/scripts/maintenance-triage.sh +0 -167
  360. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  361. package/src/cli/repositories/goal-registry.ts +0 -239
  362. package/src/runtime/assistant/action-proposals.ts +0 -279
  363. package/src/runtime/assistant/intent.ts +0 -395
  364. package/src/runtime/assistant/model-provider.ts +0 -460
  365. package/src/runtime/assistant/openapi.ts +0 -223
  366. package/src/runtime/assistant/policy.ts +0 -89
  367. package/src/runtime/assistant/readiness.ts +0 -258
  368. package/src/runtime/assistant/routine-runtime.ts +0 -477
  369. package/src/runtime/assistant/schedule-binding.ts +0 -171
  370. package/src/runtime/assistant/standing-grants.ts +0 -375
  371. package/src/runtime/assistant/store.ts +0 -243
  372. package/src/runtime/assistant/types.ts +0 -135
  373. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  374. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  375. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  376. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  377. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  378. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  379. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  380. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  381. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  382. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  383. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  384. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  385. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  386. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  387. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  388. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  389. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  390. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  391. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  392. package/src/runtime/workflow/findings/store.ts +0 -204
  393. package/src/runtime/workflow/findings/types.ts +0 -58
  394. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  395. package/src/runtime/workflow/portfolio/store.ts +0 -185
  396. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -1,4 +1,5 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'fs';
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 = 3): string[] {
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 (entryName === 'node_modules' || entryName === '.git' || entryName === '.forge' || entryName === '.ai') continue;
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
- export function iosSchemesList(repository: RepositoryRecord, input: { workspace?: string; project?: string } = {}) {
302
+ function probeIosSchemesList(repository: RepositoryRecord, input: { workspace?: string; project?: string } = {}) {
257
303
  const unsupported = assertDarwin();
258
304
  if (unsupported) return unsupported;
259
- const discovered = iosProjectDiscover(repository);
260
- const workspace = safeRepoRelativePath(repository.canonicalRoot, input.workspace ?? discovered.workspace);
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 = /Unable to boot|current state: Booted|already booted/i.test(boot.stderr + boot.stdout);
344
+ const alreadyBooted = /current state:\s*Booted|already booted/i.test(boot.stderr + boot.stdout);
285
345
  if (!boot.ok && !alreadyBooted) {
286
- return { ready: false, booted: false, error: { code: 'SIMULATOR_BOOT_FAILED', message: boot.stderr || boot.stdout }, command: boot.command };
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: true,
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: Shutdown|already shutdown|Unable to shutdown/i.test(result.stderr + result.stdout);
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: { scheme?: string; udid?: string; workspace?: string; project?: string; configuration?: string; timeoutMs?: number; simulatorName?: string }) {
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 derivedDataRoot = safeArtifactDir(repository, 'DerivedData');
341
- const derivedDataPath = join(derivedDataRoot, `${timestamp()}-${sanitize(scheme)}`);
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', input.configuration ?? 'Debug');
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 selected = selectBuiltAppProduct(scheme, builtApps);
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: input.configuration ?? 'Debug',
475
+ configuration,
476
+ destination,
359
477
  derivedDataPath: relative(repository.canonicalRoot, derivedDataPath),
478
+ derivedDataReused,
360
479
  appPath: selected.appPath,
361
480
  builtApps,
362
- command: result.command,
363
- stdout: boundedText(result.stdout),
364
- stderr: boundedText(result.stderr),
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 build-specific DerivedData directory.' } };
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 readBuiltAppBundleId(repository: RepositoryRecord, appPath: string): string | undefined {
417
- const appRoot = assertRepoBoundedAppPath(repository, appPath);
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', 'CFBundleIdentifier', 'raw', '-o', '-', plist]);
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 preferred = iosSimulatorsList({ name: input.simulatorName ?? 'iPhone 16 Pro' });
428
- if (preferred.ready) {
429
- const preferredReady = preferred as { ready: true; devices: Array<{ udid: string }> };
430
- if (preferredReady.devices[0]?.udid) return preferredReady.devices[0].udid;
431
- }
432
- const fallback = iosSimulatorsList();
433
- if (fallback.ready) {
434
- const fallbackReady = fallback as { ready: true; devices: Array<{ udid: string }> };
435
- if (fallbackReady.devices[0]?.udid) return fallbackReady.devices[0].udid;
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 assertRepoBoundedAppPath(repository: RepositoryRecord, appPath: string): string {
441
- const resolved = resolve(repository.canonicalRoot, appPath);
442
- const allowedRoot = resolve(repository.canonicalRoot, '.forge', 'ios', 'DerivedData');
443
- const rel = relative(allowedRoot, resolved);
444
- if (rel === '' || rel === '..' || rel.startsWith(`..${sep}`)) throw new Error('IOS_APP_PATH_NOT_BOUNDED: app_path must be under .forge/ios/DerivedData');
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 = assertRepoBoundedAppPath(repository, String(input.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 { ready: result.ok, installed: result.ok, udid, appPath: relative(repository.canonicalRoot, appPath), error: result.ok ? undefined : { code: 'IOS_INSTALL_FAILED', message: result.stderr || result.stdout } };
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: result.ok,
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
- error: result.ok ? undefined : { code: 'IOS_LAUNCH_FAILED', message: result.stderr || result.stdout },
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, `${timestamp()}-${sanitize(input.label ?? udid)}.png`);
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 result = hostResult?.ok ? hostResult : simulatorResult;
518
- const text = boundedText(result.stdout || result.stderr, input.maxBytes ?? 20_000);
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, `${timestamp()}-${sanitize(input.process ?? udid)}.log`);
771
+ const file = join(dir, `${artifactStamp()}-${sanitize(input.process ?? udid)}.log`);
521
772
  writeFileSync(file, text.content, 'utf-8');
522
773
  return {
523
- ready: result.ok,
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: result.command,
779
+ command: simulatorResult.command,
529
780
  attempts: [simulatorResult.command, ...(hostResult ? [hostResult.command] : [])],
530
- source: hostResult?.ok ? 'host_unified_log_fallback' : 'simulator_unified_log',
531
- error: result.ok ? undefined : {
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: [simulatorResult.stderr || simulatorResult.stdout, hostResult?.stderr || hostResult?.stdout]
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
- failRemaining(7, 'Skipped because screenshot failed.');
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: selectedScheme,
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,