@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,517 @@
1
+ import { existsSync, readFileSync, readdirSync, rmSync } from 'node:fs';
2
+ import { relative, resolve } from 'node:path';
3
+ import { atomicJson } from '../delivery-documents.mjs';
4
+ import {
5
+ createIntent,
6
+ intentRelationshipTypes,
7
+ listDraftIntentsForReconciliation,
8
+ readDraftIntentForReconciliation,
9
+ replaceDraftIntent,
10
+ } from '../intents.mjs';
11
+ import { loadProjectConfig } from '../project.mjs';
12
+ import { selectContextualPersonas } from './persona-engagement.mjs';
13
+ import { runtimePaths } from './paths.mjs';
14
+
15
+ const DRAFT_SCHEMA = 'ewai.guided-intent-draft/v1';
16
+ const RESPONSE_SCHEMA = 'ewai.guided-intent/v1';
17
+ const MAX_FIELD_LENGTH = 8_000;
18
+ const MAX_DRAFT_BYTES = 100_000;
19
+ const slugPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
20
+ const referencePattern = /^[a-z0-9]+(?:-[a-z0-9]+)*\/[a-z0-9]+(?:-[a-z0-9]+)*$/;
21
+
22
+ const sections = Object.freeze([
23
+ {
24
+ id: 'identity', title: 'Identity', short: 'Name the intent',
25
+ question: 'What should this piece of work be called, and where does it belong?',
26
+ help: 'Choose a stable domain and lower-kebab-case slug. These form the permanent intent reference.',
27
+ perspectiveSignals: ['intent identity', 'project', 'product', 'outcomes', 'naming'],
28
+ fields: ['domain', 'slug', 'title'],
29
+ },
30
+ {
31
+ id: 'problem', title: 'Problem', short: 'Describe the present difficulty',
32
+ question: 'What observable problem are we trying to change?',
33
+ help: 'Describe the current situation and its consequence without jumping straight to a solution.',
34
+ perspectiveSignals: ['problem', 'users', 'evidence', 'product', 'service'],
35
+ fields: ['details.problem'],
36
+ },
37
+ {
38
+ id: 'outcome', title: 'Outcome', short: 'Define a useful result',
39
+ question: 'What should become true for the people or organisation involved?',
40
+ help: 'State an outcome that can be reviewed independently of a preferred implementation.',
41
+ perspectiveSignals: ['outcomes', 'product', 'value', 'users', 'acceptance'],
42
+ fields: ['details.desiredOutcome'],
43
+ },
44
+ {
45
+ id: 'users', title: 'Users', short: 'Name the affected people',
46
+ question: 'Who experiences the problem, the change, or its operational consequences?',
47
+ help: 'Use real roles or groups. A persona is a lens for questions, not evidence that a real stakeholder agrees.',
48
+ perspectiveSignals: ['users', 'personas', 'accessibility', 'stakeholders', 'service-design'],
49
+ fields: ['details.users'],
50
+ },
51
+ {
52
+ id: 'journeys', title: 'Journeys', short: 'Walk through the experience',
53
+ question: 'What are the important happy, alternate, error, and recovery paths?',
54
+ help: 'Describe the sequence from the participant’s point of view, including hand-offs where they matter.',
55
+ perspectiveSignals: ['journeys', 'users', 'service-design', 'operations', 'recovery'],
56
+ fields: ['details.journeys'],
57
+ },
58
+ {
59
+ id: 'acceptance', title: 'Acceptance', short: 'Make success observable',
60
+ question: 'What must be demonstrably true before this intent can be accepted?',
61
+ help: 'Use observable outcomes. Keep automated proof, Manual QA and representative-user evidence distinct.',
62
+ perspectiveSignals: ['acceptance', 'testing', 'outcomes', 'evidence', 'product'],
63
+ fields: ['details.acceptanceCriteria'],
64
+ },
65
+ {
66
+ id: 'constraints', title: 'Constraints', short: 'Protect the boundaries',
67
+ question: 'What technical, operational, legal, data, security, accessibility, or delivery limits apply?',
68
+ help: 'Record real boundaries and non-goals. A persona may expose a question but cannot invent an obligation.',
69
+ perspectiveSignals: ['constraints', 'dependencies', 'testing', 'operations', 'recovery', 'security', 'accessibility'],
70
+ fields: ['details.constraints'],
71
+ },
72
+ {
73
+ id: 'relationships', title: 'Relationships', short: 'Connect related intents',
74
+ question: 'Which existing intents constrain, enable, complement, conflict with, or supersede this one?',
75
+ help: 'Use exact domain/slug references and explain why the connection affects delivery.',
76
+ perspectiveSignals: ['dependencies', 'architecture', 'delivery', 'project', 'planning'],
77
+ fields: ['relationships'],
78
+ },
79
+ {
80
+ id: 'delivery-shape', title: 'Delivery shape', short: 'Keep one or split',
81
+ question: 'Is this one coherent delivery or should it be split into independently valuable child intents?',
82
+ help: 'Record the recommendation, rationale, blocking questions and an explicit reviewed decision.',
83
+ perspectiveSignals: ['delivery', 'architecture', 'planning', 'dependencies', 'outcomes'],
84
+ fields: ['deliveryShape'],
85
+ },
86
+ {
87
+ id: 'evidence', title: 'Evidence', short: 'Separate known from assumed',
88
+ question: 'What evidence supports the problem, outcome, journeys, constraints and acceptance?',
89
+ help: 'Name observed evidence and accountable decisions. Label persona and model suggestions as hypotheses.',
90
+ perspectiveSignals: ['evidence', 'research', 'decisions', 'knowledge', 'assurance'],
91
+ fields: ['details.evidence'],
92
+ },
93
+ {
94
+ id: 'open-decisions', title: 'Open decisions', short: 'Keep uncertainty visible',
95
+ question: 'What still needs a named human decision, specialist review, or representative-user evidence?',
96
+ help: 'Do not hide unresolved questions to make the draft appear complete.',
97
+ perspectiveSignals: ['decisions', 'evidence', 'risk', 'assurance', 'product'],
98
+ fields: ['details.openDecisions'],
99
+ },
100
+ {
101
+ id: 'review', title: 'Review', short: 'Preview and approve intent truth',
102
+ question: 'Is the current revision ready to become canonical intent truth?',
103
+ help: 'Review blockers, changes, active lenses and exact destinations. Intent approval grants no Build or release permission.',
104
+ perspectiveSignals: ['intent-review', 'acceptance', 'evidence', 'delivery-review', 'product'],
105
+ fields: [],
106
+ },
107
+ ]);
108
+
109
+ function failure(message, statusCode = 409) {
110
+ const error = new Error(message);
111
+ error.statusCode = statusCode;
112
+ return error;
113
+ }
114
+
115
+ function cleanText(value, label, maxLength = MAX_FIELD_LENGTH) {
116
+ const text = String(value ?? '').trim();
117
+ if (text.length > maxLength) throw failure(`${label} exceeds ${maxLength} characters`, 400);
118
+ if (/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(text)) throw failure(`${label} contains unsafe control characters`, 400);
119
+ return text;
120
+ }
121
+
122
+ function assertObject(value, label) {
123
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw failure(`${label} must be an object`, 400);
124
+ return value;
125
+ }
126
+
127
+ function assertKnownFields(value, fields, label) {
128
+ for (const key of Object.keys(value)) {
129
+ if (!fields.includes(key)) throw failure(`${label} contains unknown field: ${key}`, 400);
130
+ }
131
+ }
132
+
133
+ function textValue(value, label) {
134
+ if (Array.isArray(value)) {
135
+ if (value.length > 100) throw failure(`${label} exceeds 100 items`, 400);
136
+ return value.map((item, index) => cleanText(item, `${label} item ${index + 1}`)).filter(Boolean);
137
+ }
138
+ return cleanText(value, label);
139
+ }
140
+
141
+ function textPresent(value) {
142
+ return Array.isArray(value) ? value.some((item) => String(item).trim()) : Boolean(String(value ?? '').trim());
143
+ }
144
+
145
+ function normalisePersonaAttachment(value, index) {
146
+ const input = assertObject(value, `Persona attachment ${index + 1}`);
147
+ assertKnownFields(input, ['ref', 'role', 'depth'], `Persona attachment ${index + 1}`);
148
+ const ref = cleanText(input.ref, `Persona attachment ${index + 1} ref`, 160);
149
+ if (!/^[a-z0-9]+(?:[.-][a-z0-9]+)+$/.test(ref)) throw failure(`Invalid persona reference: ${ref}`, 400);
150
+ const role = cleanText(input.role || 'reviewer', `Persona attachment ${index + 1} role`, 80);
151
+ if (!slugPattern.test(role)) throw failure(`Invalid persona role: ${role}`, 400);
152
+ const depth = Number(input.depth ?? 3);
153
+ if (!Number.isInteger(depth) || depth < 1 || depth > 5) throw failure('Persona depth must be an integer from 1 to 5', 400);
154
+ return { ref, role, depth };
155
+ }
156
+
157
+ function normaliseRelationship(value, index) {
158
+ const input = assertObject(value, `Relationship ${index + 1}`);
159
+ assertKnownFields(input, ['type', 'target', 'rationale', 'required_before', 'required_state', 'requiredBefore', 'requiredState'], `Relationship ${index + 1}`);
160
+ const type = cleanText(input.type, `Relationship ${index + 1} type`, 80);
161
+ if (!intentRelationshipTypes.includes(type)) throw failure(`Intent relationship type must be one of: ${intentRelationshipTypes.join(', ')}`, 400);
162
+ const target = cleanText(input.target, `Relationship ${index + 1} target`, 200);
163
+ if (!referencePattern.test(target)) throw failure(`Intent relationship target must be an exact domain/slug reference: ${target}`, 400);
164
+ const rationale = cleanText(input.rationale, `Relationship ${index + 1} rationale`);
165
+ const requiredBefore = cleanText(input.required_before ?? input.requiredBefore, `Relationship ${index + 1} required before`, 40);
166
+ const requiredState = cleanText(input.required_state ?? input.requiredState, `Relationship ${index + 1} required state`, 40);
167
+ if ((requiredBefore || requiredState) && type !== 'depends-on') throw failure('required_before and required_state apply only to depends-on relationships', 400);
168
+ if (requiredBefore && !['build', 'delivery'].includes(requiredBefore)) throw failure('depends-on required_before must be build or delivery', 400);
169
+ if (requiredState && !['plan-complete', 'delivered'].includes(requiredState)) throw failure('depends-on required_state must be plan-complete or delivered', 400);
170
+ return {
171
+ type,
172
+ target,
173
+ ...(rationale ? { rationale } : {}),
174
+ ...(type === 'depends-on' ? { required_before: requiredBefore || 'delivery', required_state: requiredState || 'delivered' } : {}),
175
+ };
176
+ }
177
+
178
+ function normaliseDeliveryShape(value) {
179
+ if (value == null) return null;
180
+ const input = assertObject(value, 'Delivery shape');
181
+ assertKnownFields(input, ['recommendation', 'reason', 'suggestedChildren', 'suggested_children', 'blockingQuestions', 'blocking_questions', 'reviewedDecision', 'reviewed_decision'], 'Delivery shape');
182
+ const recommendation = cleanText(input.recommendation, 'Delivery-shape recommendation', 40);
183
+ if (recommendation && !['single', 'split', 'decision-required'].includes(recommendation)) throw failure('Delivery-shape recommendation must be single, split, or decision-required', 400);
184
+ const reviewedDecision = cleanText(input.reviewedDecision ?? input.reviewed_decision, 'Delivery-shape reviewed decision', 40);
185
+ if (reviewedDecision && !['keep-as-one', 'split', 'refine-split', 'defer'].includes(reviewedDecision)) throw failure('Delivery-shape reviewed decision is invalid', 400);
186
+ const children = input.suggestedChildren ?? input.suggested_children ?? [];
187
+ if (!Array.isArray(children) || children.length > 20) throw failure('Delivery shape supports no more than 20 suggested child intents', 400);
188
+ const suggestedChildren = children.map((child, index) => {
189
+ const item = assertObject(child, `Suggested child ${index + 1}`);
190
+ const domain = cleanText(item.domain, `Suggested child ${index + 1} domain`, 80);
191
+ const slug = cleanText(item.slug, `Suggested child ${index + 1} slug`, 120);
192
+ if (domain && !slugPattern.test(domain)) throw failure(`Suggested child domain must be lower kebab-case: ${domain}`, 400);
193
+ if (slug && !slugPattern.test(slug)) throw failure(`Suggested child slug must be lower kebab-case: ${slug}`, 400);
194
+ return {
195
+ ...(domain ? { domain } : {}),
196
+ ...(slug ? { slug } : {}),
197
+ ...(cleanText(item.title, `Suggested child ${index + 1} title`, 240) ? { title: cleanText(item.title, `Suggested child ${index + 1} title`, 240) } : {}),
198
+ outcome: cleanText(item.outcome, `Suggested child ${index + 1} outcome`),
199
+ ...(Array.isArray(item.depends_on ?? item.dependsOn) ? { depends_on: (item.depends_on ?? item.dependsOn).map((entry) => cleanText(entry, `Suggested child ${index + 1} dependency`, 200)).filter(Boolean) } : {}),
200
+ };
201
+ });
202
+ const questions = input.blockingQuestions ?? input.blocking_questions ?? [];
203
+ if (!Array.isArray(questions) || questions.length > 50) throw failure('Delivery shape supports no more than 50 blocking questions', 400);
204
+ return {
205
+ recommendation,
206
+ reason: cleanText(input.reason, 'Delivery-shape reason'),
207
+ suggestedChildren,
208
+ blockingQuestions: questions.map((question, index) => cleanText(question, `Blocking question ${index + 1}`)).filter(Boolean),
209
+ reviewedDecision: reviewedDecision || null,
210
+ };
211
+ }
212
+
213
+ function emptyIntent() {
214
+ return {
215
+ domain: 'experience', slug: '', title: '', intentMap: null, personas: [], relationships: [], deliveryShape: null,
216
+ details: { problem: '', desiredOutcome: '', users: [], journeys: [], acceptanceCriteria: [], constraints: [], evidence: [], openDecisions: [] },
217
+ };
218
+ }
219
+
220
+ function normaliseIntent(value, { lockedIdentity = null, lockedRelationships = null } = {}) {
221
+ const input = assertObject(value, 'Guided intent');
222
+ assertKnownFields(input, ['domain', 'slug', 'title', 'intentMap', 'personas', 'relationships', 'deliveryShape', 'details'], 'Guided intent');
223
+ const domain = cleanText(input.domain, 'Intent domain', 80);
224
+ const slug = cleanText(input.slug, 'Intent slug', 120);
225
+ if (domain && !slugPattern.test(domain)) throw failure(`Intent domain must be lower kebab-case: ${domain}`, 400);
226
+ if (slug && !slugPattern.test(slug)) throw failure(`Intent slug must be lower kebab-case: ${slug}`, 400);
227
+ if (lockedIdentity && (domain !== lockedIdentity.domain || slug !== lockedIdentity.slug)) throw failure('Intent identity cannot change during reconciliation', 400);
228
+ const details = assertObject(input.details ?? {}, 'Intent details');
229
+ assertKnownFields(details, ['problem', 'desiredOutcome', 'users', 'journeys', 'acceptanceCriteria', 'constraints', 'evidence', 'openDecisions'], 'Intent details');
230
+ const relationships = input.relationships ?? [];
231
+ if (!Array.isArray(relationships) || relationships.length > 50) throw failure('Intent supports no more than 50 relationships', 400);
232
+ const normalisedRelationships = relationships.map(normaliseRelationship);
233
+ if (lockedRelationships && JSON.stringify(normalisedRelationships) !== JSON.stringify(lockedRelationships)) throw failure('Intent relationships cannot change during reconciliation', 400);
234
+ const personas = input.personas ?? [];
235
+ if (!Array.isArray(personas) || personas.length > 50) throw failure('Intent supports no more than 50 persona attachments', 400);
236
+ const result = {
237
+ domain,
238
+ slug,
239
+ title: cleanText(input.title, 'Intent title', 240),
240
+ intentMap: input.intentMap ? cleanText(input.intentMap, 'Intent map', 120) : null,
241
+ personas: personas.map(normalisePersonaAttachment),
242
+ relationships: normalisedRelationships,
243
+ deliveryShape: normaliseDeliveryShape(input.deliveryShape),
244
+ details: {
245
+ problem: textValue(details.problem, 'Problem'),
246
+ desiredOutcome: textValue(details.desiredOutcome, 'Desired outcome'),
247
+ users: textValue(details.users, 'Users'),
248
+ journeys: textValue(details.journeys, 'Journeys'),
249
+ acceptanceCriteria: textValue(details.acceptanceCriteria, 'Acceptance criteria'),
250
+ constraints: textValue(details.constraints, 'Constraints'),
251
+ evidence: textValue(details.evidence, 'Evidence'),
252
+ openDecisions: textValue(details.openDecisions, 'Open decisions'),
253
+ },
254
+ };
255
+ if (Buffer.byteLength(JSON.stringify(result), 'utf8') > MAX_DRAFT_BYTES) throw failure('Guided intent draft exceeds 100000 bytes', 413);
256
+ return result;
257
+ }
258
+
259
+ function projectRelativeDestinations(projectRoot, intent) {
260
+ const { paths } = loadProjectConfig(projectRoot);
261
+ const base = relative(paths.projectRoot, resolve(paths.specsRoot, '2.Purpose/intents', intent.domain, intent.slug)).replaceAll('\\', '/');
262
+ return [`${base}.md`, `${base}.json`];
263
+ }
264
+
265
+ function anySlugCollision(root, slug, targetReference) {
266
+ const { paths } = loadProjectConfig(root);
267
+ const intentsRoot = resolve(paths.specsRoot, '2.Purpose/intents');
268
+ if (!existsSync(intentsRoot) || !slug) return null;
269
+ const pending = [intentsRoot];
270
+ while (pending.length) {
271
+ const directory = pending.pop();
272
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
273
+ const path = resolve(directory, entry.name);
274
+ if (entry.isDirectory()) pending.push(path);
275
+ else if (entry.isFile() && entry.name === `${slug}.json`) {
276
+ try {
277
+ const state = JSON.parse(readFileSync(path, 'utf8'));
278
+ const reference = `${state.domain}/${state.slug}`;
279
+ if (reference !== targetReference) return reference;
280
+ } catch {
281
+ return relative(paths.projectRoot, path).replaceAll('\\', '/');
282
+ }
283
+ }
284
+ }
285
+ }
286
+ return null;
287
+ }
288
+
289
+ function validationFor(projectRoot, draft) {
290
+ const errors = [];
291
+ const require = (path, value, label) => { if (!textPresent(value)) errors.push({ path, message: `${label} is required` }); };
292
+ const intent = draft.intent;
293
+ require('domain', intent.domain, 'Intent domain');
294
+ require('slug', intent.slug, 'Intent slug');
295
+ require('title', intent.title, 'Intent title');
296
+ require('details.problem', intent.details.problem, 'Problem');
297
+ require('details.desiredOutcome', intent.details.desiredOutcome, 'Desired outcome');
298
+ require('details.users', intent.details.users, 'Users');
299
+ require('details.journeys', intent.details.journeys, 'Journeys');
300
+ require('details.acceptanceCriteria', intent.details.acceptanceCriteria, 'Acceptance criteria');
301
+ require('details.constraints', intent.details.constraints, 'Constraints');
302
+ require('details.evidence', intent.details.evidence, 'Evidence');
303
+ require('deliveryShape.recommendation', intent.deliveryShape?.recommendation, 'Delivery-shape recommendation');
304
+ require('deliveryShape.reason', intent.deliveryShape?.reason, 'Delivery-shape reason');
305
+ require('deliveryShape.reviewedDecision', intent.deliveryShape?.reviewedDecision, 'Reviewed delivery-shape decision');
306
+ if (draft.mode === 'create' && intent.domain && intent.slug) {
307
+ const collision = anySlugCollision(projectRoot, intent.slug, `${intent.domain}/${intent.slug}`);
308
+ const exact = projectRelativeDestinations(projectRoot, intent).some((path) => existsSync(resolve(projectRoot, path)));
309
+ if (collision || exact) errors.push({ path: 'slug', message: `Intent already exists${collision ? ` at ${collision}` : ''}` });
310
+ }
311
+ if (draft.mode === 'reconcile') {
312
+ try {
313
+ const current = readDraftIntentForReconciliation(projectRoot, draft.sourceReference);
314
+ if (current.sourceUpdatedAt !== draft.sourceUpdatedAt) errors.push({ path: 'sourceReference', message: 'A newer canonical intent revision exists' });
315
+ } catch (error) {
316
+ errors.push({ path: 'sourceReference', message: error.message });
317
+ }
318
+ }
319
+ return { valid: errors.length === 0, errors };
320
+ }
321
+
322
+ function comparable(value) {
323
+ return JSON.stringify(value ?? null);
324
+ }
325
+
326
+ function changesFor(draft) {
327
+ if (draft.mode !== 'reconcile' || !draft.originalIntent) return [];
328
+ const changes = [];
329
+ for (const key of ['title', 'personas', 'deliveryShape']) {
330
+ if (comparable(draft.originalIntent[key]) !== comparable(draft.intent[key])) changes.push({ section: key, before: draft.originalIntent[key], after: draft.intent[key] });
331
+ }
332
+ for (const key of Object.keys(draft.intent.details)) {
333
+ if (comparable(draft.originalIntent.details[key]) !== comparable(draft.intent.details[key])) changes.push({ section: key, before: draft.originalIntent.details[key], after: draft.intent.details[key] });
334
+ }
335
+ return changes;
336
+ }
337
+
338
+ function sectionById(sectionId) {
339
+ const section = sections.find((candidate) => candidate.id === sectionId);
340
+ if (!section) throw failure(`Unknown intent section: ${sectionId}`, 400);
341
+ return section;
342
+ }
343
+
344
+ export function intentSectionContract() {
345
+ return { schema: 'ewai.guided-intent-sections/v1', sections: structuredClone(sections) };
346
+ }
347
+
348
+ export function selectIntentPersonas(sectionId, personaCatalogue = [], intent = {}) {
349
+ const section = sectionById(sectionId);
350
+ return selectContextualPersonas({
351
+ contextLabel: `${section.title} intent section`,
352
+ signals: section.perspectiveSignals,
353
+ context: intent,
354
+ personaCatalogue,
355
+ limit: 4,
356
+ });
357
+ }
358
+
359
+ function initialDraft() {
360
+ return { schema: DRAFT_SCHEMA, revision: 0, mode: 'create', currentSection: 'identity', sourceReference: null, sourceUpdatedAt: null, originalIntent: null, intent: emptyIntent(), createdAt: null, updatedAt: null };
361
+ }
362
+
363
+ function readDraft(projectRoot) {
364
+ const path = runtimePaths(projectRoot).guidedIntentDraftPath;
365
+ if (!existsSync(path)) return initialDraft();
366
+ let draft;
367
+ try { draft = JSON.parse(readFileSync(path, 'utf8')); }
368
+ catch (error) { throw failure(`Guided intent draft is not valid JSON: ${error.message}`); }
369
+ if (draft.schema !== DRAFT_SCHEMA || !Number.isInteger(draft.revision) || draft.revision < 1 || !['create', 'reconcile'].includes(draft.mode)) {
370
+ throw failure('Guided intent draft has an unsupported schema revision or mode');
371
+ }
372
+ sectionById(draft.currentSection);
373
+ return draft;
374
+ }
375
+
376
+ function availability(catalogue) {
377
+ const result = {};
378
+ for (const tier of ['core', 'premium', 'personal', 'project']) {
379
+ const count = catalogue.filter((persona) => persona.tier === tier).length;
380
+ result[tier] = { installed: count > 0, count, reason: count ? `${count} installed ${tier} persona${count === 1 ? '' : 's'} available.` : `No installed ${tier} personas were found.` };
381
+ }
382
+ return result;
383
+ }
384
+
385
+ function response(projectRoot, draft, options = {}) {
386
+ const catalogue = Array.isArray(options.personas) ? options.personas : [];
387
+ const activePersonas = selectIntentPersonas(draft.currentSection, catalogue, draft.intent);
388
+ const validation = validationFor(projectRoot, draft);
389
+ const destinations = draft.intent.domain && draft.intent.slug ? projectRelativeDestinations(projectRoot, draft.intent) : [];
390
+ return {
391
+ schema: RESPONSE_SCHEMA,
392
+ sections: structuredClone(sections),
393
+ draft,
394
+ validation,
395
+ preview: {
396
+ operation: draft.mode,
397
+ destinations,
398
+ missingSections: validation.errors.map(({ path, message }) => ({ path, message })),
399
+ changes: changesFor(draft),
400
+ authority: { createsIntentTruthOnly: true, buildApproved: false, manualQaApproved: false, releaseApproved: false },
401
+ },
402
+ eligibleIntents: listDraftIntentsForReconciliation(projectRoot),
403
+ activePersonas,
404
+ personaAvailability: availability(catalogue),
405
+ baseline: {
406
+ completeWithoutPremium: true,
407
+ description: 'Standard host-model questions provide the complete authoring and review baseline. Installed personas add contextual lenses.',
408
+ },
409
+ guidance: {
410
+ advisory: true,
411
+ personasAreEvidence: false,
412
+ section: draft.currentSection,
413
+ baselineQuestions: [sectionById(draft.currentSection).question, sectionById(draft.currentSection).help],
414
+ prompts: activePersonas.map((persona) => ({ personaId: persona.id, text: persona.engagementReason, classification: 'advisory-hypothesis' })),
415
+ },
416
+ };
417
+ }
418
+
419
+ export function readGuidedIntent(projectRoot, options = {}) {
420
+ return response(projectRoot, readDraft(projectRoot), options);
421
+ }
422
+
423
+ export function saveGuidedIntentDraft(projectRoot, input = {}, options = {}) {
424
+ const current = readDraft(projectRoot);
425
+ const expectedRevision = Number(input.expectedRevision);
426
+ if (!Number.isInteger(expectedRevision) || expectedRevision !== current.revision) throw failure(`A newer revision of this guided intent draft exists (current revision ${current.revision})`);
427
+ const mode = cleanText(input.mode || current.mode, 'Guided intent mode', 20);
428
+ if (!['create', 'reconcile'].includes(mode)) throw failure('Guided intent mode must be create or reconcile', 400);
429
+ const currentSection = cleanText(input.currentSection || current.currentSection, 'Current intent section', 80);
430
+ sectionById(currentSection);
431
+ let sourceReference = null;
432
+ let sourceUpdatedAt = null;
433
+ let originalIntent = null;
434
+ let proposed = input.intent;
435
+ if (mode === 'reconcile') {
436
+ sourceReference = cleanText(input.sourceReference || current.sourceReference, 'Source intent reference', 200);
437
+ if (!referencePattern.test(sourceReference)) throw failure('Reconciliation requires an exact domain/slug source reference', 400);
438
+ const imported = current.mode === 'reconcile' && current.sourceReference === sourceReference && current.originalIntent
439
+ ? { sourceUpdatedAt: current.sourceUpdatedAt, intent: current.originalIntent }
440
+ : readDraftIntentForReconciliation(projectRoot, sourceReference);
441
+ sourceUpdatedAt = imported.sourceUpdatedAt;
442
+ originalIntent = normaliseIntent(imported.intent);
443
+ proposed = proposed ?? originalIntent;
444
+ } else if (!proposed) {
445
+ proposed = current.mode === 'create' ? current.intent : emptyIntent();
446
+ }
447
+ const intent = normaliseIntent(proposed, {
448
+ ...(originalIntent ? { lockedIdentity: originalIntent, lockedRelationships: originalIntent.relationships } : {}),
449
+ });
450
+ const timestamp = options.now ?? new Date().toISOString();
451
+ const draft = {
452
+ schema: DRAFT_SCHEMA,
453
+ revision: current.revision + 1,
454
+ mode,
455
+ currentSection,
456
+ sourceReference,
457
+ sourceUpdatedAt,
458
+ originalIntent,
459
+ intent,
460
+ createdAt: current.createdAt ?? timestamp,
461
+ updatedAt: timestamp,
462
+ };
463
+ atomicJson(runtimePaths(projectRoot).guidedIntentDraftPath, draft);
464
+ return response(projectRoot, draft, options);
465
+ }
466
+
467
+ export function discardGuidedIntentDraft(projectRoot, input = {}) {
468
+ if (input.confirmed !== true) throw failure('Discarding a guided intent draft requires explicit confirmation');
469
+ const current = readDraft(projectRoot);
470
+ const expectedRevision = Number(input.expectedRevision);
471
+ if (!Number.isInteger(expectedRevision) || expectedRevision !== current.revision) throw failure(`A newer revision of this guided intent draft exists (current revision ${current.revision})`);
472
+ rmSync(runtimePaths(projectRoot).guidedIntentDraftPath, { force: true });
473
+ return { schema: 'ewai.guided-intent-discard/v1', status: 'discarded', revision: current.revision };
474
+ }
475
+
476
+ export function approveGuidedIntent(projectRoot, input = {}, options = {}) {
477
+ if (input.confirmed !== true) throw failure('Canonical intent creation requires explicit confirmation');
478
+ const approvedBy = cleanText(input.approvedBy, 'Approver name', 160);
479
+ if (!approvedBy) throw failure('Canonical intent creation requires an accountable approver name');
480
+ const draft = readDraft(projectRoot);
481
+ const expectedRevision = Number(input.expectedRevision);
482
+ if (!Number.isInteger(expectedRevision) || expectedRevision !== draft.revision) throw failure(`A newer revision of this guided intent draft exists (current revision ${draft.revision})`);
483
+ const validation = validationFor(projectRoot, draft);
484
+ if (!validation.valid) throw failure(`The current guided intent draft is incomplete or invalid: ${validation.errors[0].message}`);
485
+ const approvedAt = options.now ?? new Date().toISOString();
486
+ let created;
487
+ if (draft.mode === 'reconcile') {
488
+ const replace = options.replace ?? replaceDraftIntent;
489
+ created = replace(projectRoot, draft.sourceReference, { expectedUpdatedAt: draft.sourceUpdatedAt, intent: draft.intent, now: approvedAt });
490
+ } else {
491
+ const create = options.create ?? createIntent;
492
+ const result = create(projectRoot, {
493
+ slug: draft.intent.slug,
494
+ domain: draft.intent.domain,
495
+ title: draft.intent.title,
496
+ intentMap: draft.intent.intentMap,
497
+ personas: draft.intent.personas,
498
+ relationships: draft.intent.relationships,
499
+ deliveryShape: draft.intent.deliveryShape,
500
+ details: draft.intent.details,
501
+ });
502
+ created = {
503
+ reference: `${result.domain}/${result.slug}`,
504
+ markdownPath: relative(resolve(projectRoot), result.path).replaceAll('\\', '/'),
505
+ jsonPath: relative(resolve(projectRoot), String(result.path).replace(/\.md$/i, '.json')).replaceAll('\\', '/'),
506
+ };
507
+ }
508
+ rmSync(runtimePaths(projectRoot).guidedIntentDraftPath, { force: true });
509
+ return {
510
+ schema: 'ewai.guided-intent-approval/v1',
511
+ status: 'completed',
512
+ mode: draft.mode,
513
+ approval: { approvedBy, approvedAt, revision: draft.revision },
514
+ created,
515
+ authority: { intentApproved: true, buildApproved: false, manualQaApproved: false, certificationGranted: false, releaseApproved: false },
516
+ };
517
+ }