@moretea-labs/forge 1.5.0-rc.1 → 1.5.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 (259) hide show
  1. package/AGENTS.md +9 -11
  2. package/CHANGELOG.md +19 -0
  3. package/CLAUDE.md +8 -10
  4. package/DESIGN.md +278 -0
  5. package/README.md +70 -90
  6. package/README.zh-CN.md +50 -55
  7. package/SKILL.md +8 -11
  8. package/THIRD_PARTY_NOTICES.md +9 -3
  9. package/assets/branding/README.md +4 -2
  10. package/assets/hooks/lib/workflow-state.sh +19 -18
  11. package/assets/hooks/post-edit-guard.sh +6 -75
  12. package/assets/hooks/prompt-guard.sh +24 -12
  13. package/assets/hooks/session-start-context.sh +17 -17
  14. package/assets/hooks/stop-orchestrator.sh +2 -2
  15. package/assets/partials/04-project-structure.partial.md +6 -6
  16. package/assets/partials/05-workflow.partial.md +6 -6
  17. package/assets/partials/07-footer.partial.md +1 -1
  18. package/assets/partials/08-orchestration.partial.md +2 -2
  19. package/assets/partials-agents/02-operating-mode.partial.md +5 -5
  20. package/assets/partials-agents/03-orchestration.partial.md +3 -3
  21. package/assets/partials-agents/04-task-protocol.partial.md +3 -3
  22. package/assets/partials-agents/08-deep-docs.partial.md +1 -1
  23. package/assets/reference-configs/agentic-development-flow.md +49 -86
  24. package/assets/reference-configs/external-tooling.md +21 -61
  25. package/assets/reference-configs/handoff-protocol.md +25 -20
  26. package/assets/reference-configs/harness-overview.md +8 -8
  27. package/assets/templates/helpers/archive-workflow.sh +0 -1
  28. package/assets/templates/helpers/capture-plan.sh +3 -3
  29. package/assets/templates/helpers/check-task-workflow.sh +2 -2
  30. package/assets/templates/helpers/codex-handoff-resume.sh +5 -5
  31. package/assets/templates/helpers/ensure-task-workflow.sh +37 -35
  32. package/assets/templates/helpers/new-plan.sh +1 -1
  33. package/assets/templates/helpers/plan-to-todo.sh +2 -2
  34. package/assets/templates/helpers/prepare-codex-handoff.sh +2 -2
  35. package/assets/templates/helpers/prepare-handoff.sh +9 -9
  36. package/assets/templates/helpers/refresh-current-status.sh +2 -2
  37. package/assets/templates/helpers/ship-worktrees.sh +1 -1
  38. package/assets/templates/helpers/sprint-backlog.sh +1 -1
  39. package/assets/templates/helpers/verify-sprint.sh +7 -7
  40. package/assets/templates/helpers/workstream-sync.sh +1 -1
  41. package/assets/templates/plan.template.md +1 -1
  42. package/assets/workflow-contract.v1.json +6 -3
  43. package/docs/architecture/current/README.md +10 -9
  44. package/docs/architecture/current/approved-target-architecture.zh-CN.md +2 -2
  45. package/docs/architecture/current/architecture-invariants.md +2 -2
  46. package/docs/architecture/current/automation-and-schedule-engine.md +15 -15
  47. package/docs/architecture/current/codegraph-context-provider.md +2 -2
  48. package/docs/architecture/current/control-plane-authority-inventory.md +12 -0
  49. package/docs/architecture/current/dispatch-and-agent-strategy.md +11 -11
  50. package/docs/architecture/current/entity-model.md +0 -27
  51. package/docs/architecture/current/failure-recovery.md +0 -24
  52. package/docs/architecture/current/governance.md +1 -1
  53. package/docs/architecture/current/implementation-status.md +1 -1
  54. package/docs/architecture/current/legacy-governance.md +58 -0
  55. package/docs/architecture/current/multi-repository-execution.md +1 -1
  56. package/docs/architecture/current/peer-supercontroller-runtime.md +1 -1
  57. package/docs/architecture/current/personal-assistant-plugin-baseline.md +4 -5
  58. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -10
  59. package/docs/architecture/current/target-requirements-traceability.md +2 -2
  60. package/docs/architecture/current/thin-harness-governance-policy.md +1 -1
  61. package/docs/architecture/current/thin-harness-v1.md +9 -11
  62. package/docs/architecture/domains/runtime-harness.md +1 -1
  63. package/docs/forge-autonomous-goal-loop.md +3 -1
  64. package/docs/forge-chatgpt-mcp-setup.md +51 -184
  65. package/docs/images/forge-banner-cn.svg +26 -1
  66. package/docs/images/forge-banner.svg +26 -1
  67. package/docs/operations/features.md +1 -1
  68. package/docs/operations/homebrew.md +3 -3
  69. package/docs/operations/mcp-tool-exposure.md +5 -5
  70. package/docs/operations/platform-support.md +45 -37
  71. package/docs/operations/platform-support.zh-CN.md +40 -38
  72. package/docs/operations/releasing.md +3 -3
  73. package/docs/operations/releasing.zh-CN.md +3 -3
  74. package/docs/operations/troubleshooting.md +11 -6
  75. package/docs/operations/troubleshooting.zh-CN.md +11 -6
  76. package/docs/public-usage-guide.md +28 -21
  77. package/docs/public-usage-guide.zh-CN.md +28 -21
  78. package/docs/tutorials/01-install-and-start.md +56 -35
  79. package/docs/tutorials/01-install-and-start.zh-CN.md +56 -35
  80. package/docs/tutorials/02-connect-chatgpt.md +112 -46
  81. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +113 -45
  82. package/docs/wiki/Architecture.md +1 -1
  83. package/docs/wiki/Installation.md +2 -2
  84. package/docs/wiki/Quick-Start.md +1 -1
  85. package/docs/wiki/Work-Lifecycle.md +1 -1
  86. package/install.ps1 +4 -5
  87. package/install.sh +4 -6
  88. package/package.json +15 -5
  89. package/scripts/archive-workflow.sh +0 -1
  90. package/scripts/benchmark-thin-harness-mcp-e2e.ts +27 -14
  91. package/scripts/build-controller-ui.ts +28 -0
  92. package/scripts/capture-plan.sh +4 -4
  93. package/scripts/check-agent-tooling.sh +12 -1
  94. package/scripts/check-platform-support.mjs +2 -2
  95. package/scripts/check-public-docs.sh +16 -5
  96. package/scripts/check-release-readiness.sh +5 -11
  97. package/scripts/check-runtime-architecture.mjs +14 -22
  98. package/scripts/check-tarball-install-smoke.sh +3 -2
  99. package/scripts/check-task-workflow.sh +2 -2
  100. package/scripts/codex-handoff-resume.sh +5 -5
  101. package/scripts/create-project-dirs.sh +3 -3
  102. package/scripts/ensure-task-workflow.sh +45 -35
  103. package/scripts/init-project.sh +4 -4
  104. package/scripts/install-desktop-operator-registration.ts +2 -2
  105. package/scripts/lib/project-init-lib.sh +46 -44
  106. package/scripts/migrate-project-template.sh +7 -4
  107. package/scripts/new-plan.sh +1 -1
  108. package/scripts/plan-to-todo.sh +2 -2
  109. package/scripts/prepare-codex-handoff.sh +2 -2
  110. package/scripts/prepare-handoff.sh +9 -9
  111. package/scripts/refresh-current-status.sh +2 -2
  112. package/scripts/setup-plugins.sh +3 -3
  113. package/scripts/ship-worktrees.sh +1 -1
  114. package/scripts/sprint-backlog.sh +5 -5
  115. package/scripts/sync-codex-installed-copies.sh +2 -3
  116. package/scripts/verify-sprint.sh +7 -7
  117. package/scripts/workstream-sync.sh +1 -1
  118. package/src/cli/chatgpt-browser/bridge-extension.ts +1 -1
  119. package/src/cli/chatgpt-browser/bridge-provider.ts +29 -0
  120. package/src/cli/chatgpt-browser/file-policy.ts +3 -1
  121. package/src/cli/commands/chatgpt.ts +51 -0
  122. package/src/cli/commands/doctor.ts +1 -1
  123. package/src/cli/commands/init-hook.ts +211 -70
  124. package/src/cli/commands/init.ts +5 -6
  125. package/src/cli/commands/mcp.ts +29 -5
  126. package/src/cli/commands/runtime.ts +60 -8
  127. package/src/cli/commands/setup-profile.ts +441 -0
  128. package/src/cli/controller/check-runner.ts +20 -3
  129. package/src/cli/controller/codex-continuation.ts +4 -7
  130. package/src/cli/controller/context-pack.ts +118 -14
  131. package/src/cli/controller/operational-plan.ts +4 -4
  132. package/src/cli/controller/task-ledger.ts +10 -10
  133. package/src/cli/controller/work-mode.ts +6 -13
  134. package/src/cli/index.ts +14 -35
  135. package/src/cli/local-bridge/connector-freshness.ts +1 -1
  136. package/src/cli/local-bridge/console-automations.ts +160 -0
  137. package/src/cli/local-bridge/dashboard.ts +8 -1303
  138. package/src/cli/local-bridge/facade-api.ts +16 -7
  139. package/src/cli/local-bridge/frontend/src/App.tsx +10 -0
  140. package/src/cli/local-bridge/frontend/src/components/Button.tsx +1 -0
  141. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +3 -0
  142. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +1 -0
  143. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +1 -0
  144. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +1 -0
  145. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +3 -0
  146. package/src/cli/local-bridge/frontend/src/design/icons.tsx +11 -0
  147. package/src/cli/local-bridge/frontend/src/design/tokens.css +8 -0
  148. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +3 -0
  149. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +4 -0
  150. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +3 -0
  151. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +2 -0
  152. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +2 -0
  153. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +2 -0
  154. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +3 -0
  155. package/src/cli/local-bridge/frontend/src/lib/api.ts +23 -0
  156. package/src/cli/local-bridge/frontend/src/lib/format.ts +4 -0
  157. package/src/cli/local-bridge/frontend/src/lib/routes.ts +7 -0
  158. package/src/cli/local-bridge/frontend/src/main.tsx +1 -0
  159. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +1 -0
  160. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +2 -0
  161. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +3 -0
  162. package/src/cli/local-bridge/frontend/src/styles.css +12 -0
  163. package/src/cli/local-bridge/frontend/src/types.ts +14 -0
  164. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +1 -0
  165. package/src/cli/local-bridge/frontend/vite.config.ts +27 -0
  166. package/src/cli/local-bridge/server.ts +44 -0
  167. package/src/cli/local-bridge/ui-dist/app.css +1 -0
  168. package/src/cli/local-bridge/ui-dist/app.js +11094 -0
  169. package/src/cli/mcp/instructions.ts +3 -3
  170. package/src/cli/mcp/legacy-tool-service.ts +27 -23
  171. package/src/cli/mcp/multi-repository.ts +2 -0
  172. package/src/cli/mcp/policy.ts +7 -5
  173. package/src/cli/mcp/repository-tools.ts +53 -14
  174. package/src/cli/mcp/server.ts +5 -10
  175. package/src/cli/mcp/setup.ts +211 -275
  176. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  177. package/src/cli/mcp/toolset-names.ts +1 -1
  178. package/src/cli/mcp/toolset.ts +0 -1
  179. package/src/cli/mcp/transports/http.ts +24 -4
  180. package/src/cli/repositories/branch-name-policy.ts +0 -7
  181. package/src/cli/repositories/controller-home.ts +110 -3
  182. package/src/cli/repositories/safe-patch.ts +1 -1
  183. package/src/cli/repositories/selected-path-actions.ts +26 -24
  184. package/src/core/adoption/gitignore-plan.ts +2 -3
  185. package/src/core/adoption/plan.ts +4 -1
  186. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +556 -0
  187. package/src/runtime/control-plane/execution/work-handle-store.ts +128 -1
  188. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +277 -0
  189. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +169 -0
  190. package/src/runtime/control-plane/facade/capability-registry.ts +0 -10
  191. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +44 -25
  192. package/src/runtime/control-plane/facade/suggested-actions.ts +7 -1
  193. package/src/runtime/control-plane/facade/types.ts +0 -1
  194. package/src/runtime/control-plane/global-scheduler/scheduler.ts +30 -8
  195. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +114 -0
  196. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +175 -0
  197. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +161 -0
  198. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +64 -0
  199. package/src/runtime/control-plane/launcher/thin-launcher.ts +117 -24
  200. package/src/runtime/control-plane/routing/route-policy.ts +14 -14
  201. package/src/runtime/control-plane/runtime-cleanup.ts +0 -1
  202. package/src/runtime/evidence/event-ledger.ts +1 -1
  203. package/src/runtime/{workflow/campaigns/workspace.ts → execution/managed-workspace.ts} +41 -43
  204. package/src/runtime/execution/process-runtime/check-facade.ts +6 -0
  205. package/src/runtime/execution/process-runtime/check-result.ts +45 -0
  206. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +42 -16
  207. package/src/runtime/execution/process-runtime/command-facade.ts +4 -2
  208. package/src/runtime/execution/process-runtime/gc.ts +1 -1
  209. package/src/runtime/execution/process-runtime/index.ts +3 -0
  210. package/src/runtime/execution/process-runtime/interactive-admission.ts +14 -5
  211. package/src/runtime/execution/process-runtime/persisted-check.ts +285 -0
  212. package/src/runtime/execution/process-runtime/resource-claims.ts +30 -0
  213. package/src/runtime/execution/process-runtime/runtime.ts +18 -1
  214. package/src/runtime/execution/process-runtime/store.ts +1 -19
  215. package/src/runtime/execution/process-runtime/types.ts +4 -0
  216. package/src/runtime/execution/thin-harness/execution-router.ts +0 -1
  217. package/src/runtime/execution/thin-harness/fast-executor.ts +1 -1
  218. package/src/runtime/execution/thin-harness/lightweight-lanes.ts +1 -2
  219. package/src/runtime/execution/thin-harness/types.ts +0 -1
  220. package/src/runtime/execution/workers/executor.ts +1 -1
  221. package/src/runtime/gateway/mcp/execution-tools.ts +1 -1
  222. package/src/runtime/gateway/mcp/persisted-check-process.ts +3 -255
  223. package/src/runtime/gateway/mcp/process-tools.ts +16 -3
  224. package/src/runtime/gateway/mcp/resource-policy.ts +0 -9
  225. package/src/runtime/gateway/mcp/router.ts +117 -182
  226. package/src/runtime/gateway/mcp/runtime-tools.ts +331 -409
  227. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +1 -219
  228. package/src/runtime/plugins/browser-adapter.ts +5 -0
  229. package/src/runtime/plugins/desktop-operator-registration.ts +1 -1
  230. package/src/runtime/plugins/store.ts +14 -6
  231. package/src/runtime/plugins/types.ts +1 -1
  232. package/src/runtime/projections/materialized-view.ts +1 -23
  233. package/src/runtime/recovery/maintenance-executor.ts +9 -0
  234. package/src/runtime/resources/ownership.ts +0 -1
  235. package/src/runtime/root/gateway-adapter.ts +2 -0
  236. package/src/runtime/root/mcp-transport.ts +10 -2
  237. package/src/runtime/root/package-runtime-release.ts +161 -0
  238. package/src/runtime/root/package-runtime-service.ts +172 -0
  239. package/src/runtime/root/runtime.ts +2 -1
  240. package/src/runtime/root/scheduler.ts +5 -0
  241. package/src/runtime/root/service.ts +18 -5
  242. package/src/runtime/standalone-recovery/core.ts +65 -16
  243. package/src/runtime/workflow/findings/store.ts +145 -106
  244. package/src/runtime/workflow/findings/types.ts +22 -0
  245. package/src/runtime/workflow/schedules/engine.ts +132 -2
  246. package/src/runtime/workflow/schedules/pending-activation.ts +139 -0
  247. package/src/runtime/workflow/schedules/work-continuation.ts +218 -0
  248. package/docs/CHANGELOG.md +0 -978
  249. package/docs/README.zh-CN.md +0 -58
  250. package/scripts/smoke-chatgpt-supervised-automation.ts +0 -208
  251. package/src/cli/commands/controller.ts +0 -680
  252. package/src/runtime/workflow/campaigns/cleanup.ts +0 -263
  253. package/src/runtime/workflow/campaigns/engine.ts +0 -112
  254. package/src/runtime/workflow/campaigns/normalize.ts +0 -100
  255. package/src/runtime/workflow/campaigns/review.ts +0 -315
  256. package/src/runtime/workflow/campaigns/store.ts +0 -526
  257. package/src/runtime/workflow/campaigns/supervisor.ts +0 -112
  258. package/src/runtime/workflow/campaigns/types.ts +0 -290
  259. /package/src/runtime/{workflow/campaigns → execution}/workspace-agent.ts +0 -0
