create-ailk 0.21.0 → 0.23.0

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 (342) hide show
  1. package/component-catalog.json +186 -8
  2. package/component-catalog.md +9 -7
  3. package/dist/add-module.js +20 -14
  4. package/dist/add-route.d.ts +55 -1
  5. package/dist/add-route.js +294 -12
  6. package/dist/cli.js +27 -10
  7. package/dist/derive-architecture.d.ts +12 -3
  8. package/dist/derive-architecture.js +20 -8
  9. package/dist/design-package.js +7 -2
  10. package/dist/lib/design-checks.js +4 -2
  11. package/dist/lib/env-catalog.js +5 -5
  12. package/dist/lib/npm-auth.d.ts +9 -0
  13. package/dist/lib/npm-auth.js +48 -0
  14. package/dist/lib/scaffold-render-sweep.d.ts +3 -2
  15. package/dist/lib/scaffold-render-sweep.js +5 -8
  16. package/dist/module-architecture.js +99 -9
  17. package/dist/paid-sections.d.ts +21 -2
  18. package/dist/paid-sections.js +69 -7
  19. package/dist/programmatic.js +19 -5
  20. package/dist/seed-flows.d.ts +4 -1
  21. package/dist/seed-flows.js +50 -4
  22. package/dist/site-chrome.d.ts +8 -0
  23. package/dist/site-chrome.js +17 -1
  24. package/dist/spoke-checkout.d.ts +65 -41
  25. package/dist/spoke-checkout.js +208 -430
  26. package/dist/starter-grid.d.ts +0 -3
  27. package/dist/starter-grid.js +24 -14
  28. package/dist/surfaces.js +52 -26
  29. package/dist/sync-routes.d.ts +38 -1
  30. package/dist/sync-routes.js +187 -13
  31. package/dist/template-record.d.ts +4 -3
  32. package/dist/template-record.js +8 -4
  33. package/dist/topology.js +25 -1
  34. package/dist/update.js +15 -0
  35. package/package.json +3 -3
  36. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +4 -49
  37. package/site-templates/_base/content/en/Pricing/pricing.mdx +3 -21
  38. package/site-templates/designs/minimalism/DESIGN.md +1 -1
  39. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +23 -20
  40. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +28 -14
  41. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +15 -39
  42. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +38 -34
  43. package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +6 -43
  44. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +14 -31
  45. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -25
  46. package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  47. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +14 -31
  48. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -25
  49. package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  50. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +14 -31
  51. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +26 -25
  52. package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  53. package/templates/.env.example +9 -7
  54. package/templates/.github/workflows/ci.yml +13 -0
  55. package/templates/README.md +1 -0
  56. package/templates/_gitignore +4 -0
  57. package/templates/apps/api/.env.example +27 -0
  58. package/templates/apps/api/CLAUDE.md +87 -17
  59. package/templates/apps/api/package.json +1 -0
  60. package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +2 -0
  61. package/templates/apps/api/src/bin/stripe-connections-rekey.ts +45 -0
  62. package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +185 -0
  63. package/templates/apps/api/src/config/index.ts +109 -0
  64. package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -0
  65. package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +19 -0
  66. package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +149 -0
  67. package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +116 -0
  68. package/templates/apps/api/src/lib/consent-store.ts +28 -0
  69. package/templates/apps/api/src/lib/error-serializer.ts +74 -7
  70. package/templates/apps/api/src/lib/flow-completion-store.ts +136 -0
  71. package/templates/apps/api/src/lib/hub-sign-in-store.ts +38 -2
  72. package/templates/apps/api/src/lib/proposal-paid-store.ts +21 -4
  73. package/templates/apps/api/src/lib/stripe.ts +74 -4
  74. package/templates/apps/api/src/lib/webhook-idempotency.ts +66 -2
  75. package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +14 -3
  76. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +218 -0
  77. package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +174 -0
  78. package/templates/apps/api/src/middleware/auth.ts +13 -6
  79. package/templates/apps/api/src/middleware/rate-limit.ts +226 -4
  80. package/templates/apps/api/src/openapi/spec.ts +33 -3
  81. package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +49 -0
  82. package/templates/apps/api/src/routes/auth/hub-sign-in.ts +41 -0
  83. package/templates/apps/api/src/routes/billing/plan-checkout.ts +325 -0
  84. package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +11 -0
  85. package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +1300 -0
  86. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +11 -0
  87. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +49 -0
  88. package/templates/apps/api/src/routes/checkout/session.ts +57 -25
  89. package/templates/apps/api/src/routes/checkout/sessions.ts +232 -82
  90. package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +386 -0
  91. package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +160 -0
  92. package/templates/apps/api/src/routes/consent/account.ts +268 -0
  93. package/templates/apps/api/src/routes/consent/index.ts +15 -7
  94. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +36 -0
  95. package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +731 -0
  96. package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +753 -0
  97. package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +762 -0
  98. package/templates/apps/api/src/routes/content/get-page.ts +4 -0
  99. package/templates/apps/api/src/routes/content/index.ts +16 -2
  100. package/templates/apps/api/src/routes/content/offerings.ts +184 -0
  101. package/templates/apps/api/src/routes/content/pricing.ts +176 -0
  102. package/templates/apps/api/src/routes/content/shown-offerings.ts +229 -0
  103. package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +515 -0
  104. package/templates/apps/api/src/routes/leads/index.ts +164 -7
  105. package/templates/apps/api/src/routes/webhooks/README.md +66 -0
  106. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +649 -0
  107. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +814 -0
  108. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +80 -68
  109. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +5 -5
  110. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +8 -2
  111. package/templates/apps/api/src/routes/webhooks/index.ts +23 -0
  112. package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +384 -0
  113. package/templates/apps/api/src/routes/webhooks/stripe.ts +245 -36
  114. package/templates/apps/api/src/server.ts +163 -46
  115. package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +313 -0
  116. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +51 -2
  117. package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +234 -0
  118. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +1383 -133
  119. package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +389 -0
  120. package/templates/apps/api/src/services/__tests__/person.test.ts +232 -1
  121. package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +444 -0
  122. package/templates/apps/api/src/services/checkout-buyer.ts +100 -4
  123. package/templates/apps/api/src/services/consent.ts +95 -0
  124. package/templates/apps/api/src/services/deliverable-fulfillment.ts +13 -5
  125. package/templates/apps/api/src/services/entitlements.ts +157 -1
  126. package/templates/apps/api/src/services/lead-routing.ts +431 -47
  127. package/templates/apps/api/src/services/magnet-completion.ts +242 -0
  128. package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +295 -0
  129. package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +268 -0
  130. package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +868 -0
  131. package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +416 -0
  132. package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +396 -0
  133. package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +681 -0
  134. package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +231 -0
  135. package/templates/apps/api/src/services/offerings/catalog.ts +308 -0
  136. package/templates/apps/api/src/services/offerings/checkout-authorization.ts +385 -0
  137. package/templates/apps/api/src/services/offerings/checkout-session-record.ts +192 -0
  138. package/templates/apps/api/src/services/offerings/connection.ts +1003 -0
  139. package/templates/apps/api/src/services/offerings/index.ts +614 -0
  140. package/templates/apps/api/src/services/offerings/source.ts +140 -0
  141. package/templates/apps/api/src/services/offerings/store.ts +150 -0
  142. package/templates/apps/api/src/services/person.ts +164 -17
  143. package/templates/apps/api/src/services/plan-offerings.ts +125 -0
  144. package/templates/apps/api/src/services/public-site-resolver.ts +27 -15
  145. package/templates/apps/mcp/CLAUDE.md +22 -14
  146. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +9 -4
  147. package/templates/apps/mcp/__tests__/get_offerings.test.ts +358 -0
  148. package/templates/apps/mcp/__tests__/get_pricing.test.ts +364 -0
  149. package/templates/apps/mcp/__tests__/http-transport.test.ts +4 -4
  150. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +2 -2
  151. package/templates/apps/mcp/__tests__/server.test.ts +13 -11
  152. package/templates/apps/mcp/__tests__/start_purchase.test.ts +208 -12
  153. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +11 -11
  154. package/templates/apps/mcp/src/lib/shown-offerings.ts +105 -0
  155. package/templates/apps/mcp/src/server.ts +5 -0
  156. package/templates/apps/mcp/src/tools/get_offerings.ts +47 -30
  157. package/templates/apps/mcp/src/tools/get_pricing.ts +40 -40
  158. package/templates/apps/mcp/src/tools/purchase.ts +30 -2
  159. package/templates/apps/mcp/src/workspace-server.ts +15 -0
  160. package/templates/apps/web/.env.example +24 -31
  161. package/templates/apps/web/CLAUDE.md +393 -9
  162. package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +6 -2
  163. package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +5 -4
  164. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +5 -1
  165. package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +2 -0
  166. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +3 -14
  167. package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +2 -0
  168. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +5 -1
  169. package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +160 -8
  170. package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/sign-in-card.test.tsx +12 -9
  171. package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +105 -2
  172. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +36 -3
  173. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +47 -1
  174. package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +60 -22
  175. package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +16 -8
  176. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +85 -31
  177. package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +18 -4
  178. package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +3 -1
  179. package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +7 -4
  180. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +227 -207
  181. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout-sign-out.test.tsx +17 -1
  182. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +143 -1
  183. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-menu.test.tsx +4 -0
  184. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +385 -89
  185. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/popups.test.tsx +5 -0
  186. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +44 -5
  187. package/templates/apps/web/app/[locale]/(authed)/account/account-frame.tsx +68 -34
  188. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +0 -8
  189. package/templates/apps/web/app/[locale]/(authed)/account/agreements-actions.ts +39 -151
  190. package/templates/apps/web/app/[locale]/(authed)/account/agreements-gate.ts +61 -0
  191. package/templates/apps/web/app/[locale]/(authed)/account/agreements-prompt.tsx +143 -140
  192. package/templates/apps/web/app/[locale]/(authed)/account/layout.tsx +40 -6
  193. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +68 -54
  194. package/templates/apps/web/app/[locale]/(authed)/account/privacy/page.tsx +5 -1
  195. package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +189 -0
  196. package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +295 -0
  197. package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +120 -0
  198. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +23 -0
  199. package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +319 -0
  200. package/templates/apps/web/app/[locale]/careers/__tests__/careers-hub.test.tsx +172 -0
  201. package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +149 -0
  202. package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +141 -0
  203. package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +424 -0
  204. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +72 -11
  205. package/templates/apps/web/app/[locale]/careers/page.tsx +129 -5
  206. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
  207. package/templates/apps/web/app/[locale]/checkout/__tests__/pages.test.tsx +139 -0
  208. package/templates/apps/web/app/[locale]/checkout/__tests__/thank-you-account.test.tsx +136 -0
  209. package/templates/apps/web/app/[locale]/checkout/thank-you/page.tsx +114 -0
  210. package/templates/apps/web/app/[locale]/checkout/unavailable/page.tsx +51 -0
  211. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +1 -1
  212. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +17 -1
  213. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +79 -4
  214. package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +160 -13
  215. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +466 -36
  216. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +7 -0
  217. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +172 -57
  218. package/templates/apps/web/app/[locale]/contact/intake-script.ts +26 -0
  219. package/templates/apps/web/app/[locale]/contact/page.tsx +99 -60
  220. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +2 -2
  221. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +2 -2
  222. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +341 -4
  223. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.count.test.tsx +43 -0
  224. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.deliverable.test.tsx +200 -0
  225. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.ending.test.tsx +197 -0
  226. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.invite.test.tsx +257 -0
  227. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/account-invite.actions.test.ts +69 -0
  228. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +430 -0
  229. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +21 -9
  230. package/templates/apps/web/app/[locale]/flows/[slug]/account-invite.actions.ts +35 -0
  231. package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +352 -0
  232. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +12 -73
  233. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +10 -31
  234. package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +1 -1
  235. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  236. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +2 -2
  237. package/templates/apps/web/app/[locale]/layout.tsx +55 -4
  238. package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +174 -104
  239. package/templates/apps/web/app/[locale]/newsletter/page.tsx +95 -58
  240. package/templates/apps/web/app/[locale]/page.tsx +13 -1
  241. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +2 -2
  242. package/templates/apps/web/app/[locale]/pricing/__tests__/__snapshots__/unconnected.test.tsx.snap +7 -0
  243. package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +599 -0
  244. package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +216 -0
  245. package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +236 -0
  246. package/templates/apps/web/app/[locale]/pricing/page.tsx +367 -6
  247. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +2 -2
  248. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +2 -2
  249. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +2 -2
  250. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  251. package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +4 -1
  252. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  253. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +1 -1
  254. package/templates/apps/web/app/[locale]/thanks/[slug]/AccountInviteLink.tsx +89 -0
  255. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/AccountInviteLink.test.tsx +104 -0
  256. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +38 -0
  257. package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +9 -0
  258. package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +17 -0
  259. package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +62 -3
  260. package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +70 -18
  261. package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +30 -9
  262. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  263. package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +114 -0
  264. package/templates/apps/web/app/api/auth/hub/authorize/route.ts +20 -3
  265. package/templates/apps/web/app/api/hub-session/__tests__/routes.test.ts +44 -1
  266. package/templates/apps/web/app/api/hub-session/sign-in/route.ts +22 -3
  267. package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +316 -0
  268. package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +810 -0
  269. package/templates/apps/web/auth.ts +28 -2
  270. package/templates/apps/web/lib/__tests__/account-invite.test.ts +63 -0
  271. package/templates/apps/web/lib/__tests__/auth-routes.test.ts +27 -1
  272. package/templates/apps/web/lib/__tests__/consent-policies.test.ts +23 -0
  273. package/templates/apps/web/lib/__tests__/deck-page.test.ts +7 -1
  274. package/templates/apps/web/lib/__tests__/in-page-magnet.test.ts +265 -0
  275. package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +57 -0
  276. package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +293 -0
  277. package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +601 -0
  278. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +101 -25
  279. package/templates/apps/web/lib/account-invite.ts +37 -0
  280. package/templates/apps/web/lib/auth-routes.ts +36 -2
  281. package/templates/apps/web/lib/checkout-config.ts +16 -0
  282. package/templates/apps/web/lib/consent-policies.ts +22 -0
  283. package/templates/apps/web/lib/deck-page.ts +7 -0
  284. package/templates/apps/web/lib/deliverable-grant.ts +18 -0
  285. package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +111 -0
  286. package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +88 -32
  287. package/templates/apps/web/lib/in-page-magnet.ts +416 -0
  288. package/templates/apps/web/lib/magnet-routes.ts +42 -0
  289. package/templates/apps/web/lib/modules.generated.ts +9 -0
  290. package/templates/apps/web/lib/newsletter.actions.ts +202 -0
  291. package/templates/apps/web/lib/offerings-pricing.ts +326 -0
  292. package/templates/apps/web/lib/posting-title.ts +17 -0
  293. package/templates/apps/web/lib/prisma-for-auth.ts +8 -0
  294. package/templates/apps/web/lib/sign-up-agreements.ts +449 -0
  295. package/templates/apps/web/lib/site-chrome.ts +2 -0
  296. package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
  297. package/templates/apps/web/messages/en.json +71 -20
  298. package/templates/apps/web/middleware.ts +12 -2
  299. package/templates/apps/web/package.json +1 -0
  300. package/templates/apps/web/security-headers.config.ts +17 -0
  301. package/templates/apps/web/src/lib/__tests__/lead-write.test.ts +187 -0
  302. package/templates/apps/web/src/lib/__tests__/rate-limit.test.ts +67 -0
  303. package/templates/apps/web/src/lib/lead-write.ts +90 -0
  304. package/templates/apps/web/src/lib/rate-limit.ts +79 -0
  305. package/templates/content/en/BlogList/blog.mdx +4 -71
  306. package/templates/content/en/BookCall/book-a-call.mdx +1 -41
  307. package/templates/content/en/BookCall/onsite-estimate.mdx +1 -17
  308. package/templates/content/en/BookCall/onsite-service.mdx +1 -17
  309. package/templates/content/en/BookCall/start-project.mdx +1 -17
  310. package/templates/content/en/ContactPage/contact.mdx +6 -63
  311. package/templates/content/en/HomePage/home.mdx +3 -21
  312. package/templates/database/CHANGELOG.md +69 -0
  313. package/templates/database/__tests__/consent-source-checkout-migration.test.ts +79 -0
  314. package/templates/database/__tests__/flow-completion-migration.test.ts +115 -0
  315. package/templates/database/__tests__/flow-completion.db.test.ts +136 -0
  316. package/templates/database/__tests__/in-page-magnets-migration.test.ts +161 -0
  317. package/templates/database/__tests__/offerings-checkout-session.db.test.ts +270 -0
  318. package/templates/database/__tests__/offerings-migration.test.ts +197 -0
  319. package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +86 -0
  320. package/templates/database/__tests__/offerings.db.test.ts +303 -0
  321. package/templates/database/__tests__/seed.test.ts +64 -1
  322. package/templates/database/auth/schema.prisma +4 -0
  323. package/templates/database/config/schema.prisma +91 -0
  324. package/templates/database/inbox/schema.prisma +17 -2
  325. package/templates/database/migrations/20261018120000_flow_completion/migration.sql +52 -0
  326. package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +27 -0
  327. package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +146 -0
  328. package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +77 -0
  329. package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +45 -0
  330. package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +47 -0
  331. package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +16 -0
  332. package/templates/database/package.json +1 -1
  333. package/templates/database/seed.ts +62 -3
  334. package/versions.json +35 -35
  335. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -199
  336. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up.actions.ts +0 -125
  337. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +0 -93
  338. package/templates/apps/web/app/[locale]/downloads/page.tsx +0 -37
  339. package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +0 -139
  340. package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +0 -93
  341. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +0 -120
  342. package/templates/apps/web/lib/meeting-slugs.ts +0 -63
