create-ailk 0.5.0 → 0.6.1

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 (329) hide show
  1. package/README.md +55 -8
  2. package/component-catalog.json +336 -27
  3. package/component-catalog.md +25 -12
  4. package/dist/cli.js +54 -5
  5. package/dist/copy.d.ts +14 -0
  6. package/dist/copy.js +38 -1
  7. package/dist/lib/api-boot-assertion.d.ts +46 -0
  8. package/dist/lib/api-boot-assertion.js +70 -0
  9. package/dist/lib/extract-module-bundle.d.ts +20 -1
  10. package/dist/lib/extract-module-bundle.js +64 -5
  11. package/dist/lib/import-graph.d.ts +29 -0
  12. package/dist/lib/import-graph.js +140 -0
  13. package/dist/lib/module-license-gate.d.ts +6 -1
  14. package/dist/lib/module-license-gate.js +6 -1
  15. package/dist/lib/pro-callout.js +1 -1
  16. package/dist/module-architecture.d.ts +1 -0
  17. package/dist/module-architecture.js +253 -127
  18. package/dist/modules/commerce/0.6.0.tar.gz +0 -0
  19. package/dist/modules/docs-blog/0.6.0.tar.gz +0 -0
  20. package/dist/modules/multi-tenant/0.6.0.tar.gz +0 -0
  21. package/dist/modules/scheduling/0.6.0.tar.gz +0 -0
  22. package/dist/modules/signal-collectors/0.6.0.tar.gz +0 -0
  23. package/dist/modules/workspace/0.6.0.tar.gz +0 -0
  24. package/dist/programmatic.js +14 -10
  25. package/dist/surfaces.js +105 -7
  26. package/dist/template-manifest.d.ts +33 -0
  27. package/dist/template-manifest.js +99 -16
  28. package/dist/template-names.d.ts +1 -1
  29. package/dist/template-names.js +1 -0
  30. package/package.json +3 -2
  31. package/site-templates/marketing-site/DESIGN.md +148 -0
  32. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +22 -2
  33. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +9 -2
  34. package/site-templates/marketing-site/ailk-template.json +1 -1
  35. package/site-templates/marketing-site-free/DESIGN.md +148 -0
  36. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +2 -2
  37. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +1 -0
  38. package/site-templates/minimalism-site/DESIGN.md +148 -0
  39. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +10 -2
  40. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +47 -35
  41. package/site-templates/minimalism-site/ailk-template.json +2 -1
  42. package/site-templates/minimalism-site/assets/wordmark-placeholder.svg +28 -0
  43. package/site-templates/minimalism-site/content/en/HomePage/home.mdx +49 -19
  44. package/site-templates/saas-product/DESIGN.md +148 -0
  45. package/site-templates/simple-service/DESIGN.md +148 -0
  46. package/site-templates/simple-service/TEMPLATE-MANIFEST.json +240 -0
  47. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +278 -0
  48. package/site-templates/simple-service/ailk-template.json +10 -0
  49. package/site-templates/simple-service/content/en/AboutPage/about.mdx +38 -0
  50. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +67 -0
  51. package/site-templates/simple-service/content/en/FAQPage/faq.mdx +56 -0
  52. package/site-templates/simple-service/content/en/HomePage/home.mdx +85 -0
  53. package/site-templates/simple-service/content/en/Service/landscape-design.mdx +40 -0
  54. package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +40 -0
  55. package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +40 -0
  56. package/site-templates/simple-service/content/en/ServicesList/services.mdx +72 -0
  57. package/site-templates/simple-service/provenance.json +4 -0
  58. package/templates/.env.example +72 -7
  59. package/templates/CLAUDE.md +2 -3
  60. package/templates/DESIGN.md +155 -0
  61. package/templates/README.md +42 -8
  62. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +8 -0
  63. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +12 -0
  64. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +12 -0
  65. package/templates/apps/api/.env.example +13 -0
  66. package/templates/apps/api/CLAUDE.md +26 -2
  67. package/templates/apps/api/jest.config.cjs +3 -0
  68. package/templates/apps/api/package.json +1 -0
  69. package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +407 -0
  70. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +83 -7
  71. package/templates/apps/api/src/__tests__/server.test.ts +471 -0
  72. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +45 -0
  73. package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +2 -0
  74. package/templates/apps/api/src/config/index.ts +62 -0
  75. package/templates/apps/api/src/config/modules.ts +7 -3
  76. package/templates/apps/api/src/lib/__mocks__/prisma.ts +238 -2
  77. package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +143 -0
  78. package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +23 -0
  79. package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +125 -0
  80. package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +20 -0
  81. package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +317 -0
  82. package/templates/apps/api/src/lib/__tests__/site.test.ts +238 -0
  83. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +75 -0
  84. package/templates/apps/api/src/lib/auth-email-budget.ts +160 -0
  85. package/templates/apps/api/src/lib/content-adapter.ts +57 -31
  86. package/templates/apps/api/src/lib/deliverable-asset-store.ts +76 -0
  87. package/templates/apps/api/src/lib/error-serializer.ts +23 -3
  88. package/templates/apps/api/src/lib/outbound-http.ts +7 -2
  89. package/templates/apps/api/src/lib/require-commerce-claim.ts +250 -0
  90. package/templates/apps/api/src/lib/site.ts +208 -9
  91. package/templates/apps/api/src/lib/stripe.ts +66 -0
  92. package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +182 -0
  93. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +42 -0
  94. package/templates/apps/api/src/middleware/rate-limit.ts +7 -1
  95. package/templates/apps/api/src/openapi/spec.ts +543 -2
  96. package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +48 -0
  97. package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +309 -0
  98. package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +123 -0
  99. package/templates/apps/api/src/routes/auth/transactional-email.ts +273 -0
  100. package/templates/apps/api/src/routes/auth/user.ts +61 -0
  101. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +434 -0
  102. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +888 -0
  103. package/templates/apps/api/src/routes/checkout/index.ts +34 -0
  104. package/templates/apps/api/src/routes/checkout/session.ts +139 -0
  105. package/templates/apps/api/src/routes/checkout/sessions.ts +197 -0
  106. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +554 -0
  107. package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +108 -0
  108. package/templates/apps/api/src/routes/content/__tests__/search.test.ts +450 -0
  109. package/templates/apps/api/src/routes/content/get-page.ts +94 -12
  110. package/templates/apps/api/src/routes/content/index.ts +13 -73
  111. package/templates/apps/api/src/routes/content/list-pages.ts +15 -3
  112. package/templates/apps/api/src/routes/content/rate-limit.ts +15 -6
  113. package/templates/apps/api/src/routes/content/search.ts +129 -0
  114. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +447 -31
  115. package/templates/apps/api/src/routes/leads/index.ts +100 -13
  116. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +51 -0
  117. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +351 -0
  118. package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +51 -0
  119. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -2
  120. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +4 -1
  121. package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +58 -0
  122. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +107 -6
  123. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +87 -2
  124. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +51 -0
  125. package/templates/apps/api/src/routes/project-listings/start.ts +5 -1
  126. package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +686 -0
  127. package/templates/apps/api/src/routes/requests/confirm.ts +601 -0
  128. package/templates/apps/api/src/routes/requests/index.ts +54 -0
  129. package/templates/apps/api/src/routes/webhooks/README.md +265 -0
  130. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +749 -0
  131. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +330 -0
  132. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +262 -0
  133. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +285 -0
  134. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +213 -0
  135. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +301 -0
  136. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +648 -0
  137. package/templates/apps/api/src/routes/webhooks/index.ts +89 -0
  138. package/templates/apps/api/src/routes/webhooks/stripe.ts +1399 -0
  139. package/templates/apps/api/src/server.ts +439 -49
  140. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +39 -0
  141. package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +217 -40
  142. package/templates/apps/api/src/services/__tests__/audit-log.test.ts +620 -0
  143. package/templates/apps/api/src/services/__tests__/consent.test.ts +2 -0
  144. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +300 -5
  145. package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +20 -0
  146. package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +368 -0
  147. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +160 -0
  148. package/templates/apps/api/src/services/__tests__/listing-config.test.ts +166 -0
  149. package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +154 -0
  150. package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +137 -0
  151. package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +444 -0
  152. package/templates/apps/api/src/services/__tests__/revalidation.test.ts +238 -0
  153. package/templates/apps/api/src/services/__tests__/verified-email.test.ts +78 -0
  154. package/templates/apps/api/src/services/abandon-email.ts +8 -2
  155. package/templates/apps/api/src/services/aeo-score.ts +49 -12
  156. package/templates/apps/api/src/services/audit-log.ts +668 -0
  157. package/templates/apps/api/src/services/consent.ts +18 -0
  158. package/templates/apps/api/src/services/default-tenant.ts +70 -31
  159. package/templates/apps/api/src/services/deliverable-fulfillment.ts +141 -13
  160. package/templates/apps/api/src/services/dunning-policy.ts +294 -0
  161. package/templates/apps/api/src/services/entitlements.ts +80 -0
  162. package/templates/apps/api/src/services/lead-promotion.ts +13 -14
  163. package/templates/apps/api/src/services/lead-routing.ts +161 -2
  164. package/templates/apps/api/src/services/leads-inbox.ts +23 -2
  165. package/templates/apps/api/src/services/listing-config.ts +27 -9
  166. package/templates/apps/api/src/services/project-listings.ts +201 -6
  167. package/templates/apps/api/src/services/public-site-resolver.ts +106 -0
  168. package/templates/apps/api/src/services/request-confirm.ts +646 -0
  169. package/templates/apps/api/src/services/revalidation.ts +173 -0
  170. package/templates/apps/api/src/services/verified-email.ts +71 -0
  171. package/templates/apps/api/src/vercel-handler.ts +75 -35
  172. package/templates/apps/mcp/.env.example +17 -0
  173. package/templates/apps/mcp/CLAUDE.md +57 -24
  174. package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +6 -0
  175. package/templates/apps/mcp/__tests__/audit.test.ts +137 -8
  176. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +79 -36
  177. package/templates/apps/mcp/__tests__/get_offerings.test.ts +111 -2
  178. package/templates/apps/mcp/__tests__/get_pricing.test.ts +82 -2
  179. package/templates/apps/mcp/__tests__/http-transport.test.ts +541 -2
  180. package/templates/apps/mcp/__tests__/index.test.ts +39 -0
  181. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +143 -0
  182. package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +111 -0
  183. package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +65 -0
  184. package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +188 -0
  185. package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +165 -0
  186. package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +124 -0
  187. package/templates/apps/mcp/__tests__/list_pages.test.ts +116 -0
  188. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +7 -1
  189. package/templates/apps/mcp/__tests__/parity.test.ts +10 -7
  190. package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +89 -22
  191. package/templates/apps/mcp/__tests__/read_content.test.ts +322 -1
  192. package/templates/apps/mcp/__tests__/search_content.test.ts +105 -2
  193. package/templates/apps/mcp/__tests__/server.test.ts +67 -14
  194. package/templates/apps/mcp/__tests__/start_purchase.test.ts +252 -0
  195. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +300 -123
  196. package/templates/apps/mcp/jest.config.cjs +13 -0
  197. package/templates/apps/mcp/package.json +2 -0
  198. package/templates/apps/mcp/src/abuse/index.ts +159 -24
  199. package/templates/apps/mcp/src/config/modules.ts +10 -5
  200. package/templates/apps/mcp/src/http-transport.ts +102 -29
  201. package/templates/apps/mcp/src/index.ts +73 -11
  202. package/templates/apps/mcp/src/lib/audit-single-page.ts +90 -3
  203. package/templates/apps/mcp/src/lib/commerce-claim.ts +94 -0
  204. package/templates/apps/mcp/src/lib/content-adapter.ts +113 -0
  205. package/templates/apps/mcp/src/lib/empty-content-adapter.ts +139 -0
  206. package/templates/apps/mcp/src/lib/prisma.ts +39 -0
  207. package/templates/apps/mcp/src/lib/public-workspace.ts +133 -0
  208. package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +59 -0
  209. package/templates/apps/mcp/src/server.ts +59 -35
  210. package/templates/apps/mcp/src/tools/audit.ts +26 -2
  211. package/templates/apps/mcp/src/tools/get_business_hours.ts +4 -3
  212. package/templates/apps/mcp/src/tools/get_faq.ts +4 -3
  213. package/templates/apps/mcp/src/tools/get_locations.ts +5 -3
  214. package/templates/apps/mcp/src/tools/get_offerings.ts +16 -4
  215. package/templates/apps/mcp/src/tools/get_pricing.ts +25 -6
  216. package/templates/apps/mcp/src/tools/get_site_info.ts +4 -3
  217. package/templates/apps/mcp/src/tools/index.ts +6 -4
  218. package/templates/apps/mcp/src/tools/list_pages.ts +4 -3
  219. package/templates/apps/mcp/src/tools/purchase.ts +128 -45
  220. package/templates/apps/mcp/src/tools/read_content.ts +20 -6
  221. package/templates/apps/mcp/src/tools/search_content.ts +11 -4
  222. package/templates/apps/web/.env.example +71 -5
  223. package/templates/apps/web/CLAUDE.md +44 -0
  224. package/templates/apps/web/PRODUCT.md +86 -0
  225. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +18 -2
  226. package/templates/apps/web/app/.well-known/mcp.json/route.ts +11 -3
  227. package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +282 -0
  228. package/templates/apps/web/app/[locale]/(auth)/__tests__/password-reset.test.tsx +515 -0
  229. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +89 -0
  230. package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +109 -0
  231. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +92 -0
  232. package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +135 -0
  233. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +109 -0
  234. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/page.test.tsx +48 -2
  235. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/page.tsx +20 -6
  236. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +125 -0
  237. package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +96 -0
  238. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +101 -0
  239. package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +102 -0
  240. package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +87 -0
  241. package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +20 -10
  242. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +84 -0
  243. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +58 -0
  244. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +63 -8
  245. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +18 -1
  246. package/templates/apps/web/app/[locale]/cancellation/page.tsx +44 -0
  247. package/templates/apps/web/app/[locale]/layout.tsx +133 -87
  248. package/templates/apps/web/app/[locale]/purchase/__tests__/cancel.test.tsx +162 -0
  249. package/templates/apps/web/app/[locale]/purchase/__tests__/module-scope.test.ts +68 -0
  250. package/templates/apps/web/app/[locale]/purchase/__tests__/success.test.tsx +361 -0
  251. package/templates/apps/web/app/[locale]/purchase/cancel/page.tsx +115 -0
  252. package/templates/apps/web/app/[locale]/purchase/success/page.tsx +307 -0
  253. package/templates/apps/web/app/[locale]/returns/page.tsx +44 -0
  254. package/templates/apps/web/app/[locale]/shipping/page.tsx +44 -0
  255. package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +670 -0
  256. package/templates/apps/web/app/api/revalidate/route.ts +194 -0
  257. package/templates/apps/web/auth.ts +407 -8
  258. package/templates/apps/web/jest.config.cjs +8 -4
  259. package/templates/apps/web/lib/__tests__/auth-email.test.ts +214 -0
  260. package/templates/apps/web/lib/__tests__/auth-routes.test.ts +74 -0
  261. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +72 -0
  262. package/templates/apps/web/lib/__tests__/site-theme.test.ts +135 -20
  263. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +18 -1
  264. package/templates/apps/web/lib/auth-email.ts +318 -0
  265. package/templates/apps/web/lib/auth-env.ts +85 -0
  266. package/templates/apps/web/lib/auth-routes.ts +55 -0
  267. package/templates/apps/web/lib/meeting-slugs.ts +53 -0
  268. package/templates/apps/web/lib/site-brand.tsx +4 -8
  269. package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
  270. package/templates/apps/web/messages/en.json +187 -0
  271. package/templates/apps/web/middleware.ts +1 -1
  272. package/templates/apps/web/package.json +2 -1
  273. package/templates/apps/web/src/lib/commerce-claim.ts +33 -0
  274. package/templates/apps/web/src/lib/commerce-sections.ts +93 -0
  275. package/templates/content/_site.mdx +18 -3
  276. package/templates/content/en/CancellationPolicyPage/cancellation.mdx +108 -0
  277. package/templates/content/en/ReturnsPolicyPage/returns.mdx +108 -0
  278. package/templates/content/en/ShippingPolicyPage/shipping.mdx +108 -0
  279. package/templates/database/CHANGELOG.md +149 -0
  280. package/templates/database/README.md +10 -3
  281. package/templates/database/__tests__/appointment.db.test.ts +106 -0
  282. package/templates/database/__tests__/artifact-content-store.db.test.ts +316 -0
  283. package/templates/database/__tests__/composite-fence.db.test.ts +206 -0
  284. package/templates/database/__tests__/fence-fan-out.db.test.ts +711 -0
  285. package/templates/database/__tests__/fixtures/appointment-create-probe.ts +79 -0
  286. package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +100 -0
  287. package/templates/database/__tests__/flow-workspace-key.db.test.ts +263 -0
  288. package/templates/database/__tests__/prefixed-ids.db.test.ts +85 -9
  289. package/templates/database/__tests__/project-listing-consent-at.db.test.ts +111 -0
  290. package/templates/database/__tests__/tenancy.test.ts +141 -0
  291. package/templates/database/config/schema.prisma +79 -2
  292. package/templates/database/content/schema.prisma +87 -11
  293. package/templates/database/inbox/schema.prisma +452 -9
  294. package/templates/database/jest.config.cjs +5 -3
  295. package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +15 -0
  296. package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +158 -0
  297. package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +40 -0
  298. package/templates/database/migrations/20260918120000_add_appointment/migration.sql +67 -0
  299. package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +70 -0
  300. package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +181 -0
  301. package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +172 -0
  302. package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +41 -0
  303. package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +185 -0
  304. package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +70 -0
  305. package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +57 -0
  306. package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +68 -0
  307. package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +1067 -0
  308. package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +210 -0
  309. package/templates/database/package.json +3 -2
  310. package/templates/database/tenancy.ts +86 -1
  311. package/templates/database/tsconfig.json +1 -0
  312. package/versions.json +34 -34
  313. package/templates/apps/api/src/routes/content/__tests__/create.test.ts +0 -253
  314. package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +0 -215
  315. package/templates/apps/api/src/routes/content/__tests__/update.test.ts +0 -266
  316. package/templates/apps/api/src/routes/content/create.ts +0 -115
  317. package/templates/apps/api/src/routes/content/delete.ts +0 -82
  318. package/templates/apps/api/src/routes/content/update.ts +0 -112
  319. package/templates/apps/mcp/__tests__/create_page.test.ts +0 -202
  320. package/templates/apps/mcp/__tests__/delete_page.test.ts +0 -177
  321. package/templates/apps/mcp/__tests__/update_page.test.ts +0 -196
  322. package/templates/apps/mcp/src/tools/create_page.ts +0 -96
  323. package/templates/apps/mcp/src/tools/delete_page.ts +0 -74
  324. package/templates/apps/mcp/src/tools/update_page.ts +0 -92
  325. package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +0 -123
  326. package/templates/apps/web/public/logo-dark.svg +0 -21
  327. package/templates/apps/web/public/logo-light.svg +0 -21
  328. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/next-path.test.ts +0 -0
  329. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/next-path.ts +0 -0
