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
package/src/memory.ts ADDED
@@ -0,0 +1,273 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import {
5
+ STATE_SCHEMA_VERSION,
6
+ type KnowledgeCategory,
7
+ type KnowledgeConflict,
8
+ type KnowledgeGap,
9
+ type KnowledgeSelection,
10
+ type KnowledgeSource,
11
+ type ProjectKnowledgeMap,
12
+ type WorkflowRole,
13
+ } from './contracts.js';
14
+ import { WorkflowError } from './errors.js';
15
+
16
+ export const KNOWLEDGE_CATEGORIES: readonly KnowledgeCategory[] = [
17
+ 'agent-guidance',
18
+ 'onboarding',
19
+ 'architecture',
20
+ 'decisions',
21
+ 'commands',
22
+ 'ownership',
23
+ 'testing',
24
+ 'operations',
25
+ 'domain',
26
+ ];
27
+
28
+ const ignoredDirectories = new Set([
29
+ '.git',
30
+ '.worktrees',
31
+ '.test-dist',
32
+ 'node_modules',
33
+ 'dist',
34
+ 'build',
35
+ 'coverage',
36
+ ]);
37
+
38
+ export function scanProjectKnowledge(
39
+ repositoryRoot: string,
40
+ projectId: string,
41
+ now: Date = new Date(),
42
+ ): ProjectKnowledgeMap {
43
+ const root = realpathSync(repositoryRoot);
44
+ const timestamp = now.toISOString();
45
+ const entries: KnowledgeSource[] = [];
46
+ walk(root, root, (absolute, relative) => {
47
+ const category = classifyKnowledgePath(relative);
48
+ if (!category) return;
49
+ const scope = normalizeScope(path.posix.dirname(relative));
50
+ entries.push({
51
+ id: sourceId(category, relative),
52
+ category,
53
+ path: relative,
54
+ scope,
55
+ authority: 'supporting',
56
+ contentHash: hashFile(absolute),
57
+ discoveredBy: 'repository-scan',
58
+ lastVerifiedAt: timestamp,
59
+ });
60
+ });
61
+ entries.sort(compareSources);
62
+ const gaps = knowledgeGaps(entries);
63
+ return {
64
+ schemaVersion: STATE_SCHEMA_VERSION,
65
+ revision: 0,
66
+ id: 'PROJECT-KNOWLEDGE-MAP',
67
+ projectId,
68
+ createdAt: timestamp,
69
+ updatedAt: timestamp,
70
+ kind: 'knowledge-map',
71
+ status: 'awaiting_approval',
72
+ entries,
73
+ gaps,
74
+ conflicts: [],
75
+ approval: null,
76
+ };
77
+ }
78
+
79
+ export function applyKnowledgeSelections(
80
+ map: ProjectKnowledgeMap,
81
+ selections: KnowledgeSelection[],
82
+ actor: string,
83
+ now: Date = new Date(),
84
+ ): ProjectKnowledgeMap {
85
+ const byId = new Map(map.entries.map((entry) => [entry.id, entry]));
86
+ const selectedIds = new Set<string>();
87
+ for (const selection of selections) {
88
+ if (selectedIds.has(selection.sourceId)) {
89
+ throw new WorkflowError('INVALID_ARGUMENT', `Duplicate knowledge selection: ${selection.sourceId}`);
90
+ }
91
+ selectedIds.add(selection.sourceId);
92
+ if (!byId.has(selection.sourceId)) {
93
+ throw new WorkflowError('INVALID_ARGUMENT', `Unknown knowledge source: ${selection.sourceId}`);
94
+ }
95
+ }
96
+ const authority = new Map(selections.map((selection) => [selection.sourceId, selection.authority]));
97
+ const entries = map.entries
98
+ .map((entry) => ({ ...entry, authority: authority.get(entry.id) ?? entry.authority }))
99
+ .sort(compareSources);
100
+ const conflicts = knowledgeConflicts(entries);
101
+ const candidate: ProjectKnowledgeMap = {
102
+ ...map,
103
+ entries,
104
+ gaps: knowledgeGaps(entries),
105
+ conflicts,
106
+ approval: null,
107
+ status: conflicts.length > 0 ? 'conflicted' : 'awaiting_approval',
108
+ };
109
+ if (conflicts.length > 0) return candidate;
110
+ const mapHash = hashKnowledgeMap(candidate);
111
+ return {
112
+ ...candidate,
113
+ status: 'active',
114
+ approval: {
115
+ mapHash,
116
+ actor: actor.trim() || 'user',
117
+ approvedAt: now.toISOString(),
118
+ },
119
+ };
120
+ }
121
+
122
+ export function reconcileKnowledgeMap(
123
+ current: ProjectKnowledgeMap,
124
+ scanned: ProjectKnowledgeMap,
125
+ ): ProjectKnowledgeMap {
126
+ const previous = new Map(current.entries.map((entry) => [`${entry.category}\0${entry.path}\0${entry.scope}`, entry]));
127
+ const entries = scanned.entries.map((entry) => {
128
+ const existing = previous.get(`${entry.category}\0${entry.path}\0${entry.scope}`);
129
+ return existing ? { ...entry, authority: existing.authority } : entry;
130
+ });
131
+ const conflicts = knowledgeConflicts(entries);
132
+ const candidate = {
133
+ ...scanned,
134
+ createdAt: current.createdAt,
135
+ entries: entries.sort(compareSources),
136
+ gaps: knowledgeGaps(entries),
137
+ conflicts,
138
+ status: conflicts.length > 0 ? 'conflicted' as const : 'awaiting_approval' as const,
139
+ };
140
+ if (current.status !== 'active' || !current.approval) return candidate;
141
+ return hashKnowledgeMap(candidate) === current.approval.mapHash
142
+ ? { ...candidate, status: 'active', approval: current.approval }
143
+ : { ...candidate, status: conflicts.length > 0 ? 'conflicted' : 'stale', approval: null };
144
+ }
145
+
146
+ export function inspectKnowledgeMap(
147
+ current: ProjectKnowledgeMap,
148
+ scanned: ProjectKnowledgeMap,
149
+ ): ProjectKnowledgeMap {
150
+ const reconciled = reconcileKnowledgeMap(current, scanned);
151
+ return { ...reconciled, revision: current.revision, updatedAt: current.updatedAt };
152
+ }
153
+
154
+ export function hashKnowledgeMap(map: Pick<ProjectKnowledgeMap, 'projectId' | 'entries' | 'gaps' | 'conflicts'>): string {
155
+ const stable = {
156
+ projectId: map.projectId,
157
+ entries: [...map.entries].sort(compareSources).map(({ lastVerifiedAt: _verified, ...entry }) => entry),
158
+ gaps: [...map.gaps].sort(compareGaps),
159
+ conflicts: [...map.conflicts].sort(compareConflicts),
160
+ };
161
+ return createHash('sha256').update(JSON.stringify(stable)).digest('hex');
162
+ }
163
+
164
+ export function selectKnowledgeSources(
165
+ map: ProjectKnowledgeMap,
166
+ role: WorkflowRole,
167
+ targetPaths: string[] = [],
168
+ ): string[] {
169
+ const targets = new Set(targetPaths.map(normalizeRelativePath));
170
+ const roleCategories: Record<WorkflowRole, Set<KnowledgeCategory>> = {
171
+ 'scope-lead': new Set(KNOWLEDGE_CATEGORIES),
172
+ 'technical-planner': new Set(['agent-guidance', 'onboarding', 'architecture', 'decisions', 'commands', 'ownership', 'testing', 'domain']),
173
+ 'delivery-coordinator': new Set(['agent-guidance', 'commands', 'ownership', 'testing', 'operations']),
174
+ worker: new Set(['agent-guidance', 'commands', 'testing']),
175
+ 'independent-reviewer': new Set(KNOWLEDGE_CATEGORIES),
176
+ };
177
+ return map.entries
178
+ .filter((entry) => entry.authority === 'canonical' || targets.has(entry.path))
179
+ .filter((entry) => roleCategories[role].has(entry.category) || targets.has(entry.path))
180
+ .map((entry) => entry.path)
181
+ .sort();
182
+ }
183
+
184
+ export function normalizeRelativePath(value: string): string {
185
+ const normalized = value.replaceAll('\\', '/').replace(/^\.\//, '');
186
+ if (!normalized || normalized === '.' || path.posix.isAbsolute(normalized)) {
187
+ throw new WorkflowError('INVALID_ARGUMENT', `Knowledge path must be repository-relative: ${value}`);
188
+ }
189
+ const clean = path.posix.normalize(normalized);
190
+ if (clean === '..' || clean.startsWith('../')) {
191
+ throw new WorkflowError('INVALID_ARGUMENT', `Knowledge path escapes repository root: ${value}`);
192
+ }
193
+ return clean;
194
+ }
195
+
196
+ function walk(root: string, directory: string, visit: (absolute: string, relative: string) => void): void {
197
+ const entries = readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
198
+ for (const entry of entries) {
199
+ if (entry.isDirectory() && ignoredDirectories.has(entry.name)) continue;
200
+ const absolute = path.join(directory, entry.name);
201
+ if (entry.isSymbolicLink()) continue;
202
+ if (entry.isDirectory()) {
203
+ walk(root, absolute, visit);
204
+ continue;
205
+ }
206
+ if (!entry.isFile() || !lstatSync(absolute).isFile()) continue;
207
+ const relative = path.relative(root, absolute).replaceAll(path.sep, '/');
208
+ visit(absolute, normalizeRelativePath(relative));
209
+ }
210
+ }
211
+
212
+ function classifyKnowledgePath(relative: string): KnowledgeCategory | null {
213
+ const lower = relative.toLowerCase();
214
+ const base = path.posix.basename(lower);
215
+ if (base === 'agents.md') return 'agent-guidance';
216
+ if (/^readme(?:\.[a-z0-9_-]+)?$/.test(base)) return 'onboarding';
217
+ if (base === 'codeowners' || base === 'owners') return 'ownership';
218
+ if (base === 'package.json' || base === 'makefile' || base === 'justfile' || /^taskfile(?:\..+)?$/.test(base)) return 'commands';
219
+ if (/(^|\/)(adr|adrs|decisions)(\/|$)/.test(lower) || /^adr[-_].+\.md$/.test(base)) return 'decisions';
220
+ if (/(^|\/)(architecture|architectures)(\/|$)/.test(lower) || /architecture.*\.md$/.test(base)) return 'architecture';
221
+ if (/(^|\/)(domain|domains)(\/|$)/.test(lower)) return 'domain';
222
+ if (/(^|\/)(operations|runbooks?)(\/|$)/.test(lower) || /^dockerfile/.test(base) || /^compose.*\.ya?ml$/.test(base)) return 'operations';
223
+ if (/(^|\/)(\.github\/workflows|tests?|testing)(\/|$)/.test(lower) || /^(vitest|jest|playwright|cypress|pytest|test).*(json|js|ts|mjs|cjs|toml|ini)$/.test(base)) return 'testing';
224
+ return null;
225
+ }
226
+
227
+ function knowledgeGaps(entries: KnowledgeSource[]): KnowledgeGap[] {
228
+ const present = new Set(entries.map((entry) => entry.category));
229
+ return KNOWLEDGE_CATEGORIES
230
+ .filter((category) => !present.has(category))
231
+ .map((category) => ({ category, scope: '.', reason: `No ${category} source was discovered.` }));
232
+ }
233
+
234
+ function knowledgeConflicts(entries: KnowledgeSource[]): KnowledgeConflict[] {
235
+ const groups = new Map<string, KnowledgeSource[]>();
236
+ for (const entry of entries.filter((candidate) => candidate.authority === 'canonical')) {
237
+ const key = `${entry.category}\0${entry.scope}`;
238
+ groups.set(key, [...(groups.get(key) ?? []), entry]);
239
+ }
240
+ return [...groups.values()]
241
+ .filter((group) => group.length > 1)
242
+ .map((group) => ({
243
+ category: group[0]!.category,
244
+ scope: group[0]!.scope,
245
+ sourceIds: group.map((entry) => entry.id).sort(),
246
+ reason: 'More than one canonical source exists for the same category and scope.',
247
+ }))
248
+ .sort(compareConflicts);
249
+ }
250
+
251
+ function sourceId(category: KnowledgeCategory, relative: string): string {
252
+ return `KS-${createHash('sha256').update(`${category}\0${relative}`).digest('hex').slice(0, 16)}`;
253
+ }
254
+
255
+ function hashFile(file: string): string {
256
+ return createHash('sha256').update(readFileSync(file)).digest('hex');
257
+ }
258
+
259
+ function normalizeScope(value: string): string {
260
+ return value === '.' || value === '' ? '.' : normalizeRelativePath(value);
261
+ }
262
+
263
+ function compareSources(left: KnowledgeSource, right: KnowledgeSource): number {
264
+ return `${left.category}\0${left.scope}\0${left.path}`.localeCompare(`${right.category}\0${right.scope}\0${right.path}`);
265
+ }
266
+
267
+ function compareGaps(left: KnowledgeGap, right: KnowledgeGap): number {
268
+ return `${left.category}\0${left.scope}`.localeCompare(`${right.category}\0${right.scope}`);
269
+ }
270
+
271
+ function compareConflicts(left: KnowledgeConflict, right: KnowledgeConflict): number {
272
+ return `${left.category}\0${left.scope}`.localeCompare(`${right.category}\0${right.scope}`);
273
+ }
@@ -0,0 +1,161 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, lstatSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { gzipSync } from 'node:zlib';
5
+ import { STATE_SCHEMA_VERSION } from './contracts.js';
6
+ import { WorkflowError } from './errors.js';
7
+ import { ensureDirectory } from './fs-utils.js';
8
+ import { resolveRepositoryIdentity, runGit } from './repository.js';
9
+ import { FileStateStore } from './state/store.js';
10
+
11
+ const LEGACY_CANDIDATES = ['.tasks', '.increments', '.codex-workflow.json', '.codex-workflow.lock.json'];
12
+
13
+ interface SnapshotFile {
14
+ path: string;
15
+ mode: number;
16
+ size: number;
17
+ sha256: string;
18
+ contentBase64: string;
19
+ }
20
+
21
+ interface LegacySnapshot {
22
+ format: 'codex-workflow-v1-snapshot';
23
+ version: 1;
24
+ projectId: string;
25
+ repositoryRootFingerprint: string;
26
+ git: {
27
+ head: string;
28
+ branch: string | null;
29
+ statusPorcelainV1: string[];
30
+ refs: string[];
31
+ };
32
+ files: SnapshotFile[];
33
+ }
34
+
35
+ export interface MigrationReport {
36
+ dryRun: boolean;
37
+ snapshotRequested: boolean;
38
+ projectId: string;
39
+ stateSchemaVersion: number;
40
+ entities: { discoveries: number; tasks: number; milestones: number };
41
+ actions: string[];
42
+ legacy: {
43
+ detected: boolean;
44
+ paths: string[];
45
+ automaticMappingSupported: false;
46
+ };
47
+ snapshot: null | {
48
+ format: LegacySnapshot['format'];
49
+ contentHash: string;
50
+ file: string;
51
+ fileCount: number;
52
+ };
53
+ }
54
+
55
+ export function migrateState(
56
+ repository: string,
57
+ store: FileStateStore,
58
+ dryRun: boolean,
59
+ snapshotRequested = !dryRun,
60
+ ): MigrationReport {
61
+ if (dryRun && snapshotRequested) {
62
+ throw new WorkflowError('INVALID_ARGUMENT', 'Choose either migration dry-run or snapshot creation.');
63
+ }
64
+ const identity = resolveRepositoryIdentity(repository);
65
+ store.registerProject(identity);
66
+ const legacyPaths = LEGACY_CANDIDATES.filter((candidate) => existsSync(path.join(identity.repositoryRoot, candidate)));
67
+ const report: MigrationReport = {
68
+ dryRun,
69
+ snapshotRequested,
70
+ projectId: identity.projectId,
71
+ stateSchemaVersion: STATE_SCHEMA_VERSION,
72
+ entities: {
73
+ discoveries: store.listDiscoveries(identity.projectId).length,
74
+ tasks: store.listTasks(identity.projectId).length,
75
+ milestones: store.listMilestones(identity.projectId).length,
76
+ },
77
+ actions: [],
78
+ legacy: {
79
+ detected: legacyPaths.length > 0,
80
+ paths: legacyPaths,
81
+ automaticMappingSupported: false,
82
+ },
83
+ snapshot: null,
84
+ };
85
+ if (!snapshotRequested) return report;
86
+ if (legacyPaths.length === 0) {
87
+ throw new WorkflowError('NOT_FOUND', 'No legacy V1 workflow state was detected.');
88
+ }
89
+ const snapshot = buildSnapshot(identity.repositoryRoot, identity.projectId, legacyPaths);
90
+ const bytes = gzipSync(Buffer.from(`${JSON.stringify(snapshot)}\n`, 'utf8'), { level: 9 });
91
+ const contentHash = createHash('sha256').update(bytes).digest('hex');
92
+ const directory = store.snapshotRoot(identity.projectId);
93
+ const file = path.join(directory, `${contentHash}.snapshot.json.gz`);
94
+ ensureDirectory(directory);
95
+ if (!existsSync(file)) writeFileSync(file, bytes, { mode: 0o600, flag: 'wx' });
96
+ report.actions.push('Created immutable legacy snapshot; no V1 state was imported or modified.');
97
+ report.snapshot = {
98
+ format: snapshot.format,
99
+ contentHash,
100
+ file,
101
+ fileCount: snapshot.files.length,
102
+ };
103
+ return report;
104
+ }
105
+
106
+ function buildSnapshot(repositoryRoot: string, projectId: string, legacyPaths: string[]): LegacySnapshot {
107
+ const files = legacyPaths.flatMap((relative) => collectFiles(repositoryRoot, relative));
108
+ files.sort((left, right) => left.path.localeCompare(right.path));
109
+ const branch = safeGit(repositoryRoot, ['symbolic-ref', '--short', 'HEAD']) || null;
110
+ return {
111
+ format: 'codex-workflow-v1-snapshot',
112
+ version: 1,
113
+ projectId,
114
+ repositoryRootFingerprint: createHash('sha256').update(path.resolve(repositoryRoot)).digest('hex'),
115
+ git: {
116
+ head: runGit(repositoryRoot, ['rev-parse', 'HEAD']),
117
+ branch,
118
+ statusPorcelainV1: safeGit(repositoryRoot, ['status', '--porcelain=v1', '--untracked-files=all'])
119
+ .split('\n')
120
+ .filter(Boolean)
121
+ .sort(),
122
+ refs: safeGit(repositoryRoot, ['for-each-ref', '--format=%(refname) %(objectname)'])
123
+ .split('\n')
124
+ .filter(Boolean)
125
+ .sort(),
126
+ },
127
+ files,
128
+ };
129
+ }
130
+
131
+ function collectFiles(repositoryRoot: string, relative: string): SnapshotFile[] {
132
+ const absolute = path.join(repositoryRoot, relative);
133
+ const stat = lstatSync(absolute);
134
+ if (stat.isSymbolicLink()) {
135
+ throw new WorkflowError('TRANSITION_BLOCKED', `Legacy snapshot rejects symbolic link: ${relative}`);
136
+ }
137
+ if (stat.isDirectory()) {
138
+ return readdirSync(absolute)
139
+ .sort()
140
+ .flatMap((name) => collectFiles(repositoryRoot, path.join(relative, name)));
141
+ }
142
+ if (!stat.isFile()) {
143
+ throw new WorkflowError('TRANSITION_BLOCKED', `Legacy snapshot supports regular files only: ${relative}`);
144
+ }
145
+ const content = readFileSync(absolute);
146
+ return [{
147
+ path: relative.split(path.sep).join('/'),
148
+ mode: stat.mode & 0o777,
149
+ size: content.length,
150
+ sha256: createHash('sha256').update(content).digest('hex'),
151
+ contentBase64: content.toString('base64'),
152
+ }];
153
+ }
154
+
155
+ function safeGit(repositoryRoot: string, args: string[]): string {
156
+ try {
157
+ return runGit(repositoryRoot, args);
158
+ } catch {
159
+ return '';
160
+ }
161
+ }
@@ -0,0 +1,70 @@
1
+ import { isCliCommand, type CliCommand } from './cli-actions.js';
2
+
3
+ type ObjectValue = Record<string, unknown>;
4
+ const object = (value: unknown): ObjectValue =>
5
+ value !== null && typeof value === 'object' && !Array.isArray(value) ? value as ObjectValue : {};
6
+ const unblocked = (value: ObjectValue) => value.eligible !== false && value.blocked !== true
7
+ && value.state !== 'blocked' && (!Array.isArray(value.blockers) || value.blockers.length === 0);
8
+
9
+ /**
10
+ * Project explicit current navigation contracts, not authority from status or
11
+ * diagnostic prose. Core still checks actor, revision, credentials and inputs.
12
+ * Consumers must pass the actual successful `next` value, not a transcript's
13
+ * independently declared action list. This is the single action projection for
14
+ * maintained runners and transcript validation; the CLI registry owns spelling.
15
+ */
16
+ export function projectNextActions(input: unknown): CliCommand[] {
17
+ const next = object(input);
18
+ const actions = new Set<CliCommand>();
19
+ const add = (action: unknown) => {
20
+ if (typeof action === 'string' && action !== next.blockedAction && isCliCommand(action)) actions.add(action);
21
+ };
22
+ add(next.action);
23
+ if (Array.isArray(next.allowedActions)) next.allowedActions.forEach(add);
24
+
25
+ for (const [location, primary] of [['strictStepReview', 'task step-review'], ['strictTaskReview', 'task review-launch']] as const) {
26
+ const review = object(next[location]);
27
+ const sealed = object(review.externalSealedReview);
28
+ if (next.action === primary && review.reviewRequired === true && unblocked(review) && unblocked(sealed)) {
29
+ add(sealed.packetCommand);
30
+ add(sealed.recordCommand);
31
+ }
32
+ }
33
+ const recovery = object(next.writerCredentialRecovery);
34
+ if (unblocked(recovery) && typeof recovery.requiredActor === 'string' && recovery.requiredActor.trim()) add(recovery.action);
35
+
36
+ const eligibleOption = (candidate: unknown) => {
37
+ const option = object(candidate);
38
+ if (unblocked(option) && (option.eligible === true || option.state === 'eligible')) {
39
+ add(option.action);
40
+ add(option.command);
41
+ if (Array.isArray(option.commands)) option.commands.forEach(add);
42
+ }
43
+ };
44
+ eligibleOption(next.preExecutionReplanOption);
45
+ eligibleOption(next.correctiveDecisionOption);
46
+ if (Array.isArray(next.options)) next.options.forEach(eligibleOption);
47
+
48
+ // Core emits this closed branch only after assessMilestoneInitialAssembly is
49
+ // eligible. Individual entries intentionally have no redundant eligible flag.
50
+ const assembly = object(next.milestoneInitialAssembly);
51
+ if (unblocked(assembly) && Array.isArray(assembly.options)) {
52
+ for (const candidate of assembly.options) {
53
+ const option = object(candidate);
54
+ if (!unblocked(option)) continue;
55
+ add(option.action);
56
+ add(option.command);
57
+ if (Array.isArray(option.commands)) option.commands.forEach(add);
58
+ }
59
+ }
60
+ // These options are emitted only at the Milestone authorization boundary.
61
+ // Listing a direct-human command never substitutes for its required consent.
62
+ if (next.scope === 'milestone' && next.status === 'awaiting_execution_authorization'
63
+ && Array.isArray(next.milestoneAuthorizationOptions)) {
64
+ for (const candidate of next.milestoneAuthorizationOptions) {
65
+ const option = object(candidate);
66
+ if (unblocked(option)) add(option.action);
67
+ }
68
+ }
69
+ return [...actions];
70
+ }