@@ -19,12 +19,50 @@ import {
19
19
  import { tmpdir } from "node:os";
20
20
  import { join, resolve } from "node:path";
21
21
 
22
+ import { apiClient } from "@working-theory/api-client";
23
+ import { getPricingTiers } from "@working-theory/content-adapters";
24
+ import * as contentAdapters from "@working-theory/content-adapters";
25
+ import { getMcpToolCatalog } from "@working-theory/metadata";
26
+ import {
27
+ shownOfferingsAnswerSchema,
28
+ type ShownOfferingsAnswer,
29
+ } from "@working-theory/validation";
30
+
31
+ import {
32
+ __setMcpCommerceClaimForTest,
33
+ resetMcpCommerceClaim,
34
+ } from "../src/lib/commerce-claim.js";
35
+ import { getMcpContentAdapter } from "../src/lib/content-adapter.js";
36
+ import { MCP_SHOWN_SET_BUDGET_MS } from "../src/lib/shown-offerings.js";
22
37
  import {
23
38
  ailkGetPricing,
24
39
  getPricingInputSchema,
25
40
  getPricingOutputSchema,
26
41
  } from "../src/tools/get_pricing.js";
27
42
 
43
+ /** While set, the shown-set mapping throws (#8933 AC-3) — a plain switch, not a mock. */
44
+ const mockMappingThrows = { on: false };
45
+
46
+ // The real package, with the shown-set mapping behind a switch a case can
47
+ // throw through. Not a stand-in: every other export is the real one.
48
+ jest.mock("@working-theory/content-adapters", () => {
49
+ const actual = jest.requireActual<
50
+ typeof import("@working-theory/content-adapters")
51
+ >("@working-theory/content-adapters");
52
+ return {
53
+ __esModule: true,
54
+ ...actual,
55
+ pricingTiersFromShownSet: (
56
+ ...args: Parameters<typeof actual.pricingTiersFromShownSet>
57
+ ) => {
58
+ if (mockMappingThrows.on) {
59
+ throw new Error("mapping refused Growth: For a growing practice.");
60
+ }
61
+ return actual.pricingTiersFromShownSet(...args);
62
+ },
63
+ };
64
+ });
65
+
28
66
  const REPO_ROOT = resolve(__dirname, "../../..");
29
67
  const CONTENT_ROOT = `${REPO_ROOT}/content`;
30
68
 
@@ -157,4 +195,330 @@ describe("ailkGetPricing — offering-field fallback (#5838)", () => {
157
195
  const result = await ailkGetPricing({ slug: "free" }, root);
158
196
  expect(result).toBeNull();
159
197
  });
198
+
199
+ /**
200
+ * #7178 D5 — the tool answers through the shared seam its HTTP twin
201
+ * (`GET /v1/content/pricing`) calls. The route's own suite pins
202
+ * route ≡ `getPricingTiers`; this pins tool ≡ `getPricingTiers`.
203
+ */
204
+ it.each(["priced", "free"])(
205
+ "deep-equals getPricingTiers for slug %s — the seam its twin route shares (#7178)",
206
+ async (slug) => {
207
+ const result = await ailkGetPricing({ slug }, root);
208
+ const expected = await getPricingTiers(
209
+ getMcpContentAdapter({ contentRoot: root, locale: "en" }),
210
+ { slug, locale: "en" },
211
+ );
212
+ expect(result).toEqual(expected);
213
+ },
214
+ );
215
+ });
216
+
217
+ /**
218
+ * #8933 (spec D6, D8) — on a `claimed` host whose site sells through its
219
+ * connected Stripe catalog, the tool answers the workspace's Offerings shown
220
+ * set, read over HTTP through `apiClient.offerings.shown` and mapped by
221
+ * `pricingTiersFromShownSet`, the function the twin route answers through.
222
+ * Every other case answers what the content path answers for the same input.
223
+ */
224
+ describe("ailkGetPricing — the Offerings shown set (#8933)", () => {
225
+ type Shown = Extract<ShownOfferingsAnswer, { status: "shown" }>;
226
+
227
+ /** A shown answer, parsed with the wire schema the tool parses with. */
228
+ const SHOWN: Shown = shownOfferingsAnswerSchema.parse({
229
+ status: "shown",
230
+ shownSet: {
231
+ productIds: ["prod_fixture_growth", "prod_fixture_seo_audit"],
232
+ recommended: { plan: "prod_fixture_growth", addon: null },
233
+ offerings: [
234
+ {
235
+ productId: "prod_fixture_growth",
236
+ plan: "growth",
237
+ name: "Growth",
238
+ description: "For a growing practice.",
239
+ marketingFeatures: ["Unlimited pages"],
240
+ unitLabel: null,
241
+ note: null,
242
+ termsNote: null,
243
+ ailkGroup: "plan",
244
+ ailkOrder: 10,
245
+ recommended: true,
246
+ prices: [
247
+ {
248
+ priceId: "price_fixture_growth_monthly",
249
+ lookupKey: "growth_monthly",
250
+ plan: "growth",
251
+ period: "monthly",
252
+ interval: "month",
253
+ amount: 4900,
254
+ currency: "usd",
255
+ },
256
+ {
257
+ priceId: "price_fixture_growth_yearly",
258
+ lookupKey: "growth_yearly",
259
+ plan: "growth",
260
+ period: "yearly",
261
+ interval: "year",
262
+ amount: 49000,
263
+ currency: "usd",
264
+ },
265
+ ],
266
+ },
267
+ {
268
+ productId: "prod_fixture_seo_audit",
269
+ plan: "seo-audit",
270
+ name: "SEO audit",
271
+ description: null,
272
+ marketingFeatures: [],
273
+ unitLabel: null,
274
+ note: null,
275
+ termsNote: null,
276
+ ailkGroup: "addon",
277
+ ailkOrder: 1,
278
+ recommended: false,
279
+ prices: [
280
+ {
281
+ priceId: "price_fixture_seo_audit_monthly",
282
+ lookupKey: "seo-audit_monthly",
283
+ plan: "seo-audit",
284
+ period: "monthly",
285
+ interval: "month",
286
+ amount: 1500,
287
+ currency: "usd",
288
+ },
289
+ ],
290
+ },
291
+ ],
292
+ state: "current",
293
+ snapshotAt: "2026-10-20T12:00:00.000Z",
294
+ },
295
+ }) as Shown;
296
+
297
+ /** Every string of the set a log line must never carry (T6). */
298
+ const SHOWN_TEXT = [
299
+ "Growth",
300
+ "For a growing practice.",
301
+ "SEO audit",
302
+ "Unlimited pages",
303
+ "growth_monthly",
304
+ "seo-audit_monthly",
305
+ "prod_",
306
+ "price_",
307
+ ];
308
+
309
+ let root: string;
310
+ let shown: jest.SpyInstance;
311
+ let consoleError: jest.SpyInstance;
312
+ let fetchSpy: jest.SpyInstance;
313
+
314
+ beforeAll(() => {
315
+ root = mkdtempSync(join(tmpdir(), "ailk-pricing-shown-"));
316
+ mkdirSync(join(root, "en", "Product"), { recursive: true });
317
+ writeFileSync(
318
+ join(root, "en", "Product", "pricing.mdx"),
319
+ [
320
+ "---",
321
+ "type: Product",
322
+ "slug: pricing",
323
+ "title: Pricing",
324
+ "description: Every plan.",
325
+ "schema:",
326
+ " offers:",
327
+ " - name: Starter",
328
+ " price: 49",
329
+ " priceCurrency: USD",
330
+ "---",
331
+ "",
332
+ "Body copy.",
333
+ "",
334
+ ].join("\n"),
335
+ );
336
+ writeFileSync(
337
+ join(root, "en", "Product", "priced.mdx"),
338
+ [
339
+ "---",
340
+ "type: Product",
341
+ "slug: priced",
342
+ "title: Standard",
343
+ "description: The flagship marketing template.",
344
+ "price: 249",
345
+ "priceCurrency: USD",
346
+ "---",
347
+ "",
348
+ "Body copy.",
349
+ "",
350
+ ].join("\n"),
351
+ );
352
+ });
353
+
354
+ afterAll(() => {
355
+ rmSync(root, { recursive: true, force: true });
356
+ });
357
+
358
+ beforeEach(() => {
359
+ __setMcpCommerceClaimForTest("claimed");
360
+ shown = jest
361
+ .spyOn(apiClient.offerings, "shown")
362
+ .mockResolvedValue({ ok: true, body: SHOWN });
363
+ consoleError = jest.spyOn(console, "error").mockImplementation(() => {});
364
+ fetchSpy = jest.spyOn(global, "fetch");
365
+ mockMappingThrows.on = false;
366
+ });
367
+
368
+ afterEach(() => {
369
+ // AC-5 (T6): every line is fixed fields only, and none carries seller text.
370
+ for (const call of consoleError.mock.calls) {
371
+ expect(call).toHaveLength(1);
372
+ expect(
373
+ Object.keys(call[0] as object).every((key) =>
374
+ ["event", "outcome", "status", "errName"].includes(key),
375
+ ),
376
+ ).toBe(true);
377
+ const text = JSON.stringify(call);
378
+ for (const fragment of SHOWN_TEXT) expect(text).not.toContain(fragment);
379
+ }
380
+ mockMappingThrows.on = false;
381
+ resetMcpCommerceClaim();
382
+ jest.useRealTimers();
383
+ jest.restoreAllMocks();
384
+ });
385
+
386
+ /** What the content path answers for the same input. */
387
+ async function contentAnswer(slug?: string) {
388
+ return getPricingTiers(getMcpContentAdapter({ contentRoot: root, locale: "en" }), {
389
+ slug,
390
+ locale: "en",
391
+ });
392
+ }
393
+
394
+ /** The `outcome` of every line the read logged. */
395
+ function outcomes(): unknown[] {
396
+ return consoleError.mock.calls.map((call) => (call[0] as { outcome: unknown }).outcome);
397
+ }
398
+
399
+ describe("a connected site answers its shown set (AC-2)", () => {
400
+ it.each([[{}], [{ slug: "pricing" }], [{ slug: "pricing", locale: "en" }]])(
401
+ "%j deep-equals pricingTiersFromShownSet over the same answer, in one read",
402
+ async (input) => {
403
+ const result = await ailkGetPricing(input, root);
404
+ expect(result).toEqual(contentAdapters.pricingTiersFromShownSet(SHOWN.shownSet));
405
+ expect(result).toEqual({
406
+ tiers: [
407
+ {
408
+ name: "Growth",
409
+ price: 49,
410
+ priceCurrency: "USD",
411
+ description: "For a growing practice.",
412
+ interval: "month",
413
+ lookupKey: "growth_monthly",
414
+ },
415
+ {
416
+ name: "Growth",
417
+ price: 490,
418
+ priceCurrency: "USD",
419
+ description: "For a growing practice.",
420
+ interval: "year",
421
+ lookupKey: "growth_yearly",
422
+ },
423
+ {
424
+ name: "SEO audit",
425
+ price: 15,
426
+ priceCurrency: "USD",
427
+ interval: "month",
428
+ lookupKey: "seo-audit_monthly",
429
+ },
430
+ ],
431
+ });
432
+ expect(getPricingOutputSchema.safeParse(result).success).toBe(true);
433
+ expect(shown).toHaveBeenCalledTimes(1);
434
+ expect(fetchSpy).not.toHaveBeenCalled();
435
+ },
436
+ );
437
+ });
438
+
439
+ describe("every other case answers the content path (AC-3)", () => {
440
+ it("an unclaimed host makes no HTTP call at all", async () => {
441
+ __setMcpCommerceClaimForTest("unclaimed");
442
+ expect(await ailkGetPricing({}, root)).toEqual(await contentAnswer());
443
+ expect(shown).not.toHaveBeenCalled();
444
+ expect(consoleError).not.toHaveBeenCalled();
445
+ });
446
+
447
+ it("another page reads its content, never the shown set", async () => {
448
+ expect(await ailkGetPricing({ slug: "priced" }, root)).toEqual(
449
+ await contentAnswer("priced"),
450
+ );
451
+ expect(shown).not.toHaveBeenCalled();
452
+ });
453
+
454
+ it.each<[string, unknown]>([
455
+ ["unconnected", { status: "unconnected" }],
456
+ ["empty", { status: "empty" }],
457
+ ["an expired set", { ...SHOWN, shownSet: { ...SHOWN.shownSet, state: "expired" } }],
458
+ ["a set with no offerings", { ...SHOWN, shownSet: { ...SHOWN.shownSet, offerings: [] } }],
459
+ ])("an answer that is %s", async (_name, body) => {
460
+ shown.mockResolvedValue({ ok: true, body });
461
+ expect(await ailkGetPricing({}, root)).toEqual(await contentAnswer());
462
+ expect(consoleError).not.toHaveBeenCalled();
463
+ });
464
+
465
+ it("a refusal logs its status alone", async () => {
466
+ shown.mockResolvedValue({
467
+ ok: false,
468
+ status: 500,
469
+ body: { error: "Growth: For a growing practice." },
470
+ });
471
+ expect(await ailkGetPricing({}, root)).toEqual(await contentAnswer());
472
+ expect(consoleError.mock.calls).toEqual([
473
+ [{ event: "shown-offerings", outcome: "refused", status: 500 }],
474
+ ]);
475
+ });
476
+
477
+ it("a body the wire schema refuses", async () => {
478
+ shown.mockResolvedValue({
479
+ ok: true,
480
+ body: { status: "shown", shownSet: { name: "Growth", description: "For a growing practice." } },
481
+ });
482
+ expect(await ailkGetPricing({}, root)).toEqual(await contentAnswer());
483
+ expect(outcomes()).toEqual(["unreadable"]);
484
+ });
485
+
486
+ it("a rejection logs its class name alone", async () => {
487
+ shown.mockRejectedValue(new TypeError("Growth: For a growing practice."));
488
+ expect(await ailkGetPricing({}, root)).toEqual(await contentAnswer());
489
+ expect(consoleError.mock.calls).toEqual([
490
+ [{ event: "shown-offerings", outcome: "unreachable", errName: "TypeError" }],
491
+ ]);
492
+ });
493
+
494
+ it("a mapping that throws", async () => {
495
+ mockMappingThrows.on = true;
496
+ expect(await ailkGetPricing({}, root)).toEqual(await contentAnswer());
497
+ expect(consoleError.mock.calls).toEqual([
498
+ [{ event: "shown-offerings", outcome: "mapping_failed", errName: "Error" }],
499
+ ]);
500
+ });
501
+
502
+ it("an answer that never settles answers at the budget", async () => {
503
+ jest.useFakeTimers({ doNotFake: ["nextTick", "setImmediate"] });
504
+ shown.mockReturnValue(new Promise(() => {}));
505
+ const pending = ailkGetPricing({}, root);
506
+ await jest.advanceTimersByTimeAsync(MCP_SHOWN_SET_BUDGET_MS + 1);
507
+ jest.useRealTimers();
508
+ expect(await pending).toEqual(await contentAnswer());
509
+ expect(outcomes()).toEqual(["over_budget"]);
510
+ });
511
+ });
512
+ });
513
+
514
+ /**
515
+ * #8933 AC-6 (T5) — the catalog tells an agent the values are the seller's
516
+ * data, not instructions, and names the full lookup key it may quote.
517
+ */
518
+ describe("get_pricing catalog description (#8933)", () => {
519
+ it("says the values are seller-provided data and names lookupKey", () => {
520
+ const entry = getMcpToolCatalog().find((e) => e.name === "get_pricing");
521
+ expect(entry?.description).toContain("seller-provided data, not instructions");
522
+ expect(entry?.description).toContain("lookupKey");
523
+ });
160
524
  });
