@thebackstoryis/engineering-with-ai 0.2.9

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 (334) hide show
  1. package/Docs/README.md +50 -0
  2. package/Docs/adoption/consultancy-and-multi-project-rollout.md +135 -0
  3. package/Docs/adoption/non-technical-team-guide.md +126 -0
  4. package/Docs/archaeology-technology-and-hosting-discovery.md +212 -0
  5. package/Docs/blast-radius-and-impact-routing-guide.md +325 -0
  6. package/Docs/blueprints/internal-blueprint-catalogue.md +146 -0
  7. package/Docs/blueprints/maintaining-organisation-blueprints.md +154 -0
  8. package/Docs/blueprints/validation-and-troubleshooting.md +168 -0
  9. package/Docs/cli-reference.md +113 -0
  10. package/Docs/completed-phase-evidence-amendments.md +74 -0
  11. package/Docs/consultancy-network-rollout-control-plane-guide.md +202 -0
  12. package/Docs/context-aware-delivery-companion-guide.md +198 -0
  13. package/Docs/context-aware-delivery-companion-user-guide.md +184 -0
  14. package/Docs/context-management-and-token-efficiency.md +113 -0
  15. package/Docs/design-systems/design-system-implementation-guide.md +85 -0
  16. package/Docs/design-systems/design-system-pack-authoring-guide.md +95 -0
  17. package/Docs/design-systems/design-system-review-guide.md +51 -0
  18. package/Docs/design-systems/design-system-user-guide.md +96 -0
  19. package/Docs/design-systems/product-owner-guide.md +49 -0
  20. package/Docs/designing-organisation-blueprint-packs.md +384 -0
  21. package/Docs/developer-delivery-guide.md +224 -0
  22. package/Docs/error-reporting-guide.md +110 -0
  23. package/Docs/error-reporting-provider-guide.md +49 -0
  24. package/Docs/examples/error-report-adapter.md +70 -0
  25. package/Docs/examples/meeting-review.md +76 -0
  26. package/Docs/examples/minimal-design-system.md +67 -0
  27. package/Docs/examples/prototype-review-inputs.md +175 -0
  28. package/Docs/examples/reproducible-archaeology-depth-example.md +144 -0
  29. package/Docs/examples/test-scenario-input.md +68 -0
  30. package/Docs/examples/worked-examples.md +147 -0
  31. package/Docs/existing-project-onboarding-guide.md +214 -0
  32. package/Docs/explanation/core-concepts.md +26 -0
  33. package/Docs/explanation/delivery-workflow.md +48 -0
  34. package/Docs/governance/governance-team-guide.md +139 -0
  35. package/Docs/governed-starter-project-materialisation-guide.md +284 -0
  36. package/Docs/guide-catalogue.md +117 -0
  37. package/Docs/guided-discovery-facilitator-guide.md +172 -0
  38. package/Docs/guided-intent-workspace-guide.md +109 -0
  39. package/Docs/guided-phase-evidence-drafting-guide.md +119 -0
  40. package/Docs/human-approval-and-assurance-guide.md +146 -0
  41. package/Docs/knowledge-proposals-implementer-guide.md +106 -0
  42. package/Docs/knowledge-proposals-user-guide.md +247 -0
  43. package/Docs/maintainers/context-benchmarks.md +29 -0
  44. package/Docs/maintainers/contributing.md +58 -0
  45. package/Docs/maintainers/evidence-depth-acceptance.md +72 -0
  46. package/Docs/maintainers/verification-walkthroughs.md +104 -0
  47. package/Docs/meeting-evidence-implementer-guide.md +132 -0
  48. package/Docs/meeting-evidence-user-guide.md +200 -0
  49. package/Docs/operations/dashboard-and-delivery-state.md +153 -0
  50. package/Docs/operations/dashboard-configuration.md +87 -0
  51. package/Docs/operations/installation-updating-and-entitlements.md +135 -0
  52. package/Docs/operations/premium-personas-setup.md +76 -0
  53. package/Docs/operations/troubleshooting-and-recovery.md +205 -0
  54. package/Docs/organisation-rollout-guide.md +142 -0
  55. package/Docs/persona-entitlement-provider-guide.md +199 -0
  56. package/Docs/persona-guided-prototype-iteration.md +129 -0
  57. package/Docs/personas/organisation-specific-personas.md +103 -0
  58. package/Docs/personas/persona-authoring-cookbook.md +176 -0
  59. package/Docs/personas/persona-engagement-ui.md +133 -0
  60. package/Docs/personas/persona-governance.md +118 -0
  61. package/Docs/platform-export-analysis-guide.md +336 -0
  62. package/Docs/policies/governance-owner-guide.md +36 -0
  63. package/Docs/policies/implementation-guide.md +42 -0
  64. package/Docs/policies/organisation-policy-design-gates.md +58 -0
  65. package/Docs/policies/policy-pack-authoring-guide.md +108 -0
  66. package/Docs/policies/product-owner-guide.md +43 -0
  67. package/Docs/policies/technical-owner-guide.md +37 -0
  68. package/Docs/product-owner-guide.md +327 -0
  69. package/Docs/project-portfolio-orchestration-guide.md +199 -0
  70. package/Docs/quality/manual-qa-and-acceptance.md +162 -0
  71. package/Docs/quality/persona-driven-test-scenarios.md +172 -0
  72. package/Docs/quality/reproducible-archaeology-depth-review-checklist.md +89 -0
  73. package/Docs/reference/capabilities-and-project-layout.md +678 -0
  74. package/Docs/reference/cli-and-configuration.md +398 -0
  75. package/Docs/reference/contributions-api.md +23 -0
  76. package/Docs/reference/security-adapter-authoring.md +81 -0
  77. package/Docs/reference/starter-adapter-authoring.md +74 -0
  78. package/Docs/repository-source-map-guide.md +381 -0
  79. package/Docs/reproducible-archaeology-and-discovery-depth.md +292 -0
  80. package/Docs/screen-prototype-creation-guide.md +324 -0
  81. package/Docs/security-validation-guide.md +353 -0
  82. package/Docs/solution-readiness-review-guide.md +123 -0
  83. package/Docs/standards/project-standards-authoring.md +157 -0
  84. package/Docs/team-hub-guide.md +162 -0
  85. package/Docs/team-hub-resource-registry-guide.md +167 -0
  86. package/Docs/tutorials/first-delivery.md +83 -0
  87. package/Docs/tutorials/first-session.md +62 -0
  88. package/Docs/using-lifecycle-hooks.md +381 -0
  89. package/Docs/working-with-personas.md +274 -0
  90. package/LICENSE +165 -0
  91. package/README.md +96 -0
  92. package/agents-src/claude/ewai-security-reviewer.md +15 -0
  93. package/bin/ewai +5 -0
  94. package/config/archaeology-record-families.yaml +59 -0
  95. package/config/delivery-artifacts.yaml +121 -0
  96. package/config/delivery-stages.yaml +77 -0
  97. package/config/design-system.schema.json +46 -0
  98. package/config/error-reporting.schema.json +79 -0
  99. package/config/evidence-depth.schema.json +53 -0
  100. package/config/intent.schema.json +90 -0
  101. package/config/knowledge-proposals-proposal.schema.json +34 -0
  102. package/config/lifecycle-event.schema.json +68 -0
  103. package/config/lifecycle-handler.schema.json +45 -0
  104. package/config/lifecycle-hook-ack.schema.json +19 -0
  105. package/config/meeting-evidence-candidate.schema.json +102 -0
  106. package/config/organisation-policy.schema.json +137 -0
  107. package/config/pack.schema.json +250 -0
  108. package/config/persona-pack.schema.json +21 -0
  109. package/config/persona.schema.json +17 -0
  110. package/config/policy-evaluation.schema.json +77 -0
  111. package/config/policy-facts.schema.json +140 -0
  112. package/config/portfolio.schema.json +68 -0
  113. package/config/project.schema.json +313 -0
  114. package/config/prototype-iteration.schema.json +128 -0
  115. package/config/rollout.schema.json +87 -0
  116. package/config/security-adapter.schema.json +31 -0
  117. package/config/security-scan-request.schema.json +64 -0
  118. package/config/security-scan-response.schema.json +52 -0
  119. package/config/security-validation-policy.schema.json +74 -0
  120. package/config/starter-source-acknowledgement.schema.json +13 -0
  121. package/config/starter-source-adapter.schema.json +38 -0
  122. package/config/starter-source-request.schema.json +61 -0
  123. package/package.json +77 -0
  124. package/packs/core/pack.yaml +7 -0
  125. package/packs/design-systems/default/experience-promise.md +9 -0
  126. package/packs/design-systems/default/intentional-review.md +10 -0
  127. package/packs/design-systems/default/interaction-and-entry.md +9 -0
  128. package/packs/design-systems/default/meaningful-content-and-states.md +9 -0
  129. package/packs/design-systems/default/pack.yaml +44 -0
  130. package/packs/design-systems/default/principles.md +10 -0
  131. package/packs/personas/core/pack.yaml +7 -0
  132. package/packs/personas/core/personas/archaeologist.md +37 -0
  133. package/packs/personas/core/personas/end-user.md +17 -0
  134. package/packs/personas/core/personas/maintainer.md +17 -0
  135. package/packs/personas/core/personas/operator.md +17 -0
  136. package/packs/personas/core/personas/specs-knowledge-curator.md +35 -0
  137. package/packs/technologies/laravel/pack.yaml +30 -0
  138. package/packs/technologies/laravel-nuxt/pack.yaml +30 -0
  139. package/packs/technologies/nuxt/pack.yaml +30 -0
  140. package/packs/technologies/power-platform/pack.yaml +31 -0
  141. package/packs/technologies/salesforce/pack.yaml +25 -0
  142. package/public/app.js +4896 -0
  143. package/public/apple-touch-icon.png +0 -0
  144. package/public/assets/backstory-icon.png +0 -0
  145. package/public/dashboard-navigation.js +98 -0
  146. package/public/favicon-16.png +0 -0
  147. package/public/favicon-32.png +0 -0
  148. package/public/favicon.ico +0 -0
  149. package/public/index.html +789 -0
  150. package/public/styles.css +2693 -0
  151. package/public/team-hub/app.js +202 -0
  152. package/public/team-hub/index.html +79 -0
  153. package/public/team-hub/styles.css +90 -0
  154. package/scripts/publication-check.mjs +140 -0
  155. package/scripts/setup.mjs +21 -0
  156. package/skills-src/ewai-archaeology/SKILL.md +334 -0
  157. package/skills-src/ewai-archaeology/agents/openai.yaml +4 -0
  158. package/skills-src/ewai-archaeology/references/archaeology-contract.md +201 -0
  159. package/skills-src/ewai-archaeology/references/lifecycle-reconstruction.md +177 -0
  160. package/skills-src/ewai-archaeology/references/maximum-detail-reconstruction.md +97 -0
  161. package/skills-src/ewai-archaeology/references/model-routing.md +26 -0
  162. package/skills-src/ewai-architecture/SKILL.md +108 -0
  163. package/skills-src/ewai-architecture/agents/openai.yaml +4 -0
  164. package/skills-src/ewai-architecture/references/architecture-contract.md +176 -0
  165. package/skills-src/ewai-context/SKILL.md +68 -0
  166. package/skills-src/ewai-context/agents/openai.yaml +4 -0
  167. package/skills-src/ewai-context-import/SKILL.md +118 -0
  168. package/skills-src/ewai-context-import/agents/openai.yaml +4 -0
  169. package/skills-src/ewai-context-import/references/context-import-contract.md +106 -0
  170. package/skills-src/ewai-dashboard-configuration/SKILL.md +20 -0
  171. package/skills-src/ewai-deliver/SKILL.md +122 -0
  172. package/skills-src/ewai-deliver/references/delivery-evidence.md +92 -0
  173. package/skills-src/ewai-deliver/references/phase-routing.md +31 -0
  174. package/skills-src/ewai-design-system-apply/SKILL.md +27 -0
  175. package/skills-src/ewai-design-system-apply/agents/openai.yaml +4 -0
  176. package/skills-src/ewai-design-system-apply/references/application-contract.md +36 -0
  177. package/skills-src/ewai-design-system-author/SKILL.md +28 -0
  178. package/skills-src/ewai-design-system-author/agents/openai.yaml +4 -0
  179. package/skills-src/ewai-design-system-author/references/authoring-contract.md +38 -0
  180. package/skills-src/ewai-design-system-review/SKILL.md +26 -0
  181. package/skills-src/ewai-design-system-review/agents/openai.yaml +4 -0
  182. package/skills-src/ewai-design-system-review/references/review-contract.md +40 -0
  183. package/skills-src/ewai-error-reporting/SKILL.md +46 -0
  184. package/skills-src/ewai-error-reporting/agents/openai.yaml +4 -0
  185. package/skills-src/ewai-error-reporting/references/provider-contract.md +74 -0
  186. package/skills-src/ewai-evidence-depth/SKILL.md +72 -0
  187. package/skills-src/ewai-evidence-depth/agents/openai.yaml +4 -0
  188. package/skills-src/ewai-evidence-depth/references/evidence-depth-contract.md +127 -0
  189. package/skills-src/ewai-intent/SKILL.md +68 -0
  190. package/skills-src/ewai-intent/agents/openai.yaml +4 -0
  191. package/skills-src/ewai-intent/references/intent-contract.md +42 -0
  192. package/skills-src/ewai-knowledge-proposals/SKILL.md +105 -0
  193. package/skills-src/ewai-knowledge-proposals/agents/openai.yaml +4 -0
  194. package/skills-src/ewai-knowledge-proposals/references/proposal-contract.md +59 -0
  195. package/skills-src/ewai-meeting-evidence/SKILL.md +106 -0
  196. package/skills-src/ewai-meeting-evidence/agents/openai.yaml +4 -0
  197. package/skills-src/ewai-meeting-evidence/references/candidate-contract.md +64 -0
  198. package/skills-src/ewai-organisation-policy/SKILL.md +62 -0
  199. package/skills-src/ewai-organisation-policy/agents/openai.yaml +4 -0
  200. package/skills-src/ewai-organisation-policy/references/policy-contract.md +94 -0
  201. package/skills-src/ewai-palace-housekeeping/SKILL.md +55 -0
  202. package/skills-src/ewai-palace-housekeeping/agents/openai.yaml +4 -0
  203. package/skills-src/ewai-persona-entitlement/SKILL.md +60 -0
  204. package/skills-src/ewai-persona-entitlement/agents/openai.yaml +4 -0
  205. package/skills-src/ewai-phase-evidence/SKILL.md +79 -0
  206. package/skills-src/ewai-phase-evidence/agents/openai.yaml +4 -0
  207. package/skills-src/ewai-pipeline/SKILL.md +130 -0
  208. package/skills-src/ewai-pipeline/agents/openai.yaml +4 -0
  209. package/skills-src/ewai-pipeline/references/cli.md +86 -0
  210. package/skills-src/ewai-pipeline/references/specs-contract.md +16 -0
  211. package/skills-src/ewai-portfolio/SKILL.md +70 -0
  212. package/skills-src/ewai-portfolio/agents/openai.yaml +4 -0
  213. package/skills-src/ewai-portfolio/references/portfolio-contract.md +67 -0
  214. package/skills-src/ewai-project-discovery/SKILL.md +95 -0
  215. package/skills-src/ewai-project-discovery/agents/openai.yaml +4 -0
  216. package/skills-src/ewai-project-discovery/references/discovery-contract.md +34 -0
  217. package/skills-src/ewai-prototype-iteration/SKILL.md +30 -0
  218. package/skills-src/ewai-prototype-iteration/agents/openai.yaml +4 -0
  219. package/skills-src/ewai-prototype-iteration/references/review-contract.md +49 -0
  220. package/skills-src/ewai-retro/SKILL.md +48 -0
  221. package/skills-src/ewai-retro/agents/openai.yaml +4 -0
  222. package/skills-src/ewai-retro/references/asset-routing.md +14 -0
  223. package/skills-src/ewai-rollout/SKILL.md +74 -0
  224. package/skills-src/ewai-rollout/agents/openai.yaml +4 -0
  225. package/skills-src/ewai-rollout/references/rollout-contract.md +74 -0
  226. package/skills-src/ewai-shape-intents/SKILL.md +84 -0
  227. package/skills-src/ewai-shape-intents/agents/openai.yaml +4 -0
  228. package/skills-src/ewai-shape-intents/references/intent-mapping-contract.md +109 -0
  229. package/skills-src/ewai-solution-readiness/SKILL.md +55 -0
  230. package/skills-src/ewai-solution-readiness/agents/openai.yaml +4 -0
  231. package/skills-src/ewai-standards-check/SKILL.md +93 -0
  232. package/skills-src/ewai-standards-check/agents/openai.yaml +4 -0
  233. package/skills-src/ewai-standards-check/references/report-contract.md +116 -0
  234. package/skills-src/ewai-test-scenarios/SKILL.md +94 -0
  235. package/skills-src/ewai-test-scenarios/agents/openai.yaml +4 -0
  236. package/skills-src/ewai-test-scenarios/references/scenario-contract.md +88 -0
  237. package/src/afk-worker.mjs +16 -0
  238. package/src/archaeology.mjs +1333 -0
  239. package/src/checkin.mjs +261 -0
  240. package/src/cli.mjs +2427 -0
  241. package/src/companion-guidance.mjs +257 -0
  242. package/src/companion-opening.mjs +62 -0
  243. package/src/companion.mjs +256 -0
  244. package/src/context.mjs +210 -0
  245. package/src/dashboard-preferences.mjs +80 -0
  246. package/src/delivery-artifacts.mjs +204 -0
  247. package/src/delivery-documents.mjs +248 -0
  248. package/src/delivery-gates.mjs +317 -0
  249. package/src/delivery.mjs +1433 -0
  250. package/src/design-system-application.mjs +291 -0
  251. package/src/design-system-authoring.mjs +101 -0
  252. package/src/design-systems.mjs +466 -0
  253. package/src/discovery.mjs +1314 -0
  254. package/src/error-reporting.mjs +323 -0
  255. package/src/evidence-depth.mjs +543 -0
  256. package/src/execution-state.mjs +243 -0
  257. package/src/install.mjs +166 -0
  258. package/src/intent-dependencies.mjs +117 -0
  259. package/src/intent-maps.mjs +402 -0
  260. package/src/intents.mjs +747 -0
  261. package/src/knowledge-proposals.mjs +717 -0
  262. package/src/launcher.mjs +51 -0
  263. package/src/meeting-evidence.mjs +703 -0
  264. package/src/network-rollout.mjs +386 -0
  265. package/src/organisation-blueprints.mjs +438 -0
  266. package/src/organisation-policies.mjs +448 -0
  267. package/src/packs.mjs +44 -0
  268. package/src/paths.mjs +62 -0
  269. package/src/persona-entitlements.mjs +438 -0
  270. package/src/persona-licence-config.mjs +98 -0
  271. package/src/persona-website-provider.mjs +134 -0
  272. package/src/persona-zip.mjs +87 -0
  273. package/src/personas.mjs +159 -0
  274. package/src/platform-metadata-analysis.mjs +314 -0
  275. package/src/policy-design-gates.mjs +623 -0
  276. package/src/policy-gate-integration.mjs +318 -0
  277. package/src/portfolio.mjs +509 -0
  278. package/src/power-platform-source-map.mjs +190 -0
  279. package/src/project.mjs +449 -0
  280. package/src/prototype-iterations.mjs +730 -0
  281. package/src/repository-source-map.mjs +603 -0
  282. package/src/runtime/afk-conductor.mjs +973 -0
  283. package/src/runtime/context-assembly.mjs +457 -0
  284. package/src/runtime/context-benchmarks.mjs +115 -0
  285. package/src/runtime/dashboard-actions.mjs +109 -0
  286. package/src/runtime/dashboard-handoffs.mjs +149 -0
  287. package/src/runtime/dashboard-server.mjs +1272 -0
  288. package/src/runtime/dashboard.mjs +197 -0
  289. package/src/runtime/database.mjs +789 -0
  290. package/src/runtime/error-reporting.mjs +581 -0
  291. package/src/runtime/evidence-depth-workspace.mjs +412 -0
  292. package/src/runtime/execution-leases.mjs +299 -0
  293. package/src/runtime/guided-discovery.mjs +350 -0
  294. package/src/runtime/guided-intents.mjs +517 -0
  295. package/src/runtime/impact-analysis.mjs +535 -0
  296. package/src/runtime/intents.mjs +222 -0
  297. package/src/runtime/knowledge.mjs +86 -0
  298. package/src/runtime/lifecycle-hooks.mjs +1239 -0
  299. package/src/runtime/mcp-config.mjs +110 -0
  300. package/src/runtime/mcp-server.mjs +1885 -0
  301. package/src/runtime/palace.mjs +362 -0
  302. package/src/runtime/paths.mjs +58 -0
  303. package/src/runtime/persona-engagement.mjs +255 -0
  304. package/src/runtime/phase-contributions.mjs +594 -0
  305. package/src/runtime/policy-workspace.mjs +170 -0
  306. package/src/runtime/prototype-iterations.mjs +235 -0
  307. package/src/runtime/provider-adapters.mjs +163 -0
  308. package/src/runtime/repository-index.mjs +838 -0
  309. package/src/runtime/runs.mjs +185 -0
  310. package/src/runtime/security-validation.mjs +1230 -0
  311. package/src/runtime/starter-materialisation.mjs +1155 -0
  312. package/src/runtime/team-hub-client.mjs +479 -0
  313. package/src/runtime/team-hub-database.mjs +288 -0
  314. package/src/runtime/team-hub-server.mjs +191 -0
  315. package/src/runtime/team-hub.mjs +110 -0
  316. package/src/runtime/tree-sitter-index.mjs +390 -0
  317. package/src/runtime/version.mjs +1 -0
  318. package/src/runtime/work.mjs +633 -0
  319. package/src/salesforce-source-map.mjs +212 -0
  320. package/src/security-validation-config.mjs +224 -0
  321. package/src/solution-readiness.mjs +620 -0
  322. package/src/starter-materialisation-contract.mjs +407 -0
  323. package/src/task-graph.mjs +544 -0
  324. package/src/team-hub-resources.mjs +239 -0
  325. package/src/team-hub.mjs +242 -0
  326. package/src/test-scenarios.mjs +622 -0
  327. package/src/validation-config.mjs +289 -0
  328. package/templates/SPECS/1.Scope/personas/registry.yaml +12 -0
  329. package/templates/SPECS/5.Strategy/patterns/context-packet.md +119 -0
  330. package/templates/SPECS/6.Build/_tracker-template.md +16 -0
  331. package/templates/SPECS/pipeline.yaml +62 -0
  332. package/templates/discovery-answers.yaml +86 -0
  333. package/templates/intent-body.md +21 -0
  334. package/tests/fixtures/context-benchmarks.json +9 -0
