codex-workflow-v2 2.0.0-beta.13.9 → 2.0.0-beta.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/README.md +27 -388
  2. package/dist/reviewer-runtime-build.json +98 -34
  3. package/dist/src/alpha6/adoption.d.ts +3 -1
  4. package/dist/src/alpha6/adoption.js +48 -8
  5. package/dist/src/alpha6/adoption.js.map +1 -1
  6. package/dist/src/alpha6/captured-check-evidence.d.ts +51 -0
  7. package/dist/src/alpha6/captured-check-evidence.js +152 -0
  8. package/dist/src/alpha6/captured-check-evidence.js.map +1 -0
  9. package/dist/src/alpha6/component-owner.d.ts +15 -1
  10. package/dist/src/alpha6/component-owner.js +44 -2
  11. package/dist/src/alpha6/component-owner.js.map +1 -1
  12. package/dist/src/alpha6/corrective-decision-boundary.d.ts +4 -0
  13. package/dist/src/alpha6/corrective-decision-boundary.js +53 -0
  14. package/dist/src/alpha6/corrective-decision-boundary.js.map +1 -0
  15. package/dist/src/alpha6/downstream-proof.d.ts +1 -25
  16. package/dist/src/alpha6/downstream-proof.js +0 -171
  17. package/dist/src/alpha6/downstream-proof.js.map +1 -1
  18. package/dist/src/alpha6/literal-test-invocation.d.ts +2 -0
  19. package/dist/src/alpha6/literal-test-invocation.js +106 -0
  20. package/dist/src/alpha6/literal-test-invocation.js.map +1 -0
  21. package/dist/src/alpha6/milestone.d.ts +2 -0
  22. package/dist/src/alpha6/milestone.js +10 -1
  23. package/dist/src/alpha6/milestone.js.map +1 -1
  24. package/dist/src/alpha6/navigation-dirty-carryover.d.ts +11 -0
  25. package/dist/src/alpha6/navigation-dirty-carryover.js +130 -0
  26. package/dist/src/alpha6/navigation-dirty-carryover.js.map +1 -0
  27. package/dist/src/alpha6/plan-integrity.js +7 -5
  28. package/dist/src/alpha6/plan-integrity.js.map +1 -1
  29. package/dist/src/alpha6/remediation.d.ts +8 -21
  30. package/dist/src/alpha6/remediation.js +225 -474
  31. package/dist/src/alpha6/remediation.js.map +1 -1
  32. package/dist/src/alpha6/root-cause-replan-carryover.d.ts +4 -1
  33. package/dist/src/alpha6/root-cause-replan-carryover.js +29 -5
  34. package/dist/src/alpha6/root-cause-replan-carryover.js.map +1 -1
  35. package/dist/src/alpha7/autonomy.d.ts +9 -0
  36. package/dist/src/alpha7/autonomy.js +40 -1
  37. package/dist/src/alpha7/autonomy.js.map +1 -1
  38. package/dist/src/beta1/project-transaction.d.ts +3 -1
  39. package/dist/src/beta1/project-transaction.js +20 -6
  40. package/dist/src/beta1/project-transaction.js.map +1 -1
  41. package/dist/src/checks/runner.d.ts +26 -0
  42. package/dist/src/checks/runner.js +223 -0
  43. package/dist/src/checks/runner.js.map +1 -0
  44. package/dist/src/checks/task-sync.d.ts +10 -0
  45. package/dist/src/checks/task-sync.js +41 -0
  46. package/dist/src/checks/task-sync.js.map +1 -0
  47. package/dist/src/checks/worker.d.ts +1 -0
  48. package/dist/src/checks/worker.js +154 -0
  49. package/dist/src/checks/worker.js.map +1 -0
  50. package/dist/src/cli-actions.d.ts +3 -3
  51. package/dist/src/cli-actions.js +8 -15
  52. package/dist/src/cli-actions.js.map +1 -1
  53. package/dist/src/cli.js +43 -97
  54. package/dist/src/cli.js.map +1 -1
  55. package/dist/src/contracts.d.ts +54 -34
  56. package/dist/src/delegation.d.ts +5 -1
  57. package/dist/src/delegation.js +4 -0
  58. package/dist/src/delegation.js.map +1 -1
  59. package/dist/src/dependency-provenance.d.ts +2 -2
  60. package/dist/src/dependency-provenance.js +12 -69
  61. package/dist/src/dependency-provenance.js.map +1 -1
  62. package/dist/src/domain/base-sync-conflict.d.ts +13 -0
  63. package/dist/src/domain/base-sync-conflict.js +32 -0
  64. package/dist/src/domain/base-sync-conflict.js.map +1 -0
  65. package/dist/src/domain/step-start-admission.d.ts +4 -0
  66. package/dist/src/domain/step-start-admission.js +9 -0
  67. package/dist/src/domain/step-start-admission.js.map +1 -0
  68. package/dist/src/domain/virgin-registration.d.ts +3 -0
  69. package/dist/src/domain/virgin-registration.js +65 -0
  70. package/dist/src/domain/virgin-registration.js.map +1 -0
  71. package/dist/src/errors.d.ts +1 -1
  72. package/dist/src/errors.js.map +1 -1
  73. package/dist/src/failed-checkpoint-update.d.ts +69 -0
  74. package/dist/src/failed-checkpoint-update.js +171 -0
  75. package/dist/src/failed-checkpoint-update.js.map +1 -0
  76. package/dist/src/gateway-handshake.js +0 -8
  77. package/dist/src/gateway-handshake.js.map +1 -1
  78. package/dist/src/git.d.ts +3 -1
  79. package/dist/src/git.js +41 -14
  80. package/dist/src/git.js.map +1 -1
  81. package/dist/src/graph.js +25 -3
  82. package/dist/src/graph.js.map +1 -1
  83. package/dist/src/index.d.ts +1 -0
  84. package/dist/src/lifecycle/corrective-replan-credential-core.d.ts +2 -0
  85. package/dist/src/lifecycle/corrective-replan-credential-core.js +45 -9
  86. package/dist/src/lifecycle/corrective-replan-credential-core.js.map +1 -1
  87. package/dist/src/lifecycle/corrective-replan.js +12 -8
  88. package/dist/src/lifecycle/corrective-replan.js.map +1 -1
  89. package/dist/src/lifecycle/semantic-registry.js +4 -2
  90. package/dist/src/lifecycle/semantic-registry.js.map +1 -1
  91. package/dist/src/navigation-actions.d.ts +9 -0
  92. package/dist/src/navigation-actions.js +72 -0
  93. package/dist/src/navigation-actions.js.map +1 -0
  94. package/dist/src/navigation-update-artifact.d.ts +5 -0
  95. package/dist/src/navigation-update-artifact.js +227 -0
  96. package/dist/src/navigation-update-artifact.js.map +1 -0
  97. package/dist/src/navigation-update.d.ts +39 -0
  98. package/dist/src/navigation-update.js +82 -0
  99. package/dist/src/navigation-update.js.map +1 -0
  100. package/dist/src/observation.js +52 -29
  101. package/dist/src/observation.js.map +1 -1
  102. package/dist/src/observed-routes.js +10 -6
  103. package/dist/src/observed-routes.js.map +1 -1
  104. package/dist/src/pending-review-update.d.ts +0 -13
  105. package/dist/src/pending-review-update.js +1 -6
  106. package/dist/src/pending-review-update.js.map +1 -1
  107. package/dist/src/reviewer.d.ts +1 -1
  108. package/dist/src/reviewer.js +20 -17
  109. package/dist/src/reviewer.js.map +1 -1
  110. package/dist/src/runtime.d.ts +3 -0
  111. package/dist/src/runtime.js +6 -0
  112. package/dist/src/runtime.js.map +1 -0
  113. package/dist/src/state/corrective-replan-executor.d.ts +10 -0
  114. package/dist/src/state/corrective-replan-executor.js +38 -1
  115. package/dist/src/state/corrective-replan-executor.js.map +1 -1
  116. package/dist/src/state/corrective-replan-public-schema.js +15 -2
  117. package/dist/src/state/corrective-replan-public-schema.js.map +1 -1
  118. package/dist/src/state/corrective-replan-public.d.ts +9 -1
  119. package/dist/src/state/corrective-replan-public.js +50 -8
  120. package/dist/src/state/corrective-replan-public.js.map +1 -1
  121. package/dist/src/state/corrective-replan-transaction.d.ts +1 -0
  122. package/dist/src/state/corrective-replan-transaction.js +12 -11
  123. package/dist/src/state/corrective-replan-transaction.js.map +1 -1
  124. package/dist/src/state/corrective-yield-transaction.d.ts +1 -0
  125. package/dist/src/state/corrective-yield-transaction.js +11 -10
  126. package/dist/src/state/corrective-yield-transaction.js.map +1 -1
  127. package/dist/src/state/lock.d.ts +4 -0
  128. package/dist/src/state/lock.js +21 -0
  129. package/dist/src/state/lock.js.map +1 -1
  130. package/dist/src/version.d.ts +1 -1
  131. package/dist/src/version.js +1 -1
  132. package/dist/src/workflow-blocker-route.d.ts +8 -0
  133. package/dist/src/workflow-blocker-route.js +106 -0
  134. package/dist/src/workflow-blocker-route.js.map +1 -0
  135. package/dist/src/workflow.d.ts +88 -64
  136. package/dist/src/workflow.js +832 -1456
  137. package/dist/src/workflow.js.map +1 -1
  138. package/docs/autonomy-guardrails.md +26 -304
  139. package/docs/decisions.md +14 -112
  140. package/docs/development-flow.md +36 -237
  141. package/docs/project-memory.md +31 -50
  142. package/docs/release-app-evidence.md +190 -0
  143. package/docs/release.md +101 -388
  144. package/docs/updating-existing-project.md +65 -726
  145. package/package.json +11 -13
  146. package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +2 -2
  147. package/plugins/codex-workflow-gateway/references/chat-dispatch.md +88 -200
  148. package/plugins/codex-workflow-gateway/references/codebase-memory-routing.md +57 -0
  149. package/plugins/codex-workflow-gateway/references/protocol.md +51 -458
  150. package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +20 -4
  151. package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +21 -34
  152. package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +23 -5
  153. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +92 -778
  154. package/references/state-machine.md +7 -5
  155. package/roles/delivery-coordinator.md +31 -11
  156. package/roles/scope-lead.md +1 -1
  157. package/roles/technical-planner.md +5 -2
  158. package/schemas/adoption-posture-event.schema.json +11 -0
  159. package/schemas/authorization-event.schema.json +1 -1
  160. package/schemas/corrective-decision-event.schema.json +26 -4
  161. package/schemas/corrective-replan-credentials.private.schema.json +8 -5
  162. package/schemas/corrective-replan-execution-event.private.schema.json +9 -0
  163. package/schemas/corrective-replan-public.schema.json +2 -1
  164. package/schemas/delegation-grant.schema.json +1 -1
  165. package/schemas/project-knowledge-map.schema.json +3 -1
  166. package/schemas/remediation-event.schema.json +10 -1
  167. package/schemas/task.schema.json +64 -2
  168. package/schemas/transition-definition.schema.json +4 -0
  169. package/schemas/transition-payloads.schema.json +16 -1
  170. package/src/alpha6/adoption.ts +1146 -0
  171. package/src/alpha6/captured-check-evidence.ts +132 -0
  172. package/src/alpha6/check-support-anchor.ts +128 -0
  173. package/src/alpha6/component-owner.ts +168 -0
  174. package/src/alpha6/corrective-decision-boundary.ts +45 -0
  175. package/src/alpha6/downstream-proof.ts +520 -0
  176. package/src/alpha6/failed-step-planning-recovery.ts +88 -0
  177. package/src/alpha6/handoff.ts +1443 -0
  178. package/src/alpha6/journal.ts +473 -0
  179. package/src/alpha6/literal-test-invocation.ts +84 -0
  180. package/src/alpha6/mechanical-feasibility.ts +488 -0
  181. package/src/alpha6/milestone.ts +2192 -0
  182. package/src/alpha6/navigation-dirty-carryover.ts +154 -0
  183. package/src/alpha6/npm-check-contract.ts +47 -0
  184. package/src/alpha6/plan-integrity.ts +298 -0
  185. package/src/alpha6/plan-risk.ts +1480 -0
  186. package/src/alpha6/preexecution-replan.ts +187 -0
  187. package/src/alpha6/remediation-cause.ts +98 -0
  188. package/src/alpha6/remediation.ts +2505 -0
  189. package/src/alpha6/review.ts +1198 -0
  190. package/src/alpha6/root-cause-replan-carryover.ts +491 -0
  191. package/src/alpha6/store-sidecars.ts +335 -0
  192. package/src/alpha7/autonomy.ts +449 -0
  193. package/src/alpha7/corrective-recovery.ts +1332 -0
  194. package/src/artifacts.ts +130 -0
  195. package/src/beta1/project-transaction.ts +364 -0
  196. package/src/change-explanation.ts +153 -0
  197. package/src/checks/runner.ts +245 -0
  198. package/src/checks/task-sync.ts +41 -0
  199. package/src/checks/worker.ts +149 -0
  200. package/src/cli-actions.ts +112 -0
  201. package/src/cli.ts +1490 -0
  202. package/src/contracts.ts +1512 -0
  203. package/src/credential-output.ts +89 -0
  204. package/src/credential-transport.ts +215 -0
  205. package/src/delegation.ts +194 -0
  206. package/src/dependency-provenance.ts +472 -0
  207. package/src/diagnostics.ts +93 -0
  208. package/src/domain/base-sync-conflict.ts +35 -0
  209. package/src/domain/completed-step-carryover.ts +76 -0
  210. package/src/domain/discovery.ts +27 -0
  211. package/src/domain/plan-semantics.ts +58 -0
  212. package/src/domain/step-start-admission.ts +10 -0
  213. package/src/domain/validation.ts +177 -0
  214. package/src/domain/virgin-registration.ts +46 -0
  215. package/src/errors.ts +24 -0
  216. package/src/failed-checkpoint-update.ts +203 -0
  217. package/src/fs-utils.ts +61 -0
  218. package/src/gateway-handshake.ts +95 -0
  219. package/src/git.ts +183 -0
  220. package/src/graph.ts +342 -0
  221. package/src/historical-step-provenance.ts +136 -0
  222. package/src/index.ts +23 -0
  223. package/src/lifecycle/canonical-hash.ts +28 -0
  224. package/src/lifecycle/catalog.ts +202 -0
  225. package/src/lifecycle/compiler-inspection.ts +29 -0
  226. package/src/lifecycle/core-static-readiness.ts +132 -0
  227. package/src/lifecycle/corrective-replan-authority.ts +136 -0
  228. package/src/lifecycle/corrective-replan-binding-manifest.ts +51 -0
  229. package/src/lifecycle/corrective-replan-credential-core.ts +437 -0
  230. package/src/lifecycle/corrective-replan-credential-schema.ts +54 -0
  231. package/src/lifecycle/corrective-replan-credentials.ts +48 -0
  232. package/src/lifecycle/corrective-replan.ts +847 -0
  233. package/src/lifecycle/evaluator.ts +48 -0
  234. package/src/lifecycle/fingerprint.ts +488 -0
  235. package/src/lifecycle/immutable.ts +8 -0
  236. package/src/lifecycle/implementation-table.ts +118 -0
  237. package/src/lifecycle/index.ts +8 -0
  238. package/src/lifecycle/schema-artifact.ts +263 -0
  239. package/src/lifecycle/semantic-registry.ts +574 -0
  240. package/src/lifecycle/types.ts +838 -0
  241. package/src/memory.ts +273 -0
  242. package/src/migration.ts +161 -0
  243. package/src/navigation-actions.ts +70 -0
  244. package/src/navigation-update-artifact.ts +198 -0
  245. package/src/navigation-update.ts +121 -0
  246. package/src/observation.ts +225 -0
  247. package/src/observed-routes.ts +665 -0
  248. package/src/operational-contract.ts +125 -0
  249. package/src/pending-review-update.ts +175 -0
  250. package/src/repository.ts +99 -0
  251. package/src/reviewer.ts +1879 -0
  252. package/src/runtime.ts +6 -0
  253. package/src/state/corrective-replan-executor.ts +819 -0
  254. package/src/state/corrective-replan-public-schema.ts +83 -0
  255. package/src/state/corrective-replan-public.ts +952 -0
  256. package/src/state/corrective-replan-transaction.ts +949 -0
  257. package/src/state/corrective-yield-executor.ts +327 -0
  258. package/src/state/corrective-yield-transaction.ts +730 -0
  259. package/src/state/lock.ts +902 -0
  260. package/src/state/store.ts +567 -0
  261. package/src/transition-core.ts +330 -0
  262. package/src/ulid.ts +24 -0
  263. package/src/version.ts +2 -0
  264. package/src/workflow-blocker-route.ts +109 -0
  265. package/src/workflow.ts +10342 -0
  266. package/docs/alpha7.1-implementation-brief.md +0 -268
  267. package/docs/alpha7.2-corrective-context-refresh-brief.md +0 -484
  268. package/docs/alpha7.2.1-remediation-recovery-brief.md +0 -86
  269. package/docs/beta1-stabilization-brief.md +0 -165
  270. package/docs/beta11-plan-integrity-recovery-brief.md +0 -38
  271. package/docs/beta13.2-signal-review-recovery.md +0 -38
  272. package/docs/beta2-initial-assembly-navigation-brief.md +0 -616
  273. package/docs/change-model.md +0 -118
  274. package/docs/delegated-approval.md +0 -254
  275. package/docs/lifecycle/state-machine-stabilization.md +0 -641
  276. package/docs/pdf/README.md +0 -24
  277. package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
  278. package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
  279. package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
  280. package/docs/pdf/requirements.txt +0 -1
  281. package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +0 -478
  282. package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +0 -506
  283. package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +0 -778
  284. package/docs/pending-review-update.md +0 -15
  285. package/docs/problem-briefs/01-pre-implementation-integrity.md +0 -482
  286. package/docs/problem-briefs/02-minimal-step-integrity.md +0 -411
  287. package/docs/problem-briefs/03-minimal-agent-context-integrity.md +0 -358
  288. package/docs/problem-briefs/04-task-dependency-and-structural-replacement-integrity.md +0 -573
  289. package/docs/problem-briefs/BRIEF-TEMPLATE.md +0 -56
  290. package/docs/problem-briefs/README.md +0 -120
  291. package/docs/problem-briefs/evidence/p01-mechanical-feasibility-corpus.md +0 -90
  292. package/docs/problem-briefs/evidence/signal-v4-pre-m3-replay.md +0 -246
  293. package/docs/split-required-recovery.md +0 -47
  294. package/docs/stable-release-defect-register.md +0 -730
  295. package/docs/validation-report.md +0 -182
  296. package/scripts/generate-pdf-docs.py +0 -524
  297. package/scripts/run-pdf-docs.mjs +0 -62