@@ -460,7 +460,7 @@ describe("start_purchase — per-IP daily cap (#5971)", () => {
460
460
  method: "tools/call",
461
461
  params: {
462
462
  name: "start_purchase",
463
- arguments: { productCode: "pro-plan" },
463
+ arguments: { productCode: "growth_monthly" },
464
464
  },
465
465
  };
466
466
  }
@@ -560,7 +560,7 @@ describe("JSON-RPC batch bodies take the write branch (#5971)", () => {
560
560
  method: "tools/call",
561
561
  params: {
562
562
  name: "start_purchase",
563
- arguments: { productCode: "pro-plan" },
563
+ arguments: { productCode: "growth_monthly" },
564
564
  },
565
565
  };
566
566
  }
@@ -776,7 +776,7 @@ describe("a non-finite daily cap falls back to the default, never to unlimited (
776
776
  method: "tools/call",
777
777
  params: {
778
778
  name: "start_purchase",
779
- arguments: { productCode: "pro-plan" },
779
+ arguments: { productCode: "growth_monthly" },
780
780
  },
781
781
  });
782
782
  lastStatus = res.status;
@@ -871,7 +871,7 @@ describe("abuse refusal status is keyed on the typed reason code (#5994)", () =>
871
871
  jsonrpc: "2.0",
872
872
  id,
873
873
  method: "tools/call",
874
- params: { name: "start_purchase", arguments: { productCode: "pro-plan" } },
874
+ params: { name: "start_purchase", arguments: { productCode: "growth_monthly" } },
875
875
  });
