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
@@ -8,19 +8,83 @@
8
8
  * - ailkReadContent returns null (not throws) for an unknown slug
9
9
  * - Output validates against readContentOutputSchema
10
10
  * - No console.* calls in handler (structural test)
11
+ * - #6030 AC-1: BlogPosting and DocsDetail — the two types this issue admits —
12
+ * are readable through the adapter on both the MDX leg and the database
13
+ * leg, and both are asserted against the shared adapter seam
14
+ * (`ContentAdapter.getPageSource`) per the API↔MCP parity contract (D3).
11
15
  */
12
16
 
13
- import { readFileSync } from "node:fs";
17
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync } from "node:fs";
18
+ import { tmpdir } from "node:os";
14
19
  import { resolve, join } from "node:path";
15
20
 
21
+ import {
22
+ createMdxAdapter,
23
+ resetResolvedContentAdapter,
24
+ type ArtifactPrismaClient,
25
+ } from "@working-theory/content-adapters";
26
+
16
27
  import {
17
28
  ailkReadContent,
18
29
  readContentInputSchema,
19
30
  readContentOutputSchema,
20
31
  } from "../src/tools/read_content.js";
32
+ import {
33
+ __setMcpPublicWorkspaceForTest,
34
+ resetMcpPublicWorkspace,
35
+ } from "../src/lib/public-workspace.js";
21
36
 
22
37
  const REPO_ROOT = resolve(__dirname, "../../..");
23
38
  const CONTENT_ROOT = join(REPO_ROOT, "content");
39
+ const WORKSPACE_ID = "wksp_00000000-0000-7000-8000-000000000001";
40
+
41
+ /**
42
+ * A structural `ArtifactPrismaClient` stub (#5876 spec D11) — `artifact` and
43
+ * `artifactVersion` jest.fn()s, injected directly, no database.
44
+ */
45
+ function makeArtifactPrismaClientStub(overrides: {
46
+ artifact?: { publishedVersionId: string | null } | null;
47
+ version?: { content: unknown } | null;
48
+ }): ArtifactPrismaClient {
49
+ const artifact =
50
+ overrides.artifact === undefined || overrides.artifact === null
51
+ ? null
52
+ : {
53
+ id: "artifact_1",
54
+ workspaceId: WORKSPACE_ID,
55
+ contentType: "text/page",
56
+ locale: "en",
57
+ slug: "home",
58
+ title: "Home",
59
+ publishedVersionId: overrides.artifact.publishedVersionId,
60
+ };
61
+ const version =
62
+ overrides.version === undefined || overrides.version === null
63
+ ? null
64
+ : {
65
+ id: "version_1",
66
+ artifactId: "artifact_1",
67
+ workspaceId: WORKSPACE_ID,
68
+ content: overrides.version.content,
69
+ isCurrent: false,
70
+ };
71
+ return {
72
+ artifact: {
73
+ findFirst: jest.fn().mockResolvedValue(artifact),
74
+ findMany: jest.fn().mockResolvedValue([]),
75
+ create: jest.fn(),
76
+ updateMany: jest.fn(),
77
+ deleteMany: jest.fn(),
78
+ },
79
+ artifactVersion: {
80
+ findFirst: jest.fn().mockResolvedValue(version),
81
+ findMany: jest.fn().mockResolvedValue([]),
82
+ create: jest.fn(),
83
+ updateMany: jest.fn(),
84
+ },
85
+ $transaction: jest.fn(),
86
+ };
87
+ }
24
88
 
