@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,134 @@
1
+ import { hostname, homedir } from 'node:os';
2
+ import { readFileSync } from 'node:fs';
3
+ import { readPersonaLicence, personaLicencePath, personaInstallationSecret, writePrivatePersonaJson } from './persona-licence-config.mjs';
4
+ import { MAX_PERSONA_ZIP_BYTES } from './persona-zip.mjs';
5
+
6
+ export const DEFAULT_PERSONA_SERVER = 'https://www.conversationalcoding.dev';
7
+ const api = '/wp-json/conversational-coding/v1/persona-pack';
8
+ const semanticVersion = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
9
+ const safeId = v => typeof v === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(v);
10
+ const messages = {
11
+ invalid_licence_key: 'That licence key could not be verified. Check the key in My Account and try again.',
12
+ invalid_licence: 'That licence key could not be verified. Check the key in My Account and try again.',
13
+ installation_limit_reached: 'This licence is already active on three machines. Deactivate a machine in My Account, then try again.',
14
+ subscription_expired: 'This subscription has expired. Installed individual personas remain usable, but updates have ended.',
15
+ subscription_inactive: 'This subscription is not currently available. Check My Account.',
16
+ invalid_activation: 'This machine activation could not be verified. Configure the licence again.',
17
+ };
18
+ export function personaServer(value = DEFAULT_PERSONA_SERVER) {
19
+ let url;
20
+ try { url = new URL(value); } catch { throw new Error('Persona server must be a safe HTTPS origin'); }
21
+ if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash || url.pathname !== '/') throw new Error('Persona server must be a safe HTTPS origin');
22
+ // Explicit alternative origins are for independently approved self-hosted deployments.
23
+ return url.origin;
24
+ }
25
+ export function parsePersonaRelease(release) {
26
+ if (!release || release.schema !== 'ewai.persona-pack/v1' || release.id !== 'ewai.personas.professional'
27
+ || !semanticVersion.test(release.version) || !/^[a-f0-9]{64}$/.test(release.artefact?.sha256)
28
+ || !Number.isSafeInteger(release.artefact.size) || release.artefact.size < 22 || release.artefact.size > MAX_PERSONA_ZIP_BYTES) throw new Error('The persona release contract could not be verified');
29
+ return { version: release.version, sha256: release.artefact.sha256, size: release.artefact.size };
30
+ }
31
+ export function parsePersonaSubscription(data) {
32
+ if (!data || !safeId(data.id) || !['individual', 'team'].includes(data.plan_type)
33
+ || !['active', 'grace', 'cancelled', 'expired', 'pending', 'revoked', 'refunded'].includes(data.status)
34
+ || typeof data.ends_at !== 'string' || !Number.isFinite(Date.parse(data.ends_at))) throw new Error('The persona subscription contract could not be verified');
35
+ return { id: data.id, planType: data.plan_type, status: data.status, endsAt: new Date(data.ends_at).toISOString() };
36
+ }
37
+
38
+ async function request(url, options, limit, context) {
39
+ const signal = AbortSignal.timeout(context.timeout ?? 8000);
40
+ let rejectAbort;
41
+ const aborted = new Promise((_, reject) => {
42
+ rejectAbort = () => reject(new Error('timeout'));
43
+ signal.addEventListener('abort', rejectAbort, { once: true });
44
+ });
45
+ // Keep a deadline alive even for a test/custom transport with no socket.
46
+ const deadline = setTimeout(rejectAbort, context.timeout ?? 8000);
47
+ try {
48
+ const response = await Promise.race([(context.fetchImpl ?? fetch)(url, { ...options, redirect: 'error', signal }), aborted]);
49
+ if (response.redirected) throw new Error('redirect');
50
+ const length = response.headers.get('content-length');
51
+ if (length && (!/^\d+$/.test(length) || Number(length) > limit)) throw new Error('size');
52
+ const reader = response.body?.getReader(); if (!reader) throw new Error('empty');
53
+ const chunks = []; let size = 0;
54
+ try {
55
+ for (;;) {
56
+ const part = await Promise.race([reader.read(), aborted]);
57
+ if (part.done) break;
58
+ size += part.value.byteLength; if (size > limit) throw new Error('size');
59
+ chunks.push(Buffer.from(part.value));
60
+ }
61
+ } finally { void reader.cancel().catch(() => {}); }
62
+ return { response, bytes: Buffer.concat(chunks, size) };
63
+ } catch { throw new Error('Persona access could not be checked safely. Check your connection and try again; installed content has not been removed.'); }
64
+ finally { clearTimeout(deadline); signal.removeEventListener('abort', rejectAbort); }
65
+ }
66
+ async function jsonRequest(url, options, context) {
67
+ const { response, bytes } = await request(url, options, 64 * 1024, context);
68
+ let body;
69
+ try { body = JSON.parse(bytes.toString('utf8')); } catch { throw new Error('The persona service returned an unsupported response. Installed content has not been removed.'); }
70
+ if (!response.ok) {
71
+ const code = Object.hasOwn(messages, body?.code) ? body.code : 'provider-access-unknown';
72
+ const error = new Error(messages[code] || 'Persona access could not be verified. Installed content has not been removed.');
73
+ error.code = code; throw error;
74
+ }
75
+ return body;
76
+ }
77
+ const packageVersion = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
78
+ const version = options => options.clientVersion || packageVersion;
79
+ const headers = token => ({ Accept: 'application/json', ...(token ? { Authorization: 'Bearer ' + token } : {}) });
80
+
81
+ export async function configurePersonaLicence(options = {}) {
82
+ const home = options.home ?? homedir(), server = personaServer(options.server);
83
+ const prior = readPersonaLicence(home);
84
+ const licenceKey = String(options.licenceKey ?? '').trim();
85
+ if (!licenceKey || licenceKey.length > 512 || /[\x00-\x1f\x7f]/.test(licenceKey)) throw new Error('Enter a valid licence key using the private terminal prompt');
86
+ const installationSecret = personaInstallationSecret(home);
87
+ const machineName = String(options.machineName ?? hostname()).trim();
88
+ if (!machineName || machineName.length > 120 || /[\x00-\x1f\x7f]/.test(machineName)) throw new Error('Machine name must be a short readable label');
89
+ const data = await jsonRequest(server + api + '/activations', {
90
+ method: 'POST', headers: { ...headers(), 'Content-Type': 'application/json' },
91
+ body: JSON.stringify({ licence_key: licenceKey, installation_secret: installationSecret, machine_name: machineName, product: 'engineering-with-ai', client_version: version(options) }),
92
+ }, options);
93
+ if (data.schema !== 'cce.activation/v1' || !safeId(data.activation_id) || typeof data.activation_token !== 'string'
94
+ || data.activation_token.length < 16 || data.activation_token.length > 512 || /[\x00-\x20\x7f]/.test(data.activation_token)) throw new Error('Machine activation returned an unsupported contract; existing credentials were kept');
95
+ // Older server activations can be stored, but cannot prove plan-specific expiry.
96
+ const subscription = data.subscription ? parsePersonaSubscription(data.subscription) : null;
97
+ const seatId = safeId(data.seat_id) ? data.seat_id : null;
98
+ writePrivatePersonaJson(home, personaLicencePath(home), {
99
+ schema: 'ewai.persona-licence/v1', server, licenceKey, installationSecret,
100
+ activationId: data.activation_id, activationToken: data.activation_token, seatId, subscription, machineName,
101
+ });
102
+ return { schema: 'ewai.persona-licence-setup/v1', status: 'configured', provider: 'wordpress-edd', replaced: Boolean(prior), machineLimit: 3 };
103
+ }
104
+
105
+ export async function websitePersonaStatus(credentials, options = {}) {
106
+ const server = personaServer(credentials.server);
107
+ const data = await jsonRequest(server + api + '/status?product=engineering-with-ai&client_version=' + encodeURIComponent(version(options)), { headers: headers(credentials.activationToken) }, options);
108
+ if (data.schema !== 'cce.entitlement-status/v1' || !['available', 'unavailable'].includes(data.access)
109
+ || !safeId(data.seat?.id) || data.activation?.id !== credentials.activationId) throw new Error('The persona status contract could not be verified; installed content has not been removed');
110
+ const subscription = parsePersonaSubscription(data.subscription);
111
+ if (credentials.seatId && credentials.seatId !== data.seat.id) throw new Error('The licensed seat changed unexpectedly; configure the licence again');
112
+ if (data.access === 'available' && (!['active', 'grace', 'cancelled'].includes(subscription.status) || Date.parse(subscription.endsAt) <= Date.now())) throw new Error('The persona status contract is inconsistent');
113
+ if (subscription.status === 'expired' && (data.access !== 'unavailable' || Date.parse(subscription.endsAt) > Date.now())) throw new Error('The persona expiry contract is inconsistent');
114
+ return { access: data.access, subscription, seatId: data.seat.id, server, release: data.access === 'available' ? parsePersonaRelease(data.release) : null };
115
+ }
116
+
117
+ export async function acquireWebsitePersonaArchive(credentials, status, options = {}) {
118
+ if (status.access !== 'available') throw new Error('Persona updates are unavailable for this subscription');
119
+ const server = personaServer(credentials.server);
120
+ const grant = await jsonRequest(server + api + '/download-grants', {
121
+ method: 'POST', headers: { ...headers(credentials.activationToken), 'Content-Type': 'application/json' },
122
+ body: JSON.stringify({ product: 'engineering-with-ai', client_version: version(options) }),
123
+ }, options);
124
+ let url; try { url = new URL(grant.download_url); } catch { throw new Error('The persona download grant is invalid'); }
125
+ const release = parsePersonaRelease(grant.release);
126
+ if (grant.schema !== 'cce.download-grant/v1' || url.origin !== server || url.username || url.password || url.hash
127
+ || url.pathname !== '/' || [...url.searchParams.keys()].some(k => k !== 'cce-download')
128
+ || !url.searchParams.get('cce-download') || url.searchParams.getAll('cce-download').length !== 1
129
+ || !Number.isFinite(Date.parse(grant.expires_at)) || Date.parse(grant.expires_at) <= Date.now()
130
+ || release.sha256 !== status.release.sha256 || release.version !== status.release.version || release.size !== status.release.size) throw new Error('The persona download grant could not be verified; check for updates and retry');
131
+ const { response, bytes } = await request(url.href, { headers: { Accept: 'application/zip' } }, MAX_PERSONA_ZIP_BYTES, options);
132
+ if (!response.ok) throw new Error('The short-lived persona download could not be completed; request a fresh download');
133
+ return bytes;
134
+ }
@@ -0,0 +1,87 @@
1
+ import { inflateRawSync } from 'node:zlib';
2
+ import { existsSync, lstatSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';
3
+ import { dirname, resolve } from 'node:path';
4
+
5
+ const MAX_FILES = 1000, MAX_FILE = 1024 * 1024, MAX_TOTAL = 16 * 1024 * 1024;
6
+ export const MAX_PERSONA_ZIP_BYTES = 20 * 1024 * 1024;
7
+ const crcTable = Array.from({ length: 256 }, (_, i) => {
8
+ let c = i; for (let j = 0; j < 8; j++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
9
+ return c >>> 0;
10
+ });
11
+ export function crc32(bytes) {
12
+ let c = 0xffffffff; for (const b of bytes) c = crcTable[(c ^ b) & 255] ^ (c >>> 8);
13
+ return (c ^ 0xffffffff) >>> 0;
14
+ }
15
+ function range(bytes, start, size) {
16
+ if (!Number.isSafeInteger(start) || start < 0 || size < 0 || start + size > bytes.length) throw new Error('Malformed persona ZIP bounds');
17
+ return bytes.subarray(start, start + size);
18
+ }
19
+ function safeName(raw) {
20
+ let name;
21
+ try { name = new TextDecoder('utf-8', { fatal: true }).decode(raw); }
22
+ catch { throw new Error('Persona ZIP path is not UTF-8'); }
23
+ if (!name || name.length > 512 || /[\\:\x00-\x1f\x7f]/.test(name) || name.startsWith('/')) throw new Error('Unsafe persona ZIP path');
24
+ const directory = name.endsWith('/'), parts = (directory ? name.slice(0, -1) : name).split('/');
25
+ if (parts.some(p => !p || p === '.' || p === '..' || p.startsWith('.') || /[. ]$/.test(p)
26
+ || /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(?:\.|$)/i.test(p))) throw new Error('Unsafe persona ZIP path');
27
+ if (!directory && !/\.(md|markdown|txt|yaml|yml|json|csv)$/i.test(name)
28
+ && !/^(license|notice|readme)$/i.test(parts.at(-1))) throw new Error('Persona ZIP file type is not allowed');
29
+ return { name, directory };
30
+ }
31
+
32
+ // Deliberately supports the small release format, not arbitrary ZIP/ZIP64 files.
33
+ export function extractPersonaZip(input, destination) {
34
+ const bytes = Buffer.from(input);
35
+ if (bytes.length < 22 || bytes.length > MAX_PERSONA_ZIP_BYTES) throw new Error('Persona ZIP exceeds archive bounds');
36
+ if (!existsSync(destination) || !lstatSync(destination).isDirectory() || lstatSync(destination).isSymbolicLink() || readdirSync(destination).length) throw new Error('Persona ZIP requires an empty private staging directory');
37
+ let end = -1;
38
+ for (let i = bytes.length - 22; i >= Math.max(0, bytes.length - 65557); i--) {
39
+ if (bytes.readUInt32LE(i) === 0x06054b50 && i + 22 + bytes.readUInt16LE(i + 20) === bytes.length) { end = i; break; }
40
+ }
41
+ if (end < 0) throw new Error('Malformed persona ZIP directory');
42
+ const count = bytes.readUInt16LE(end + 10), size = bytes.readUInt32LE(end + 12), start = bytes.readUInt32LE(end + 16);
43
+ if (bytes.readUInt16LE(end + 4) || bytes.readUInt16LE(end + 6) || count !== bytes.readUInt16LE(end + 8)
44
+ || count === 0 || count > MAX_FILES || count === 65535 || start + size !== end) throw new Error('Unsupported persona ZIP directory');
45
+ const entries = [], names = new Map(); let position = start, total = 0, lastEnd = 0;
46
+ for (let i = 0; i < count; i++) {
47
+ const c = range(bytes, position, 46);
48
+ if (c.readUInt32LE(0) !== 0x02014b50) throw new Error('Malformed persona ZIP entry');
49
+ const flags = c.readUInt16LE(8), method = c.readUInt16LE(10), compressed = c.readUInt32LE(20), uncompressed = c.readUInt32LE(24);
50
+ const n = c.readUInt16LE(28), extra = c.readUInt16LE(30), comment = c.readUInt16LE(32), offset = c.readUInt32LE(42);
51
+ const raw = range(bytes, position + 46, n), path = safeName(raw), mode = c.readUInt32LE(38) >>> 16, type = mode & 0xf000;
52
+ range(bytes, position, 46 + n + extra + comment);
53
+ if ((flags & ~0x80e) || method !== 8 && (flags & 6) || ![0, 8].includes(method) || c.readUInt16LE(34)
54
+ || uncompressed > MAX_FILE || compressed > MAX_PERSONA_ZIP_BYTES || (type && type !== (path.directory ? 0x4000 : 0x8000))
55
+ || (mode & 0o111) && !path.directory) throw new Error('Unsupported or unsafe persona ZIP entry');
56
+ total += uncompressed; if (total > MAX_TOTAL) throw new Error('Persona ZIP exceeds total content size');
57
+ const normalized = path.name.replace(/\/$/, '').toLowerCase();
58
+ if (names.has(normalized)) throw new Error('Duplicate persona ZIP path');
59
+ names.set(normalized, path.directory);
60
+ const h = range(bytes, offset, 30);
61
+ if (offset < lastEnd || h.readUInt32LE(0) !== 0x04034b50 || h.readUInt16LE(6) !== flags || h.readUInt16LE(8) !== method
62
+ || h.readUInt16LE(26) !== n || !range(bytes, offset + 30, n).equals(raw)) throw new Error('Malformed persona ZIP local entry');
63
+ const dataStart = offset + 30 + n + h.readUInt16LE(28), dataEnd = dataStart + compressed;
64
+ if (dataEnd > start || dataStart < offset + 30) throw new Error('Malformed persona ZIP data bounds');
65
+ const data = range(bytes, dataStart, compressed);
66
+ let body;
67
+ try { body = method === 0 ? Buffer.from(data) : inflateRawSync(data, { maxOutputLength: MAX_FILE }); }
68
+ catch { throw new Error('Persona ZIP inflation failed or exceeded bounds'); }
69
+ if (body.length !== uncompressed || crc32(body) !== c.readUInt32LE(16)) throw new Error('Persona ZIP CRC or size mismatch');
70
+ if (path.directory && body.length) throw new Error('Persona ZIP directory contains data');
71
+ if (!(flags & 8) && (h.readUInt32LE(14) !== c.readUInt32LE(16) || h.readUInt32LE(18) !== compressed || h.readUInt32LE(22) !== uncompressed)) throw new Error('Malformed persona ZIP header');
72
+ lastEnd = dataEnd;
73
+ entries.push({ ...path, body }); position += 46 + n + extra + comment;
74
+ }
75
+ if (position !== end) throw new Error('Malformed persona ZIP directory length');
76
+ for (const e of entries) {
77
+ const parts = e.name.replace(/\/$/, '').toLowerCase().split('/');
78
+ for (let i = 1; i < parts.length; i++) if (names.get(parts.slice(0, i).join('/')) === false) throw new Error('Conflicting persona ZIP paths');
79
+ }
80
+ // Validate every entry before writing any content.
81
+ for (const e of entries) {
82
+ const path = resolve(destination, e.name);
83
+ if (e.directory) mkdirSync(path, { recursive: true, mode: 0o700 });
84
+ else { mkdirSync(dirname(path), { recursive: true, mode: 0o700 }); writeFileSync(path, e.body, { flag: 'wx', mode: 0o600 }); }
85
+ }
86
+ return { fileCount: entries.filter(e => !e.directory).length, totalBytes: total };
87
+ }
@@ -0,0 +1,159 @@
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { basename, extname, resolve } from 'node:path';
4
+ import YAML from 'yaml';
5
+ import { projectPaths } from './paths.mjs';
6
+
7
+ const personaSlugPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
8
+ export const personaRefPattern = /^[a-z0-9][a-z0-9._-]*$/;
9
+
10
+ export function validatePersonaRef(value) {
11
+ const reference = String(value ?? '').trim();
12
+ if (!personaRefPattern.test(reference)) throw new Error(`Invalid persona reference: ${reference}`);
13
+ return reference;
14
+ }
15
+
16
+ function findMarkdown(root) {
17
+ if (!existsSync(root)) return [];
18
+ const found = [];
19
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
20
+ const path = resolve(root, entry.name);
21
+ if (entry.isDirectory()) found.push(...findMarkdown(path));
22
+ if (entry.isFile() && extname(entry.name).toLowerCase() === '.md') found.push(path);
23
+ }
24
+ return found;
25
+ }
26
+
27
+ export function readPersona(path) {
28
+ const content = readFileSync(path, 'utf8');
29
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
30
+ let metadata = {};
31
+ let parseStatus = 'none';
32
+ if (match) {
33
+ try {
34
+ metadata = YAML.parse(match[1]) ?? {};
35
+ parseStatus = 'parsed';
36
+ } catch {
37
+ const legacyName = match[1].match(/^name:\s*(.+)$/m)?.[1]?.trim();
38
+ metadata = legacyName ? { name: legacyName } : {};
39
+ parseStatus = 'legacy-fallback';
40
+ }
41
+ }
42
+ const name = metadata?.name || basename(path, extname(path));
43
+ const id = validatePersonaRef(metadata?.id || name);
44
+ const body = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '').replace(/^#\s+.+\r?\n+/, '');
45
+ const summary = body.split(/\r?\n\s*\r?\n/).find((paragraph) => paragraph.trim() && !paragraph.trim().startsWith('#')) ?? '';
46
+ return {
47
+ id,
48
+ name,
49
+ description: metadata?.description || summary.replace(/[`*_>#\[\]]/g, '').replace(/\s+/g, ' ').trim().slice(0, 240),
50
+ category: metadata?.category || '',
51
+ pack: metadata?.pack || '',
52
+ tier: metadata?.tier || '',
53
+ version: metadata?.version || '',
54
+ tags: metadata?.tags || [],
55
+ capabilities: metadata?.capabilities || [],
56
+ parseStatus,
57
+ path
58
+ };
59
+ }
60
+
61
+ export function listPersonas(roots, query = '') {
62
+ const normalizedQuery = query.trim().toLowerCase();
63
+ const unique = new Map();
64
+
65
+ for (const root of roots) {
66
+ for (const path of findMarkdown(root)) {
67
+ let persona;
68
+ try {
69
+ persona = readPersona(path);
70
+ } catch {
71
+ continue;
72
+ }
73
+ const key = `${persona.id}:${path}`;
74
+ if (!normalizedQuery || JSON.stringify(persona).toLowerCase().includes(normalizedQuery)) {
75
+ unique.set(key, persona);
76
+ }
77
+ }
78
+ }
79
+
80
+ return [...unique.values()].sort((left, right) => left.name.localeCompare(right.name));
81
+ }
82
+
83
+ export function indexPersonas(roots, query = '') {
84
+ return listPersonas(roots, query).map((persona) => ({
85
+ id: persona.id,
86
+ name: persona.name,
87
+ description: persona.description,
88
+ category: persona.category,
89
+ tier: persona.tier,
90
+ tags: persona.tags,
91
+ capabilities: persona.capabilities,
92
+ path: persona.path
93
+ }));
94
+ }
95
+
96
+ export function personalPersonaRoot(home = homedir()) {
97
+ return resolve(home, '.ewai/personas');
98
+ }
99
+
100
+ export function projectPersonaRoot(projectRoot) {
101
+ if (!projectRoot) throw new Error('A project root is required for project personas');
102
+ return resolve(projectPaths(projectRoot).personaRoot, 'project');
103
+ }
104
+
105
+ export function personaLibraryRoot(scope, options = {}) {
106
+ if (scope === 'personal') return personalPersonaRoot(options.home);
107
+ if (scope === 'project') return projectPersonaRoot(options.projectRoot);
108
+ throw new Error(`Unsupported persona scope: ${scope}`);
109
+ }
110
+
111
+ function personaName(slug) {
112
+ return slug
113
+ .split('-')
114
+ .map((part) => `${part[0].toUpperCase()}${part.slice(1)}`)
115
+ .join(' ');
116
+ }
117
+
118
+ function personaTemplate({ scope, slug, name, category }) {
119
+ const metadata = {
120
+ schema: 'ewai.persona/v1',
121
+ id: `${scope}.${slug}`,
122
+ name,
123
+ version: '0.1.0',
124
+ description: `A ${scope} persona for ${name}.`,
125
+ category,
126
+ pack: `ewai.personas.${scope}`,
127
+ tier: scope,
128
+ tags: [],
129
+ capabilities: []
130
+ };
131
+
132
+ return `---\n${YAML.stringify(metadata, { lineWidth: 0 }).trim()}\n---\n\n# ${name}\n\n## Mission\n\nDescribe the outcome this persona cares about and the perspective it brings.\n\n## Operating stance\n\n- Describe how this persona approaches decisions.\n- Record the standards, evidence, or experience it should use.\n- Make uncertainty and assumptions explicit.\n\n## Questions to keep asking\n\n- What would this persona need to know?\n- What risks or opportunities would it notice?\n- What evidence would satisfy it?\n\n## Boundaries\n\n- This persona advises; it does not replace real stakeholders or grant authority.\n- Evidence from real people takes priority over simulated feedback.\n`;
133
+ }
134
+
135
+ export function createPersona(options = {}) {
136
+ const scope = options.scope || 'personal';
137
+ const slug = options.slug?.trim() || '';
138
+ if (!personaSlugPattern.test(slug)) {
139
+ throw new Error('Persona slug must use lowercase letters, numbers, and single hyphens');
140
+ }
141
+
142
+ const root = personaLibraryRoot(scope, options);
143
+ const path = resolve(root, `${slug}.md`);
144
+ if (existsSync(path) && !options.force) {
145
+ throw new Error(`Persona already exists: ${path}`);
146
+ }
147
+
148
+ const name = options.name?.trim() || personaName(slug);
149
+ const category = options.category?.trim() || 'general';
150
+ mkdirSync(root, { recursive: true });
151
+ writeFileSync(path, personaTemplate({ scope, slug, name, category }), 'utf8');
152
+
153
+ return {
154
+ schema: 'ewai.persona-create-result/v1',
155
+ id: `${scope}.${slug}`,
156
+ scope,
157
+ path
158
+ };
159
+ }