@@ -307,6 +307,45 @@ describe("runAbandonEmailSweep", () => {
307
307
  expect(call[0].fromAddress).toBe("owner-noreply@example.com");
308
308
  });
309
309
 
310
+ // ── #4740 — an empty-string override is an env gap, not a sender ─────────
311
+
312
+ it("falls back to AILK_LEAD_NOTIFICATION_FROM when AILK_ABANDON_EMAIL_FROM is set to an empty string", async () => {
313
+ process.env.AILK_ABANDON_EMAIL_FROM = "";
314
+ process.env.AILK_LEAD_NOTIFICATION_FROM = "owner-noreply@example.com";
315
+ (prisma.flowDefinition.findMany as jest.Mock).mockResolvedValue([
316
+ flowDefRow(),
317
+ ]);
318
+ (prisma.flowSession.findMany as jest.Mock).mockResolvedValue([
319
+ sessionRow(),
320
+ ]);
321
+ (prisma.flowSession.update as jest.Mock).mockResolvedValue({});
322
+
323
+ await runAbandonEmailSweep({ now: NOW });
324
+
325
+ const [call] = mockSendAbandonEmail.mock.calls;
326
+ expect(call[0].fromAddress).toBe("owner-noreply@example.com");
327
+ });
328
+
329
+ it("leaves an enabled candidate UNSTAMPED when BOTH sender env vars are empty strings", async () => {
330
+ process.env.AILK_ABANDON_EMAIL_FROM = "";
331
+ process.env.AILK_LEAD_NOTIFICATION_FROM = "";
332
+ (prisma.flowDefinition.findMany as jest.Mock).mockResolvedValue([
333
+ flowDefRow(),
334
+ ]);
335
+ (prisma.flowSession.findMany as jest.Mock).mockResolvedValue([
336
+ sessionRow(),
337
+ ]);
338
+
339
+ const result = await runAbandonEmailSweep({ now: NOW });
340
+
341
+ expect(mockSendAbandonEmail).not.toHaveBeenCalled();
342
+ expect(prisma.flowSession.update).not.toHaveBeenCalled();
343
+ expect(result).toMatchObject({
344
+ ok: true,
345
+ summary: { selected: 1, sent: 0, dark: 0, failed: 0, skipped: 1 },
346
+ });
347
+ });
348
+
310
349
  // ── AC3 — locale resolution ──────────────────────────────────────────────