25
89
  describe("readContentInputSchema", () => {
26
90
  it("accepts a valid type + slug", () => {
@@ -142,6 +206,263 @@ describe("ailkReadContent handler", () => {
142
206
  });
143
207
  });
144
208
 
209
+ describe("ailkReadContent — CONTENT_ADAPTER=database (#5876)", () => {
210
+ const previous = process.env.CONTENT_ADAPTER;
211
+
212
+ beforeEach(() => {
213
+ process.env.CONTENT_ADAPTER = "database";
214
+ // resolveContentAdapter() memoises per (kind, workspaceId, contentRoot,
215
+ // defaultLocale, manifestPath) — NOT per prisma instance — so every case
216
+ // below (same workspaceId, same defaults) would otherwise reuse the FIRST
217
+ // test's adapter, bound to the FIRST test's stub.
218
+ resetResolvedContentAdapter();
219
+ });
220
+
221
+ afterEach(() => {
222
+ if (previous === undefined) delete process.env.CONTENT_ADAPTER;
223
+ else process.env.CONTENT_ADAPTER = previous;
224
+ resetMcpPublicWorkspace();
225
+ resetResolvedContentAdapter();
226
+ });
227
+
228
+ it("AC-1: returns { slug, type, frontmatter, body } for a published page", async () => {
229
+ const stub = makeArtifactPrismaClientStub({
230
+ artifact: { publishedVersionId: "version_1" },
231
+ version: {
232
+ content: {
233
+ type: "HomePage",
234
+ slug: "home",
235
+ title: "Home",
236
+ description: "The homepage.",
237
+ body: "# Hello",
238
+ },
239
+ },
240
+ });
241
+ __setMcpPublicWorkspaceForTest({
242
+ kind: "resolved",
243
+ workspaceId: WORKSPACE_ID,
244
+ prisma: stub,
245
+ });
246
+
247
+ const result = await ailkReadContent({ type: "HomePage", slug: "home" });
248
+
249
+ expect(result).not.toBeNull();
250
+ expect(result!.slug).toBe("home");
251
+ expect(result!.type).toBe("HomePage");
252
+ expect(result!.body).toBe("# Hello");
253
+ // The reach test (§5.2 AC-1) — the read is bound to the resolved workspace.
254
+ expect(stub.artifact.findFirst).toHaveBeenCalledWith(
255
+ expect.objectContaining({
256
+ where: expect.objectContaining({ workspaceId: WORKSPACE_ID }),
257
+ }),
258
+ );
259
+ });
260
+
261
+ it("AC-1: returns null for a draft-only artifact (no published version)", async () => {
262
+ const stub = makeArtifactPrismaClientStub({
263
+ artifact: { publishedVersionId: null },
264
+ });
265
+ __setMcpPublicWorkspaceForTest({
266
+ kind: "resolved",
267
+ workspaceId: WORKSPACE_ID,
268
+ prisma: stub,
269
+ });
270
+
271
+ const result = await ailkReadContent({ type: "HomePage", slug: "home" });
272
+
273
+ expect(result).toBeNull();
274
+ });
275
+
276
+ it("AC-1: returns null for an unknown slug", async () => {
277
+ const stub = makeArtifactPrismaClientStub({ artifact: null });
278
+ __setMcpPublicWorkspaceForTest({
279
+ kind: "resolved",
280
+ workspaceId: WORKSPACE_ID,
281
+ prisma: stub,
282
+ });
283
+
284
+ const result = await ailkReadContent({
285
+ type: "HomePage",
286
+ slug: "does-not-exist",
287
+ });
288
+
289
+ expect(result).toBeNull();
290
+ });
291
+
292
+ it("AC-2: returns null when the workspace is unresolved, with no content lookup", async () => {
293
+ const stub = makeArtifactPrismaClientStub({});
294
+ __setMcpPublicWorkspaceForTest({ kind: "unresolved", reason: "ambiguous" });
295
+
296
+ const result = await ailkReadContent({ type: "HomePage", slug: "home" });
297
+
298
+ expect(result).toBeNull();
299
+ expect(stub.artifact.findFirst).not.toHaveBeenCalled();
300
+ });
301
+ });
302
+
303
+ describe("ailkReadContent — BlogPosting and DocsDetail, MDX leg (#6030 AC-1)", () => {
304
+ let root = "";
305
+
306
+ beforeAll(() => {
307
+ root = mkdtempSync(join(tmpdir(), "ailk-mcp-6030-"));
308
+ for (const { type, slug, body } of [
309
+ { type: "BlogPosting", slug: "launch-post", body: "# Launch post\n\nBody." },
310
+ { type: "DocsDetail", slug: "getting-started", body: "# Getting started\n\nBody." },
311
+ ]) {
312
+ const dir = join(root, "en", type);
313
+ mkdirSync(dir, { recursive: true });
314
+ writeFileSync(
315
+ join(dir, `${slug}.mdx`),
316
+ [
317
+ "---",
318
+ `type: "${type}"`,
319
+ `slug: "${slug}"`,
320
+ `title: "${type} ${slug} title"`,
321
+ `description: "${type} ${slug} description"`,
322
+ "---",
323
+ "",
324
+ body,
325
+ "",
326
+ ].join("\n"),
327
+ "utf8",
328
+ );
329
+ }
330
+ });
331
+
332
+ afterAll(() => {
333
+ rmSync(root, { recursive: true, force: true });
334
+ });
335
+
336
+ it.each(["BlogPosting", "DocsDetail"] as const)(
337
+ "returns the page for %s, deep-equal to the projection over getPageSource (D3)",
338
+ async (type) => {
339
+ const slug = type === "BlogPosting" ? "launch-post" : "getting-started";
340
+ const result = await ailkReadContent({ type, slug, locale: "en" }, root);
341
+
342
+ // The shared seam D3 asserts from both ends: read_content's payload is
343
+ // derived from exactly what ContentAdapter.getPageSource(type, slug,
344
+ // locale) returns for the arguments the tool builds — not restated.
345
+ const adapter = createMdxAdapter({ contentRoot: root });
346
+ const source = await adapter.getPageSource(type, slug, "en");
347
+ expect(source).not.toBeNull();
348
+ const expected = readContentOutputSchema.parse({
349
+ slug: source!.frontmatter.slug ?? slug,
350
+ type: source!.frontmatter.type ?? type,
351
+ frontmatter: source!.frontmatter,
352
+ body: source!.body,
353
+ });
354
+
355
+ expect(result).toEqual(expected);
356
+ expect(result!.slug).toBe(slug);
357
+ expect(result!.type).toBe(type);
358
+ },
359
+ );
360
+
361
+ it.each(["BlogPosting", "DocsDetail"] as const)(
362
+ "returns null for an absent slug of %s",
363
+ async (type) => {
364
+ const result = await ailkReadContent(
365
+ { type, slug: "does-not-exist", locale: "en" },
366
+ root,
367
+ );
368
+ expect(result).toBeNull();
369
+ },
370
+ );
371
+ });
372
+
373
+ describe("ailkReadContent — BlogPosting and DocsDetail, CONTENT_ADAPTER=database (#6030 AC-1)", () => {
374
+ const previous = process.env.CONTENT_ADAPTER;
375
+
376
+ beforeEach(() => {
377
+ process.env.CONTENT_ADAPTER = "database";
378
+ resetResolvedContentAdapter();
379
+ });
380
+
381
+ afterEach(() => {
382
+ if (previous === undefined) delete process.env.CONTENT_ADAPTER;
383
+ else process.env.CONTENT_ADAPTER = previous;
384
+ resetMcpPublicWorkspace();
385
+ resetResolvedContentAdapter();
386
+ });
387
+
388
+ it.each(["BlogPosting", "DocsDetail"] as const)(
389
+ "returns { slug, type, frontmatter, body } for a published %s",
390
+ async (type) => {
391
+ const slug = "probe";
392
+ const stub = makeArtifactPrismaClientStub({
393
+ artifact: { publishedVersionId: "version_1" },
394
+ version: {
395
+ content: {
396
+ type,
397
+ slug,
398
+ title: `${type} title`,
399
+ description: `${type} description`,
400
+ body: "# Published body",
401
+ },
402
+ },
403
+ });
404
+ __setMcpPublicWorkspaceForTest({
405
+ kind: "resolved",
406
+ workspaceId: WORKSPACE_ID,
407
+ prisma: stub,
408
+ });
409
+
410
+ const result = await ailkReadContent({ type, slug });
411
+
412
+ expect(result).not.toBeNull();
413
+ expect(result!.slug).toBe(slug);
414
+ expect(result!.type).toBe(type);
415
+ expect(result!.body).toBe("# Published body");
416
+ },
417
+ );
418
+
419
+ // D4 — the publication guard must hold for every newly-admitted type, not
420
+ // only HomePage's existing case.
421
+ it.each(["BlogPosting", "DocsDetail"] as const)(
422
+ "returns null for a draft-only %s artifact (no published version)",
423
+ async (type) => {
424
+ const stub = makeArtifactPrismaClientStub({
425
+ artifact: { publishedVersionId: null },
426
+ });
427
+ __setMcpPublicWorkspaceForTest({
428
+ kind: "resolved",
429
+ workspaceId: WORKSPACE_ID,
430
+ prisma: stub,
431
+ });
432
+
433
+ const result = await ailkReadContent({ type, slug: "probe" });
434
+
435
+ expect(result).toBeNull();
436
+ },
437
+ );
438
+
439
+ // D4 — type-as-address: a page stored under a different frontmatter.type
440
+ // than requested reads as absent, never as a cross-type hit.
441
+ it("returns null when the requested type does not match the stored frontmatter.type", async () => {
442
+ const stub = makeArtifactPrismaClientStub({
443
+ artifact: { publishedVersionId: "version_1" },
444
+ version: {
445
+ content: {
446
+ type: "BlogPosting",
447
+ slug: "probe",
448
+ title: "A blog post",
449
+ description: "Stored as BlogPosting.",
450
+ body: "# Body",
451
+ },
452
+ },
453
+ });
454
+ __setMcpPublicWorkspaceForTest({
455
+ kind: "resolved",
456
+ workspaceId: WORKSPACE_ID,
457
+ prisma: stub,
458
+ });
459
+
460
+ const result = await ailkReadContent({ type: "DocsDetail", slug: "probe" });
461
+
462
+ expect(result).toBeNull();
463
+ });
464
+ });
465
+
145
466
  describe("architectural guard — no console calls", () => {
146
467
  it("apps/mcp/src/tools/read_content.ts does not call console.*", () => {
147
468
  const src = readFileSync(
@@ -9,10 +9,13 @@
9
9
  * - ailkSearchContent happy path returns results array with correct shape
10
10
  * - ailkSearchContent returns empty results for a query with no matches
11
11
  * - Malformed input causes Zod rejection
12
+ * - #5831: searchContentInputSchema accepts a registry category slug and
13
+ * rejects one outside the registry; the handler forwards it to the adapter
12
14
  */
13
15
 
14
- import { readFileSync } from "node:fs";
15
- import { resolve } from "node:path";
16
+ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
17
+ import { tmpdir } from "node:os";
18
+ import { join, resolve } from "node:path";
16
19
 
17
20
  import {
18
21
  ailkSearchContent,
@@ -100,6 +103,106 @@ describe("searchContentInputSchema", () => {
100
103
  const result = searchContentInputSchema.safeParse({});
101
104
  expect(result.success).toBe(false);
102
105
  });
106
+
107
+ it("accepts a category slug from the registry (#5831)", () => {
108
+ const result = searchContentInputSchema.safeParse({
109
+ query: "roof",
110
+ category: "roofing",
111
+ });
112
+ expect(result.success).toBe(true);
113
+ });
114
+
115
+ it("rejects a category slug outside the registry with an enum error (#5831)", () => {
116
+ const result = searchContentInputSchema.safeParse({
117
+ query: "roof",
118
+ category: "not-a-real-category",
119
+ });
120
+ expect(result.success).toBe(false);
121
+ expect(result.error!.issues[0]).toMatchObject({
122
+ code: "invalid_value",
123
+ path: ["category"],
124
+ });
125
+ });
126
+
127
+ it("rejects a GROUP slug — groups are derived, never tagged (#5831)", () => {
128
+ const result = searchContentInputSchema.safeParse({
129
+ query: "roof",
130
+ category: "professional-services",
131
+ });
132
+ expect(result.success).toBe(false);
133
+ });
134
+ });
135
+
136
+ describe("ailkSearchContent — category filter (#5831)", () => {
137
+ let root = "";
138
+
139
+ beforeAll(() => {
140
+ root = mkdtempSync(join(tmpdir(), "ailk-mcp-category-"));
141
+ const dir = join(root, "en", "Product");
142
+ mkdirSync(dir, { recursive: true });
143
+ writeFileSync(
144
+ join(dir, "roof-replacement.mdx"),
145
+ [
146
+ "---",
147
+ 'type: "Product"',
148
+ 'slug: "roof-replacement"',
149
+ 'title: "Roof Replacement"',
150
+ 'description: "A full roof replacement service."',
151
+ "categories:",
152
+ " - roofing",
153
+ "---",
154
+ "",
155
+ "We replace a roof in a day.",
156
+ "",
157
+ ].join("\n"),
158
+ "utf8",
159
+ );
160
+ writeFileSync(
161
+ join(dir, "attic-ventilation.mdx"),
162
+ [
163
+ "---",
164
+ 'type: "Product"',
165
+ 'slug: "attic-ventilation"',
166
+ 'title: "Attic Ventilation"',
167
+ 'description: "Ventilation that protects the roof deck."',
168
+ "categories:",
169
+ " - hvac",
170
+ "---",
171
+ "",
172
+ "Attic airflow keeps a roof healthy.",
173
+ "",
174
+ ].join("\n"),
175
+ "utf8",
176
+ );
177
+ });
178
+
179
+ afterAll(() => {
180
+ rmSync(root, { recursive: true, force: true });
181
+ });
182
+
183
+ it("forwards category to the adapter — only the tagged page comes back", async () => {
184
+ const result = await ailkSearchContent(
185
+ { query: "roof", category: "roofing" },
186
+ root,
187
+ );
188
+ expect(result.results.map((r) => r.slug)).toEqual(["roof-replacement"]);
189
+ });
190
+
191
+ it("returns every match when no category is passed", async () => {
192
+ const result = await ailkSearchContent({ query: "roof" }, root);
193
+ expect(result.results.map((r) => r.slug).sort()).toEqual([
194
+ "attic-ventilation",
195
+ "roof-replacement",
196
+ ]);
197
+ });
198
+
199
+ it("returns [] for a registry slug nothing is tagged with", async () => {
200
+ const result = await ailkSearchContent(
201
+ { query: "roof", category: "legal" },
202
+ root,
203
+ );
204
+ expect(result.results).toEqual([]);
205
+ });
103
206
  });
104
207
 
105
208
  describe("ailkSearchContent handler", () => {
@@ -6,9 +6,10 @@
6
6
  * - list_pages and read_content tools are registered
7
7
  * - Each registered tool has a non-empty description
8
8
  *
9
- * Note: `start_purchase` registration is asserted in
10
- * `purchase-disabled.test.ts` as NOT-registered (S15a / issue #610). The
11
- * prior `start_purchase` assertions in this file moved there.
9
+ * Note: the full `start_purchase` claim MATRIX — absent on a keyless boot,
10
+ * present on a keyed one — is asserted in `purchase-disabled.test.ts`
11
+ * (#5971, superseding S15a / issue #610). The describe block at the end of
12
+ * this file pins the registration half from the bootstrap's own side.
12
13
  *
13
14
  * Strategy: createMcpServer() returns the McpServer instance before connecting
14
15
  * to a transport. Tests inspect server._registeredTools (plain object in SDK
@@ -17,7 +18,11 @@
17
18
  * casts below. Pinned to @modelcontextprotocol/sdk ^1.29.0.
18
19
  */
19
20
 
20
- import { createMcpServer } from "../src/server.js";
21
+ import {
22
+ __setMcpCommerceClaimForTest,
23
+ resetMcpCommerceClaim,
24
+ } from "../src/lib/commerce-claim.js";
25
+ import { createMcpServer, listRegisteredTools } from "../src/server.js";
21
26
 
22
27
  describe("createMcpServer", () => {
23
28
  it("creates an McpServer without throwing", () => {
@@ -90,26 +95,74 @@ const I4_TOOLS = [
90
95
  "get_faq",
91
96
  ] as const;
92
97
 
93
- describe.each(I4_TOOLS.map((name) => [name]))("%s tool registration", (toolName) => {
94
- it(`server has the ${toolName} tool registered`, () => {
98
+ describe.each(I4_TOOLS.map((name) => [name]))(
99
+ "%s tool registration",
100
+ (toolName) => {
101
+ it(`server has the ${toolName} tool registered`, () => {
102
+ const server = createMcpServer();
103
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- #506/#507: pre-existing test-hygiene violation
104
+ const tools = (server as any)._registeredTools as Record<string, unknown>;
105
+ expect(toolName in tools).toBe(true);
106
+ });
107
+
108
+ it(`${toolName} descriptor has a non-empty description`, () => {
109
+ const server = createMcpServer();
110
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- #506/#507: pre-existing test-hygiene violation
111
+ const tools = (server as any)._registeredTools as Record<
112
+ string,
113
+ { description?: string }
114
+ >;
115
+ const tool = tools[toolName];
116
+ expect(tool).toBeDefined();
117
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- #506/#507: pre-existing test-hygiene violation
118
+ expect(typeof (tool as any).description).toBe("string");
119
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- #506/#507: pre-existing test-hygiene violation
120
+ expect((tool as any).description.length).toBeGreaterThan(0);
121
+ });
122
+ },
123
+ );
124
+
125
+ describe("start_purchase registration — gated on the commerce claim (#5971)", () => {
126
+ afterEach(() => {
127
+ resetMcpCommerceClaim();
128
+ });
129
+
130
+ it("is not registered when the deployment holds no commerce claim", () => {
131
+ __setMcpCommerceClaimForTest("unclaimed");
95
132
  const server = createMcpServer();
96
133
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- #506/#507: pre-existing test-hygiene violation
97
134
  const tools = (server as any)._registeredTools as Record<string, unknown>;
98
- expect(toolName in tools).toBe(true);
135
+
136
+ expect("start_purchase" in tools).toBe(false);
137
+ expect(listRegisteredTools().map((t) => t.name)).not.toContain(
138
+ "start_purchase",
139
+ );
99
140
  });
100
141
 
101
- it(`${toolName} descriptor has a non-empty description`, () => {
142
+ it("is registered, with a non-empty description, when the claim is held", () => {
143
+ __setMcpCommerceClaimForTest("claimed");
102
144
  const server = createMcpServer();
103
145
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- #506/#507: pre-existing test-hygiene violation
104
146
  const tools = (server as any)._registeredTools as Record<
105
147
  string,
106
148
  { description?: string }
107
149
  >;
108
- const tool = tools[toolName];
109
- expect(tool).toBeDefined();
110
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- #506/#507: pre-existing test-hygiene violation
111
- expect(typeof (tool as any).description).toBe("string");
112
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- #506/#507: pre-existing test-hygiene violation
113
- expect((tool as any).description.length).toBeGreaterThan(0);
150
+
151
+ expect("start_purchase" in tools).toBe(true);
152
+ expect(tools["start_purchase"]?.description ?? "").not.toBe("");
153
+ });
154
+
155
+ it("registering it never changes what the other tools do", () => {
156
+ __setMcpCommerceClaimForTest("unclaimed");
157
+ const keyless = listRegisteredTools()
158
+ .map((t) => t.name)
159
+ .sort();
160
+
161
+ __setMcpCommerceClaimForTest("claimed");
162
+ const keyed = listRegisteredTools()
163
+ .map((t) => t.name)
164
+ .sort();
165
+
166
+ expect(keyed).toEqual([...keyless, "start_purchase"].sort());
114
167
  });
115
168
  });