@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,386 @@
1
+ import { existsSync, lstatSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import YAML from 'yaml';
4
+ import { z } from 'zod';
5
+ import { readPortfolioResolution } from './portfolio.mjs';
6
+ import { projectPaths } from './paths.mjs';
7
+ import { loadProjectConfig } from './project.mjs';
8
+ import { ASSURANCE_NOTICE, normaliseSecurityValidationConfig } from './security-validation-config.mjs';
9
+ import { selectContextualPersonas } from './runtime/persona-engagement.mjs';
10
+ import { listSecurityRuns } from './runtime/security-validation.mjs';
11
+
12
+ export const ROLLOUT_SCHEMA = 'ewai.rollout/v1';
13
+ export const ROLLOUT_WORKSPACE_SCHEMA = 'ewai.rollout-workspace/v1';
14
+ export const ROLLOUT_ASSURANCE_NOTICE = ASSURANCE_NOTICE;
15
+ export const ROLLOUT_ADVISORY_NOTICE = 'Rollout and persona analysis is advisory. Organisation Blueprint adoption, evidence adequacy, accepted risk, Manual QA, deployment and release decisions remain with named accountable humans.';
16
+
17
+ const MAX_MANIFEST_BYTES = 256 * 1024;
18
+ const MAX_CHILD_FILE_BYTES = 512 * 1024;
19
+ const EVIDENCE_CLASSES = Object.freeze(['blueprint', 'delivery', 'standards', 'manual-qa', 'security']);
20
+ const slug = z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).max(100);
21
+ const boundedText = (maximum) => z.string().trim().min(1).max(maximum);
22
+ const digest = z.string().regex(/^sha256:[a-f0-9]{64}$/);
23
+ const packSchema = z.object({
24
+ id: z.string().regex(/^org\.[a-z0-9-]+\.[a-z0-9.-]+$/).max(160),
25
+ version: z.string().regex(/^[0-9]+\.[0-9]+\.[0-9]+$/).max(40),
26
+ digest: digest.optional(),
27
+ }).strict();
28
+ const baselineSchema = z.object({
29
+ id: slug,
30
+ name: boundedText(160),
31
+ owner: boundedText(160),
32
+ pack: packSchema,
33
+ }).strict();
34
+ const assignmentSchema = z.object({
35
+ id: slug,
36
+ owner: boundedText(160),
37
+ review_owner: boundedText(160).optional(),
38
+ }).strict();
39
+ const reviewDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/).refine((value) => {
40
+ const parsed = Date.parse(`${value}T00:00:00.000Z`);
41
+ return Number.isFinite(parsed) && new Date(parsed).toISOString().slice(0, 10) === value;
42
+ }, 'Review date must be a valid ISO date.');
43
+ const cohortSchema = z.object({
44
+ id: slug,
45
+ name: boundedText(160),
46
+ baseline: slug,
47
+ owner: boundedText(160),
48
+ review_by: reviewDate,
49
+ required_evidence: z.array(z.enum(EVIDENCE_CLASSES)).min(1).max(5),
50
+ projects: z.array(assignmentSchema).min(1).max(100),
51
+ }).strict();
52
+ const manifestSchema = z.object({
53
+ schema: z.literal(ROLLOUT_SCHEMA),
54
+ id: slug,
55
+ name: boundedText(160),
56
+ owner: boundedText(160),
57
+ stale_after_days: z.number().int().min(1).max(3650),
58
+ baselines: z.array(baselineSchema).min(1).max(25),
59
+ cohorts: z.array(cohortSchema).min(1).max(50),
60
+ }).strict();
61
+
62
+ function diagnostic(code, message, path = '') {
63
+ return { code, message, ...(path ? { path } : {}) };
64
+ }
65
+
66
+ function duplicate(values) {
67
+ const seen = new Set();
68
+ return values.find((value) => seen.has(value) || !seen.add(value));
69
+ }
70
+
71
+ export function validateRolloutManifest(input, options = {}) {
72
+ const parsed = manifestSchema.safeParse(input);
73
+ if (!parsed.success) {
74
+ return {
75
+ valid: false,
76
+ diagnostics: parsed.error.issues.map((issue) => diagnostic('rollout.schema', issue.message, issue.path.join('.'))),
77
+ };
78
+ }
79
+ const manifest = parsed.data;
80
+ const diagnostics = [];
81
+ const duplicateBaseline = duplicate(manifest.baselines.map((baseline) => baseline.id));
82
+ if (duplicateBaseline) diagnostics.push(diagnostic('rollout.duplicate-baseline', `Duplicate baseline ID: ${duplicateBaseline}`, 'baselines'));
83
+ const duplicateCohort = duplicate(manifest.cohorts.map((cohort) => cohort.id));
84
+ if (duplicateCohort) diagnostics.push(diagnostic('rollout.duplicate-cohort', `Duplicate cohort ID: ${duplicateCohort}`, 'cohorts'));
85
+ const baselineIds = new Set(manifest.baselines.map((baseline) => baseline.id));
86
+ const assignments = [];
87
+ for (const cohort of manifest.cohorts) {
88
+ if (!baselineIds.has(cohort.baseline)) diagnostics.push(diagnostic('rollout.unknown-baseline', `Cohort ${cohort.id} references an unknown baseline.`, `cohorts.${cohort.id}.baseline`));
89
+ const duplicateEvidence = duplicate(cohort.required_evidence);
90
+ if (duplicateEvidence) diagnostics.push(diagnostic('rollout.duplicate-evidence', `Cohort ${cohort.id} repeats evidence class ${duplicateEvidence}.`, `cohorts.${cohort.id}.required_evidence`));
91
+ assignments.push(...cohort.projects.map((project) => ({ id: project.id, cohort: cohort.id })));
92
+ }
93
+ if (assignments.length > 100) diagnostics.push(diagnostic('rollout.assignment-limit', 'Rollout policy may contain at most 100 project assignments.', 'cohorts'));
94
+ const duplicateAssignment = duplicate(assignments.map((assignment) => assignment.id));
95
+ if (duplicateAssignment) diagnostics.push(diagnostic('rollout.duplicate-assignment', `Project ${duplicateAssignment} may be assigned to one cohort only.`, 'cohorts'));
96
+ if (Array.isArray(options.portfolioProjectIds)) {
97
+ const allowed = new Set(options.portfolioProjectIds);
98
+ for (const assignment of assignments) {
99
+ if (!allowed.has(assignment.id)) diagnostics.push(diagnostic('rollout.unknown-project', `Project ${assignment.id} is not a Portfolio project member.`, `cohorts.${assignment.cohort}.projects`));
100
+ }
101
+ }
102
+ return { valid: diagnostics.length === 0, diagnostics, manifest };
103
+ }
104
+
105
+ function personaAvailability(catalogue) {
106
+ const counts = Object.fromEntries(['project', 'core', 'personal', 'premium'].map((tier) => [tier, catalogue.filter((persona) => String(persona?.tier ?? 'core') === tier).length]));
107
+ return {
108
+ baseline: { available: true, method: 'standard-llm-with-installed-project-and-core-personas' },
109
+ project: { installed: counts.project > 0, count: counts.project },
110
+ core: { installed: counts.core > 0, count: counts.core },
111
+ personal: { installed: counts.personal > 0, count: counts.personal, required: false },
112
+ premium: { installed: counts.premium > 0, count: counts.premium, required: false, syncAttempted: false },
113
+ };
114
+ }
115
+
116
+ function baseWorkspace(catalogue) {
117
+ return {
118
+ schema: ROLLOUT_WORKSPACE_SCHEMA,
119
+ policy: null,
120
+ portfolio: null,
121
+ diagnostics: [],
122
+ cohorts: [],
123
+ selectedAssurance: null,
124
+ activePersonas: [],
125
+ personaAvailability: personaAvailability(catalogue),
126
+ review: {
127
+ standardLlmAvailable: true,
128
+ questions: [
129
+ 'Which declared baseline difference needs a named project-owner decision?',
130
+ 'Which required evidence is unavailable, stale, missing or not configured?',
131
+ 'Which conclusion is observed evidence and which is a persona hypothesis?',
132
+ 'Who owns the next topology, adoption or assurance action?',
133
+ ],
134
+ evidenceClasses: ['declared-policy', 'observed-project-evidence', 'automated-check', 'persona-hypothesis', 'human-decision'],
135
+ },
136
+ notices: { advisory: ROLLOUT_ADVISORY_NOTICE, security: ROLLOUT_ASSURANCE_NOTICE },
137
+ guide: 'Docs/consultancy-network-rollout-control-plane-guide.md',
138
+ };
139
+ }
140
+
141
+ function boundedFocus(value) {
142
+ const focus = String(value ?? '').trim();
143
+ if (focus.length > 500 || focus.includes('\0')) throw new Error('Rollout focus must be safe text of at most 500 characters.');
144
+ return focus;
145
+ }
146
+
147
+ function boundedRead(path, label) {
148
+ if (lstatSync(path).isSymbolicLink() || statSync(path).size > MAX_CHILD_FILE_BYTES) throw new Error(`${label} is unavailable or exceeds its size limit.`);
149
+ return readFileSync(path, 'utf8');
150
+ }
151
+
152
+ function freshness(updatedAt, nowValue, staleAfterDays) {
153
+ const updated = Date.parse(String(updatedAt ?? ''));
154
+ const now = Date.parse(String(nowValue ?? ''));
155
+ if (!Number.isFinite(updated) || !Number.isFinite(now)) return { state: 'missing', observedAt: updatedAt || null };
156
+ const ageDays = Math.max(0, Math.floor((now - updated) / 86_400_000));
157
+ return { state: ageDays > staleAfterDays ? 'stale' : 'present', observedAt: updatedAt, ageDays };
158
+ }
159
+
160
+ function latestDelivery(specsRoot) {
161
+ const buildRoot = resolve(specsRoot, '6.Build');
162
+ if (!existsSync(buildRoot) || lstatSync(buildRoot).isSymbolicLink()) return null;
163
+ const records = [];
164
+ for (const entry of readdirSync(buildRoot, { withFileTypes: true }).slice(0, 200)) {
165
+ if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
166
+ const path = resolve(buildRoot, entry.name, 'delivery-state.json');
167
+ if (!existsSync(path)) continue;
168
+ try {
169
+ const state = JSON.parse(boundedRead(path, 'Delivery state'));
170
+ if (state.schema !== 'ewai.delivery-state/v1') continue;
171
+ const manualQa = (state.humanGates ?? []).find((gate) => gate.id === 'manual-qa');
172
+ records.push({
173
+ status: String(state.status ?? 'unknown').slice(0, 80),
174
+ currentPhase: String(state.currentPhase ?? 'unknown').slice(0, 80),
175
+ updatedAt: String(state.updatedAt ?? '').slice(0, 80) || null,
176
+ buildApproved: Boolean(state.approvals?.build),
177
+ manualQa: String(manualQa?.status ?? 'missing').slice(0, 80),
178
+ });
179
+ } catch {
180
+ // An unreadable record is not promoted into aggregate evidence.
181
+ }
182
+ }
183
+ return records.sort((left, right) => String(right.updatedAt ?? '').localeCompare(String(left.updatedAt ?? '')))[0] ?? null;
184
+ }
185
+
186
+ function hasStandards(specsRoot) {
187
+ const root = resolve(specsRoot, '4.Constraints/standards');
188
+ if (!existsSync(root) || lstatSync(root).isSymbolicLink()) return false;
189
+ const queue = [{ root, depth: 0 }];
190
+ let inspected = 0;
191
+ while (queue.length && inspected < 200) {
192
+ const current = queue.shift();
193
+ for (const entry of readdirSync(current.root, { withFileTypes: true })) {
194
+ inspected += 1;
195
+ if (entry.isSymbolicLink()) continue;
196
+ if (entry.isFile() && entry.name.endsWith('.md')) return true;
197
+ if (entry.isDirectory() && current.depth < 6) queue.push({ root: resolve(current.root, entry.name), depth: current.depth + 1 });
198
+ if (inspected >= 200) break;
199
+ }
200
+ }
201
+ return false;
202
+ }
203
+
204
+ function safeBlueprint(config) {
205
+ const pin = config?.blueprints?.organisation?.root;
206
+ const parsed = packSchema.safeParse(pin);
207
+ return parsed.success ? parsed.data : null;
208
+ }
209
+
210
+ function securityEvidence(childRoot, config, nowValue) {
211
+ let policy;
212
+ try {
213
+ policy = normaliseSecurityValidationConfig(config);
214
+ } catch {
215
+ return { state: 'unavailable', configured: false, profileCount: 0, latestRun: null, limitation: 'Security configuration is invalid or unavailable.' };
216
+ }
217
+ if (!policy.enabled) return { state: 'not-configured', configured: false, profileCount: 0, latestRun: null, limitation: 'Security validation is not configured for this project.' };
218
+ try {
219
+ const latest = listSecurityRuns(childRoot).runs[0] ?? null;
220
+ if (!latest) return { state: 'missing', configured: true, profileCount: policy.profiles.length, latestRun: null, limitation: 'Security validation is configured but no run evidence is available.' };
221
+ const requiredHours = Math.min(...policy.profiles.map((profile) => profile.freshness_hours));
222
+ const observed = freshness(latest.completedAt, nowValue, requiredHours / 24);
223
+ return {
224
+ state: latest.complete ? observed.state : 'missing',
225
+ configured: true,
226
+ profileCount: policy.profiles.length,
227
+ latestRun: { status: latest.status, complete: latest.complete, outcome: latest.outcome, completedAt: latest.completedAt },
228
+ limitation: 'Structural run state only; detailed provider output and adequacy are not projected.',
229
+ };
230
+ } catch {
231
+ return { state: 'unavailable', configured: true, profileCount: policy.profiles.length, latestRun: null, limitation: 'Security run metadata is unavailable.' };
232
+ }
233
+ }
234
+
235
+ function unavailableProject(assignment, portfolioMember, requiredEvidence, portfolioOwner) {
236
+ return {
237
+ id: assignment.id,
238
+ name: portfolioMember?.name ?? assignment.id,
239
+ owner: assignment.owner,
240
+ reviewOwner: assignment.review_owner ?? assignment.owner,
241
+ expectedBlueprint: null,
242
+ observedBlueprint: null,
243
+ adoption: { state: 'unavailable', reason: 'The Portfolio project root is unavailable.', evidenceClass: 'observed-project-evidence' },
244
+ evidence: requiredEvidence.map((item) => ({ class: item, state: 'unavailable', limitation: 'Project-owned evidence is unavailable.' })),
245
+ limitations: ['The project could not be read through the configured Portfolio topology.'],
246
+ nextRoute: { owner: assignment.owner, topologyOwner: portfolioOwner, action: 'Restore Portfolio topology or project availability, then refresh the read-only snapshot.', evidenceClass: 'human-decision' },
247
+ };
248
+ }
249
+
250
+ function projectSnapshot(member, assignment, baseline, cohort, portfolioOwner, options) {
251
+ if (!member?.root) return unavailableProject(assignment, member, cohort.required_evidence, portfolioOwner);
252
+ try {
253
+ const loaded = loadProjectConfig(member.root);
254
+ const observedBlueprint = safeBlueprint(loaded.config);
255
+ const delivery = latestDelivery(loaded.paths.specsRoot);
256
+ const deliveryFreshness = freshness(delivery?.updatedAt, options.now, options.staleAfterDays);
257
+ const security = securityEvidence(member.root, loaded.config, options.now);
258
+ const evidenceByClass = {
259
+ blueprint: { state: observedBlueprint ? 'present' : 'missing', limitation: 'Exact project-owned Blueprint pin only; receipt bodies are not projected.' },
260
+ delivery: { state: delivery ? deliveryFreshness.state : 'missing', observedAt: delivery?.updatedAt ?? null, limitation: 'Latest structural delivery state only.' },
261
+ standards: { state: hasStandards(loaded.paths.specsRoot) ? 'present' : 'missing', limitation: 'Standards presence only; content and adequacy are not projected.' },
262
+ 'manual-qa': { state: ['approved', 'completed', 'passed'].includes(delivery?.manualQa) ? 'present' : 'missing', limitation: 'Named-human gate state only; evidence bodies are not projected.' },
263
+ security,
264
+ };
265
+ let adoption;
266
+ if (delivery && deliveryFreshness.state === 'stale') adoption = { state: 'stale', reason: 'Project delivery evidence is older than the rollout freshness window.' };
267
+ else if (!observedBlueprint) adoption = { state: 'not-adopted', reason: 'No project-owned Organisation Blueprint pin is present.' };
268
+ else {
269
+ const expected = baseline.pack;
270
+ const exact = observedBlueprint.id === expected.id
271
+ && observedBlueprint.version === expected.version
272
+ && (expected.digest === undefined || observedBlueprint.digest === expected.digest);
273
+ adoption = exact
274
+ ? { state: 'aligned', reason: 'The project-owned Blueprint pin exactly matches the declared baseline.' }
275
+ : { state: 'review-required', reason: 'The project-owned Blueprint pin differs from the declared baseline; exact comparison does not infer compatibility or version precedence.' };
276
+ }
277
+ const evidence = cohort.required_evidence.map((item) => ({ class: item, ...evidenceByClass[item] }));
278
+ const unresolved = adoption.state !== 'aligned' || evidence.some((item) => item.state !== 'present');
279
+ return {
280
+ id: assignment.id,
281
+ name: member.name,
282
+ owner: assignment.owner,
283
+ reviewOwner: assignment.review_owner ?? assignment.owner,
284
+ expectedBlueprint: baseline.pack,
285
+ observedBlueprint,
286
+ adoption: { ...adoption, evidenceClass: 'automated-check' },
287
+ evidence,
288
+ delivery: delivery ? { status: delivery.status, currentPhase: delivery.currentPhase, updatedAt: delivery.updatedAt, buildApproved: delivery.buildApproved, manualQa: delivery.manualQa } : null,
289
+ limitations: evidence.filter((item) => item.state !== 'present').map((item) => `${item.class}: ${item.limitation}`),
290
+ nextRoute: {
291
+ owner: assignment.review_owner ?? assignment.owner,
292
+ topologyOwner: portfolioOwner,
293
+ action: unresolved ? 'Review the project-owned adoption or evidence gap.' : 'No aggregate action; project governance retains the next decision.',
294
+ evidenceClass: 'human-decision',
295
+ },
296
+ };
297
+ } catch {
298
+ return { ...unavailableProject(assignment, member, cohort.required_evidence, portfolioOwner), adoption: { state: 'invalid', reason: 'Project configuration or evidence is invalid.', evidenceClass: 'observed-project-evidence' } };
299
+ }
300
+ }
301
+
302
+ export function readRolloutWorkspace(projectRoot, options = {}) {
303
+ const root = resolve(projectRoot);
304
+ const catalogue = Array.isArray(options.personas) ? options.personas : Array.isArray(options.personaCatalogue) ? options.personaCatalogue : [];
305
+ const base = baseWorkspace(catalogue);
306
+ const focus = boundedFocus(options.focus);
307
+ let paths;
308
+ try {
309
+ paths = projectPaths(root);
310
+ } catch {
311
+ return { ...base, status: 'invalid', diagnostics: [diagnostic('rollout.project-root', 'The EWAI project root is invalid.')] };
312
+ }
313
+ const policyPath = resolve(paths.specsRoot, '1.Scope/rollout.yaml');
314
+ if (!existsSync(policyPath)) return { ...base, status: 'not-configured' };
315
+ if (lstatSync(policyPath).isSymbolicLink() || statSync(policyPath).size > MAX_MANIFEST_BYTES) {
316
+ return { ...base, status: 'invalid', diagnostics: [diagnostic('rollout.policy-file', 'Rollout policy is symbolic or exceeds its size limit.')] };
317
+ }
318
+ let raw;
319
+ try {
320
+ raw = YAML.parse(readFileSync(policyPath, 'utf8'));
321
+ } catch {
322
+ return { ...base, status: 'invalid', diagnostics: [diagnostic('rollout.policy-yaml', 'Rollout policy is invalid YAML.')] };
323
+ }
324
+ const portfolioRead = readPortfolioResolution(root, { allowUnavailableProjects: true });
325
+ if (portfolioRead.status !== 'ready') {
326
+ return { ...base, status: 'invalid', diagnostics: [diagnostic('rollout.portfolio', 'A valid Portfolio is required before rollout status can be derived.')] };
327
+ }
328
+ const projectMembers = portfolioRead.resolution.members.filter((member) => member.kind === 'project');
329
+ const validation = validateRolloutManifest(raw, { portfolioProjectIds: projectMembers.map((member) => member.id) });
330
+ if (!validation.valid) return { ...base, status: 'invalid', diagnostics: validation.diagnostics };
331
+ const manifest = validation.manifest;
332
+ const baselines = new Map(manifest.baselines.map((baseline) => [baseline.id, baseline]));
333
+ const portfolioMembers = new Map(projectMembers.map((member) => [member.id, member]));
334
+ const now = options.now ?? new Date().toISOString();
335
+ const cohorts = manifest.cohorts.map((cohort) => {
336
+ const baseline = baselines.get(cohort.baseline);
337
+ return {
338
+ id: cohort.id,
339
+ name: cohort.name,
340
+ owner: cohort.owner,
341
+ reviewBy: cohort.review_by,
342
+ requiredEvidence: [...cohort.required_evidence],
343
+ baseline: { id: baseline.id, name: baseline.name, owner: baseline.owner, pack: baseline.pack },
344
+ projects: cohort.projects.map((assignment) => projectSnapshot(
345
+ portfolioMembers.get(assignment.id), assignment, baseline, cohort,
346
+ portfolioRead.resolution.manifest.owner,
347
+ { now, staleAfterDays: manifest.stale_after_days },
348
+ )),
349
+ };
350
+ });
351
+ const projects = cohorts.flatMap((cohort) => cohort.projects.map((project) => ({ cohort, project })));
352
+ let selectedAssurance = null;
353
+ if (options.projectId !== undefined) {
354
+ const projectId = String(options.projectId ?? '').trim();
355
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(projectId) || projectId.length > 100) throw new Error('Rollout project ID is invalid or unassigned.');
356
+ const selected = projects.find((candidate) => candidate.project.id === projectId);
357
+ if (!selected) throw new Error('Rollout project ID is invalid or unassigned.');
358
+ selectedAssurance = {
359
+ project: selected.project,
360
+ cohort: { id: selected.cohort.id, name: selected.cohort.name, owner: selected.cohort.owner, reviewBy: selected.cohort.reviewBy },
361
+ structuralOnly: true,
362
+ adequacyVerdict: null,
363
+ };
364
+ }
365
+ const signals = [
366
+ 'rollout', 'adoption', manifest.name, manifest.owner, focus,
367
+ ...(selectedAssurance ? [selectedAssurance.project.name, selectedAssurance.project.adoption.state] : []),
368
+ ];
369
+ const activePersonas = selectContextualPersonas({
370
+ signals,
371
+ context: { rollout: manifest.id, focus, selectedProject: selectedAssurance?.project.id ?? null },
372
+ contextLabel: focus ? `rollout context: ${focus.slice(0, 120)}` : 'rollout adoption and accountable ownership',
373
+ personaCatalogue: catalogue,
374
+ limit: 3,
375
+ });
376
+ const attention = cohorts.some((cohort) => cohort.projects.some((project) => project.adoption.state !== 'aligned' || project.evidence.some((item) => item.state !== 'present')));
377
+ return {
378
+ ...base,
379
+ status: attention ? 'attention' : 'ready',
380
+ policy: { id: manifest.id, name: manifest.name, owner: manifest.owner, staleAfterDays: manifest.stale_after_days },
381
+ portfolio: { id: portfolioRead.resolution.manifest.id, name: portfolioRead.resolution.manifest.name, owner: portfolioRead.resolution.manifest.owner },
382
+ cohorts,
383
+ selectedAssurance,
384
+ activePersonas,
385
+ };
386
+ }