@@ -0,0 +1,95 @@
1
+ import { PROTOCOL_VERSION, STATE_SCHEMA_VERSION } from './contracts.js';
2
+ import { PACKAGE_NAME, PACKAGE_VERSION } from './version.js';
3
+
4
+ export interface GatewayHandshake {
5
+ packageName: typeof PACKAGE_NAME;
6
+ packageVersion: typeof PACKAGE_VERSION;
7
+ protocolVersion: typeof PROTOCOL_VERSION;
8
+ stateSchemaVersion: typeof STATE_SCHEMA_VERSION;
9
+ capabilities: string[];
10
+ }
11
+
12
+ export function gatewayHandshake(): GatewayHandshake {
13
+ return {
14
+ packageName: PACKAGE_NAME,
15
+ packageVersion: PACKAGE_VERSION,
16
+ protocolVersion: PROTOCOL_VERSION,
17
+ stateSchemaVersion: STATE_SCHEMA_VERSION,
18
+ capabilities: workflowCapabilities(),
19
+ };
20
+ }
21
+
22
+ export function workflowCapabilities(): string[] {
23
+ return [
24
+ 'project-memory-v1',
25
+ 'graph-binding-v1',
26
+ 'local-state-v1',
27
+ 'milestone-lifecycle-v1',
28
+ 'milestone-human-final-gate-v1',
29
+ 'delegated-approval-v1',
30
+ 'task-knowledge-rebind-v1',
31
+ 'task-context-refresh-v1',
32
+ 'corrective-plan-audit-v1',
33
+ 'safe-update-preflight-v1',
34
+ 'bounded-dependency-provenance-recovery-v1',
35
+ 'task-local-dependency-provenance-recovery-v1',
36
+ 'root-cause-corrective-replan-dirty-carryover-v1',
37
+ 'stop-override-context-rebind-v1',
38
+ 'stop-override-context-rebind-chain-v1',
39
+ 'stop-override-post-audit-dependency-revision-v1',
40
+ 'retained-reviewed-history-recovery-v1',
41
+ 'historical-step-provenance-recovery-v1',
42
+ 'milestone-progress-projection-v1',
43
+ 'bounded-plan-integrity-recovery-v1',
44
+ 'check-support-scope-augmentation-v1',
45
+ 'historical-check-support-scope-adoption-v1',
46
+ 'historical-check-support-dirty-carryover-v1',
47
+ 'downstream-proof-predecessor-recovery-v1',
48
+ 'active-downstream-proof-dependency-recovery-v1',
49
+ 'task-mechanical-feasibility-v1',
50
+ 'task-human-gate-lease-release-v1',
51
+ 'strict-reviewer-v1',
52
+ 'legacy-snapshot-v1',
53
+ 'plan-risk-audit-sidecar-v1',
54
+ 'step-strict-review-sidecar-v1',
55
+ 'step-remediation-circuit-breaker-sidecar-v1',
56
+ 'milestone-scope-change-sidecar-v1',
57
+ 'milestone-membership-integrity-v1',
58
+ 'milestone-task-dependency-dag-v1',
59
+ 'milestone-initial-plan-transaction-v1',
60
+ 'task-c1-handoff-sidecar-v1',
61
+ 'semantic-plan-risk-audit-v2',
62
+ 'guarded-remediation-continuation-v1',
63
+ 'cause-bound-rolling-refinement-v1',
64
+ 'failed-step-continuation-transaction-v1',
65
+ 'host-safe-credential-reference-v1',
66
+ 'structural-production-edge-contract-v1',
67
+ 'structural-production-edge-authority-v1',
68
+ 'observed-route-matrix-v2',
69
+ 'compact-status-v1',
70
+ 'reviewer-runtime-preflight-v1',
71
+ 'explicit-review-mode-v1',
72
+ 'app-owned-operational-preflight-v1',
73
+ 'milestone-reverse-membership-integrity-v1',
74
+ 'task-c1-handoff-bundle-v2',
75
+ 'alpha6-adoption-posture-v1',
76
+ 'milestone-initial-assembly-v1',
77
+ 'milestone-initial-assembly-navigation-v2',
78
+ 'milestone-autonomy-contract-v1',
79
+ 'milestone-autonomy-content-refresh-v1',
80
+ 'milestone-autonomous-membership-evolution-v1',
81
+ 'audited-remediation-topology-v1',
82
+ 'audited-remediation-dependency-knowledge-refresh-v1',
83
+ 'corrective-derived-task-authority-v1',
84
+ 'task-c1-derived-worker-actor-v1',
85
+ 'guarded-corrective-auditor-routing-v1',
86
+ 'corrective-decision-context-recovery-v1',
87
+ 'plan-proof-obligations-v1',
88
+ 'structural-task-replacement-disabled-v1',
89
+ 'project-composite-transaction-journal-v1',
90
+ 'corrective-yield-journal-v1',
91
+ 'corrective-replan-journal-v1',
92
+ 'corrective-replan-human-gate-v1',
93
+ 'corrective-replan-protocol-v2',
94
+ ];
95
+ }
package/src/git.ts ADDED
@@ -0,0 +1,183 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { WorkflowError } from './errors.js';
3
+ import { runCheckCommands, type CheckExecutionOptions } from './checks/runner.js';
4
+ import { changedFiles, currentBranch, headCommit, isClean, runGit } from './repository.js';
5
+ import { pathAllowed } from './domain/validation.js';
6
+ import type { TaskState } from './contracts.js';
7
+
8
+ const PROTECTED_BRANCHES = new Set(['main', 'master', 'develop', 'staging', 'production']);
9
+
10
+ export function startLocalTaskBranch(
11
+ repositoryRoot: string,
12
+ branch: string,
13
+ baseBranch: string,
14
+ expectedBaseCommit: string | null = null,
15
+ ): string {
16
+ if (!isClean(repositoryRoot)) {
17
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Task start requires a clean checkout.', {
18
+ changedFiles: changedFiles(repositoryRoot),
19
+ });
20
+ }
21
+ if (PROTECTED_BRANCHES.has(branch)) {
22
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', `Task branch is protected: ${branch}`);
23
+ }
24
+ const baseCommit = runGit(repositoryRoot, ['rev-parse', baseBranch]);
25
+ if (expectedBaseCommit !== null && baseCommit !== expectedBaseCommit) {
26
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Task start base moved after execution authorization.', {
27
+ baseBranch,
28
+ authorizedBaseCommit: expectedBaseCommit,
29
+ currentBaseCommit: baseCommit,
30
+ });
31
+ }
32
+ runGit(repositoryRoot, ['switch', '-c', branch, baseBranch]);
33
+ return baseCommit;
34
+ }
35
+
36
+ export interface CheckResult {
37
+ command: string;
38
+ passed: boolean;
39
+ exitCode: number;
40
+ output: string;
41
+ }
42
+
43
+ /** Only ordinary exited checks can enter product failure/remediation evidence. */
44
+ export function runChecks(repositoryRoot: string, commands: string[], options: CheckExecutionOptions = {}): CheckResult[] {
45
+ const configured: CheckExecutionOptions = { ...options };
46
+ const envNumber = (name: string): number | undefined => {
47
+ const value = process.env[name];
48
+ if (value === undefined) return undefined;
49
+ const parsed = Number(value);
50
+ if (!value.trim() || !Number.isSafeInteger(parsed) || parsed <= 0) {
51
+ throw new WorkflowError('INVALID_ARGUMENT', `${name} must be a positive integer.`);
52
+ }
53
+ return parsed;
54
+ };
55
+ if (configured.timeoutMs === undefined) {
56
+ const value = envNumber('CODEX_WORKFLOW_CHECK_TIMEOUT_MS');
57
+ if (value !== undefined) configured.timeoutMs = value;
58
+ }
59
+ if (configured.totalTimeoutMs === undefined) {
60
+ const value = envNumber('CODEX_WORKFLOW_CHECK_TOTAL_TIMEOUT_MS');
61
+ if (value !== undefined) configured.totalTimeoutMs = value;
62
+ }
63
+ if (configured.cancellationFile === undefined && process.env.CODEX_WORKFLOW_CHECK_CANCEL_FILE !== undefined) {
64
+ configured.cancellationFile = process.env.CODEX_WORKFLOW_CHECK_CANCEL_FILE;
65
+ }
66
+ const results = runCheckCommands(repositoryRoot, commands, configured);
67
+ if (results.some(result => result.outcome === 'infra')) {
68
+ throw new WorkflowError('CHECK_INFRASTRUCTURE_FAILED',
69
+ 'Check execution ended with an infrastructure failure. Product failure and corrective replanning were not inferred.', {
70
+ checks: results,
71
+ retryRequiresFreshObservation: true,
72
+ automaticRetry: false,
73
+ });
74
+ }
75
+ return results.map(result => {
76
+ if (result.exitCode === null || result.signal !== null || result.error !== null || !result.cleanupComplete) {
77
+ throw new WorkflowError('CHECK_INFRASTRUCTURE_FAILED', 'Check result lacks a verified ordinary exit.', { checks: results });
78
+ }
79
+ return { command: result.command, passed: result.outcome === 'passed', exitCode: result.exitCode, output: result.output };
80
+ });
81
+ }
82
+
83
+ export function commitStep(
84
+ repositoryRoot: string,
85
+ taskId: string,
86
+ stepId: string,
87
+ title: string,
88
+ allowedWrites: string[],
89
+ ): { commitSha: string; files: string[] } {
90
+ const files = changedFiles(repositoryRoot);
91
+ if (files.length === 0) throw new WorkflowError('TRANSITION_BLOCKED', `${stepId} has no changed files.`);
92
+ const unexpected = files.filter((file) => !pathAllowed(file, allowedWrites));
93
+ if (unexpected.length > 0) {
94
+ throw new WorkflowError('TRANSITION_BLOCKED', `${stepId} changed files outside allowedWrites.`, { unexpected });
95
+ }
96
+ runGit(repositoryRoot, ['add', '--', ...files]);
97
+ runGit(repositoryRoot, [
98
+ 'commit',
99
+ '-m',
100
+ `${taskId}/${stepId}: ${title}`,
101
+ '-m',
102
+ `Task: ${taskId}\nStep: ${stepId}`,
103
+ ]);
104
+ return { commitSha: headCommit(repositoryRoot), files };
105
+ }
106
+
107
+ export function mergeTaskBranch(repositoryRoot: string, taskBranch: string, baseBranch: string): string {
108
+ if (!isClean(repositoryRoot)) throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Merge requires a clean checkout.');
109
+ if (currentBranch(repositoryRoot) !== taskBranch) {
110
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', `Expected current task branch ${taskBranch}.`);
111
+ }
112
+ runGit(repositoryRoot, ['switch', baseBranch]);
113
+ const result = spawnSync('git', ['-C', repositoryRoot, 'merge', '--no-ff', taskBranch, '-m', `${taskBranch}: merge task`], {
114
+ encoding: 'utf8',
115
+ });
116
+ if (result.status !== 0) {
117
+ spawnSync('git', ['-C', repositoryRoot, 'merge', '--abort'], { encoding: 'utf8' });
118
+ runGit(repositoryRoot, ['switch', taskBranch]);
119
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', `Merge conflict while merging ${taskBranch}.`, {
120
+ stdout: result.stdout.trim(),
121
+ stderr: result.stderr.trim(),
122
+ });
123
+ }
124
+ return headCommit(repositoryRoot);
125
+ }
126
+
127
+ export function syncBaseIntoTask(
128
+ repositoryRoot: string,
129
+ taskBranch: string,
130
+ baseBranch: string,
131
+ ): { baseCommit: string; syncCommit: string } {
132
+ if (!isClean(repositoryRoot)) throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Base sync requires a clean checkout.');
133
+ if (currentBranch(repositoryRoot) !== taskBranch) {
134
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', `Expected current task branch ${taskBranch}.`);
135
+ }
136
+ const result = spawnSync('git', ['-C', repositoryRoot, 'merge', '--no-edit', baseBranch], { encoding: 'utf8' });
137
+ if (result.status !== 0) {
138
+ const abort = spawnSync('git', ['-C', repositoryRoot, 'merge', '--abort'], { encoding: 'utf8' });
139
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', `Base sync conflict from ${baseBranch}.`, {
140
+ cleanAbortVerified: abort.status === 0 && isClean(repositoryRoot),
141
+ stdout: result.stdout.trim(),
142
+ stderr: result.stderr.trim(),
143
+ });
144
+ }
145
+ return {
146
+ baseCommit: runGit(repositoryRoot, ['rev-parse', baseBranch]),
147
+ syncCommit: headCommit(repositoryRoot),
148
+ };
149
+ }
150
+
151
+ export function validateTaskHistory(
152
+ repositoryRoot: string,
153
+ task: TaskState,
154
+ historyBoundary = 'HEAD',
155
+ ): void {
156
+ if (!task.baseCommit) throw new WorkflowError('STATE_CORRUPT', `Task ${task.id} has no base commit.`);
157
+ const boundaryCommit = runGit(repositoryRoot, ['rev-parse', historyBoundary]);
158
+ const actualOutput = runGit(repositoryRoot, ['rev-list', '--reverse', `${task.baseCommit}..${boundaryCommit}`]);
159
+ const actual = actualOutput ? actualOutput.split('\n') : [];
160
+ const expected = [...new Set([
161
+ ...task.steps.flatMap((step) => (step.evidence ? [step.evidence.commitSha] : [])),
162
+ ...task.systemCommits,
163
+ ...(task.invalidatedStepCommits ?? []),
164
+ ...(task.historicalStepProvenanceRecoveries ?? []).map((recovery) => recovery.commitSha),
165
+ ...(task.dependencyProvenanceRecoveries ?? []).flatMap((recovery) => recovery.retainedHistoricalCommits ?? []),
166
+ ])];
167
+ const missing = expected.filter((commit) => !actual.includes(commit));
168
+ const unexpected = actual.filter((commit) => !expected.includes(commit));
169
+ if (missing.length > 0 || unexpected.length > 0 || actual.length !== expected.length) {
170
+ throw new WorkflowError(
171
+ 'GIT_PRECONDITION_FAILED',
172
+ 'Task branch history contains commits not recorded by Workflow V2. Workers must leave Step changes uncommitted and let task step-complete create the commit. Stop and request coordinator-led Git recovery; do not create another commit or edit workflow state.',
173
+ {
174
+ missing,
175
+ unexpected,
176
+ baseCommit: task.baseCommit,
177
+ headCommit: headCommit(repositoryRoot),
178
+ historyBoundary: boundaryCommit,
179
+ recovery: 'Restore the pre-commit working changes under coordinator control, then invoke task step-complete.',
180
+ },
181
+ );
182
+ }
183
+ }
package/src/graph.ts ADDED
@@ -0,0 +1,342 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import {
3
+ copyFileSync,
4
+ existsSync,
5
+ lstatSync,
6
+ mkdirSync,
7
+ readdirSync,
8
+ readFileSync,
9
+ renameSync,
10
+ rmSync,
11
+ } from 'node:fs';
12
+ import os from 'node:os';
13
+ import path from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { STATE_SCHEMA_VERSION, PROTOCOL_VERSION } from './contracts.js';
16
+ import type {
17
+ GraphBinding,
18
+ GraphBindingSnapshot,
19
+ GraphKind,
20
+ GraphMode,
21
+ GraphRefreshEvidence,
22
+ GraphRefreshRequest,
23
+ } from './contracts.js';
24
+ import { WorkflowError } from './errors.js';
25
+ import { writeJsonAtomic } from './fs-utils.js';
26
+ import { headCommit } from './repository.js';
27
+ import { PACKAGE_NAME, PACKAGE_VERSION } from './version.js';
28
+
29
+ export interface GraphManifest {
30
+ packageName: string;
31
+ packageVersion: string;
32
+ protocolVersion: number;
33
+ stateSchemaVersion: number;
34
+ createdAt: string;
35
+ files: Array<{ path: string; sha256: string }>;
36
+ }
37
+
38
+ const WORKFLOW_GRAPH_SOURCE_ROOTS = ['src', 'roles', 'references', 'schemas', 'plugins/codex-workflow-gateway'] as const;
39
+
40
+ export function graphViewRoot(): string {
41
+ const codexHome = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
42
+ return path.join(codexHome, 'workflow-cache', 'codex-workflow', PACKAGE_VERSION);
43
+ }
44
+
45
+ export function prepareGraphView(target = graphViewRoot()): { root: string; manifest: GraphManifest } {
46
+ const packageRoot = resolvePackageRoot();
47
+ const temporary = `${target}.tmp-${randomUUID()}`;
48
+ rmSync(temporary, { recursive: true, force: true });
49
+ mkdirSync(temporary, { recursive: true, mode: 0o700 });
50
+ try {
51
+ for (const relative of ['dist/src', 'roles', 'references', 'schemas']) {
52
+ const source = path.join(packageRoot, relative);
53
+ if (!existsSync(source)) {
54
+ throw new WorkflowError('NOT_FOUND', `Graph source is missing from package: ${relative}`);
55
+ }
56
+ copyTreeWithoutSymlinks(source, path.join(temporary, relative));
57
+ }
58
+ const files = listFiles(temporary).map((file) => ({
59
+ path: path.relative(temporary, file).replaceAll('\\', '/'),
60
+ sha256: createHash('sha256').update(readFileSync(file)).digest('hex'),
61
+ }));
62
+ const manifest: GraphManifest = {
63
+ packageName: PACKAGE_NAME,
64
+ packageVersion: PACKAGE_VERSION,
65
+ protocolVersion: PROTOCOL_VERSION,
66
+ stateSchemaVersion: STATE_SCHEMA_VERSION,
67
+ createdAt: new Date().toISOString(),
68
+ files,
69
+ };
70
+ writeJsonAtomic(path.join(temporary, 'manifest.json'), manifest);
71
+ rmSync(target, { recursive: true, force: true });
72
+ mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
73
+ renameSync(temporary, target);
74
+ return { root: target, manifest };
75
+ } finally {
76
+ rmSync(temporary, { recursive: true, force: true });
77
+ }
78
+ }
79
+
80
+ export function inspectGraphView(target = graphViewRoot()): { present: boolean; current: boolean; root: string } {
81
+ const manifestFile = path.join(target, 'manifest.json');
82
+ if (!existsSync(manifestFile)) return { present: false, current: false, root: target };
83
+ try {
84
+ const manifest = JSON.parse(readFileSync(manifestFile, 'utf8')) as GraphManifest;
85
+ return {
86
+ present: true,
87
+ current:
88
+ manifest.packageName === PACKAGE_NAME &&
89
+ manifest.packageVersion === PACKAGE_VERSION &&
90
+ manifest.protocolVersion === PROTOCOL_VERSION &&
91
+ manifest.stateSchemaVersion === STATE_SCHEMA_VERSION,
92
+ root: target,
93
+ };
94
+ } catch {
95
+ return { present: true, current: false, root: target };
96
+ }
97
+ }
98
+
99
+ export function createGraphRefreshRequest(
100
+ repositoryRoot: string,
101
+ projectId: string,
102
+ graphKind: GraphKind,
103
+ mode: GraphMode = 'moderate',
104
+ now: Date = new Date(),
105
+ ): GraphRefreshRequest {
106
+ if (graphKind === 'product') {
107
+ const sourceRoot = path.resolve(repositoryRoot);
108
+ return {
109
+ protocolVersion: PROTOCOL_VERSION,
110
+ requestId: randomUUID(),
111
+ projectId,
112
+ graphKind,
113
+ sourceRoot,
114
+ rootFingerprint: createHash('sha256').update(`${projectId}\0${sourceRoot}`).digest('hex'),
115
+ indexedHead: headCommit(sourceRoot),
116
+ packageVersion: null,
117
+ manifestHash: null,
118
+ mode,
119
+ persistence: false,
120
+ issuedAt: now.toISOString(),
121
+ };
122
+ }
123
+ const packageRoot = resolvePackageRoot();
124
+ const manifest = workflowPackageGraphManifest(packageRoot, now);
125
+ const manifestHash = hashGraphManifest(manifest);
126
+ return {
127
+ protocolVersion: PROTOCOL_VERSION,
128
+ requestId: randomUUID(),
129
+ projectId,
130
+ graphKind,
131
+ sourceRoot: packageRoot,
132
+ rootFingerprint: createHash('sha256').update(`${PACKAGE_NAME}\0${PACKAGE_VERSION}\0${manifestHash}`).digest('hex'),
133
+ indexedHead: null,
134
+ packageVersion: PACKAGE_VERSION,
135
+ manifestHash,
136
+ mode,
137
+ persistence: false,
138
+ issuedAt: now.toISOString(),
139
+ };
140
+ }
141
+
142
+ function workflowPackageGraphManifest(packageRoot: string, now: Date): GraphManifest {
143
+ const files = WORKFLOW_GRAPH_SOURCE_ROOTS.flatMap((relative) => {
144
+ const source = path.join(packageRoot, relative);
145
+ if (!existsSync(source)) {
146
+ throw new WorkflowError('NOT_FOUND', `Workflow graph source is missing from package: ${relative}`);
147
+ }
148
+ return listFiles(source).map((file) => ({
149
+ path: path.relative(packageRoot, file).replaceAll('\\', '/'),
150
+ sha256: createHash('sha256').update(readFileSync(file)).digest('hex'),
151
+ }));
152
+ }).sort((left, right) => left.path.localeCompare(right.path));
153
+ return {
154
+ packageName: PACKAGE_NAME,
155
+ packageVersion: PACKAGE_VERSION,
156
+ protocolVersion: PROTOCOL_VERSION,
157
+ stateSchemaVersion: STATE_SCHEMA_VERSION,
158
+ createdAt: now.toISOString(),
159
+ files,
160
+ };
161
+ }
162
+
163
+ export function bindGraphEvidence(
164
+ projectId: string,
165
+ request: GraphRefreshRequest,
166
+ evidence: GraphRefreshEvidence,
167
+ existing: GraphBinding | null,
168
+ now: Date = new Date(),
169
+ ): GraphBinding {
170
+ validateGraphEvidence(request, evidence);
171
+ const timestamp = now.toISOString();
172
+ const snapshot: GraphBindingSnapshot = {
173
+ projectKey: evidence.projectKey,
174
+ rootFingerprint: evidence.rootFingerprint,
175
+ indexedHead: evidence.indexedHead,
176
+ packageVersion: evidence.packageVersion,
177
+ manifestHash: evidence.manifestHash,
178
+ mode: evidence.mode,
179
+ status: evidence.status,
180
+ nodeCount: evidence.nodeCount,
181
+ edgeCount: evidence.edgeCount,
182
+ observedAt: evidence.observedAt,
183
+ evidenceHash: createHash('sha256').update(JSON.stringify(evidence)).digest('hex'),
184
+ fallbackReason: null,
185
+ };
186
+ const retainPrevious = request.graphKind === 'workflow' && existing?.packageVersion !== snapshot.packageVersion;
187
+ return {
188
+ schemaVersion: STATE_SCHEMA_VERSION,
189
+ revision: existing?.revision ?? 0,
190
+ id: `GRAPH-${request.graphKind}`,
191
+ projectId,
192
+ createdAt: existing?.createdAt ?? timestamp,
193
+ updatedAt: timestamp,
194
+ kind: 'graph-binding',
195
+ graphKind: request.graphKind,
196
+ provider: 'codebase-memory-mcp',
197
+ ...snapshot,
198
+ previous: retainPrevious && existing ? graphSnapshot(existing) : existing?.previous ?? null,
199
+ };
200
+ }
201
+
202
+ export function fallbackGraphBinding(
203
+ projectId: string,
204
+ request: GraphRefreshRequest,
205
+ reason: string,
206
+ existing: GraphBinding | null,
207
+ now: Date = new Date(),
208
+ ): GraphBinding {
209
+ if (!reason.trim()) throw new WorkflowError('INVALID_ARGUMENT', 'Graph fallback requires a reason.');
210
+ const timestamp = now.toISOString();
211
+ const evidenceHash = createHash('sha256')
212
+ .update(JSON.stringify({ requestId: request.requestId, reason: reason.trim(), observedAt: timestamp }))
213
+ .digest('hex');
214
+ return {
215
+ schemaVersion: STATE_SCHEMA_VERSION,
216
+ revision: existing?.revision ?? 0,
217
+ id: `GRAPH-${request.graphKind}`,
218
+ projectId,
219
+ createdAt: existing?.createdAt ?? timestamp,
220
+ updatedAt: timestamp,
221
+ kind: 'graph-binding',
222
+ graphKind: request.graphKind,
223
+ provider: 'codebase-memory-mcp',
224
+ projectKey: existing?.projectKey ?? null,
225
+ rootFingerprint: request.rootFingerprint,
226
+ indexedHead: request.indexedHead,
227
+ packageVersion: request.packageVersion,
228
+ manifestHash: request.manifestHash,
229
+ mode: request.mode,
230
+ status: 'fallback',
231
+ nodeCount: existing?.nodeCount ?? null,
232
+ edgeCount: existing?.edgeCount ?? null,
233
+ observedAt: timestamp,
234
+ evidenceHash,
235
+ fallbackReason: reason.trim(),
236
+ previous: existing?.previous ?? null,
237
+ };
238
+ }
239
+
240
+ export function inspectGraphBinding(binding: GraphBinding, request: GraphRefreshRequest): GraphBinding {
241
+ const stale =
242
+ binding.rootFingerprint !== request.rootFingerprint ||
243
+ binding.mode !== request.mode ||
244
+ binding.indexedHead !== request.indexedHead ||
245
+ binding.packageVersion !== request.packageVersion ||
246
+ binding.manifestHash !== request.manifestHash;
247
+ return stale && binding.status === 'ready' ? { ...binding, status: 'stale' } : binding;
248
+ }
249
+
250
+ export function validateGraphRequestCurrent(request: GraphRefreshRequest, current: GraphRefreshRequest): void {
251
+ const fields: Array<keyof GraphRefreshRequest> = [
252
+ 'projectId',
253
+ 'graphKind',
254
+ 'sourceRoot',
255
+ 'rootFingerprint',
256
+ 'indexedHead',
257
+ 'packageVersion',
258
+ 'manifestHash',
259
+ 'mode',
260
+ 'persistence',
261
+ ];
262
+ const stale = fields.filter((field) => request[field] !== current[field]);
263
+ if (request.protocolVersion !== PROTOCOL_VERSION || stale.length > 0) {
264
+ throw new WorkflowError('STATE_CONFLICT', 'Graph refresh request is stale or incompatible.', { stale });
265
+ }
266
+ }
267
+
268
+ export function hashGraphManifest(manifest: GraphManifest): string {
269
+ return createHash('sha256').update(JSON.stringify({
270
+ packageName: manifest.packageName,
271
+ packageVersion: manifest.packageVersion,
272
+ protocolVersion: manifest.protocolVersion,
273
+ stateSchemaVersion: manifest.stateSchemaVersion,
274
+ files: manifest.files,
275
+ })).digest('hex');
276
+ }
277
+
278
+ function validateGraphEvidence(request: GraphRefreshRequest, evidence: GraphRefreshEvidence): void {
279
+ const invalid =
280
+ evidence.requestId !== request.requestId ||
281
+ evidence.provider !== 'codebase-memory-mcp' ||
282
+ evidence.rootFingerprint !== request.rootFingerprint ||
283
+ evidence.indexedHead !== request.indexedHead ||
284
+ evidence.packageVersion !== request.packageVersion ||
285
+ evidence.manifestHash !== request.manifestHash ||
286
+ evidence.mode !== request.mode;
287
+ if (invalid || !evidence.projectKey.trim()) {
288
+ throw new WorkflowError('INVALID_ARGUMENT', 'Graph evidence does not match the refresh request.');
289
+ }
290
+ }
291
+
292
+ function graphSnapshot(binding: GraphBinding): GraphBindingSnapshot {
293
+ return {
294
+ projectKey: binding.projectKey,
295
+ rootFingerprint: binding.rootFingerprint,
296
+ indexedHead: binding.indexedHead,
297
+ packageVersion: binding.packageVersion,
298
+ manifestHash: binding.manifestHash,
299
+ mode: binding.mode,
300
+ status: binding.status,
301
+ nodeCount: binding.nodeCount,
302
+ edgeCount: binding.edgeCount,
303
+ observedAt: binding.observedAt,
304
+ evidenceHash: binding.evidenceHash,
305
+ fallbackReason: binding.fallbackReason,
306
+ };
307
+ }
308
+
309
+ function resolvePackageRoot(): string {
310
+ let cursor = path.dirname(fileURLToPath(import.meta.url));
311
+ for (let depth = 0; depth < 4; depth += 1) {
312
+ const packageFile = path.join(cursor, 'package.json');
313
+ if (existsSync(packageFile)) {
314
+ const parsed = JSON.parse(readFileSync(packageFile, 'utf8')) as { name?: string };
315
+ if (parsed.name === PACKAGE_NAME) return cursor;
316
+ }
317
+ cursor = path.dirname(cursor);
318
+ }
319
+ throw new WorkflowError('NOT_FOUND', `Cannot resolve installed ${PACKAGE_NAME} package root.`);
320
+ }
321
+
322
+ function copyTreeWithoutSymlinks(source: string, destination: string): void {
323
+ const metadata = lstatSync(source);
324
+ if (metadata.isSymbolicLink()) {
325
+ throw new WorkflowError('STATE_CORRUPT', `Graph source must not be a symlink: ${source}`);
326
+ }
327
+ if (metadata.isDirectory()) {
328
+ mkdirSync(destination, { recursive: true, mode: 0o700 });
329
+ for (const entry of readdirSync(source)) {
330
+ copyTreeWithoutSymlinks(path.join(source, entry), path.join(destination, entry));
331
+ }
332
+ return;
333
+ }
334
+ copyFileSync(source, destination);
335
+ }
336
+
337
+ function listFiles(root: string): string[] {
338
+ return readdirSync(root, { withFileTypes: true }).flatMap((entry) => {
339
+ const target = path.join(root, entry.name);
340
+ return entry.isDirectory() ? listFiles(target) : [target];
341
+ }).sort();
342
+ }