@@ -4,14 +4,13 @@ import {
4
4
  buildMultiRepositoryToolDefinitions,
5
5
  type MultiRepositoryMcpToolContext,
6
6
  } from '../../../cli/mcp/multi-repository';
7
- import { repositoryToolDefinitions } from '../../../cli/mcp/repository-tools';
7
+ import { callRepositoryTool, repositoryToolDefinitions } from '../../../cli/mcp/repository-tools';
8
8
  import { runtimeToolDefinitions } from './runtime-tools';
9
9
  import { executionToolDefinitions } from './execution-tools';
10
10
  import { processToolDefinitions } from './process-tools';
11
11
  import { resolveRepositorySelection } from '../../../cli/repositories/registry';
12
12
  import { executionIdentityForRepository } from '../../control-plane/execution/execution-identity';
13
- import { getEditSession, recordEditSessionProcessCheckReceipts } from '../../../cli/editing/edit-session';
14
- import { controllerCheckExecutionIdentity, listControllerChecks } from '../../../cli/controller/check-runner';
13
+ import { startOrJoinEditValidation } from '../../control-plane/execution/edit-validation-coordinator';
15
14
  import type {
16
15
  ExecutionOperationMetadata,
17
16
  ExecutionTimeoutPolicy,
@@ -25,18 +24,13 @@ import {
25
24
  } from '../../execution/thin-harness';
26
25
  import {
27
26
  checkRequiresDurableWorkflow,
28
- claimsForCheck,
29
27
  classifyRepositoryCommandRoute,
30
- getProcessRecord,
31
- processCheckCompletionReceipt,
32
- processCheckSemanticScopeKey,
33
28
  } from '../../execution/process-runtime';
34
29
  import { runPersistedCheckViaProcessRuntime } from './persisted-check-process';
35
30
  import {
36
31
  isProcessIsolatedReadDiagnostic,
37
32
  runReadOnlyDiagnosticViaProcessRuntime,
38
33
  } from '../../diagnostics/process-facade';
39
- import { resourceClaimsConflict } from '../../resources/claims/conflicts';
40
34
 
41
35
  const DIRECT_REPOSITORY_TOOLS = new Set(['repository_list', 'repository_get', 'repository_workbench', 'repository_command_preview']);
42
36
  export const RETIRED_AGENT_OPERATIONS = new Set([
@@ -353,7 +347,21 @@ export function classifyGatewayExecutionPath(
353
347
  reasons: opts.forceDurable ? ['force_durable'] : ['gateway_isolated_tool'],
354
348
  };
355
349
  }
356
- if (wantsAsyncExecution(args)) {
350
+ const selfManagedProcessAsync = (
351
+ name === 'repository_command_execute'
352
+ || name === 'run_check'
353
+ || name === 'verify_edit_session'
354
+ ) && (
355
+ args.apply_mode === 'async'
356
+ || args.mode === 'async'
357
+ || args.async === true
358
+ || args.background === true
359
+ );
360
+ // Async on Process-Runtime-owned tools means return the managed Process
361
+ // handle immediately. It must not promote local work into the retired
362
+ // ExecutionJob path. Explicit durable mode still crosses the external
363
+ // Controller boundary.
364
+ if (wantsAsyncExecution(args) && !selfManagedProcessAsync) {
357
365
  return {
358
366
  path: 'durable',
359
367
  reasons: ['caller_requested_async_or_durable'],
@@ -362,7 +370,7 @@ export function classifyGatewayExecutionPath(
362
370
  // run_check: Process Runtime for ordinary checks; Durable only for release/multi-phase.
363
371
  if (name === 'run_check') {
364
372
  const checkId = String(args.check_id ?? args.checkId ?? '').trim();
365
- if (args.apply_mode === 'async' || args.mode === 'durable' || args.force_durable === true) {
373
+ if (args.mode === 'durable' || args.force_durable === true) {
366
374
  return { path: 'durable', reasons: ['caller_requested_durable_check'] };
367
375
  }
368
376
  if (checkId && checkRequiresDurableWorkflow(checkId)) {
@@ -394,7 +402,7 @@ export function classifyGatewayExecutionPath(
394
402
  // return a managed handle, but it must never be promoted to a retired
395
403
  // ExecutionJob/LocalJob merely because the edit session has checks.
396
404
  if (name === 'verify_edit_session') {
397
- if (args.apply_mode === 'async' || args.mode === 'durable' || args.force_durable === true) {
405
+ if (args.mode === 'durable' || args.force_durable === true) {
398
406
  return { path: 'durable', reasons: ['caller_requested_durable_edit_verification'] };
399
407
  }
400
408
  const checkIds = Array.isArray(args.check_ids)
@@ -424,8 +432,9 @@ export function classifyGatewayExecutionPath(
424
432
  }
425
433
 
426
434
  // repository_command_execute has one authoritative classifier. Process Runtime
427
- // owns every local single-process command regardless of timeout; explicit
428
- // async/durable requests were handled above and remain external-controller work.
435
+ // owns every local single-process command regardless of timeout. Async requests
436
+ // stay here and return a handle; explicit durable mode remains an external-
437
+ // Controller boundary.
429
438
  if (name === 'repository_command_execute') {
430
439
  const command = args.command;
431
440
  if (!(typeof command === 'string' || Array.isArray(command))) {
@@ -678,6 +687,29 @@ export function injectDurableCommandFields(tool: McpToolDefinition): McpToolDefi
678
687
  };
679
688
  }
680
689
 
690
+ async function runEditSessionValidationViaProcessRuntime(
691
+ ctx: MultiRepositoryMcpToolContext,
692
+ repository: NonNullable<ReturnType<typeof resolveRepositorySelection>>,
693
+ editSessionId: string,
694
+ args: Record<string, unknown>,
695
+ ): Promise<CallToolResult> {
696
+ const validation = await startOrJoinEditValidation(ctx.controllerHome, repository, {
697
+ editSessionId,
698
+ checkIds: Array.isArray(args.check_ids) ? args.check_ids.map(String) : undefined,
699
+ requestId: typeof args.request_id === 'string' ? args.request_id : undefined,
700
+ validationRequestId: typeof args.validation_request_id === 'string' ? args.validation_request_id : undefined,
701
+ reviewer: typeof args.reviewer === 'string' ? args.reviewer : undefined,
702
+ note: typeof args.note === 'string' ? args.note : undefined,
703
+ timeoutMs: typeof args.check_timeout_ms === 'number'
704
+ ? args.check_timeout_ms
705
+ : typeof args.timeout_ms === 'number'
706
+ ? args.timeout_ms
707
+ : undefined,
708
+ leaseWaitMs: typeof args.lease_wait_ms === 'number' ? args.lease_wait_ms : undefined,
709
+ });
710
+ return result(validation as unknown as Record<string, unknown>, validation.accepted === false);
711
+ }
712
+
681
713
  export async function routeDurableMcpCall(
682
714
  ctx: MultiRepositoryMcpToolContext,
683
715
  name: string,
@@ -805,9 +837,8 @@ export async function routeDurableMcpCall(
805
837
  }
806
838
  }
807
839
 
808
- // Edit-session verification uses the same persisted Process Runtime checks as run_check.
809
- // The deterministic per-revision request IDs make retries collect the original process
810
- // instead of launching a duplicate command.
840
+ // Edit-session verification uses the same resource-aware Process Runtime
841
+ // coordinator as the stable Direct Edit composite below.
811
842
  if (name === 'verify_edit_session' && classification.path === 'fast') {
812
843
  const repository = resolveRepositorySelection({
813
844
  repoId: typeof args.repo_id === 'string' ? args.repo_id : undefined,
@@ -823,175 +854,77 @@ export async function routeDurableMcpCall(
823
854
  if (!editSessionId) {
824
855
  return result({ accepted: false, mode: 'reject', path: 'reject', message: 'verify_edit_session requires session_id' });
825
856
  }
826
- const editSession = getEditSession(repository.canonicalRoot, editSessionId);
827
- const checkIds = Array.from(new Set(
828
- (Array.isArray(args.check_ids) ? args.check_ids.map(String) : editSession.requestedChecks)
829
- .map((entry) => entry.trim())
830
- .filter(Boolean),
831
- ));
832
- const requestBase = typeof args.request_id === 'string' && args.request_id.trim()
833
- ? args.request_id.trim()
834
- : `verify-edit:${editSession.sessionId}:r${editSession.currentRevision}:${createHash('sha256').update(JSON.stringify(checkIds)).digest('hex').slice(0, 16)}`;
835
- if (checkIds.length === 0) {
836
- const checked = recordEditSessionProcessCheckReceipts(repository.canonicalRoot, editSession.sessionId, {
837
- repoId: repository.repoId,
838
- checkoutId: repository.activeCheckoutId,
839
- receipts: [],
840
- reviewer: typeof args.reviewer === 'string' ? args.reviewer : undefined,
841
- note: typeof args.note === 'string' ? args.note : undefined,
842
- });
843
- return result({ accepted: true, mode: 'direct', path: 'process_direct', completed: true, ok: true, session: checked, receipts: [] });
844
- }
845
- // Parallel-first without a timeout-based correctness tradeoff. Build
846
- // conflict-connected lanes from the same Resource Claim model used by the
847
- // Lease store. Different lanes run concurrently; checks in one lane retain
848
- // the historical managed-process behavior: once a check is still running,
849
- // stop that lane and continue it on the caller's retry. This avoids turning
850
- // a legitimate long serialization wait into PROCESS_LEASE_CONFLICT.
851
- const availableChecks = new Map(listControllerChecks(repository.canonicalRoot).map((check) => [check.id, check] as const));
852
- const claimSets = checkIds.map((checkId) => {
853
- const check = availableChecks.get(checkId);
854
- return claimsForCheck(
855
- checkId,
856
- check?.command,
857
- repository.repoId,
858
- repository.activeCheckoutId,
859
- check?.effects,
860
- );
857
+ return runEditSessionValidationViaProcessRuntime(ctx, repository, editSessionId, args);
858
+ }
859
+
860
+ // Stable Direct Edit composite: checks are opt-in. Without check_ids this
861
+ // tool remains the ordinary synchronous patch path. With check_ids, the same
862
+ // call applies one coherent edit batch, returns its review evidence, and
863
+ // starts revision-bound validation without waiting on long checks.
864
+ if (name === 'repository_safe_patch_apply' && classification.path === 'direct'
865
+ && (args.validation_only === true || (Array.isArray(args.check_ids) && args.check_ids.length > 0))) {
866
+ const repository = resolveRepositorySelection({
867
+ repoId: typeof args.repo_id === 'string' ? args.repo_id : undefined,
868
+ checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined,
869
+ explicitPath: ctx.explicitRepository?.canonicalRoot,
870
+ controllerHome: ctx.controllerHome,
871
+ allowSoleRepository: true,
861
872
  });
862
- const lanes: number[][] = [];
863
- const assigned = new Set<number>();
864
- for (let start = 0; start < checkIds.length; start += 1) {
865
- if (assigned.has(start)) continue;
866
- const lane: number[] = [];
867
- const queue = [start];
868
- assigned.add(start);
869
- while (queue.length > 0) {
870
- const current = queue.shift()!;
871
- lane.push(current);
872
- for (let candidate = 0; candidate < checkIds.length; candidate += 1) {
873
- if (assigned.has(candidate)) continue;
874
- const conflicts = claimSets[current]!.some((left) =>
875
- claimSets[candidate]!.some((right) => resourceClaimsConflict(left, right)));
876
- if (!conflicts) continue;
877
- assigned.add(candidate);
878
- queue.push(candidate);
879
- }
880
- }
881
- lanes.push(lane.sort((left, right) => left - right));
873
+ if (!repository) {
874
+ return result({ accepted: false, mode: 'reject', path: 'reject', message: 'repository_safe_patch_apply requires a resolvable repository' }, true);
882
875
  }
883
- const leaseWaitMs = typeof args.lease_wait_ms === 'number' && Number.isFinite(args.lease_wait_ms)
884
- ? Math.max(0, Math.min(Math.trunc(args.lease_wait_ms), 15_000))
885
- : 0;
886
- const facadesByIndex: Array<Awaited<ReturnType<typeof runPersistedCheckViaProcessRuntime>> | undefined> = new Array(checkIds.length);
887
- await Promise.all(lanes.map(async (lane) => {
888
- for (const index of lane) {
889
- const checkId = checkIds[index]!;
890
- const checkRequestId = `${requestBase}:check:${index + 1}:${createHash('sha256').update(checkId).digest('hex').slice(0, 12)}`;
891
- const facade = await runPersistedCheckViaProcessRuntime({
892
- controllerHome: ctx.controllerHome,
893
- repoId: repository.repoId,
894
- checkoutId: repository.activeCheckoutId,
895
- repoRoot: repository.canonicalRoot,
896
- executionIdentity: executionIdentityForRepository(repository),
897
- checkId,
898
- timeoutMs: typeof args.timeout_ms === 'number' ? args.timeout_ms : undefined,
899
- interactiveWaitMs: typeof args.interactive_wait_ms === 'number' ? args.interactive_wait_ms : undefined,
900
- requestId: checkRequestId,
901
- commandId: checkRequestId,
902
- verificationBinding: {
903
- editSessionId: editSession.sessionId,
904
- editRevision: editSession.currentRevision,
905
- issueId: editSession.issueId,
906
- taskId: editSession.taskId,
907
- },
908
- leaseWaitMs,
909
- });
910
- facadesByIndex[index] = facade;
911
- if (facade.mode === 'durable' || !facade.process || !facade.process.completed) break;
876
+
877
+ if (args.validation_only === true) {
878
+ const editSessionId = String(args.session_id ?? '').trim();
879
+ if (!editSessionId) {
880
+ return result({ accepted: false, mode: 'reject', path: 'reject', message: 'validation_only requires session_id' }, true);
912
881
  }
913
- }));
914
- const facades = facadesByIndex.filter((facade): facade is Awaited<ReturnType<typeof runPersistedCheckViaProcessRuntime>> => Boolean(facade));
915
- const durableFailure = facades.find((facade) => facade.mode === 'durable' || !facade.process);
916
- if (durableFailure) {
917
- return result({
918
- accepted: false,
919
- mode: 'durable',
920
- path: 'durable',
921
- checkId: durableFailure.checkId,
922
- message: durableFailure.durable?.reason ?? 'check requires durable workflow',
923
- suggestedOperation: durableFailure.durable?.suggestedOperation,
924
- });
925
- }
926
- const processes = facades.map((facade) => facade.process!);
927
- const pending = processes.find((process) => !process.completed);
928
- if (pending || processes.length !== checkIds.length) {
882
+ const validation = await runEditSessionValidationViaProcessRuntime(ctx, repository, editSessionId, args);
883
+ const validationPayload = (validation.structuredContent ?? {}) as Record<string, unknown>;
929
884
  return result({
930
- accepted: true,
931
- mode: 'managed',
932
- path: 'process_managed',
933
- completed: false,
934
- sessionId: editSession.sessionId,
935
- editRevision: editSession.currentRevision,
936
- requestId: requestBase,
937
- processes,
938
- next: `Re-run verify_edit_session with request_id=${requestBase}; Process Runtime will reuse the same process records.`,
939
- durableSideEffects: { executionJobCount: 0, localJobCount: 0, workerSpawnCount: 0, projectionUpdateCount: 0 },
940
- });
885
+ operation: 'repository_safe_patch_apply',
886
+ validationOnly: true,
887
+ sessionId: editSessionId,
888
+ validation: validationPayload,
889
+ validationRequestId: validationPayload.validationRequestId,
890
+ completed: validationPayload.completed === true,
891
+ ok: validationPayload.ok,
892
+ acceptanceReady: validationPayload.completed === true && validationPayload.ok === true,
893
+ next: validationPayload.completed === true
894
+ ? validationPayload.ok === true
895
+ ? 'Validation passed for the exact edit revision; delivery may proceed if policy and semantic review are satisfied.'
896
+ : 'Validation completed with failures; return the failure evidence to ChatGPT for repair reasoning.'
897
+ : validationPayload.next,
898
+ }, validation.isError === true);
941
899
  }
942
- const receipts = processes.map((handle, index) => {
943
- const record = getProcessRecord(ctx.controllerHome, repository.repoId, handle.processId);
944
- if (!record) throw new Error(`PROCESS_CHECK_RECEIPT_MISSING: process record not found: ${handle.processId}`);
945
- const checkId = checkIds[index]!;
946
- const currentIdentity = record.checkExecution
947
- ? controllerCheckExecutionIdentity(repository.canonicalRoot, checkId, record.checkExecution.timeoutMs)
948
- : undefined;
949
- return processCheckCompletionReceipt(record, {
950
- repoId: repository.repoId,
951
- checkoutId: repository.activeCheckoutId,
952
- editSessionId: editSession.sessionId,
953
- editRevision: editSession.currentRevision,
954
- issueId: editSession.issueId,
955
- taskId: editSession.taskId,
956
- checkId,
957
- processId: handle.processId,
958
- ...(currentIdentity ? {
959
- checkExecution: {
960
- cacheKey: currentIdentity.cacheKey,
961
- revision: currentIdentity.revision,
962
- definitionDigest: currentIdentity.definitionDigest,
963
- environmentFingerprint: currentIdentity.environmentFingerprint,
964
- timeoutMs: currentIdentity.timeoutMs,
965
- scopeKey: processCheckSemanticScopeKey({
966
- checkoutId: repository.activeCheckoutId,
967
- verificationBinding: {
968
- editSessionId: editSession.sessionId,
969
- editRevision: editSession.currentRevision,
970
- issueId: editSession.issueId,
971
- taskId: editSession.taskId,
972
- },
973
- }, currentIdentity.reuseScope),
974
- },
975
- } : {}),
976
- });
977
- });
978
- const checked = recordEditSessionProcessCheckReceipts(repository.canonicalRoot, editSession.sessionId, {
979
- repoId: repository.repoId,
980
- checkoutId: repository.activeCheckoutId,
981
- receipts,
982
- reviewer: typeof args.reviewer === 'string' ? args.reviewer : undefined,
983
- note: typeof args.note === 'string' ? args.note : undefined,
984
- });
900
+
901
+ const applied = await callRepositoryTool(ctx.controllerHome, name, args);
902
+ if (!applied || applied.isError === true) return applied;
903
+ const patchPayload = (applied.structuredContent ?? {}) as Record<string, unknown>;
904
+ if (patchPayload.status !== 'applied') return applied;
905
+ const session = patchPayload.session && typeof patchPayload.session === 'object'
906
+ ? patchPayload.session as Record<string, unknown>
907
+ : undefined;
908
+ const editSessionId = typeof session?.sessionId === 'string' ? session.sessionId : '';
909
+ if (!editSessionId) return applied;
910
+ const validation = await runEditSessionValidationViaProcessRuntime(ctx, repository, editSessionId, args);
911
+ const validationPayload = (validation.structuredContent ?? {}) as Record<string, unknown>;
912
+ const validationCompleted = validationPayload.completed === true;
913
+ const validationPassed = validationCompleted && validationPayload.ok === true;
985
914
  return result({
986
- accepted: true,
987
- mode: 'direct',
988
- path: 'process_direct',
989
- completed: true,
990
- ok: receipts.every((receipt) => receipt.ok),
991
- session: checked,
992
- receipts,
993
- durableSideEffects: { executionJobCount: 0, localJobCount: 0, workerSpawnCount: 0, projectionUpdateCount: 0 },
994
- });
915
+ ...patchPayload,
916
+ validation: validationPayload,
917
+ validationStarted: true,
918
+ validationCompleted,
919
+ validationPassed,
920
+ acceptanceReady: validationPassed,
921
+ validationRequestId: validationPayload.validationRequestId,
922
+ next: validationCompleted
923
+ ? validationPassed
924
+ ? 'Patch review evidence and requested validation are complete; proceed to delivery only after ChatGPT semantic review is satisfied.'
925
+ : 'Patch applied but validation failed; return the failure evidence to ChatGPT before any delivery.'
926
+ : validationPayload.next,
927
+ }, validation.isError === true);
995
928
  }
996
929
 
997
930
  // run_check Process Runtime facade — execute here so legacy LocalBridgeJob path is skipped.
@@ -1030,9 +963,11 @@ export async function routeDurableMcpCall(
1030
963
  executionIdentity: executionIdentityForRepository(repository),
1031
964
  checkId,
1032
965
  timeoutMs: typeof args.timeout_ms === 'number' ? args.timeout_ms : undefined,
1033
- interactiveWaitMs: typeof args.interactive_wait_ms === 'number' ? args.interactive_wait_ms : undefined,
966
+ interactiveWaitMs: args.apply_mode === 'async' || args.mode === 'async' || args.async === true || args.background === true
967
+ ? 0
968
+ : typeof args.interactive_wait_ms === 'number' ? args.interactive_wait_ms : undefined,
1034
969
  requestId: typeof args.request_id === 'string' ? args.request_id : undefined,
1035
- forceDurable: args.force_durable === true,
970
+ forceDurable: args.force_durable === true || args.mode === 'durable',
1036
971
  });
1037
972
  if (facade.mode === 'durable') {
1038
973
  // Multi-phase/release should already be classified durable. Remaining durable
@@ -1077,7 +1012,7 @@ export async function routeDurableMcpCall(
1077
1012
  durableSideEffects: facade.durableSideEffects,
1078
1013
  next: handle?.completed
1079
1014
  ? 'Check finished on Process Runtime without ExecutionJob / LocalBridgeJob / Worker.'
1080
- : `Check still running as managed process ${handle?.processId}. Poll process_get or process_wait with process_id=${handle?.processId}; do not re-run the same check.`,
1015
+ : `Check still running as managed process ${handle?.processId}. Continue independent work; use process_wait once when this result becomes a real dependency. Do not re-run or repeatedly poll the same check.`,
1081
1016
  });
1082
1017
  }
1083
1018