ai-delivery-workflow 0.2.2

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 (211) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -0
  3. package/bin/ai-delivery.mjs +138 -0
  4. package/bin/validate-skills.mjs +12 -0
  5. package/docs/DUAL-REPOSITORY-WORKSPACE.zh-CN.md +125 -0
  6. package/docs/EVOLUTION-MAINTENANCE.zh-CN.md +75 -0
  7. package/docs/EVOLUTION-USER-GUIDE.zh-CN.md +160 -0
  8. package/docs/FILE-REFERENCE.zh-CN.md +503 -0
  9. package/docs/PROJECT-MANUAL.zh-CN.md +931 -0
  10. package/docs/STATE-CLI-MAINTENANCE.zh-CN.md +135 -0
  11. package/docs/STATE-CLI-USER-GUIDE.zh-CN.md +186 -0
  12. package/docs/VIEWER-MAINTENANCE.zh-CN.md +373 -0
  13. package/docs/VIEWER-USER-GUIDE.zh-CN.md +235 -0
  14. package/lib/delivery-state.mjs +1220 -0
  15. package/lib/evolution.mjs +971 -0
  16. package/lib/project-bootstrap.mjs +927 -0
  17. package/lib/project-installer.mjs +1184 -0
  18. package/lib/skill-validator.mjs +154 -0
  19. package/lib/toml-hooks.mjs +97 -0
  20. package/lib/workspace.mjs +302 -0
  21. package/lib/yaml-runtime.mjs +18 -0
  22. package/package.json +39 -0
  23. package/skills/ai-delivery-assemble-release/SKILL.md +27 -0
  24. package/skills/ai-delivery-assemble-release/agents/openai.yaml +4 -0
  25. package/skills/ai-delivery-assemble-release/assets/release-candidate-manifest.yaml +22 -0
  26. package/skills/ai-delivery-assemble-release/assets/release-git-plan.yaml +9 -0
  27. package/skills/ai-delivery-assemble-release/assets/release-material-index.csv +2 -0
  28. package/skills/ai-delivery-assemble-release/assets/version-inclusion.csv +2 -0
  29. package/skills/ai-delivery-assemble-release/references/release-assembly-contract.md +7 -0
  30. package/skills/ai-delivery-assemble-release/scripts/plan-release.mjs +298 -0
  31. package/skills/ai-delivery-bootstrap/SKILL.md +59 -0
  32. package/skills/ai-delivery-bootstrap/agents/openai.yaml +4 -0
  33. package/skills/ai-delivery-bootstrap/references/bootstrap-contract.md +70 -0
  34. package/skills/ai-delivery-checkpoint-task/SKILL.md +63 -0
  35. package/skills/ai-delivery-checkpoint-task/agents/openai.yaml +4 -0
  36. package/skills/ai-delivery-checkpoint-task/assets/codex-hook-config.toml +92 -0
  37. package/skills/ai-delivery-checkpoint-task/assets/runtime-template/resume.md +15 -0
  38. package/skills/ai-delivery-checkpoint-task/assets/runtime-template/task.json +30 -0
  39. package/skills/ai-delivery-checkpoint-task/assets/runtime-template/version-archive.json +10 -0
  40. package/skills/ai-delivery-checkpoint-task/references/checkpoint-contract.md +95 -0
  41. package/skills/ai-delivery-checkpoint-task/references/checkpoint-recovery.zh-CN.md +111 -0
  42. package/skills/ai-delivery-checkpoint-task/scripts/hook-event.mjs +131 -0
  43. package/skills/ai-delivery-checkpoint-task/scripts/task-state.mjs +1073 -0
  44. package/skills/ai-delivery-close-version/SKILL.md +33 -0
  45. package/skills/ai-delivery-close-version/agents/openai.yaml +4 -0
  46. package/skills/ai-delivery-close-version/assets/line-advance-record.yaml +12 -0
  47. package/skills/ai-delivery-close-version/assets/version-lineage.yaml +12 -0
  48. package/skills/ai-delivery-close-version/assets/version-manifest.yaml +20 -0
  49. package/skills/ai-delivery-close-version/references/version-closeout-contract.md +20 -0
  50. package/skills/ai-delivery-define-product/SKILL.md +76 -0
  51. package/skills/ai-delivery-define-product/agents/openai.yaml +4 -0
  52. package/skills/ai-delivery-define-product/assets/product-template/acceptance-criteria.csv +1 -0
  53. package/skills/ai-delivery-define-product/assets/product-template/discovery-baseline.md +19 -0
  54. package/skills/ai-delivery-define-product/assets/product-template/feature-detail.md +19 -0
  55. package/skills/ai-delivery-define-product/assets/product-template/feature-terminals.csv +1 -0
  56. package/skills/ai-delivery-define-product/assets/product-template/features.csv +1 -0
  57. package/skills/ai-delivery-define-product/assets/product-template/product-baseline.yaml +8 -0
  58. package/skills/ai-delivery-define-product/assets/product-template/product-manual.md +24 -0
  59. package/skills/ai-delivery-define-product/assets/product-template/terminals.csv +1 -0
  60. package/skills/ai-delivery-define-product/assets/product-template/user-stories.csv +1 -0
  61. package/skills/ai-delivery-define-product/references/product-contract.md +57 -0
  62. package/skills/ai-delivery-deploy-production/SKILL.md +45 -0
  63. package/skills/ai-delivery-deploy-production/agents/openai.yaml +4 -0
  64. package/skills/ai-delivery-deploy-production/references/deployment-contract.md +7 -0
  65. package/skills/ai-delivery-design-architecture/SKILL.md +67 -0
  66. package/skills/ai-delivery-design-architecture/agents/openai.yaml +4 -0
  67. package/skills/ai-delivery-design-architecture/assets/architecture-template/architecture-baseline.md +25 -0
  68. package/skills/ai-delivery-design-architecture/assets/architecture-template/architecture-gate.yaml +15 -0
  69. package/skills/ai-delivery-design-architecture/assets/architecture-template/prototype-architecture-validation.yaml +58 -0
  70. package/skills/ai-delivery-design-architecture/references/architecture-contract.md +32 -0
  71. package/skills/ai-delivery-design-experience/SKILL.md +71 -0
  72. package/skills/ai-delivery-design-experience/agents/openai.yaml +4 -0
  73. package/skills/ai-delivery-design-experience/assets/experience-template/canvas-catalog.csv +1 -0
  74. package/skills/ai-delivery-design-experience/assets/experience-template/canvas-pages.csv +1 -0
  75. package/skills/ai-delivery-design-experience/assets/experience-template/component-state-matrix.csv +1 -0
  76. package/skills/ai-delivery-design-experience/assets/experience-template/design-tokens.json +19 -0
  77. package/skills/ai-delivery-design-experience/assets/experience-template/experience-baseline.yaml +17 -0
  78. package/skills/ai-delivery-design-experience/assets/experience-template/experience-change-domains.csv +1 -0
  79. package/skills/ai-delivery-design-experience/assets/experience-template/experience-change-set.csv +1 -0
  80. package/skills/ai-delivery-design-experience/assets/experience-template/feature-screen-coverage.csv +1 -0
  81. package/skills/ai-delivery-design-experience/assets/experience-template/formal-ui-confirmation.yaml +33 -0
  82. package/skills/ai-delivery-design-experience/assets/experience-template/interaction-contract.csv +1 -0
  83. package/skills/ai-delivery-design-experience/assets/experience-template/low-fidelity-confirmation.yaml +32 -0
  84. package/skills/ai-delivery-design-experience/assets/experience-template/page-catalog.csv +1 -0
  85. package/skills/ai-delivery-design-experience/assets/experience-template/page-component-map.csv +1 -0
  86. package/skills/ai-delivery-design-experience/assets/experience-template/product-prototype-reconciliation.yaml +28 -0
  87. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-adjustment-log.csv +1 -0
  88. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-confirmation.yaml +99 -0
  89. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-file-terminals.csv +1 -0
  90. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-files.csv +1 -0
  91. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-impact-assessment.yaml +25 -0
  92. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-manifest.yaml +74 -0
  93. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-product-coverage.csv +1 -0
  94. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-comments.csv +1 -0
  95. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-decision.yaml +29 -0
  96. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-sessions.csv +1 -0
  97. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-set.yaml +22 -0
  98. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-tool-candidates.csv +1 -0
  99. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-tool-selection.yaml +22 -0
  100. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-traceability.csv +1 -0
  101. package/skills/ai-delivery-design-experience/assets/experience-template/requirement-change-set.csv +1 -0
  102. package/skills/ai-delivery-design-experience/assets/experience-template/screen-states.csv +1 -0
  103. package/skills/ai-delivery-design-experience/assets/experience-template/ui-framework-candidates.csv +1 -0
  104. package/skills/ai-delivery-design-experience/assets/experience-template/ui-framework-selection.yaml +18 -0
  105. package/skills/ai-delivery-design-experience/assets/experience-template/ui-handoff.yaml +44 -0
  106. package/skills/ai-delivery-design-experience/assets/experience-template/visual-direction.yaml +16 -0
  107. package/skills/ai-delivery-design-experience/assets/experience-template/visual-system-confirmation.yaml +31 -0
  108. package/skills/ai-delivery-design-experience/references/experience-contract.md +82 -0
  109. package/skills/ai-delivery-design-experience/references/prototype-management-contract.md +68 -0
  110. package/skills/ai-delivery-design-tests/SKILL.md +56 -0
  111. package/skills/ai-delivery-design-tests/agents/openai.yaml +4 -0
  112. package/skills/ai-delivery-design-tests/assets/test-design-template/iteration-test-contract.md +19 -0
  113. package/skills/ai-delivery-design-tests/assets/test-design-template/prototype-test-scope.csv +1 -0
  114. package/skills/ai-delivery-design-tests/assets/test-design-template/prototype-test-validation.yaml +73 -0
  115. package/skills/ai-delivery-design-tests/references/test-design-contract.md +31 -0
  116. package/skills/ai-delivery-develop-iteration/SKILL.md +66 -0
  117. package/skills/ai-delivery-develop-iteration/agents/openai.yaml +4 -0
  118. package/skills/ai-delivery-develop-iteration/assets/development-template/task-experience-change-traceability.csv +1 -0
  119. package/skills/ai-delivery-develop-iteration/assets/development-template/task-prototype-traceability.csv +1 -0
  120. package/skills/ai-delivery-develop-iteration/references/development-contract.md +39 -0
  121. package/skills/ai-delivery-evolve-workflow/SKILL.md +57 -0
  122. package/skills/ai-delivery-evolve-workflow/agents/openai.yaml +4 -0
  123. package/skills/ai-delivery-evolve-workflow/references/evolution-contract.md +42 -0
  124. package/skills/ai-delivery-execute-test/SKILL.md +34 -0
  125. package/skills/ai-delivery-execute-test/agents/openai.yaml +4 -0
  126. package/skills/ai-delivery-execute-test/references/test-execution-contract.md +7 -0
  127. package/skills/ai-delivery-execute-work-package/SKILL.md +53 -0
  128. package/skills/ai-delivery-execute-work-package/agents/openai.yaml +4 -0
  129. package/skills/ai-delivery-execute-work-package/references/work-package-contract.md +20 -0
  130. package/skills/ai-delivery-manage-git/SKILL.md +60 -0
  131. package/skills/ai-delivery-manage-git/agents/openai.yaml +4 -0
  132. package/skills/ai-delivery-manage-git/references/git-policy.md +42 -0
  133. package/skills/ai-delivery-manage-standards/SKILL.md +44 -0
  134. package/skills/ai-delivery-manage-standards/agents/openai.yaml +4 -0
  135. package/skills/ai-delivery-manage-standards/assets/standards-template/standard.md +25 -0
  136. package/skills/ai-delivery-manage-standards/references/standards-contract.md +20 -0
  137. package/skills/ai-delivery-orchestrate/SKILL.md +105 -0
  138. package/skills/ai-delivery-orchestrate/agents/openai.yaml +4 -0
  139. package/skills/ai-delivery-orchestrate/assets/project-template/.codex/config.toml +92 -0
  140. package/skills/ai-delivery-orchestrate/assets/project-template/.gitattributes +6 -0
  141. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/delivery/artifact-registry.yaml +3 -0
  142. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/delivery/project.yaml +6 -0
  143. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/delivery/standards-baseline.yaml +8 -0
  144. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/delivery/workflow-state.yaml +34 -0
  145. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/evolution/decisions/.gitkeep +0 -0
  146. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/evolution/experiments/.gitkeep +0 -0
  147. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/evolution/feedback/.gitkeep +0 -0
  148. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/evolution/index.yaml +7 -0
  149. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/evolution/promotion/.gitkeep +0 -0
  150. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/evolution/proposals/.gitkeep +0 -0
  151. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/extensions/.gitkeep +0 -0
  152. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/standards/STD-ARTIFACT-PATH-001.md +47 -0
  153. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/standards/STD-DOC-LANG-001.md +62 -0
  154. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/standards/STD-TEST-LOCATION-001.md +23 -0
  155. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/standards/index.yaml +36 -0
  156. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/bootstrap/bootstrap.mjs +33 -0
  157. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/evolution/evolve.mjs +15 -0
  158. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/hooks/hook-event.mjs +131 -0
  159. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/state/state.mjs +21 -0
  160. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/public/app.js +546 -0
  161. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/public/index.html +199 -0
  162. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/public/styles.css +529 -0
  163. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/server.mjs +780 -0
  164. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/start.cmd +4 -0
  165. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/start.ps1 +2 -0
  166. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/start.sh +4 -0
  167. package/skills/ai-delivery-orchestrate/assets/project-template/AGENTS.md +44 -0
  168. package/skills/ai-delivery-orchestrate/references/formal-state-contract.md +64 -0
  169. package/skills/ai-delivery-orchestrate/references/workflow-model.md +29 -0
  170. package/skills/ai-delivery-orchestrate-release/SKILL.md +33 -0
  171. package/skills/ai-delivery-orchestrate-release/agents/openai.yaml +4 -0
  172. package/skills/ai-delivery-orchestrate-release/assets/release-request.yaml +9 -0
  173. package/skills/ai-delivery-orchestrate-release/assets/version-selection.yaml +16 -0
  174. package/skills/ai-delivery-orchestrate-release/references/release-workflow-contract.md +11 -0
  175. package/skills/ai-delivery-plan-iteration/SKILL.md +57 -0
  176. package/skills/ai-delivery-plan-iteration/agents/openai.yaml +4 -0
  177. package/skills/ai-delivery-plan-iteration/assets/planning-template/backlog.csv +1 -0
  178. package/skills/ai-delivery-plan-iteration/assets/planning-template/slice-dependencies.csv +1 -0
  179. package/skills/ai-delivery-plan-iteration/assets/planning-template/slice-experience-change-traceability.csv +1 -0
  180. package/skills/ai-delivery-plan-iteration/assets/planning-template/slice-prototype-traceability.csv +1 -0
  181. package/skills/ai-delivery-plan-iteration/assets/planning-template/slice-sources.csv +1 -0
  182. package/skills/ai-delivery-plan-iteration/assets/planning-template/vertical-slices.csv +1 -0
  183. package/skills/ai-delivery-plan-iteration/references/planning-contract.md +40 -0
  184. package/skills/ai-delivery-prepare-platform/SKILL.md +49 -0
  185. package/skills/ai-delivery-prepare-platform/agents/openai.yaml +4 -0
  186. package/skills/ai-delivery-prepare-platform/assets/platform-template/environment-ledger.csv +1 -0
  187. package/skills/ai-delivery-prepare-platform/references/platform-contract.md +18 -0
  188. package/skills/ai-delivery-prepare-release/SKILL.md +47 -0
  189. package/skills/ai-delivery-prepare-release/agents/openai.yaml +4 -0
  190. package/skills/ai-delivery-prepare-release/assets/release-template/delivery-items.csv +1 -0
  191. package/skills/ai-delivery-prepare-release/assets/release-template/release-manifest.yaml +18 -0
  192. package/skills/ai-delivery-prepare-release/references/release-contract.md +21 -0
  193. package/skills/ai-delivery-review-change/SKILL.md +40 -0
  194. package/skills/ai-delivery-review-change/agents/openai.yaml +4 -0
  195. package/skills/ai-delivery-review-change/references/review-contract.md +20 -0
  196. package/skills/ai-delivery-triage-failure/SKILL.md +45 -0
  197. package/skills/ai-delivery-triage-failure/agents/openai.yaml +4 -0
  198. package/skills/ai-delivery-triage-failure/references/failure-routing-contract.md +13 -0
  199. package/skills/ai-delivery-validate-artifacts/SKILL.md +37 -0
  200. package/skills/ai-delivery-validate-artifacts/agents/openai.yaml +4 -0
  201. package/skills/ai-delivery-validate-artifacts/references/artifact-contract.md +44 -0
  202. package/skills/ai-delivery-verify-candidate/SKILL.md +56 -0
  203. package/skills/ai-delivery-verify-candidate/agents/openai.yaml +4 -0
  204. package/skills/ai-delivery-verify-candidate/assets/verification-template/defect-artifacts.csv +1 -0
  205. package/skills/ai-delivery-verify-candidate/assets/verification-template/defects.csv +1 -0
  206. package/skills/ai-delivery-verify-candidate/assets/verification-template/test-work-packages.csv +1 -0
  207. package/skills/ai-delivery-verify-candidate/references/candidate-verification-contract.md +23 -0
  208. package/skills/ai-delivery-verify-production/SKILL.md +50 -0
  209. package/skills/ai-delivery-verify-production/agents/openai.yaml +4 -0
  210. package/skills/ai-delivery-verify-production/assets/production-verification-template/production-verification.md +15 -0
  211. package/skills/ai-delivery-verify-production/references/production-verification-contract.md +14 -0