311
350
 
312
351
  it("resolves copy at the session's persisted locale", async () => {
@@ -5,6 +5,7 @@
5
5
  * fixture loading (AC1, AC3-AC7).
6
6
  */
7
7
  import {
8
+ AEO_SCORE_EXCLUDED_READINESS_RULES,
8
9
  AEO_SCORE_RULES,
9
10
  AEO_DIMENSION_SEVERITY,
10
11
  AEO_SCORE_DEFAULT_BUDGET_MS,
@@ -14,11 +15,17 @@ import {
14
15
  scoreUrl,
15
16
  loadExampleScore,
16
17
  } from "../aeo-score";
17
- import { aeoScoreResultSchema } from "@working-theory/validation";
18
+ import {
19
+ aeoDimensionSchema,
20
+ aeoScoreResultSchema,
21
+ } from "@working-theory/validation";
18
22
  import type { CrawlResult } from "@working-theory/aeo/fetch";
19
23
 
20
24
  /** Typed as the platform signature so it assigns to `ScoreUrlDeps.fetchImpl` with no cast. */
21
- type FetchStub = jest.Mock<Promise<Response>, [RequestInfo | URL, RequestInit?]>;
25
+ type FetchStub = jest.Mock<
26
+ Promise<Response>,
27
+ [RequestInfo | URL, RequestInit?]
28
+ >;
22
29
 
23
30
  import expectedResult from "../../../../../packages/aeo/__tests__/fixtures/example-score/result.json";
24
31
 
@@ -28,7 +35,13 @@ function page(
28
35
  expectedType: string,
29
36
  jsonLd: unknown[],
30
37
  ): CrawlResult["pages"][number] {
31
- return { path, type, expectedType, url: `https://example.com${path}`, jsonLd };
38
+ return {
39
+ path,
40
+ type,
41
+ expectedType,
42
+ url: `https://example.com${path}`,
43
+ jsonLd,
44
+ };
32
45
  }
33
46
 
34
47
  /**
@@ -59,7 +72,9 @@ describe("AEO_SCORE_RULES", () => {
59
72
  ]);
60
73
  expect(AEO_SCORE_RULES.map((r) => r.id)).not.toContain("always-passes");
61
74
  expect(AEO_SCORE_RULES.map((r) => r.id)).not.toContain("always-fails");
62
- expect(AEO_SCORE_RULES.map((r) => r.id)).not.toContain("schema-correctness");
75
+ expect(AEO_SCORE_RULES.map((r) => r.id)).not.toContain(
76
+ "schema-correctness",
77
+ );
63
78
  });
64
79
 
65
80
  it("matches the prospect-audit-site severity table verbatim", () => {
@@ -83,22 +98,32 @@ describe("resolveScoreBudgetMs", () => {
83
98
 
84
99
  it("falls back to the default and logs when above the 15000ms cap", () => {
85
100
  const warn = jest.fn();
86
- expect(resolveScoreBudgetMs("20000", { warn })).toBe(AEO_SCORE_DEFAULT_BUDGET_MS);
101
+ expect(resolveScoreBudgetMs("20000", { warn })).toBe(
102
+ AEO_SCORE_DEFAULT_BUDGET_MS,
103
+ );
87
104
  expect(warn).toHaveBeenCalledTimes(1);
88
105
  expect(warn.mock.calls[0]?.[0]).toContain(String(AEO_SCORE_MAX_BUDGET_MS));
89
106
  });
90
107
 
91
108
  it("falls back silently on an unparseable/non-positive value", () => {
92
109
  const warn = jest.fn();
93
- expect(resolveScoreBudgetMs("not-a-number", { warn })).toBe(AEO_SCORE_DEFAULT_BUDGET_MS);
94
- expect(resolveScoreBudgetMs("-5", { warn })).toBe(AEO_SCORE_DEFAULT_BUDGET_MS);
110
+ expect(resolveScoreBudgetMs("not-a-number", { warn })).toBe(
111
+ AEO_SCORE_DEFAULT_BUDGET_MS,
112
+ );
113
+ expect(resolveScoreBudgetMs("-5", { warn })).toBe(
114
+ AEO_SCORE_DEFAULT_BUDGET_MS,
115
+ );
95
116
  expect(warn).not.toHaveBeenCalled();
96
117
  });
97
118
  });
98
119
 
99
120
  describe("scoreCrawl", () => {
100
121
  it("returns unavailable/no_pages_fetched for an empty crawl", () => {
101
- const crawl: CrawlResult = { base: "https://example.com", pages: [], skipped: [] };
122
+ const crawl: CrawlResult = {
123
+ base: "https://example.com",
124
+ pages: [],
125
+ skipped: [],
126
+ };
102
127
  const result = scoreCrawl(crawl);
103
128
  expect(result).toEqual({
104
129
  status: "unavailable",
@@ -150,8 +175,18 @@ describe("scoreCrawl", () => {
150
175
  it("sets meta.partial true when a page was skipped for budget_exceeded", () => {
151
176
  const crawl: CrawlResult = {
152
177
  base: "https://example.com",
153
- pages: [page("/", "homepage", "HomePage", [{ "@type": "HomePage", name: "Home" }])],
154
- skipped: [{ path: "/about", url: "https://example.com/about", reason: "budget_exceeded" }],
178
+ pages: [
179
+ page("/", "homepage", "HomePage", [
180
+ { "@type": "HomePage", name: "Home" },
181
+ ]),
182
+ ],
183
+ skipped: [
184
+ {
185
+ path: "/about",
186
+ url: "https://example.com/about",
187
+ reason: "budget_exceeded",
188
+ },
189
+ ],
155
190
  };
156
191
  const result = scoreCrawl(crawl);
157
192
  if (result.status !== "ready") throw new Error("expected ready");
@@ -162,7 +197,11 @@ describe("scoreCrawl", () => {
162
197
  it("result satisfies aeoScoreResultSchema (AC7)", () => {
163
198
  const crawl: CrawlResult = {
164
199
  base: "https://example.com",
165
- pages: [page("/", "homepage", "HomePage", [{ "@type": "HomePage", name: "Home" }])],
200
+ pages: [
201
+ page("/", "homepage", "HomePage", [
202
+ { "@type": "HomePage", name: "Home" },
203
+ ]),
204
+ ],
166
205
  skipped: [],
167
206
  };
168
207
  const result = scoreCrawl(crawl);
@@ -187,12 +226,17 @@ describe("scoreCrawl", () => {
187
226
  it("redacts a raw assertUrlSafe rejection reason to the ssrf_blocked sentinel", () => {
188
227
  const crawl: CrawlResult = {
189
228
  base: "https://example.com",
190
- pages: [page("/", "homepage", "HomePage", [{ "@type": "HomePage", name: "Home" }])],
229
+ pages: [
230
+ page("/", "homepage", "HomePage", [
231
+ { "@type": "HomePage", name: "Home" },
232
+ ]),
233
+ ],
191
234
  skipped: [
192
235
  {
193
236
  path: "/admin",
194
237
  url: "http://169.254.169.254/admin",
195
- reason: "Host 169.254.169.254 resolves to a blocked address: 169.254.169.254",
238
+ reason:
239
+ "Host 169.254.169.254 resolves to a blocked address: 169.254.169.254",
196
240
  },
197
241
  ],
198
242
  };
@@ -201,38 +245,57 @@ describe("scoreCrawl", () => {
201
245
  expect(result.meta.skipped).toHaveLength(1);
202
246
  expect(result.meta.skipped[0]?.reason).toBe("ssrf_blocked");
203
247
  expect(result.meta.skipped[0]?.reason).not.toMatch(/169\.254\.169\.254/);
204
- expect(result.meta.skipped[0]?.reason).not.toMatch(/resolves to a blocked address/i);
248
+ expect(result.meta.skipped[0]?.reason).not.toMatch(
249
+ /resolves to a blocked address/i,
250
+ );
205
251
  });
206
252
 
207
253
  it("(AC-2) logs the raw per-path rejection detail server-side before redacting it", () => {
208
254
  const crawl: CrawlResult = {
209
255
  base: "https://example.com",
210
- pages: [page("/", "homepage", "HomePage", [{ "@type": "HomePage", name: "Home" }])],
256
+ pages: [
257
+ page("/", "homepage", "HomePage", [
258
+ { "@type": "HomePage", name: "Home" },
259
+ ]),
260
+ ],
211
261
  skipped: [
212
262
  {
213
263
  path: "/admin",
214
264
  url: "http://169.254.169.254/admin",
215
- reason: "Host 169.254.169.254 resolves to a blocked address: 169.254.169.254",
265
+ reason:
266
+ "Host 169.254.169.254 resolves to a blocked address: 169.254.169.254",
216
267
  },
217
268
  ],
218
269
  };
219
270
  scoreCrawl(crawl);
220
271
  expect(warnSpy).toHaveBeenCalledTimes(1);
221
- const [message, meta] = warnSpy.mock.calls[0] as [string, Record<string, unknown>];
272
+ const [message, meta] = warnSpy.mock.calls[0] as [
273
+ string,
274
+ Record<string, unknown>,
275
+ ];
222
276
  expect(message).toMatch(/aeo-score/);
223
277
  expect(meta).toMatchObject({
224
278
  path: "/admin",
225
279
  url: "http://169.254.169.254/admin",
226
- reason: "Host 169.254.169.254 resolves to a blocked address: 169.254.169.254",
280
+ reason:
281
+ "Host 169.254.169.254 resolves to a blocked address: 169.254.169.254",
227
282
  });
228
283
  });
229
284
 
230
285
  it("leaves a non-SSRF skip reason (budget_exceeded, an HTTP failure) unchanged", () => {
231
286
  const crawl: CrawlResult = {
232
287
  base: "https://example.com",
233
- pages: [page("/", "homepage", "HomePage", [{ "@type": "HomePage", name: "Home" }])],
288
+ pages: [
289
+ page("/", "homepage", "HomePage", [
290
+ { "@type": "HomePage", name: "Home" },
291
+ ]),
292
+ ],
234
293
  skipped: [
235
- { path: "/about", url: "https://example.com/about", reason: "budget_exceeded" },
294
+ {
295
+ path: "/about",
296
+ url: "https://example.com/about",
297
+ reason: "budget_exceeded",
298
+ },
236
299
  {
237
300
  path: "/blog",
238
301
  url: "https://example.com/blog",
@@ -243,7 +306,11 @@ describe("scoreCrawl", () => {
243
306
  const result = scoreCrawl(crawl);
244
307
  if (result.status !== "ready") throw new Error("expected ready");
245
308
  expect(result.meta.skipped).toEqual([
246
- { path: "/about", url: "https://example.com/about", reason: "budget_exceeded" },
309
+ {
310
+ path: "/about",
311
+ url: "https://example.com/about",
312
+ reason: "budget_exceeded",
313
+ },
247
314
  {
248
315
  path: "/blog",
249
316
  url: "https://example.com/blog",
@@ -272,7 +339,10 @@ describe("scoreCrawl", () => {
272
339
  ),
273
340
  );
274
341
 
275
- const result = await scoreUrl("https://example.com", { fetchImpl, resolver });
342
+ const result = await scoreUrl("https://example.com", {
343
+ fetchImpl,
344
+ resolver,
345
+ });
276
346
 
277
347
  if (result.status !== "ready") {
278
348
  throw new Error(`expected ready, got ${JSON.stringify(result)}`);
@@ -294,14 +364,68 @@ describe("aeoScoreResultSchema (AC7)", () => {
294
364
  status: "ready",
295
365
  score: 50,
296
366
  dimensions: [
297
- { id: "schema-coverage", label: "x", weight: 10, score: 50, severity: "high", finding: null, pagesPassing: 1, pagesTotal: 2 },
298
- { id: "jsonld-validity", label: "x", weight: 10, score: 50, severity: "medium", finding: null, pagesPassing: 1, pagesTotal: 2 },
299
- { id: "entity-markup", label: "x", weight: 1, score: 50, severity: "high", finding: null, pagesPassing: 1, pagesTotal: 2 },
300
- { id: "faq-howto-incentive", label: "x", weight: 0, score: 50, severity: "low", finding: null, pagesPassing: 1, pagesTotal: 2 },
367
+ {
368
+ id: "schema-coverage",
369
+ label: "x",
370
+ weight: 10,
371
+ score: 50,
372
+ severity: "high",
373
+ finding: null,
374
+ pagesPassing: 1,
375
+ pagesTotal: 2,
376
+ },
377
+ {
378
+ id: "jsonld-validity",
379
+ label: "x",
380
+ weight: 10,
381
+ score: 50,
382
+ severity: "medium",
383
+ finding: null,
384
+ pagesPassing: 1,
385
+ pagesTotal: 2,
386
+ },
387
+ {
388
+ id: "entity-markup",
389
+ label: "x",
390
+ weight: 1,
391
+ score: 50,
392
+ severity: "high",
393
+ finding: null,
394
+ pagesPassing: 1,
395
+ pagesTotal: 2,
396
+ },
397
+ {
398
+ id: "faq-howto-incentive",
399
+ label: "x",
400
+ weight: 0,
401
+ score: 50,
402
+ severity: "low",
403
+ finding: null,
404
+ pagesPassing: 1,
405
+ pagesTotal: 2,
406
+ },
301
407
  ],
302
408
  lowestTwo: [
303
- { id: "schema-coverage", label: "x", weight: 10, score: 50, severity: "high", finding: null, pagesPassing: 1, pagesTotal: 2 },
304
- { id: "entity-markup", label: "x", weight: 1, score: 50, severity: "high", finding: null, pagesPassing: 1, pagesTotal: 2 },
409
+ {
410
+ id: "schema-coverage",
411
+ label: "x",
412
+ weight: 10,
413
+ score: 50,
414
+ severity: "high",
415
+ finding: null,
416
+ pagesPassing: 1,
417
+ pagesTotal: 2,
418
+ },
419
+ {
420
+ id: "entity-markup",
421
+ label: "x",
422
+ weight: 1,
423
+ score: 50,
424
+ severity: "high",
425
+ finding: null,
426
+ pagesPassing: 1,
427
+ pagesTotal: 2,
428
+ },
305
429
  ],
306
430
  meta: {
307
431
  auditedAt: new Date().toISOString(),
@@ -314,11 +438,21 @@ describe("aeoScoreResultSchema (AC7)", () => {
314
438
  }),
315
439
  ).not.toThrow();
316
440
 
317
- expect(() => aeoScoreResultSchema.parse({ status: "computing" })).not.toThrow();
441
+ expect(() =>
442
+ aeoScoreResultSchema.parse({ status: "computing" }),
443
+ ).not.toThrow();
318
444
 
319
- for (const reason of ["ssrf_blocked", "no_pages_fetched", "crawl_failed"] as const) {
445
+ for (const reason of [
446
+ "ssrf_blocked",
447
+ "no_pages_fetched",
448
+ "crawl_failed",
449
+ ] as const) {
320
450
  expect(() =>
321
- aeoScoreResultSchema.parse({ status: "unavailable", reason, message: "x" }),
451
+ aeoScoreResultSchema.parse({
452
+ status: "unavailable",
453
+ reason,
454
+ message: "x",
455
+ }),
322
456
  ).not.toThrow();
323
457
  }
324
458
  });
@@ -348,7 +482,9 @@ describe("scoreUrl", () => {
348
482
  it("(AC1) issues the five default paths concurrently — a 2s-delay fetchImpl completes well under 10s", async () => {
349
483
  const fetchImpl: FetchStub = jest.fn(async () => {
350
484
  await new Promise((resolve) => setTimeout(resolve, 2000));
351
- return new Response("<html><script type=\"application/ld+json\">{\"@type\":\"HomePage\",\"name\":\"x\"}</script></html>");
485
+ return new Response(
486
+ '<html><script type="application/ld+json">{"@type":"HomePage","name":"x"}</script></html>',
487
+ );
352
488
  });
353
489
 
354
490
  const t0 = Date.now();
@@ -363,7 +499,9 @@ describe("scoreUrl", () => {
363
499
  }, 10_000);
364
500
 
365
501
  it("(AC1) with budgetMs 500 and a 2s-delay fetchImpl, returns quickly as unavailable/no_pages_fetched", async () => {
366
- const fetchImpl: FetchStub = jest.fn(delayedResponse(2000, "<html></html>"));
502
+ const fetchImpl: FetchStub = jest.fn(
503
+ delayedResponse(2000, "<html></html>"),
504
+ );
367
505
 
368
506
  const t0 = Date.now();
369
507
  const result = await scoreUrl("https://example.com", {
@@ -387,13 +525,15 @@ describe("scoreUrl", () => {
387
525
  it("(AC1) one fast + four slow pages returns ready with meta.partial true and budget_exceeded skips", async () => {
388
526
  const homeResponse = delayedResponse(
389
527
  0,
390
- "<html><script type=\"application/ld+json\">{\"@type\":\"HomePage\",\"name\":\"x\"}</script></html>",
528
+ '<html><script type="application/ld+json">{"@type":"HomePage","name":"x"}</script></html>',
391
529
  );
392
530
  const slowResponse = delayedResponse(5000, "<html></html>");
393
- const fetchImpl: FetchStub = jest.fn((input: RequestInfo | URL, init?: RequestInit) => {
394
- const isHome = String(input).endsWith("example.com/");
395
- return isHome ? homeResponse(input, init) : slowResponse(input, init);
396
- });
531
+ const fetchImpl: FetchStub = jest.fn(
532
+ (input: RequestInfo | URL, init?: RequestInit) => {
533
+ const isHome = String(input).endsWith("example.com/");
534
+ return isHome ? homeResponse(input, init) : slowResponse(input, init);
535
+ },
536
+ );
397
537
 
398
538
  const result = await scoreUrl("https://example.com", {
399
539
  fetchImpl,
@@ -401,10 +541,13 @@ describe("scoreUrl", () => {
401
541
  budgetMs: 800,
402
542
  });
403
543
 
404
- if (result.status !== "ready") throw new Error(`expected ready, got ${JSON.stringify(result)}`);
544
+ if (result.status !== "ready")
545
+ throw new Error(`expected ready, got ${JSON.stringify(result)}`);
405
546
  expect(result.meta.partial).toBe(true);
406
547
  // Spec AC1: one fast + FOUR slow pages — all four abort at the budget.
407
- expect(result.meta.skipped.filter((s) => s.reason === "budget_exceeded").length).toBe(4);
548
+ expect(
549
+ result.meta.skipped.filter((s) => s.reason === "budget_exceeded").length,
550
+ ).toBe(4);
408
551
  expect(result.meta.pagesAudited).toBe(1);
409
552
  }, 10_000);
410
553
 
@@ -473,3 +616,37 @@ describe("loadExampleScore (AC6, D6)", () => {
473
616
  }).toEqual(expectedResult);
474
617
  });
475
618
  });
619
+
620
+ /**
621
+ * #5949 D6/FR11 — the readiness rules are deliberately NOT in this service's
622
+ * rule set. They grade frontmatter and site-set facts, and `buildAuditPayload`
623
+ * produces no frontmatter, so on a crawl input all three would pass vacuously:
624
+ * three always-100 dimensions in a closed four-member public enum.
625
+ *
626
+ * `/v1/aeo/score` has no MCP twin (apps/api/CLAUDE.md § API ↔ MCP parity), so
627
+ * the skip is not a parity divergence — and these assertions are the "stated
628
+ * and tested" half AC-3 asks for, failing loudly if either side drifts.
629
+ */
630
+ describe("readiness-rule exclusion (#5949 D6)", () => {
631
+ it("AEO_SCORE_RULES carries none of the excluded readiness rules", () => {
632
+ const ids = AEO_SCORE_RULES.map((r) => r.id);
633
+ for (const excluded of AEO_SCORE_EXCLUDED_READINESS_RULES) {
634
+ expect(ids).not.toContain(excluded);
635
+ }
636
+ });
637
+
638
+ it("the public dimension enum is still exactly the four content rules", () => {
639
+ expect([...aeoDimensionSchema.shape.id.options].sort()).toEqual([
640
+ "entity-markup",
641
+ "faq-howto-incentive",
642
+ "jsonld-validity",
643
+ "schema-coverage",
644
+ ]);
645
+ });
646
+
647
+ it("AEO_SCORE_RULES' ids are exactly the dimension enum's options", () => {
648
+ expect(AEO_SCORE_RULES.map((r) => r.id).sort()).toEqual(
649
+ [...aeoDimensionSchema.shape.id.options].sort(),
650
+ );
651
+ });
652
+ });