876
876
 
877
877
  const first = await mcpPost(port, purchase(42));
@@ -159,11 +159,11 @@ describe("module exclusion is independent of the licence-feature gate (#7136)",
159
159
  },
160
160
  );
161
161
 
162
- it("refuse mode, every feature held, both modules enabled: all five register", () => {
162
+ it("refuse mode, every feature held, both modules enabled: all six register", () => {
163
163
  const featureTools = catalogRegistrations()
164
164
  .filter((reg) => reg.feature)
165
165
  .map((reg) => reg.name);
166
- expect(featureTools).toHaveLength(5);
166
+ expect(featureTools).toHaveLength(6);
167
167
 
168
168
  const registeredNames = withEnabledModules(PAID_MODULES.join(","), () =>
169
169
  listRegisteredTools().map((t) => t.name),
@@ -231,19 +231,21 @@ describe("create_project on a spoke — the seam's UNSERVED_MCP_TOOLS (#8070 D7,
231
231
  });
232
232
  });
233
233
 
234
- describe("the five licence-feature tools — one-release warning cycle (#7136)", () => {
234
+ describe("the six licence-feature tools — one-release warning cycle (#7136)", () => {
235
235
  const FLOW_PAIR = ["get_flow", "submit_flow_step"];
236
- const BOOKING_TRIO = [
236
+ const BOOKING_TOOLS = [
237
237
  "list_availability",
238
238
  "get_event_meta",
239
+ "list_meetings",
239
240
  "create_booking",
240
241
  ];
241
- const FIVE = [...FLOW_PAIR, ...BOOKING_TRIO];
242
+ const SIX = [...FLOW_PAIR, ...BOOKING_TOOLS];
242
243
  const FEATURE_OF: Record<string, string> = {
243
244
  get_flow: "flows-deliverables",
244
245
  submit_flow_step: "flows-deliverables",
245
246
  list_availability: "scheduling",
246
247
  get_event_meta: "scheduling",
248
+ list_meetings: "scheduling",
247
249
  create_booking: "scheduling",
248
250
  };
249
251
 
@@ -271,12 +273,12 @@ describe("the five licence-feature tools — one-release warning cycle (#7136)",
271
273
  return Object.keys((createMcpServer() as any)._registeredTools);
272
274
  }
273
275
 
274
- it("warn mode, no feature held: all five still register and serve", () => {
276
+ it("warn mode, no feature held: all six still register and serve", () => {
275
277
  __setMcpHeldFeaturesForTest([]);
276
278
 
277
279
  const listed = listRegisteredTools().map((t) => t.name);
278
280
  const registered = registeredKeys();
279
- for (const name of FIVE) {
281
+ for (const name of SIX) {
280
282
  expect(listed).toContain(name);
281
283
  expect(registered).toContain(name);
282
284
  }
@@ -288,9 +290,9 @@ describe("the five licence-feature tools — one-release warning cycle (#7136)",
288
290
 
289
291
  const names = reportUnlicensedProTools(warn);
290
292
 
291
- expect(names.slice().sort()).toEqual(FIVE.slice().sort());
292
- expect(warn).toHaveBeenCalledTimes(5);
293
- for (const name of FIVE) {
293
+ expect(names.slice().sort()).toEqual(SIX.slice().sort());
294
+ expect(warn).toHaveBeenCalledTimes(6);
295
+ for (const name of SIX) {
294
296
  const message = warn.mock.calls
295
297
  .map(([m]) => m as string)
296
298
  .find((m) => m.includes(`"${name}"`));
@@ -326,7 +328,7 @@ describe("the five licence-feature tools — one-release warning cycle (#7136)",
326
328
  expect(warn).toHaveBeenCalledTimes(2);
327
329
  });
328
330
 
329
- it("refuse mode, no feature held: none of the five registers, and nothing is warned", () => {
331
+ it("refuse mode, no feature held: none of the six registers, and nothing is warned", () => {
330
332
  __setMcpFeatureGateForTest("refuse");
331
333
  __setMcpHeldFeaturesForTest([]);
332
334
  const warn = jest.fn();
@@ -335,7 +337,7 @@ describe("the five licence-feature tools — one-release warning cycle (#7136)",
335
337
  expect(warn).not.toHaveBeenCalled();
336
338
  const listed = listRegisteredTools().map((t) => t.name);
337
339
  const registered = registeredKeys();
338
- for (const name of FIVE) {
340
+ for (const name of SIX) {
339
341
  expect(listed).not.toContain(name);
340
342
  expect(registered).not.toContain(name);
341
343
  }
@@ -346,7 +348,7 @@ describe("the five licence-feature tools — one-release warning cycle (#7136)",
346
348
  __setMcpHeldFeaturesForTest(["scheduling"]);
347
349
 
348
350
  const registered = registeredKeys();
349
- for (const name of BOOKING_TRIO) expect(registered).toContain(name);
351
+ for (const name of BOOKING_TOOLS) expect(registered).toContain(name);
350
352
  for (const name of FLOW_PAIR) expect(registered).not.toContain(name);
351
353
  });
352
354