@@ -0,0 +1,1184 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+ import { createRequire } from "node:module";
7
+ import { fileURLToPath } from "node:url";
8
+ import { isSeq, parse as parseYaml, parseDocument } from "yaml";
9
+ import { inspectHookConfig, mergeHookConfig } from "./toml-hooks.mjs";
10
+ import {
11
+ LEGACY_WORKSPACE_CONFIG,
12
+ WORKSPACE_CONFIG,
13
+ gitCommonDirectory,
14
+ gitTopLevel,
15
+ initializeGitWorkspace,
16
+ readWorkspaceConfiguration,
17
+ workspaceConfigurationText,
18
+ } from "./workspace.mjs";
19
+
20
+ const require = createRequire(import.meta.url);
21
+ const PACKAGE_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
22
+ const MANAGED_START = "<!-- ai-delivery:start -->";
23
+ const MANAGED_END = "<!-- ai-delivery:end -->";
24
+ const IGNORE_ENTRIES = [".workflow/delivery/runtime/"];
25
+ const GIT_ATTRIBUTE_ENTRIES = [
26
+ "/.workflow/** text=auto eol=lf",
27
+ "/.agents/skills/ai-delivery-*/** text=auto eol=lf",
28
+ "/.codex/config.toml text eol=lf",
29
+ "/AGENTS.md text eol=lf",
30
+ "/.gitignore text eol=lf",
31
+ "/.gitattributes text eol=lf",
32
+ ];
33
+ const OBSOLETE_IGNORE_ENTRIES = new Set([".ai-delivery-task", ".ai-delivery/backups/"]);
34
+ const INSTALL_MANIFEST = path.join(".workflow", "config", "install-manifest.json");
35
+ const ACTIVE_TASK = path.join(".workflow", "delivery", "runtime", "active-task.json");
36
+ const LEGACY_ACTIVE_TASK = ".ai-delivery-task";
37
+ const WORKFLOW_CONTENT_DIRECTORIES = ["product", "architecture", "experience", "iterations", "releases"];
38
+ const CONTROL_DIRECTORIES = ["tasks", "checkpoints", "artifacts", "gates", "versions", "releases", "events", "archives"];
39
+ const ESSENTIAL_STARTUP_SKILLS = [
40
+ "ai-delivery-assemble-release",
41
+ "ai-delivery-bootstrap",
42
+ "ai-delivery-checkpoint-task",
43
+ "ai-delivery-close-version",
44
+ "ai-delivery-evolve-workflow",
45
+ "ai-delivery-orchestrate",
46
+ "ai-delivery-orchestrate-release",
47
+ ];
48
+ const DELIVERY_SKILLS = Object.freeze([
49
+ "ai-delivery-assemble-release",
50
+ "ai-delivery-bootstrap",
51
+ "ai-delivery-checkpoint-task",
52
+ "ai-delivery-close-version",
53
+ "ai-delivery-define-product",
54
+ "ai-delivery-deploy-production",
55
+ "ai-delivery-design-architecture",
56
+ "ai-delivery-design-experience",
57
+ "ai-delivery-design-tests",
58
+ "ai-delivery-develop-iteration",
59
+ "ai-delivery-evolve-workflow",
60
+ "ai-delivery-execute-test",
61
+ "ai-delivery-execute-work-package",
62
+ "ai-delivery-manage-git",
63
+ "ai-delivery-manage-standards",
64
+ "ai-delivery-orchestrate",
65
+ "ai-delivery-orchestrate-release",
66
+ "ai-delivery-plan-iteration",
67
+ "ai-delivery-prepare-platform",
68
+ "ai-delivery-prepare-release",
69
+ "ai-delivery-review-change",
70
+ "ai-delivery-triage-failure",
71
+ "ai-delivery-validate-artifacts",
72
+ "ai-delivery-verify-candidate",
73
+ "ai-delivery-verify-production",
74
+ ]);
75
+
76
+ function comparePackageVersions(left, right) {
77
+ const parse = (value) => {
78
+ const match = String(value || "").match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
79
+ if (!match) return null;
80
+ return {
81
+ core: match.slice(1, 4).map(Number),
82
+ prerelease: match[4] ? match[4].split(".") : [],
83
+ };
84
+ };
85
+ const current = parse(left);
86
+ const previous = parse(right);
87
+ if (!current || !previous) return null;
88
+ for (let index = 0; index < 3; index += 1) {
89
+ if (current.core[index] !== previous.core[index]) {
90
+ return current.core[index] > previous.core[index] ? 1 : -1;
91
+ }
92
+ }
93
+ if (!current.prerelease.length && !previous.prerelease.length) return 0;
94
+ if (!current.prerelease.length) return 1;
95
+ if (!previous.prerelease.length) return -1;
96
+ const length = Math.max(current.prerelease.length, previous.prerelease.length);
97
+ for (let index = 0; index < length; index += 1) {
98
+ const currentPart = current.prerelease[index];
99
+ const previousPart = previous.prerelease[index];
100
+ if (currentPart === undefined) return -1;
101
+ if (previousPart === undefined) return 1;
102
+ if (currentPart === previousPart) continue;
103
+ const currentNumeric = /^\d+$/.test(currentPart);
104
+ const previousNumeric = /^\d+$/.test(previousPart);
105
+ if (currentNumeric && previousNumeric) return Number(currentPart) > Number(previousPart) ? 1 : -1;
106
+ if (currentNumeric !== previousNumeric) return currentNumeric ? -1 : 1;
107
+ return currentPart > previousPart ? 1 : -1;
108
+ }
109
+ return 0;
110
+ }
111
+
112
+ function isInside(candidate, root) {
113
+ const relative = path.relative(path.resolve(root), path.resolve(candidate));
114
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
115
+ }
116
+
117
+ function samePath(left, right) {
118
+ const normalize = (value) => path.resolve(value).replaceAll("\\", "/").toLowerCase();
119
+ return normalize(left) === normalize(right);
120
+ }
121
+
122
+ function validateTarget(target, options = {}) {
123
+ if (!fs.existsSync(target) && options.create) fs.mkdirSync(target, { recursive: true });
124
+ if (!fs.existsSync(target) || !fs.statSync(target).isDirectory()) {
125
+ throw new Error(`Project directory does not exist: ${target}`);
126
+ }
127
+ const home = path.resolve(os.homedir());
128
+ const globalRoots = [path.join(home, ".codex"), path.join(home, ".agents")];
129
+ if (samePath(target, home) || globalRoots.some((root) => isInside(target, root))) {
130
+ throw new Error(`Refusing a global installation target: ${target}`);
131
+ }
132
+ }
133
+
134
+ function validateManagedPath(target, destination) {
135
+ const realTarget = fs.realpathSync(target);
136
+ let existingAncestor = destination;
137
+ while (!fs.existsSync(existingAncestor)) {
138
+ const parent = path.dirname(existingAncestor);
139
+ if (parent === existingAncestor) break;
140
+ existingAncestor = parent;
141
+ }
142
+ const realAncestor = fs.realpathSync(existingAncestor);
143
+ if (!isInside(realAncestor, realTarget)) {
144
+ throw new Error(`Managed path resolves outside the project: ${destination} -> ${realAncestor}`);
145
+ }
146
+ }
147
+
148
+ function atomicWrite(file, content) {
149
+ fs.mkdirSync(path.dirname(file), { recursive: true });
150
+ const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
151
+ fs.writeFileSync(temp, content, "utf8");
152
+ fs.renameSync(temp, file);
153
+ }
154
+
155
+ function filesUnder(root) {
156
+ if (!fs.existsSync(root)) return [];
157
+ const files = [];
158
+ const visit = (directory) => {
159
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
160
+ const absolute = path.join(directory, entry.name);
161
+ if (entry.isDirectory()) visit(absolute);
162
+ else if (entry.isFile()) files.push(absolute);
163
+ }
164
+ };
165
+ visit(root);
166
+ return files.sort((left, right) => left.localeCompare(right));
167
+ }
168
+
169
+ function normalizedManagedContent(file) {
170
+ const content = fs.readFileSync(file);
171
+ return content.includes(0) ? content : Buffer.from(content.toString("utf8").replaceAll("\r\n", "\n"));
172
+ }
173
+
174
+ function managedFilesEqual(left, right) {
175
+ return normalizedManagedContent(left).equals(normalizedManagedContent(right));
176
+ }
177
+
178
+ function directoryDigest(root) {
179
+ const hash = crypto.createHash("sha256");
180
+ for (const file of filesUnder(root)) {
181
+ hash.update(path.relative(root, file).replaceAll("\\", "/"));
182
+ hash.update("\0");
183
+ hash.update(normalizedManagedContent(file));
184
+ hash.update("\0");
185
+ }
186
+ return hash.digest("hex");
187
+ }
188
+
189
+ function managedFileDigest(file) {
190
+ return crypto.createHash("sha256").update(normalizedManagedContent(file)).digest("hex");
191
+ }
192
+
193
+ function sourceSetDigest(sources) {
194
+ const hash = crypto.createHash("sha256");
195
+ for (const item of [...sources].sort((left, right) => left.relative.localeCompare(right.relative))) {
196
+ hash.update(item.relative.replaceAll("\\", "/"));
197
+ hash.update("\0");
198
+ hash.update(normalizedManagedContent(item.source));
199
+ hash.update("\0");
200
+ }
201
+ return hash.digest("hex");
202
+ }
203
+
204
+ function packagedSkillNames(packageRoot) {
205
+ const sourceSkills = path.join(packageRoot, "skills");
206
+ const actual = fs.readdirSync(sourceSkills, { withFileTypes: true })
207
+ .filter((entry) => entry.isDirectory() && entry.name.startsWith("ai-delivery-"))
208
+ .map((entry) => entry.name)
209
+ .sort();
210
+ if (JSON.stringify(actual) !== JSON.stringify(DELIVERY_SKILLS)) {
211
+ throw new Error(`Package skill inventory must be the fixed ${DELIVERY_SKILLS.length} skills.`);
212
+ }
213
+ return [...DELIVERY_SKILLS];
214
+ }
215
+
216
+ function mergeManagedMarkdown(existing, managedTemplate) {
217
+ const body = managedTemplate.trim();
218
+ const block = `${MANAGED_START}\n${body}\n${MANAGED_END}`;
219
+ const start = existing.indexOf(MANAGED_START);
220
+ const end = existing.indexOf(MANAGED_END);
221
+ if (start === -1 && end === -1) return `${existing.trimEnd()}${existing.trim() ? "\n\n" : ""}${block}\n`;
222
+ if (start === -1 || end === -1 || end < start) {
223
+ throw new Error("AGENTS.md contains an incomplete ai-delivery managed block.");
224
+ }
225
+ return `${existing.slice(0, start)}${block}${existing.slice(end + MANAGED_END.length)}`.replace(/\s*$/, "\n");
226
+ }
227
+
228
+ function mergeGitignore(existing, requiredEntries = []) {
229
+ const lines = existing.split(/\r?\n/)
230
+ .filter((line) => !OBSOLETE_IGNORE_ENTRIES.has(line.trim()));
231
+ const additions = [...IGNORE_ENTRIES, ...requiredEntries]
232
+ .filter((entry) => !lines.some((line) => line.trim() === entry));
233
+ const retained = lines.join("\n").trimEnd();
234
+ if (!additions.length) return retained ? `${retained}\n` : "";
235
+ return `${retained}${retained ? "\n" : ""}${additions.join("\n")}\n`;
236
+ }
237
+
238
+ function mergeGitattributes(existing) {
239
+ const lines = existing.split(/\r?\n/);
240
+ const additions = GIT_ATTRIBUTE_ENTRIES
241
+ .filter((entry) => !lines.some((line) => line.trim() === entry));
242
+ const retained = lines.join("\n").trimEnd();
243
+ if (!additions.length) return retained ? `${retained}\n` : "";
244
+ return `${retained}${retained ? "\n" : ""}${additions.join("\n")}\n`;
245
+ }
246
+
247
+ function ignoresWholeWorkflow(existing) {
248
+ return existing.split(/\r?\n/)
249
+ .map((line) => line.trim())
250
+ .some((line) => new Set([".workflow", ".workflow/", "/.workflow", "/.workflow/"]).has(line));
251
+ }
252
+
253
+ function mergeStandardsIndex(existing, template) {
254
+ const defaults = parseYaml(template)?.standards;
255
+ if (!Array.isArray(defaults)) throw new Error("Packaged standards/index.yaml is invalid.");
256
+ const document = parseDocument(existing || "standards: []\n");
257
+ if (document.errors.length) throw new Error(`Existing standards/index.yaml is invalid: ${document.errors[0].message}`);
258
+ let standards = document.get("standards", true);
259
+ if (!standards) {
260
+ document.set("standards", []);
261
+ standards = document.get("standards", true);
262
+ }
263
+ if (!isSeq(standards)) throw new Error("Existing standards/index.yaml must contain a standards sequence.");
264
+ const existingValues = document.toJS().standards || [];
265
+ const added = [];
266
+ for (const standard of defaults) {
267
+ const current = existingValues.find((item) => item?.id === standard.id);
268
+ if (current) {
269
+ for (const key of ["version", "status", "path"]) {
270
+ if (current[key] !== standard[key]) {
271
+ throw new Error(`Standard ${standard.id} conflicts on ${key}; resolve it through standards governance.`);
272
+ }
273
+ }
274
+ continue;
275
+ }
276
+ standards.add(document.createNode(standard));
277
+ added.push(standard.id);
278
+ }
279
+ return { text: document.toString(), added };
280
+ }
281
+
282
+ function copyMissingTree(source, destination, actions, dryRun) {
283
+ for (const sourceFile of filesUnder(source)) {
284
+ const relative = path.relative(source, sourceFile);
285
+ const destinationFile = path.join(destination, relative);
286
+ if (fs.existsSync(destinationFile)) continue;
287
+ actions.push(`create ${path.relative(path.dirname(destination), destinationFile)}`);
288
+ if (!dryRun) {
289
+ fs.mkdirSync(path.dirname(destinationFile), { recursive: true });
290
+ fs.copyFileSync(sourceFile, destinationFile);
291
+ }
292
+ }
293
+ }
294
+
295
+ function backupAndRemove(target, destination, backupRoot, actions, dryRun) {
296
+ const relative = path.relative(target, destination);
297
+ if (!isInside(destination, target)) throw new Error(`Managed path escapes project: ${destination}`);
298
+ actions.push(`back up and replace ${relative}`);
299
+ if (dryRun) return false;
300
+ const backup = path.join(backupRoot, relative);
301
+ fs.mkdirSync(path.dirname(backup), { recursive: true });
302
+ fs.cpSync(destination, backup, { recursive: true });
303
+ fs.rmSync(destination, { recursive: true, force: true });
304
+ return true;
305
+ }
306
+
307
+ function backupAndReplace(target, destination, source, backupRoot, actions, dryRun) {
308
+ if (!backupAndRemove(target, destination, backupRoot, actions, dryRun)) return;
309
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
310
+ fs.cpSync(source, destination, { recursive: true });
311
+ }
312
+
313
+ function stateRuntimeSources(packageRoot, sourceTemplate) {
314
+ const yamlRoot = path.dirname(require.resolve("yaml/package.json"));
315
+ if (!fs.existsSync(yamlRoot)) {
316
+ throw new Error(`The packaged YAML runtime is missing: ${yamlRoot}`);
317
+ }
318
+ return [
319
+ {
320
+ source: path.join(sourceTemplate, ".workflow", "tools", "state", "state.mjs"),
321
+ relative: "state.mjs",
322
+ },
323
+ {
324
+ source: path.join(packageRoot, "lib", "delivery-state.mjs"),
325
+ relative: "delivery-state.mjs",
326
+ },
327
+ ...filesUnder(yamlRoot).map((source) => ({
328
+ source,
329
+ relative: path.join("vendor", "yaml", path.relative(yamlRoot, source)),
330
+ })),
331
+ ];
332
+ }
333
+
334
+ function evolutionRuntimeSources(packageRoot, sourceTemplate) {
335
+ return [
336
+ {
337
+ source: path.join(sourceTemplate, ".workflow", "tools", "evolution", "evolve.mjs"),
338
+ relative: "evolve.mjs",
339
+ },
340
+ {
341
+ source: path.join(packageRoot, "lib", "evolution.mjs"),
342
+ relative: "evolution-runtime.mjs",
343
+ },
344
+ ];
345
+ }
346
+
347
+ function bootstrapRuntimeSources(packageRoot, sourceTemplate) {
348
+ return [
349
+ {
350
+ source: path.join(sourceTemplate, ".workflow", "tools", "bootstrap", "bootstrap.mjs"),
351
+ relative: "bootstrap.mjs",
352
+ },
353
+ ...["project-bootstrap.mjs", "workspace.mjs", "yaml-runtime.mjs", "delivery-state.mjs"].map((name) => ({
354
+ source: path.join(packageRoot, "lib", name),
355
+ relative: name,
356
+ })),
357
+ ];
358
+ }
359
+
360
+ function managedRuntimeMatches(destination, sources) {
361
+ if (!fs.existsSync(destination)) return false;
362
+ const expected = new Map(sources.map((item) => [
363
+ item.relative.replaceAll("\\", "/"),
364
+ item.source,
365
+ ]));
366
+ const actualFiles = filesUnder(destination);
367
+ if (actualFiles.length !== expected.size) return false;
368
+ return actualFiles.every((file) => {
369
+ const relative = path.relative(destination, file).replaceAll("\\", "/");
370
+ const source = expected.get(relative);
371
+ return source && managedFilesEqual(file, source);
372
+ });
373
+ }
374
+
375
+ function installManagedRuntime(destination, sources) {
376
+ fs.mkdirSync(destination, { recursive: true });
377
+ for (const item of sources) {
378
+ const target = path.join(destination, item.relative);
379
+ fs.mkdirSync(path.dirname(target), { recursive: true });
380
+ fs.copyFileSync(item.source, target);
381
+ }
382
+ }
383
+
384
+ function planManagedRuntime({
385
+ target,
386
+ installPath,
387
+ sources,
388
+ reinstallManagedRuntime,
389
+ conflicts,
390
+ }) {
391
+ const destination = path.join(target, ...installPath.split("/"));
392
+ if (!fs.existsSync(destination)) return { type: "copy", installPath, destination, sources };
393
+ if (managedRuntimeMatches(destination, sources)) return null;
394
+ if (reinstallManagedRuntime) return { type: "replace", installPath, destination, sources };
395
+ conflicts.push(path.relative(target, destination));
396
+ return null;
397
+ }
398
+
399
+ function applyManagedRuntimePlan(target, plan, backupRoot, actions, dryRun) {
400
+ if (!plan) return;
401
+ if (plan.type === "copy") {
402
+ actions.push(`install ${plan.installPath}`);
403
+ if (!dryRun) installManagedRuntime(plan.destination, plan.sources);
404
+ return;
405
+ }
406
+ if (backupAndRemove(target, plan.destination, backupRoot, actions, dryRun)) {
407
+ installManagedRuntime(plan.destination, plan.sources);
408
+ }
409
+ }
410
+
411
+ export function initProject(packageRoot, target, options = {}) {
412
+ const { dryRun = false, force = false, packageVersion = "0.0.0" } = options;
413
+ validateTarget(target, { create: !dryRun });
414
+ const backupRoot = path.join(
415
+ target,
416
+ ".workflow",
417
+ "delivery",
418
+ "runtime",
419
+ "backups",
420
+ new Date().toISOString().replaceAll(":", "-"),
421
+ );
422
+ const legacyRoot = path.join(target, ".ai-delivery");
423
+ const legacyActiveTask = path.join(target, LEGACY_ACTIVE_TASK);
424
+ const hasLegacyRoot = fs.existsSync(legacyRoot);
425
+ const hasLegacyActiveTask = fs.existsSync(legacyActiveTask);
426
+ let previousManifest = null;
427
+ try {
428
+ previousManifest = JSON.parse(fs.readFileSync(path.join(target, INSTALL_MANIFEST), "utf8"));
429
+ } catch {
430
+ previousManifest = null;
431
+ }
432
+ const packageVersionComparison = previousManifest?.schema_version >= 5
433
+ && previousManifest.package === "ai-delivery-workflow"
434
+ ? comparePackageVersions(packageVersion, previousManifest.version)
435
+ : 0;
436
+ if (packageVersionComparison === null && previousManifest.version !== packageVersion && !force) {
437
+ throw new Error(`Cannot compare installed package version ${previousManifest.version} with ${packageVersion}; use --force only after verifying the intended replacement.`);
438
+ }
439
+ if (packageVersionComparison < 0 && !force) {
440
+ throw new Error(`Refusing package downgrade from ${previousManifest.version} to ${packageVersion}; use --force only after explicit approval.`);
441
+ }
442
+ const packageVersionUpgrade = packageVersionComparison > 0;
443
+ const reinstallManagedRuntime = force || hasLegacyRoot || hasLegacyActiveTask
444
+ || packageVersionUpgrade || (previousManifest && previousManifest.schema_version < 5);
445
+ if (!dryRun && hasLegacyRoot) {
446
+ const legacyBackup = path.join(backupRoot, "legacy-ai-delivery");
447
+ fs.mkdirSync(path.dirname(legacyBackup), { recursive: true });
448
+ fs.cpSync(legacyRoot, legacyBackup, { recursive: true });
449
+ }
450
+ if (!dryRun && hasLegacyActiveTask) {
451
+ const taskBackup = path.join(backupRoot, "legacy-active-task.json");
452
+ fs.mkdirSync(path.dirname(taskBackup), { recursive: true });
453
+ fs.copyFileSync(legacyActiveTask, taskBackup);
454
+ }
455
+ const currentWorkspace = readWorkspaceConfiguration(target, { optional: true });
456
+ const legacyWorkspaceFile = path.join(target, LEGACY_WORKSPACE_CONFIG);
457
+ const legacyWorkspace = !currentWorkspace && fs.existsSync(legacyWorkspaceFile)
458
+ ? readWorkspaceConfiguration(target, { configurationPath: LEGACY_WORKSPACE_CONFIG })
459
+ : null;
460
+ const existingWorkspace = (currentWorkspace || legacyWorkspace)?.configuration;
461
+ const codePath = options.codePath || existingWorkspace?.code_repository?.path || "code";
462
+ const codeId = options.codeId || existingWorkspace?.code_repository?.repo_id || "app";
463
+ const sharedRemote = options.sharedRemote || null;
464
+ const codeRemote = sharedRemote ? null : options.codeRemote || existingWorkspace?.code_repository?.remote || null;
465
+ const workflowRemote = sharedRemote ? null : options.workflowRemote || existingWorkspace?.workflow_repository?.remote || null;
466
+ const workspaceId = options.workspaceId || existingWorkspace?.workspace_id || null;
467
+ const workspace = initializeGitWorkspace(target, {
468
+ codePath,
469
+ codeId,
470
+ codeRemote,
471
+ workflowRemote,
472
+ sharedRemote,
473
+ workspaceId,
474
+ dryRun,
475
+ });
476
+
477
+ const sourceSkills = path.join(packageRoot, "skills");
478
+ const sourceTemplate = path.join(sourceSkills, "ai-delivery-orchestrate", "assets", "project-template");
479
+ const sourceWorkflow = path.join(sourceTemplate, ".workflow");
480
+ const sourceHook = path.join(sourceSkills, "ai-delivery-checkpoint-task", "scripts", "hook-event.mjs");
481
+ const sourceConfig = path.join(sourceSkills, "ai-delivery-checkpoint-task", "assets", "codex-hook-config.toml");
482
+ const sourceViewer = path.join(sourceTemplate, ".workflow", "tools", "viewer");
483
+ const bootstrapSources = bootstrapRuntimeSources(packageRoot, sourceTemplate);
484
+ const stateSources = stateRuntimeSources(packageRoot, sourceTemplate);
485
+ const evolutionSources = evolutionRuntimeSources(packageRoot, sourceTemplate);
486
+ const sourceStandardsIndex = path.join(sourceWorkflow, "standards", "index.yaml");
487
+ const workflowRoot = path.join(target, ".workflow");
488
+ const standardsIndexFile = path.join(workflowRoot, "standards", "index.yaml");
489
+ const legacyIndicators = [
490
+ path.join(target, "delivery", "project.yaml"),
491
+ path.join(target, "delivery", "workflow-state.yaml"),
492
+ path.join(target, "delivery", "artifact-registry.yaml"),
493
+ path.join(target, "delivery", "standards-baseline.yaml"),
494
+ path.join(target, "standards", "index.yaml"),
495
+ ].filter((candidate) => fs.existsSync(candidate));
496
+ if (fs.existsSync(workflowRoot) && legacyIndicators.length) {
497
+ throw new Error(
498
+ "Detected both .workflow and legacy delivery/standards directories. "
499
+ + "Resolve the split workflow state before initialization.",
500
+ );
501
+ }
502
+ if (legacyIndicators.length) {
503
+ throw new Error(
504
+ "Legacy workflow directories detected at delivery/ or standards/. "
505
+ + "Move them under .workflow/ before upgrading; refusing to create split workflow state.",
506
+ );
507
+ }
508
+ for (const managedPath of [
509
+ ".agents", ".codex", ".workflow", ".ai-delivery", "AGENTS.md", ".gitignore", ".gitattributes",
510
+ ]) {
511
+ validateManagedPath(target, path.join(target, managedPath));
512
+ }
513
+ const skillNames = packagedSkillNames(packageRoot);
514
+ if (!skillNames.length) throw new Error(`No packaged skills found under ${sourceSkills}`);
515
+
516
+ const actions = [];
517
+ const conflicts = [];
518
+ const skillPlans = [];
519
+ for (const name of skillNames) {
520
+ const source = path.join(sourceSkills, name);
521
+ const destination = path.join(target, ".agents", "skills", name);
522
+ if (!fs.existsSync(destination)) skillPlans.push({ type: "copy", name, source, destination });
523
+ else if (directoryDigest(source) !== directoryDigest(destination)) {
524
+ if (reinstallManagedRuntime) skillPlans.push({ type: "replace", name, source, destination });
525
+ else conflicts.push(path.relative(target, destination));
526
+ }
527
+ }
528
+
529
+ const hookDestination = path.join(target, ".workflow", "tools", "hooks", "hook-event.mjs");
530
+ const hookConflict = fs.existsSync(hookDestination)
531
+ && !managedFilesEqual(hookDestination, sourceHook);
532
+ if (hookConflict && !reinstallManagedRuntime) conflicts.push(path.relative(target, hookDestination));
533
+ const viewerDestination = path.join(target, ".workflow", "tools", "viewer");
534
+ let viewerPlan = null;
535
+ if (!fs.existsSync(viewerDestination)) {
536
+ viewerPlan = { type: "copy", source: sourceViewer, destination: viewerDestination };
537
+ } else if (directoryDigest(sourceViewer) !== directoryDigest(viewerDestination)) {
538
+ if (reinstallManagedRuntime) viewerPlan = { type: "replace", source: sourceViewer, destination: viewerDestination };
539
+ else conflicts.push(path.relative(target, viewerDestination));
540
+ }
541
+ const runtimePlans = [
542
+ [".workflow/tools/state", stateSources],
543
+ [".workflow/tools/evolution", evolutionSources],
544
+ [".workflow/tools/bootstrap", bootstrapSources],
545
+ ].map(([installPath, sources]) => planManagedRuntime({
546
+ target,
547
+ installPath,
548
+ sources,
549
+ reinstallManagedRuntime,
550
+ conflicts,
551
+ }));
552
+
553
+ const packagedStandards = parseYaml(fs.readFileSync(sourceStandardsIndex, "utf8")).standards || [];
554
+ for (const standard of packagedStandards) {
555
+ const sourceStandard = path.join(path.dirname(sourceStandardsIndex), standard.path);
556
+ const targetStandard = path.join(workflowRoot, "standards", standard.path);
557
+ const normalizedText = (file) => fs.readFileSync(file, "utf8").replaceAll("\r\n", "\n");
558
+ if (fs.existsSync(targetStandard) && normalizedText(targetStandard) !== normalizedText(sourceStandard)) {
559
+ throw new Error(`Standard ${standard.id} body conflicts with the packaged approved version; resolve it through standards governance.`);
560
+ }
561
+ }
562
+
563
+ if (conflicts.length) {
564
+ throw new Error(`Managed files already exist with different content:\n- ${conflicts.join("\n- ")}\nRerun with --force to back up and replace them.`);
565
+ }
566
+
567
+ const configFile = path.join(target, ".codex", "config.toml");
568
+ const existingConfig = fs.existsSync(configFile) ? fs.readFileSync(configFile, "utf8") : "";
569
+ const mergedConfig = existingConfig
570
+ ? mergeHookConfig(existingConfig, { force })
571
+ : { text: fs.readFileSync(sourceConfig, "utf8"), actions: ["create project Hook configuration"] };
572
+ const existingStandardsIndex = fs.existsSync(standardsIndexFile)
573
+ ? fs.readFileSync(standardsIndexFile, "utf8")
574
+ : "standards: []\n";
575
+ const mergedStandardsIndex = mergeStandardsIndex(
576
+ existingStandardsIndex,
577
+ fs.readFileSync(sourceStandardsIndex, "utf8"),
578
+ );
579
+
580
+ const agentsTemplate = fs.readFileSync(path.join(sourceTemplate, "AGENTS.md"), "utf8");
581
+ const agentsFile = path.join(target, "AGENTS.md");
582
+ const agentsText = mergeManagedMarkdown(fs.existsSync(agentsFile) ? fs.readFileSync(agentsFile, "utf8") : "", agentsTemplate);
583
+ const gitignoreFile = path.join(target, ".gitignore");
584
+ const existingGitignore = fs.existsSync(gitignoreFile) ? fs.readFileSync(gitignoreFile, "utf8") : "";
585
+ if (ignoresWholeWorkflow(existingGitignore)) {
586
+ throw new Error(
587
+ "The project .gitignore excludes the complete .workflow directory. "
588
+ + "Remove that rule and ignore only .workflow/delivery/runtime/.",
589
+ );
590
+ }
591
+ const gitignoreText = mergeGitignore(
592
+ existingGitignore,
593
+ [`/${workspace.configuration.code_repository.path}/`],
594
+ );
595
+ const gitattributesFile = path.join(target, ".gitattributes");
596
+ const existingGitattributes = fs.existsSync(gitattributesFile)
597
+ ? fs.readFileSync(gitattributesFile, "utf8")
598
+ : "";
599
+ const gitattributesText = mergeGitattributes(existingGitattributes);
600
+ if (hasLegacyRoot || hasLegacyActiveTask) {
601
+ actions.push("migrate legacy .ai-delivery runtime into .workflow");
602
+ if (!dryRun) {
603
+ if (hasLegacyActiveTask) {
604
+ const activeTask = path.join(target, ACTIVE_TASK);
605
+ if (!fs.existsSync(activeTask)) {
606
+ fs.mkdirSync(path.dirname(activeTask), { recursive: true });
607
+ fs.copyFileSync(legacyActiveTask, activeTask);
608
+ }
609
+ }
610
+ }
611
+ }
612
+
613
+ for (const plan of skillPlans) {
614
+ if (plan.type === "copy") {
615
+ actions.push(`install .agents/skills/${plan.name}`);
616
+ if (!dryRun) {
617
+ fs.mkdirSync(path.dirname(plan.destination), { recursive: true });
618
+ fs.cpSync(plan.source, plan.destination, { recursive: true });
619
+ }
620
+ } else {
621
+ backupAndReplace(target, plan.destination, plan.source, backupRoot, actions, dryRun);
622
+ }
623
+ }
624
+
625
+ if (!fs.existsSync(hookDestination)) {
626
+ actions.push("install .workflow/tools/hooks/hook-event.mjs");
627
+ if (!dryRun) {
628
+ fs.mkdirSync(path.dirname(hookDestination), { recursive: true });
629
+ fs.copyFileSync(sourceHook, hookDestination);
630
+ }
631
+ } else if (hookConflict) {
632
+ backupAndReplace(target, hookDestination, sourceHook, backupRoot, actions, dryRun);
633
+ }
634
+ if (viewerPlan?.type === "copy") {
635
+ actions.push("install .workflow/tools/viewer");
636
+ if (!dryRun) {
637
+ fs.mkdirSync(path.dirname(viewerDestination), { recursive: true });
638
+ fs.cpSync(sourceViewer, viewerDestination, { recursive: true });
639
+ }
640
+ } else if (viewerPlan?.type === "replace") {
641
+ backupAndReplace(target, viewerDestination, sourceViewer, backupRoot, actions, dryRun);
642
+ }
643
+ for (const runtimePlan of runtimePlans) {
644
+ applyManagedRuntimePlan(target, runtimePlan, backupRoot, actions, dryRun);
645
+ }
646
+
647
+ copyMissingTree(path.join(sourceWorkflow, "delivery"), path.join(workflowRoot, "delivery"), actions, dryRun);
648
+ copyMissingTree(path.join(sourceWorkflow, "standards"), path.join(workflowRoot, "standards"), actions, dryRun);
649
+ copyMissingTree(path.join(sourceWorkflow, "evolution"), path.join(workflowRoot, "evolution"), actions, dryRun);
650
+ copyMissingTree(path.join(sourceWorkflow, "extensions"), path.join(workflowRoot, "extensions"), actions, dryRun);
651
+ for (const directory of ["events", "pending", "staging"]) {
652
+ const destination = path.join(workflowRoot, "delivery", "runtime", "evolution", directory);
653
+ if (!fs.existsSync(destination)) {
654
+ actions.push(`create .workflow/delivery/runtime/evolution/${directory}/`);
655
+ if (!dryRun) fs.mkdirSync(destination, { recursive: true });
656
+ }
657
+ }
658
+ for (const directory of WORKFLOW_CONTENT_DIRECTORIES) {
659
+ const destination = path.join(workflowRoot, directory);
660
+ if (!fs.existsSync(destination)) {
661
+ actions.push(`create .workflow/${directory}/`);
662
+ if (!dryRun) fs.mkdirSync(destination, { recursive: true });
663
+ }
664
+ const marker = path.join(destination, ".gitkeep");
665
+ if (!fs.existsSync(marker)) {
666
+ actions.push(`create .workflow/${directory}/.gitkeep`);
667
+ if (!dryRun) atomicWrite(marker, "");
668
+ }
669
+ }
670
+ for (const directory of CONTROL_DIRECTORIES) {
671
+ const destination = path.join(workflowRoot, "control", directory);
672
+ if (!fs.existsSync(destination)) {
673
+ actions.push(`create .workflow/control/${directory}/`);
674
+ if (!dryRun) fs.mkdirSync(destination, { recursive: true });
675
+ }
676
+ const marker = path.join(destination, ".gitkeep");
677
+ if (!fs.existsSync(marker)) {
678
+ actions.push(`create .workflow/control/${directory}/.gitkeep`);
679
+ if (!dryRun) atomicWrite(marker, "");
680
+ }
681
+ }
682
+ if (existingStandardsIndex !== mergedStandardsIndex.text) {
683
+ actions.push(...mergedStandardsIndex.added.map((id) => `register ${id} in .workflow/standards/index.yaml`));
684
+ if (!dryRun) atomicWrite(standardsIndexFile, mergedStandardsIndex.text);
685
+ }
686
+
687
+ if (!fs.existsSync(agentsFile) || fs.readFileSync(agentsFile, "utf8") !== agentsText) {
688
+ actions.push("merge AI delivery instructions into AGENTS.md");
689
+ if (!dryRun) atomicWrite(agentsFile, agentsText);
690
+ }
691
+ if (!fs.existsSync(gitignoreFile) || fs.readFileSync(gitignoreFile, "utf8") !== gitignoreText) {
692
+ actions.push("merge local runtime entries into .gitignore");
693
+ if (!dryRun) atomicWrite(gitignoreFile, gitignoreText);
694
+ }
695
+ if (!fs.existsSync(gitattributesFile)
696
+ || fs.readFileSync(gitattributesFile, "utf8") !== gitattributesText) {
697
+ actions.push("merge byte-stable workflow rules into .gitattributes");
698
+ if (!dryRun) atomicWrite(gitattributesFile, gitattributesText);
699
+ }
700
+ if (existingConfig !== mergedConfig.text) {
701
+ actions.push(...mergedConfig.actions.map((item) => `${item} in .codex/config.toml`));
702
+ if (!dryRun) atomicWrite(configFile, mergedConfig.text);
703
+ }
704
+
705
+ const workspaceFile = path.join(target, WORKSPACE_CONFIG);
706
+ const workspaceText = workspaceConfigurationText(workspace.configuration);
707
+ if (!fs.existsSync(workspaceFile) || fs.readFileSync(workspaceFile, "utf8") !== workspaceText) {
708
+ actions.push(`write ${WORKSPACE_CONFIG.replaceAll("\\", "/")}`);
709
+ if (!dryRun) atomicWrite(workspaceFile, workspaceText);
710
+ }
711
+
712
+ const manifestFile = path.join(target, INSTALL_MANIFEST);
713
+ let existingManifest = null;
714
+ try {
715
+ existingManifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
716
+ } catch {
717
+ existingManifest = null;
718
+ }
719
+ const manifest = {
720
+ schema_version: 5,
721
+ package: "ai-delivery-workflow",
722
+ version: packageVersion,
723
+ scope: "workspace",
724
+ skill_root: ".agents/skills",
725
+ workflow_root: ".workflow",
726
+ viewer_root: ".workflow/tools/viewer",
727
+ bootstrap_cli: ".workflow/tools/bootstrap/bootstrap.mjs",
728
+ state_cli: ".workflow/tools/state/state.mjs",
729
+ evolution_cli: ".workflow/tools/evolution/evolve.mjs",
730
+ hook_protocol_version: 1,
731
+ hook_cli: ".workflow/tools/hooks/hook-event.mjs",
732
+ code_repository: workspace.configuration.code_repository,
733
+ viewer_start: {
734
+ windows_cmd: ".workflow/tools/viewer/start.cmd",
735
+ powershell: ".workflow/tools/viewer/start.ps1",
736
+ unix: ".workflow/tools/viewer/start.sh",
737
+ },
738
+ installed_skills: skillNames,
739
+ managed_assets: {
740
+ skills: Object.fromEntries(skillNames.map((name) => [
741
+ name,
742
+ directoryDigest(path.join(sourceSkills, name)),
743
+ ])),
744
+ hook: {
745
+ path: ".workflow/tools/hooks/hook-event.mjs",
746
+ sha256: managedFileDigest(sourceHook),
747
+ },
748
+ state_runtime: {
749
+ path: ".workflow/tools/state",
750
+ sha256: sourceSetDigest(stateSources),
751
+ },
752
+ evolution_runtime: {
753
+ path: ".workflow/tools/evolution",
754
+ sha256: sourceSetDigest(evolutionSources),
755
+ },
756
+ bootstrap_runtime: {
757
+ path: ".workflow/tools/bootstrap",
758
+ sha256: sourceSetDigest(bootstrapSources),
759
+ },
760
+ },
761
+ installed_at: existingManifest?.installed_at || new Date().toISOString(),
762
+ };
763
+ const manifestText = `${JSON.stringify(manifest, null, 2)}\n`;
764
+ if (!fs.existsSync(manifestFile) || fs.readFileSync(manifestFile, "utf8") !== manifestText) {
765
+ actions.push("write .workflow/config/install-manifest.json");
766
+ if (!dryRun) atomicWrite(manifestFile, manifestText);
767
+ }
768
+
769
+ if (!dryRun && (hasLegacyRoot || hasLegacyActiveTask)) {
770
+ for (const required of [
771
+ path.join(target, WORKSPACE_CONFIG),
772
+ path.join(target, INSTALL_MANIFEST),
773
+ path.join(target, ".workflow", "tools", "state", "state.mjs"),
774
+ path.join(target, ".workflow", "tools", "hooks", "hook-event.mjs"),
775
+ ]) {
776
+ if (!fs.existsSync(required)) {
777
+ throw new Error(`Legacy migration is incomplete; preserved .ai-delivery for recovery: ${required}`);
778
+ }
779
+ }
780
+ if (hasLegacyRoot) fs.rmSync(legacyRoot, { recursive: true, force: true });
781
+ if (hasLegacyActiveTask) fs.rmSync(legacyActiveTask, { force: true });
782
+ }
783
+
784
+ return {
785
+ actions: [...workspace.actions, ...actions],
786
+ summary: `${dryRun ? "Dry run" : "Initialized"} ${skillNames.length} workspace-scoped AI delivery skills in ${target}`
787
+ + (dryRun
788
+ ? ""
789
+ : "\nWorkflow viewer: .workflow\\tools\\viewer\\start.cmd (Windows) or sh ./.workflow/tools/viewer/start.sh"),
790
+ };
791
+ }
792
+
793
+ export function doctorProject(target, options = {}) {
794
+ validateTarget(target);
795
+ for (const managedPath of [".agents", ".codex", ".workflow", "AGENTS.md"]) {
796
+ validateManagedPath(target, path.join(target, managedPath));
797
+ }
798
+ const checks = [];
799
+ let workspace = null;
800
+ try {
801
+ workspace = readWorkspaceConfiguration(target);
802
+ checks.push({ ok: true, message: "workspace configuration is valid" });
803
+ } catch (error) {
804
+ checks.push({ ok: false, message: error.message });
805
+ }
806
+ const manifestFile = path.join(target, INSTALL_MANIFEST);
807
+ let manifest = null;
808
+ try {
809
+ manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
810
+ checks.push({
811
+ ok: manifest.scope === "workspace" && manifest.schema_version === 5,
812
+ message: "installation manifest uses workspace scope",
813
+ });
814
+ } catch (error) {
815
+ checks.push({ ok: false, message: `installation manifest is missing or invalid: ${error.message}` });
816
+ }
817
+
818
+ const workflowGit = gitTopLevel(target);
819
+ const codeGit = workspace ? gitTopLevel(workspace.codeRoot) : null;
820
+ const workflowCommonGit = workflowGit ? gitCommonDirectory(target) : null;
821
+ const codeCommonGit = codeGit && workspace ? gitCommonDirectory(workspace.codeRoot) : null;
822
+ checks.push({
823
+ ok: Boolean(workflowGit) && path.resolve(workflowGit).toLowerCase() === path.resolve(target).toLowerCase(),
824
+ message: "workflow repository has an independent Git root",
825
+ });
826
+ const missingControlDirectories = CONTROL_DIRECTORIES.filter((directory) =>
827
+ !fs.existsSync(path.join(target, ".workflow", "control", directory)));
828
+ checks.push({
829
+ ok: missingControlDirectories.length === 0,
830
+ message: missingControlDirectories.length
831
+ ? `shared control shard directories are missing: ${missingControlDirectories.join(", ")}`
832
+ : "shared task, artifact, Gate, version, and release shard directories are present",
833
+ });
834
+ checks.push({
835
+ ok: Boolean(codeGit)
836
+ && workspace
837
+ && path.resolve(codeGit).toLowerCase() === path.resolve(workspace.codeRoot).toLowerCase()
838
+ && path.resolve(codeGit).toLowerCase() !== path.resolve(workflowGit || target).toLowerCase(),
839
+ message: "code repository has a distinct nested Git root",
840
+ });
841
+ checks.push({
842
+ ok: Boolean(workflowCommonGit) && Boolean(codeCommonGit)
843
+ && path.resolve(workflowCommonGit).toLowerCase() !== path.resolve(codeCommonGit).toLowerCase(),
844
+ message: "workflow and code repositories use independent Git common directories",
845
+ });
846
+
847
+ const packageRoot = options.packageRoot || PACKAGE_ROOT;
848
+ let expectedSkills = ESSENTIAL_STARTUP_SKILLS;
849
+ let expectedSkillDigests = {};
850
+ let expectedHook = null;
851
+ let expectedBootstrapSources = [];
852
+ let expectedStateSources = [];
853
+ let expectedEvolutionSources = [];
854
+ try {
855
+ expectedSkills = packagedSkillNames(packageRoot);
856
+ expectedSkillDigests = Object.fromEntries(expectedSkills.map((name) => [
857
+ name,
858
+ directoryDigest(path.join(packageRoot, "skills", name)),
859
+ ]));
860
+ expectedHook = path.join(packageRoot, "skills", "ai-delivery-checkpoint-task", "scripts", "hook-event.mjs");
861
+ const sourceTemplate = path.join(packageRoot, "skills", "ai-delivery-orchestrate", "assets", "project-template");
862
+ expectedBootstrapSources = bootstrapRuntimeSources(packageRoot, sourceTemplate);
863
+ expectedStateSources = stateRuntimeSources(packageRoot, sourceTemplate);
864
+ expectedEvolutionSources = evolutionRuntimeSources(packageRoot, sourceTemplate);
865
+ } catch (error) {
866
+ checks.push({ ok: false, message: `packaged managed-asset baseline is unavailable: ${error.message}` });
867
+ }
868
+ const manifestSkills = Array.isArray(manifest?.installed_skills) ? [...manifest.installed_skills].sort() : [];
869
+ checks.push({
870
+ ok: JSON.stringify(manifestSkills) === JSON.stringify(expectedSkills),
871
+ message: JSON.stringify(manifestSkills) === JSON.stringify(expectedSkills)
872
+ ? `installation manifest declares the fixed ${expectedSkills.length}-skill inventory`
873
+ : `installation manifest skill inventory differs from the fixed ${expectedSkills.length}-skill package inventory`,
874
+ });
875
+ const missingSkills = expectedSkills.filter((name) => !fs.existsSync(path.join(target, ".agents", "skills", name, "SKILL.md")));
876
+ checks.push({
877
+ ok: expectedSkills.length > 0 && missingSkills.length === 0,
878
+ message: missingSkills.length ? `missing project skills: ${missingSkills.join(", ")}` : `${expectedSkills.length} project skills are present`,
879
+ });
880
+ const damagedSkills = expectedSkills.filter((name) => {
881
+ const installed = path.join(target, ".agents", "skills", name);
882
+ return !fs.existsSync(installed) || directoryDigest(installed) !== expectedSkillDigests[name];
883
+ });
884
+ const manifestSkillDigests = manifest?.managed_assets?.skills || {};
885
+ const inconsistentSkillDigests = expectedSkills.filter((name) =>
886
+ manifestSkillDigests[name] !== expectedSkillDigests[name]);
887
+ checks.push({
888
+ ok: damagedSkills.length === 0 && inconsistentSkillDigests.length === 0,
889
+ message: damagedSkills.length || inconsistentSkillDigests.length
890
+ ? `managed skill integrity failed: damaged=${damagedSkills.join(", ") || "none"}; manifest=${inconsistentSkillDigests.join(", ") || "none"}`
891
+ : "managed skill integrity matches the fixed package baseline",
892
+ });
893
+ try {
894
+ const config = inspectHookConfig(fs.readFileSync(path.join(target, ".codex", "config.toml"), "utf8"));
895
+ checks.push({ ok: config.hooksEnabled, message: "project Codex hooks feature is enabled" });
896
+ checks.push({ ok: config.registeredEvents.length === 10, message: `${config.registeredEvents.length}/10 delivery Hook events are registered` });
897
+ } catch (error) {
898
+ checks.push({ ok: false, message: `project Hook configuration is invalid: ${error.message}` });
899
+ }
900
+
901
+ const installedHook = path.join(target, ".workflow", "tools", "hooks", "hook-event.mjs");
902
+ const expectedHookDigest = expectedHook && fs.existsSync(expectedHook) ? managedFileDigest(expectedHook) : null;
903
+ const hookIntegrity = Boolean(expectedHookDigest)
904
+ && fs.existsSync(installedHook)
905
+ && managedFileDigest(installedHook) === expectedHookDigest
906
+ && manifest?.managed_assets?.hook?.sha256 === expectedHookDigest;
907
+ checks.push({
908
+ ok: hookIntegrity,
909
+ message: hookIntegrity ? "project Hook integrity matches the package baseline" : "project Hook integrity check failed",
910
+ });
911
+ const viewerFiles = [
912
+ "server.mjs",
913
+ "start.cmd",
914
+ "start.ps1",
915
+ "start.sh",
916
+ "public/index.html",
917
+ "public/app.js",
918
+ "public/styles.css",
919
+ ];
920
+ const missingViewerFiles = viewerFiles.filter((file) =>
921
+ !fs.existsSync(path.join(target, ".workflow", "tools", "viewer", file)));
922
+ checks.push({
923
+ ok: missingViewerFiles.length === 0,
924
+ required: false,
925
+ scope: "viewer",
926
+ message: missingViewerFiles.length
927
+ ? `workflow viewer is incomplete: ${missingViewerFiles.join(", ")}`
928
+ : "project workflow viewer and startup scripts are present",
929
+ });
930
+ const stateFiles = [
931
+ "state.mjs",
932
+ "delivery-state.mjs",
933
+ "vendor/yaml/package.json",
934
+ "vendor/yaml/dist/index.js",
935
+ ];
936
+ const missingStateFiles = stateFiles.filter((file) =>
937
+ !fs.existsSync(path.join(target, ".workflow", "tools", "state", file)));
938
+ checks.push({
939
+ ok: missingStateFiles.length === 0,
940
+ message: missingStateFiles.length
941
+ ? `formal state CLI is incomplete: ${missingStateFiles.join(", ")}`
942
+ : "project formal state CLI and vendored YAML runtime are present",
943
+ });
944
+ const installedStateRoot = path.join(target, ".workflow", "tools", "state");
945
+ const expectedStateDigest = expectedStateSources.length ? sourceSetDigest(expectedStateSources) : null;
946
+ const stateIntegrity = Boolean(expectedStateDigest)
947
+ && fs.existsSync(installedStateRoot)
948
+ && directoryDigest(installedStateRoot) === expectedStateDigest
949
+ && manifest?.managed_assets?.state_runtime?.sha256 === expectedStateDigest;
950
+ checks.push({
951
+ ok: stateIntegrity,
952
+ message: stateIntegrity
953
+ ? "formal state runtime integrity matches the package baseline"
954
+ : "formal state runtime integrity check failed",
955
+ });
956
+ const installedBootstrapRoot = path.join(target, ".workflow", "tools", "bootstrap");
957
+ const bootstrapFiles = [
958
+ "bootstrap.mjs",
959
+ "project-bootstrap.mjs",
960
+ "workspace.mjs",
961
+ "yaml-runtime.mjs",
962
+ "delivery-state.mjs",
963
+ ];
964
+ const missingBootstrapFiles = bootstrapFiles.filter((file) =>
965
+ !fs.existsSync(path.join(installedBootstrapRoot, file)));
966
+ checks.push({
967
+ ok: missingBootstrapFiles.length === 0,
968
+ message: missingBootstrapFiles.length
969
+ ? `project bootstrap CLI is incomplete: ${missingBootstrapFiles.join(", ")}`
970
+ : "project bootstrap inspect runtime is present",
971
+ });
972
+ const expectedBootstrapDigest = expectedBootstrapSources.length
973
+ ? sourceSetDigest(expectedBootstrapSources)
974
+ : null;
975
+ const bootstrapIntegrity = Boolean(expectedBootstrapDigest)
976
+ && fs.existsSync(installedBootstrapRoot)
977
+ && directoryDigest(installedBootstrapRoot) === expectedBootstrapDigest
978
+ && manifest?.managed_assets?.bootstrap_runtime?.sha256 === expectedBootstrapDigest;
979
+ checks.push({
980
+ ok: bootstrapIntegrity,
981
+ message: bootstrapIntegrity
982
+ ? "project bootstrap runtime integrity matches the package baseline"
983
+ : "project bootstrap runtime integrity check failed",
984
+ });
985
+ const bootstrapSmoke = spawnSync(
986
+ process.execPath,
987
+ [path.join(installedBootstrapRoot, "bootstrap.mjs"), "inspect"],
988
+ { cwd: target, encoding: "utf8", windowsHide: true },
989
+ );
990
+ let bootstrapOutput = null;
991
+ try { bootstrapOutput = JSON.parse(bootstrapSmoke.stdout); } catch { bootstrapOutput = null; }
992
+ checks.push({
993
+ ok: bootstrapSmoke.status === 0 && Boolean(bootstrapOutput?.state),
994
+ message: bootstrapSmoke.status === 0 && bootstrapOutput?.state
995
+ ? "project bootstrap inspect smoke check passes"
996
+ : `project bootstrap inspect smoke check failed: ${(bootstrapSmoke.stderr || bootstrapSmoke.error?.message || "no valid output").trim()}`,
997
+ });
998
+ const smokeResults = ["inspect", "verify"].map((command) => {
999
+ const result = spawnSync(
1000
+ process.execPath,
1001
+ [path.join(installedStateRoot, "state.mjs"), command],
1002
+ { cwd: target, encoding: "utf8", windowsHide: true },
1003
+ );
1004
+ let output = null;
1005
+ try {
1006
+ output = JSON.parse(result.stdout);
1007
+ } catch {
1008
+ output = null;
1009
+ }
1010
+ const validOutput = command === "verify" ? output?.valid === true : output?.command === "inspect";
1011
+ return { ...result, command, validOutput };
1012
+ });
1013
+ const smokeOk = smokeResults.every((result) => result.status === 0 && result.validOutput);
1014
+ checks.push({
1015
+ ok: smokeOk,
1016
+ message: smokeOk
1017
+ ? "formal state CLI inspect/verify smoke checks pass"
1018
+ : `formal state CLI smoke check failed: ${smokeResults.find((result) => result.status !== 0 || !result.validOutput)?.stderr.trim() || "no valid output"}`,
1019
+ });
1020
+ const installedEvolutionRoot = path.join(target, ".workflow", "tools", "evolution");
1021
+ const expectedEvolutionDigest = expectedEvolutionSources.length ? sourceSetDigest(expectedEvolutionSources) : null;
1022
+ const evolutionIntegrity = Boolean(expectedEvolutionDigest)
1023
+ && fs.existsSync(installedEvolutionRoot)
1024
+ && directoryDigest(installedEvolutionRoot) === expectedEvolutionDigest
1025
+ && manifest?.managed_assets?.evolution_runtime?.sha256 === expectedEvolutionDigest;
1026
+ checks.push({
1027
+ ok: evolutionIntegrity,
1028
+ message: evolutionIntegrity
1029
+ ? "workflow evolution runtime integrity matches the package baseline"
1030
+ : "workflow evolution runtime integrity check failed",
1031
+ });
1032
+ const evolutionSmoke = spawnSync(
1033
+ process.execPath,
1034
+ [path.join(installedEvolutionRoot, "evolve.mjs"), "inspect"],
1035
+ { cwd: target, encoding: "utf8", windowsHide: true },
1036
+ );
1037
+ let evolutionOutput = null;
1038
+ try { evolutionOutput = JSON.parse(evolutionSmoke.stdout); } catch { evolutionOutput = null; }
1039
+ checks.push({
1040
+ ok: evolutionSmoke.status === 0 && evolutionOutput?.schema_version === 1,
1041
+ message: evolutionSmoke.status === 0 && evolutionOutput?.schema_version === 1
1042
+ ? "workflow evolution CLI verify smoke check passes"
1043
+ : `workflow evolution CLI smoke check failed: ${evolutionSmoke.stderr.trim() || "no valid output"}`,
1044
+ });
1045
+ const evolutionVerify = spawnSync(
1046
+ process.execPath,
1047
+ [path.join(installedEvolutionRoot, "evolve.mjs"), "verify"],
1048
+ { cwd: target, encoding: "utf8", windowsHide: true },
1049
+ );
1050
+ let evolutionVerifyOutput = null;
1051
+ try { evolutionVerifyOutput = JSON.parse(evolutionVerify.stdout); } catch { evolutionVerifyOutput = null; }
1052
+ checks.push({
1053
+ ok: evolutionVerifyOutput?.ok === true,
1054
+ required: false,
1055
+ scope: "evolution-extensions",
1056
+ message: evolutionVerifyOutput?.ok === false
1057
+ ? `workflow extensions require quarantine or repair: ${(evolutionVerifyOutput.issues || []).map((item) => `${item.extension_id}@${item.version}: ${item.message}`).join("; ")}`
1058
+ : "registered workflow extensions pass checksum and manifest validation",
1059
+ });
1060
+ checks.push({
1061
+ ok: fs.existsSync(path.join(target, ".workflow", "delivery", "project.yaml")),
1062
+ message: "project workflow state uses .workflow/delivery",
1063
+ });
1064
+ checks.push({
1065
+ ok: fs.existsSync(path.join(target, ".workflow", "standards", "index.yaml")),
1066
+ message: "project standards use .workflow/standards",
1067
+ });
1068
+ checks.push({
1069
+ ok: fs.existsSync(path.join(target, ".workflow", "releases")),
1070
+ message: "independent releases use .workflow/releases",
1071
+ });
1072
+ const gitignoreFile = path.join(target, ".gitignore");
1073
+ const gitignore = fs.existsSync(gitignoreFile) ? fs.readFileSync(gitignoreFile, "utf8") : "";
1074
+ checks.push({
1075
+ ok: !ignoresWholeWorkflow(gitignore),
1076
+ message: ignoresWholeWorkflow(gitignore)
1077
+ ? "project .gitignore excludes the complete .workflow directory"
1078
+ : "project .gitignore keeps durable .workflow documents trackable",
1079
+ });
1080
+ const gitattributesFile = path.join(target, ".gitattributes");
1081
+ const gitattributes = fs.existsSync(gitattributesFile)
1082
+ ? fs.readFileSync(gitattributesFile, "utf8")
1083
+ : "";
1084
+ const missingAttributes = GIT_ATTRIBUTE_ENTRIES.filter((entry) =>
1085
+ !gitattributes.split(/\r?\n/).some((line) => line.trim() === entry));
1086
+ const attributeTargets = [...new Set([
1087
+ ".workflow/delivery/workflow-state.yaml",
1088
+ ".agents/skills/ai-delivery-orchestrate/SKILL.md",
1089
+ ".codex/config.toml",
1090
+ "AGENTS.md",
1091
+ ".gitignore",
1092
+ ".gitattributes",
1093
+ ...filesUnder(path.join(target, ".workflow"))
1094
+ .map((file) => path.relative(target, file).replaceAll("\\", "/")),
1095
+ ...filesUnder(path.join(target, ".agents", "skills"))
1096
+ .map((file) => path.relative(target, file).replaceAll("\\", "/"))
1097
+ .filter((file) => file.startsWith(".agents/skills/ai-delivery-")),
1098
+ ])];
1099
+ const attributeValues = new Map();
1100
+ let effectiveAttributesFailed = false;
1101
+ for (let index = 0; index < attributeTargets.length; index += 100) {
1102
+ const batch = attributeTargets.slice(index, index + 100);
1103
+ const effectiveAttributes = spawnSync(
1104
+ "git",
1105
+ ["-c", "core.quotePath=false", "check-attr", "text", "eol", "--", ...batch],
1106
+ { cwd: target, encoding: "utf8", windowsHide: true },
1107
+ );
1108
+ if (effectiveAttributes.status !== 0) {
1109
+ effectiveAttributesFailed = true;
1110
+ break;
1111
+ }
1112
+ for (const line of effectiveAttributes.stdout.split(/\r?\n/).filter(Boolean)) {
1113
+ const match = line.match(/^(.*): (text|eol): (.*)$/);
1114
+ if (match) attributeValues.set(`${match[1]}:${match[2]}`, match[3]);
1115
+ }
1116
+ }
1117
+ const invalidEffectiveAttributes = !effectiveAttributesFailed
1118
+ ? attributeTargets.filter((file) => {
1119
+ const text = attributeValues.get(`${file}:text`);
1120
+ return !new Set(["set", "auto"]).has(text) || attributeValues.get(`${file}:eol`) !== "lf";
1121
+ })
1122
+ : [...attributeTargets];
1123
+ checks.push({
1124
+ ok: missingAttributes.length === 0 && invalidEffectiveAttributes.length === 0,
1125
+ message: missingAttributes.length
1126
+ ? `project .gitattributes is missing byte-stable workflow rules: ${missingAttributes.join(", ")}`
1127
+ : invalidEffectiveAttributes.length
1128
+ ? `project .gitattributes is overridden for checksummed files: ${invalidEffectiveAttributes.join(", ")}`
1129
+ : "project .gitattributes keeps checksummed workflow files at LF",
1130
+ });
1131
+ if (workspace) {
1132
+ const expectedIgnore = `/${workspace.configuration.code_repository.path}/`;
1133
+ checks.push({
1134
+ ok: gitignore.split(/\r?\n/).some((line) => line.trim() === expectedIgnore),
1135
+ message: `workflow repository ignores ${expectedIgnore}`,
1136
+ });
1137
+ const codeRemote = spawnSync("git", ["remote", "get-url", "origin"], {
1138
+ cwd: workspace.codeRoot,
1139
+ encoding: "utf8",
1140
+ windowsHide: true,
1141
+ });
1142
+ const configuredCodeRemote = workspace.configuration.code_repository.remote || null;
1143
+ checks.push({
1144
+ ok: configuredCodeRemote
1145
+ ? codeRemote.status === 0 && codeRemote.stdout.trim() === configuredCodeRemote
1146
+ : true,
1147
+ message: configuredCodeRemote
1148
+ ? "code repository origin matches workspace configuration"
1149
+ : "code repository has no required configured remote",
1150
+ });
1151
+ const workflowRemote = spawnSync("git", ["remote", "get-url", "origin"], {
1152
+ cwd: target,
1153
+ encoding: "utf8",
1154
+ windowsHide: true,
1155
+ });
1156
+ const configuredWorkflowRemote = workspace.configuration.workflow_repository.remote || null;
1157
+ checks.push({
1158
+ ok: configuredWorkflowRemote
1159
+ ? workflowRemote.status === 0 && workflowRemote.stdout.trim() === configuredWorkflowRemote
1160
+ : true,
1161
+ message: configuredWorkflowRemote
1162
+ ? "workflow repository origin matches workspace configuration"
1163
+ : "workflow repository has no required configured remote",
1164
+ });
1165
+ const workflowBranch = spawnSync("git", ["branch", "--show-current"], {
1166
+ cwd: target, encoding: "utf8", windowsHide: true,
1167
+ }).stdout.trim();
1168
+ checks.push({
1169
+ ok: workflowBranch === workspace.configuration.workflow_repository.branch,
1170
+ message: `workflow repository uses ${workspace.configuration.workflow_repository.branch}`,
1171
+ });
1172
+ checks.push({
1173
+ ok: workspace.configuration.repository_layout === "independent-git-roots"
1174
+ && workspace.configuration.code_repository?.default_branch === "main",
1175
+ message: "workspace declares independent Git roots and code main",
1176
+ });
1177
+ }
1178
+ checks.push({
1179
+ ok: !fs.existsSync(path.join(target, ".ai-delivery")) && !fs.existsSync(path.join(target, LEGACY_ACTIVE_TASK)),
1180
+ message: "legacy .ai-delivery paths are absent",
1181
+ });
1182
+ checks.push({ ok: fs.existsSync(path.join(target, "AGENTS.md")), message: "project AGENTS.md is present" });
1183
+ return { checks, ok: checks.filter((check) => check.required !== false).every((check) => check.ok) };
1184
+ }