@@ -0,0 +1,248 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { dirname, isAbsolute, relative, resolve } from 'node:path';
4
+ import { projectPaths } from './paths.mjs';
5
+
6
+ const slugPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
7
+
8
+ export function now() {
9
+ return new Date().toISOString();
10
+ }
11
+
12
+ export function sha256(content) {
13
+ return createHash('sha256').update(content).digest('hex');
14
+ }
15
+
16
+ export function isWithin(root, path) {
17
+ const rel = relative(root, path);
18
+ return rel === '' || (rel && !rel.startsWith('..') && !isAbsolute(rel));
19
+ }
20
+
21
+ export function atomicText(path, content) {
22
+ mkdirSync(dirname(path), { recursive: true });
23
+ const temporary = `${path}.ewai-${process.pid}-${randomUUID()}.tmp`;
24
+ try {
25
+ writeFileSync(temporary, content, 'utf8');
26
+ renameSync(temporary, path);
27
+ } finally {
28
+ rmSync(temporary, { force: true });
29
+ }
30
+ }
31
+
32
+ export function atomicJson(path, value) {
33
+ atomicText(path, `${JSON.stringify(value, null, 2)}\n`);
34
+ }
35
+
36
+ export function deliveryPaths(projectRoot, slug) {
37
+ if (!slugPattern.test(slug)) throw new Error(`Delivery slug must be lower kebab-case: ${slug}`);
38
+ const root = resolve(projectRoot);
39
+ const buildRoot = projectPaths(root).buildRoot;
40
+ const deliveryRoot = resolve(buildRoot, slug);
41
+ if (!isWithin(buildRoot, deliveryRoot)) throw new Error(`Unsafe delivery slug: ${slug}`);
42
+ return {
43
+ projectRoot: root,
44
+ deliveryRoot,
45
+ statePath: resolve(deliveryRoot, 'delivery-state.json'),
46
+ trackerPath: resolve(deliveryRoot, 'tracker.md'),
47
+ contextPath: resolve(deliveryRoot, 'context-packet.md'),
48
+ runsRoot: resolve(deliveryRoot, 'runs'),
49
+ gatesRoot: resolve(deliveryRoot, 'gates')
50
+ };
51
+ }
52
+
53
+ function statusSymbol(status) {
54
+ return {
55
+ pending: '⏸', running: '🟡', completed: '✅', approved: '✅', blocked: '🚫',
56
+ failed: '🚫', issue: '⚠', waived: '⚠', skipped: '⏭', 'not-supported': '—'
57
+ }[status] ?? status;
58
+ }
59
+
60
+ function phaseRows(state, includeNotes = false) {
61
+ return [...state.phases, ...state.adjuncts].map((phase) => {
62
+ const cells = [
63
+ phase.id,
64
+ `${statusSymbol(phase.status)} ${phase.status}`,
65
+ phase.startedAt ?? '',
66
+ phase.completedAt ?? '',
67
+ phase.artefactPath ?? phase.gatePath ?? ''
68
+ ];
69
+ if (includeNotes) cells.push(phase.statusReason ?? '');
70
+ return `| ${cells.join(' | ')} |`;
71
+ });
72
+ }
73
+
74
+ function renderTracker(state) {
75
+ const rows = state.phases.map((phase) => (
76
+ `| ${phase.number} | ${phase.id} | ${statusSymbol(phase.status)} ${phase.status} | ${phase.startedAt ?? ''} | ${phase.completedAt ?? ''} | ${phase.gatePath ?? ''} |`
77
+ ));
78
+ for (const adjunct of state.adjuncts) {
79
+ rows.push(`| — | ${adjunct.id} (adjunct) | ${statusSymbol(adjunct.status)} ${adjunct.status} | ${adjunct.startedAt ?? ''} | ${adjunct.completedAt ?? ''} | ${adjunct.gatePath ?? ''} |`);
80
+ }
81
+ return `---
82
+ schema: ewai.tracker/v2
83
+ slug: ${state.slug}
84
+ status: ${state.status}
85
+ current_phase: ${state.currentPhase}
86
+ run_mode: ${state.mode}
87
+ mission: be right, not done
88
+ ---
89
+
90
+ # Pipeline Tracker: ${state.intent.title}
91
+
92
+ **Slug:** \`${state.slug}\`
93
+ **Intent file:** \`${state.intent.path}\`
94
+ **Started:** ${state.startedAt}
95
+ **Last updated:** ${state.updatedAt}
96
+ **Review intensity:** ${state.intensity}
97
+ **Run mode:** ${state.mode}
98
+ **Current status:** ${state.status}
99
+ **Current phase:** ${state.currentPhase}
100
+ **Durable JSON authority:** \`delivery-state.json\`
101
+
102
+ Mission: be right, not done
103
+
104
+ ## Phase Status
105
+
106
+ | # | Phase | Status | Started | Completed | Gate ledger |
107
+ |---|---|---|---|---|---|
108
+ ${rows.join('\n')}
109
+
110
+ ## Human gates
111
+
112
+ | Gate | Status | Evidence |
113
+ |---|---|---|
114
+ ${state.humanGates.map((gate) => `| ${gate.id} | ${gate.status} | ${gate.evidencePath ?? ''} |`).join('\n')}
115
+
116
+ ## Resume point
117
+
118
+ **Next phase:** ${state.currentPhase}
119
+ **Command:** continue this work through EWAI; the companion invokes the delivery resume harness.
120
+
121
+ ## Blockers
122
+
123
+ ${state.blockedReason || 'None.'}
124
+ `;
125
+ }
126
+
127
+ const contextStateStart = '<!-- EWAI-CONTEXT-STATE:START -->';
128
+ const contextStateEnd = '<!-- EWAI-CONTEXT-STATE:END -->';
129
+
130
+ function renderContextState(state) {
131
+ return `${contextStateStart}
132
+ ## Current EWAI delivery state
133
+
134
+ **Last updated:** ${state.updatedAt}
135
+ **Status:** ${state.status}
136
+ **Current phase:** ${state.currentPhase}
137
+ **Run mode:** ${state.mode}
138
+ **Review intensity:** ${state.intensity}
139
+
140
+ ### Phase log
141
+
142
+ | Phase | Status | Started | Completed | Key artefact | Notes |
143
+ |---|---|---|---|---|---|
144
+ ${phaseRows(state, true).join('\n')}
145
+
146
+ ### Human gates
147
+
148
+ | Gate | Status | Started | Completed | Evidence |
149
+ |---|---|---|---|---|
150
+ ${state.humanGates.map((gate) => `| ${gate.id} | ${gate.status} | ${gate.startedAt ?? ''} | ${gate.completedAt ?? ''} | ${gate.evidencePath ?? ''} |`).join('\n')}
151
+ ${contextStateEnd}`;
152
+ }
153
+
154
+ function renderContextPacket(state) {
155
+ return `${renderContextState(state)}
156
+
157
+ # Context Packet: ${state.intent.title}
158
+
159
+ **Slug:** \`${state.slug}\`
160
+ **Started:** ${state.startedAt}
161
+ **Last updated:** ${state.updatedAt}
162
+ **Current phase:** ${state.currentPhase}
163
+ **Review intensity:** ${state.intensity}
164
+ **Execution tier:** ${state.intensity}
165
+ **Input type:** intent
166
+ **Run mode:** ${state.mode}
167
+ **Branch strategy:** ${state.mode === 'shelf' ? 'no-build-branches' : 'n/a'}
168
+ **Working branch:** not recorded yet
169
+
170
+ ---
171
+
172
+ ## What this capability is
173
+
174
+ Read the accepted intent at \`${state.intent.path}\`. Carry its plain-language summary forward here during Intent; do not infer purpose from code.
175
+
176
+ ## Why it matters
177
+
178
+ Read the accepted intent and attached personas. Record who benefits and what fails without the capability during Intent.
179
+
180
+ ## Scope boundary
181
+
182
+ **In scope:**
183
+ - Defined by the accepted intent and gated amendments.
184
+
185
+ **Out of scope (explicitly deferred):**
186
+ - Nothing may be silently deferred; record each deferral and its destination.
187
+
188
+ ## Key decisions made during this run
189
+
190
+ | # | Decision | Made during | Rationale | Reversibility |
191
+ |---|---|---|---|---|
192
+ | — | No durable decisions recorded yet | — | — | — |
193
+
194
+ ## Open escalations
195
+
196
+ | # | Phase raised | Question | Status | Resolution |
197
+ |---|---|---|---|---|
198
+ | — | — | ${state.blockedReason || 'None'} | ${state.blockedReason ? 'open' : 'resolved'} | — |
199
+
200
+ ## Active constraints
201
+
202
+ - Build may not start without an explicit, durable human approval record.
203
+ - A phase may not complete without the canonical deterministic gate checker passing fresh evidence.
204
+ - Intent Markdown, adjacent intent JSON, delivery JSON, and SQLite must agree before work resumes.
205
+ - SQLite is an operational projection; project-owned SPECS artefacts retain the durable audit trail.
206
+ ${state.mode === 'shelf' ? '- Shelf mode uses no build branches; resume must pass FitCheck before Build.' : ''}
207
+
208
+ ## Push-back signals
209
+
210
+ | # | Phase / tool | Reference file | Nature of push-back | What we did instead | Proposed update |
211
+ |---|---|---|---|---|---|
212
+ | — | — | — | None recorded | — | — |
213
+
214
+ ## Handoff notes
215
+
216
+ The managed state block above records the exact resume phase. Add durable human or agent handoff notes here; EWAI preserves this section across phase transitions.
217
+
218
+ ## Links
219
+
220
+ - Tracker: \`tracker.md\`
221
+ - Durable delivery state: \`delivery-state.json\`
222
+ - Intent: \`${state.intent.path}\`
223
+ - Destination: \`destination.md\` (after Plan)
224
+ - Task graph: \`task-graph.json\` (after Plan)
225
+ - Build plan: \`build-plan.md\` (after Plan)
226
+ - Test plan: \`test-plan.md\` (after TestPlan)
227
+ - Task reports: \`tasks/T-###/report.md\` (during Build)
228
+ - Retro: \`retro.md\` (after completion)
229
+ `;
230
+ }
231
+
232
+ function updateContextState(existing, state) {
233
+ const managed = renderContextState(state);
234
+ const start = existing.indexOf(contextStateStart);
235
+ const end = existing.indexOf(contextStateEnd, start);
236
+ if (start === -1 || end === -1) return `${managed}\n\n${existing}`;
237
+ return `${existing.slice(0, start)}${managed}${existing.slice(end + contextStateEnd.length)}`;
238
+ }
239
+
240
+ export function writeDeliveryDocuments(paths, state) {
241
+ state.updatedAt = now();
242
+ atomicJson(paths.statePath, state);
243
+ atomicText(paths.trackerPath, renderTracker(state));
244
+ const context = existsSync(paths.contextPath)
245
+ ? updateContextState(readFileSync(paths.contextPath, 'utf8'), state)
246
+ : renderContextPacket(state);
247
+ atomicText(paths.contextPath, context);
248
+ }
@@ -0,0 +1,317 @@
1
+ import { existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
2
+ import { dirname, isAbsolute, relative, resolve } from 'node:path';
3
+ import { atomicJson, isWithin, now, sha256 } from './delivery-documents.mjs';
4
+ import { writeTaskGraphCheck } from './task-graph.mjs';
5
+ import { policyGateRequirementsForPhase, readPolicyEvidenceFreshness } from './policy-gate-integration.mjs';
6
+
7
+ const taskGraphPhases = new Set([
8
+ 'plan', 'pattern-validation', 'test-plan', 'build', 'delivery',
9
+ ]);
10
+
11
+ const phaseGateIds = Object.freeze({
12
+ ideate: ['phase-artifact-written'],
13
+ intent: [
14
+ 'knowledge-onboard', 'standing-standards-read', 'intent-dependency-map',
15
+ 'intent-dependency-map-check', 'intent-code-disagreements', 'intent-summary-written',
16
+ ],
17
+ reconcile: [
18
+ 'repository-truth', 'current-behaviour', 'intent-dependency-map-check',
19
+ 'reconcile-disagreements', 'reconcile-artifact-written',
20
+ ],
21
+ 'ui-design': [
22
+ 'design-system-read', 'reference-implementations-read',
23
+ 'ui-design-artifact-written', 'prototype-manifest-check',
24
+ ],
25
+ plan: [
26
+ 'task-graph-check', 'intent-dependency-map-check', 'discovery-notes',
27
+ 'scope-fit-check', 'repository-truth', 'implementation-flow-contract',
28
+ 'local-pattern-research', 'claim-ledger', 'claim-ledger-check',
29
+ 'plan-contract', 'plan-contract-check', 'plan-existence-check',
30
+ 'standards-coverage-check', 'build-contract-integrity', 'build-plan-source-edited',
31
+ ],
32
+ 'pattern-validation': [
33
+ 'task-graph-check', 'repository-patterns-verified', 'intent-dependency-map-check',
34
+ 'implementation-flow-contract-check', 'local-pattern-research-check',
35
+ 'claim-ledger-check', 'plan-contract-check', 'plan-existence-check',
36
+ 'standards-coverage-check', 'plan-standards-sweep', 'build-contract-integrity',
37
+ ],
38
+ 'test-plan': [
39
+ 'task-graph-check', 'test-standards-read', 'intent-to-test-traceability',
40
+ 'task-to-test-traceability', 'red-green-refactor-traceability',
41
+ 'claim-ledger-check', 'plan-contract-check', 'plan-existence-check',
42
+ 'standards-coverage-check', 'build-contract-integrity', 'test-plan-source-edited',
43
+ ],
44
+ 'validate-external-plan': [
45
+ 'upstream-pattern-validation-gate-check', 'claim-ledger-check',
46
+ 'plan-contract-check', 'plan-existence-check', 'standards-coverage-check',
47
+ 'review-pack', 'findings-matrix', 'actions-log',
48
+ ],
49
+ 'validate-external-test-plan': [
50
+ 'upstream-test-plan-gate-check', 'claim-ledger-check', 'plan-contract-check',
51
+ 'standards-coverage-check', 'review-pack', 'findings-matrix', 'actions-log',
52
+ ],
53
+ 'fit-check': ['phase-artifact-written'],
54
+ build: [
55
+ 'task-graph-check', 'fit-check-if-required', 'claim-ledger-check',
56
+ 'plan-contract-check', 'standards-coverage-check', 'build-contract-integrity',
57
+ 'build-plan-followed', 'changed-files-recorded',
58
+ ],
59
+ 'standards-sweep': ['standards-sweep', 'standards-compliance-confirmed'],
60
+ 'test-execute': ['planned-tests-run', 'test-execute-report'],
61
+ 'validate-external-code': [
62
+ 'upstream-standards-sweep-gate-check', 'upstream-test-execute-gate-check',
63
+ 'review-pack', 'findings-matrix', 'actions-log',
64
+ ],
65
+ delivery: [
66
+ 'task-graph-check', 'delivery-preconditions', 'qa-then-fix',
67
+ 'manual-qa-walkthrough', 'delivery-checklist',
68
+ ],
69
+ retro: ['metrics-captured', 'knowledge-curation', 'pipeline-improvements', 'retro-written'],
70
+ });
71
+
72
+ const externalPhases = new Set([
73
+ 'validate-external-plan', 'validate-external-test-plan', 'validate-external-code',
74
+ ]);
75
+
76
+ function expectedGateIds(phaseId, providers = [], policyRequirements = []) {
77
+ const base = phaseGateIds[phaseId];
78
+ if (!base) throw new Error(`Unknown EWAI phase gate contract: ${phaseId}`);
79
+ if (!externalPhases.has(phaseId)) return [...base, ...policyRequirements.map(({ id }) => id)];
80
+ const reviews = [...new Set(providers.map((provider) => String(provider).trim().toLowerCase()).filter(Boolean))]
81
+ .map((provider) => `${provider}-review`);
82
+ const reviewPack = base.indexOf('review-pack') + 1;
83
+ return [...base.slice(0, reviewPack), ...reviews, ...base.slice(reviewPack)];
84
+ }
85
+
86
+ function expectedGateIdsAtPaths(paths, slug, phaseId, providers = []) {
87
+ const policy = policyGateRequirementsForPhase(paths.projectRoot, slug, phaseId);
88
+ return expectedGateIds(phaseId, providers, policy.requirements);
89
+ }
90
+
91
+ function gatePathFor(paths, phaseId) {
92
+ return resolve(paths.gatesRoot, phaseId, 'gate-ledger.json');
93
+ }
94
+
95
+ function gateReportPathFor(paths, phaseId) {
96
+ return resolve(paths.gatesRoot, phaseId, 'phase-gate-check.md');
97
+ }
98
+
99
+ function isRealWithin(root, path) {
100
+ return isWithin(realpathSync(root), realpathSync(path));
101
+ }
102
+
103
+ export function evidenceAbsolute(projectRoot, path) {
104
+ const absolute = isAbsolute(path) ? resolve(path) : resolve(projectRoot, path);
105
+ if (!isWithin(projectRoot, absolute)) throw new Error(`Gate evidence must stay inside the project: ${path}`);
106
+ if (!existsSync(absolute)) throw new Error(`Gate evidence does not exist: ${path}`);
107
+ if (!statSync(absolute).isFile()) throw new Error(`Gate evidence must be a file: ${path}`);
108
+ if (!isRealWithin(projectRoot, absolute)) throw new Error(`Gate evidence may not escape the project through a symbolic link: ${path}`);
109
+ return absolute;
110
+ }
111
+
112
+ function renderGateReport(phaseId, errors) {
113
+ const findings = errors.length
114
+ ? errors.map((error) => `- ${error}`).join('\n')
115
+ : '- Every required gate has fresh, project-local evidence.';
116
+ return `# EWAI Phase Gate Check — ${phaseId}\n\n**Verdict:** ${errors.length ? 'FAIL' : 'PASS'}\n**Checked:** ${now()}\n\n## Findings\n\n${findings}\n`;
117
+ }
118
+
119
+ function validatePolicyGateEvidence(paths, phaseId, gate, items, errors) {
120
+ const policyRequirements = policyGateRequirementsForPhase(paths.projectRoot, gate?.slug, phaseId).requirements;
121
+ if (!policyRequirements.length) return;
122
+ let state;
123
+ try {
124
+ state = JSON.parse(readFileSync(paths.statePath, 'utf8'));
125
+ } catch (error) {
126
+ errors.push(`organisation policy gate cannot read delivery truth: ${error.message}`);
127
+ return;
128
+ }
129
+ for (const requirement of policyRequirements) {
130
+ const item = items.get(requirement.id);
131
+ if (!item || item.status !== 'pass') continue;
132
+ if (item.output_path !== requirement.artefactPath) {
133
+ errors.push(`gate ${requirement.id} must use canonical evidence ${requirement.artefactPath}.`);
134
+ continue;
135
+ }
136
+ try {
137
+ const freshness = readPolicyEvidenceFreshness(paths.projectRoot, state.intent?.id, {
138
+ slug: gate.slug,
139
+ phase: phaseId,
140
+ });
141
+ if (freshness.blocking || freshness.status !== 'current') {
142
+ errors.push(`gate ${requirement.id} policy evidence is ${freshness.status}; ${freshness.recovery?.join(' ') || 'restore current non-blocking evidence.'}`);
143
+ }
144
+ const record = JSON.parse(readFileSync(resolve(paths.deliveryRoot, requirement.artefactPath), 'utf8'));
145
+ if (record.blocking !== false || !['allow', 'allow-with-controls'].includes(record.status)) {
146
+ errors.push(`gate ${requirement.id} policy result is blocking or unresolved.`);
147
+ }
148
+ if (phaseId === 'plan' && record.controlTrace?.status !== 'pass') {
149
+ errors.push(`gate ${requirement.id} has no passing actionable control trace.`);
150
+ }
151
+ } catch (error) {
152
+ errors.push(`gate ${requirement.id} policy evidence is invalid: ${error.message}`);
153
+ }
154
+ }
155
+ }
156
+
157
+ function validateGate(paths, phaseId, gate, providers = [], writeReport = true) {
158
+ const errors = [];
159
+ const expected = expectedGateIdsAtPaths(paths, gate?.slug, phaseId, providers);
160
+ if (gate?.schema_version !== 1) errors.push('schema_version must be 1.');
161
+ if (gate?.phase !== phaseId) errors.push(`phase must be ${phaseId}.`);
162
+ if (gate?.mission !== 'be right, not done') errors.push('mission must be “be right, not done”.');
163
+ if (gate?.status !== 'pass') errors.push('ledger status must be pass.');
164
+ const honesty = gate?.honesty_check ?? {};
165
+ for (const key of ['source_sections_edited', 'stale_text_removed', 'no_append_only_corrections', 'all_code_claims_cited']) {
166
+ if (honesty[key] !== true) errors.push(`honesty_check.${key} must be true.`);
167
+ }
168
+ if (!Array.isArray(gate?.required_gates)) errors.push('required_gates must be an array.');
169
+ const items = new Map((gate?.required_gates ?? []).map((item) => [item.id, item]));
170
+ for (const id of expected) {
171
+ const item = items.get(id);
172
+ if (!item) {
173
+ errors.push(`required gate ${id} is missing.`);
174
+ continue;
175
+ }
176
+ if (item.status === 'not_applicable') {
177
+ if (!String(item.rationale ?? '').trim()) errors.push(`gate ${id} is not applicable without a rationale.`);
178
+ continue;
179
+ }
180
+ if (item.status !== 'pass') {
181
+ errors.push(`gate ${id} is ${item.status ?? 'unrecorded'}.`);
182
+ continue;
183
+ }
184
+ if (!String(item.command_or_skill ?? '').trim()) errors.push(`gate ${id} has no command_or_skill.`);
185
+ if (item.exit_status !== 0) errors.push(`gate ${id} does not have exit_status 0.`);
186
+ const evidencePath = resolve(paths.deliveryRoot, String(item.output_path ?? ''));
187
+ if (!item.output_path || !isWithin(paths.deliveryRoot, evidencePath) || !existsSync(evidencePath)
188
+ || !statSync(evidencePath).isFile() || !isRealWithin(paths.deliveryRoot, evidencePath)) {
189
+ errors.push(`gate ${id} has no valid evidence file inside the delivery root.`);
190
+ } else if (!item.evidence_hash || sha256(readFileSync(evidencePath)) !== item.evidence_hash) {
191
+ errors.push(`gate ${id} evidence is missing its hash or has changed.`);
192
+ }
193
+ }
194
+ for (const id of items.keys()) {
195
+ if (!expected.includes(id)) errors.push(`unexpected gate ${id} is not in the ${phaseId} contract.`);
196
+ }
197
+ validatePolicyGateEvidence(paths, phaseId, gate, items, errors);
198
+ if ((gate?.skipped_gates ?? []).length) errors.push('skipped_gates must be empty; use not_applicable with rationale instead.');
199
+ if ((gate?.deterministic_gate_failures_found_by_external_review ?? []).length) {
200
+ errors.push('deterministic gate failures found by external review remain unresolved.');
201
+ }
202
+ if (writeReport) {
203
+ mkdirSync(dirname(gateReportPathFor(paths, phaseId)), { recursive: true });
204
+ writeFileSync(gateReportPathFor(paths, phaseId), renderGateReport(phaseId, errors), 'utf8');
205
+ }
206
+ if (errors.length) {
207
+ const report = writeReport ? ` See ${relative(paths.projectRoot, gateReportPathFor(paths, phaseId))}.` : '';
208
+ throw new Error(`Phase ${phaseId} failed its EWAI deterministic gate check.${report}`);
209
+ }
210
+ return gate;
211
+ }
212
+
213
+ export function validatePhaseGateAtPaths(paths, phaseId, gate, options = {}) {
214
+ return validateGate(paths, phaseId, gate, options.providers ?? [], false);
215
+ }
216
+
217
+ export function readPassingGate(paths, phaseId, options = {}) {
218
+ const path = gatePathFor(paths, phaseId);
219
+ if (!existsSync(path)) throw new Error(`Phase ${phaseId} requires a passing gate ledger: ${relative(paths.projectRoot, path)}`);
220
+ const gate = JSON.parse(readFileSync(path, 'utf8'));
221
+ validateGate(paths, phaseId, gate, options.providers ?? []);
222
+ return { gate, path, sha256: sha256(readFileSync(path)) };
223
+ }
224
+
225
+ export function assertCompletedEvidenceFresh(paths, state) {
226
+ for (const phase of [...state.phases, ...state.adjuncts]) {
227
+ if (phase.status !== 'completed') continue;
228
+ const path = gatePathFor(paths, phase.id);
229
+ if (!existsSync(path)) throw new Error(`Phase ${phase.id} has no recorded EWAI gate ledger.`);
230
+ const raw = readFileSync(path);
231
+ if (!phase.gateSha256 || sha256(raw) !== phase.gateSha256) {
232
+ throw new Error(`Phase ${phase.id} gate ledger changed after completion.`);
233
+ }
234
+ validateGate(paths, phase.id, JSON.parse(raw), phase.validation?.providers ?? state.providers ?? [], false);
235
+ }
236
+ }
237
+
238
+ export function recordPhaseGateAtPaths(paths, slug, phaseId, input = {}, options = {}) {
239
+ let taskGraphCheck = null;
240
+ if (taskGraphPhases.has(phaseId)) {
241
+ taskGraphCheck = writeTaskGraphCheck(paths.deliveryRoot, phaseId, { slug });
242
+ if (taskGraphCheck.status !== 'pass') {
243
+ throw new Error(`Phase ${phaseId} failed deterministic task-graph validation. See ${relative(paths.projectRoot, resolve(paths.gatesRoot, phaseId, 'task-graph-check.md'))}.`);
244
+ }
245
+ }
246
+ const expected = expectedGateIdsAtPaths(paths, slug, phaseId, options.providers ?? []);
247
+ const supplied = new Map((Array.isArray(input.requiredGates) ? input.requiredGates : []).map((item) => [item.id, item]));
248
+ const requiredGates = expected.map((id) => {
249
+ const item = supplied.get(id) ?? { id };
250
+ const taskGraphItem = taskGraphCheck && id === 'task-graph-check';
251
+ const status = taskGraphItem ? 'pass' : item.status ?? 'blocked';
252
+ let evidenceHash = '';
253
+ let outputPath = taskGraphItem
254
+ ? relative(paths.projectRoot, resolve(paths.gatesRoot, phaseId, 'task-graph-check.md')).replaceAll('\\', '/')
255
+ : item.outputPath ?? '';
256
+ if (status === 'pass') {
257
+ const absolute = evidenceAbsolute(paths.projectRoot, outputPath);
258
+ if (!isWithin(paths.deliveryRoot, absolute)) throw new Error(`Gate evidence must stay inside the delivery root: ${outputPath}`);
259
+ outputPath = relative(paths.deliveryRoot, absolute).replaceAll('\\', '/');
260
+ evidenceHash = sha256(readFileSync(absolute));
261
+ }
262
+ return {
263
+ id,
264
+ required: item.required !== false,
265
+ command_or_skill: taskGraphItem ? 'ewai deterministic task-graph validator' : item.commandOrSkill ?? '',
266
+ output_path: outputPath,
267
+ exit_status: taskGraphItem ? 0 : item.exitStatus ?? (status === 'pass' ? 0 : null),
268
+ status,
269
+ evidence_hash: evidenceHash,
270
+ rationale: item.rationale ?? '',
271
+ };
272
+ });
273
+ const gate = {
274
+ schema_version: 1,
275
+ slug,
276
+ phase: phaseId,
277
+ mission: 'be right, not done',
278
+ status: input.status ?? 'blocked',
279
+ honesty_check: {
280
+ source_sections_edited: input.honestyCheck?.sourceSectionsEdited === true,
281
+ stale_text_removed: input.honestyCheck?.staleTextRemoved === true,
282
+ no_append_only_corrections: input.honestyCheck?.noAppendOnlyCorrections === true,
283
+ all_code_claims_cited: input.honestyCheck?.allCodeClaimsCited === true,
284
+ notes: input.honestyCheck?.notes ?? '',
285
+ },
286
+ required_gates: requiredGates,
287
+ skipped_gates: [],
288
+ deterministic_gate_failures_found_by_external_review: input.deterministicGateFailures ?? [],
289
+ generated_at: now(),
290
+ };
291
+ atomicJson(gatePathFor(paths, phaseId), gate);
292
+ if (gate.status === 'pass') validateGate(paths, phaseId, gate, options.providers ?? []);
293
+ return gate;
294
+ }
295
+
296
+ export function phaseGateTemplateAtPaths(paths, slug, phaseId, options = {}) {
297
+ return {
298
+ schema_version: 1,
299
+ slug,
300
+ phase: phaseId,
301
+ mission: 'be right, not done',
302
+ status: 'blocked',
303
+ honesty_check: {
304
+ source_sections_edited: false,
305
+ stale_text_removed: false,
306
+ no_append_only_corrections: false,
307
+ all_code_claims_cited: false,
308
+ notes: '',
309
+ },
310
+ required_gates: expectedGateIdsAtPaths(paths, slug, phaseId, options.providers ?? []).map((id) => ({
311
+ id, required: true, command_or_skill: '', output_path: '', exit_status: null,
312
+ status: 'blocked', evidence_hash: '', rationale: '',
313
+ })),
314
+ skipped_gates: [],
315
+ deterministic_gate_failures_found_by_external_review: [],
316
+ };
317
+ }