create-ailk 0.7.0 → 0.9.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 (310) hide show
  1. package/README.md +112 -10
  2. package/component-catalog.json +798 -49
  3. package/component-catalog.md +9 -6
  4. package/dist/add-module.d.ts +20 -0
  5. package/dist/add-module.js +43 -7
  6. package/dist/add-route.d.ts +56 -0
  7. package/dist/add-route.js +286 -0
  8. package/dist/apply-design-md.d.ts +7 -0
  9. package/dist/apply-design-md.js +50 -0
  10. package/dist/apply-design.d.ts +64 -0
  11. package/dist/apply-design.js +273 -0
  12. package/dist/claude-md.d.ts +111 -0
  13. package/dist/claude-md.js +334 -0
  14. package/dist/cli.js +114 -28
  15. package/dist/derive-architecture.js +41 -36
  16. package/dist/design-compile.d.ts +71 -0
  17. package/dist/design-compile.js +98 -0
  18. package/dist/design-package.d.ts +83 -0
  19. package/dist/design-package.js +468 -0
  20. package/dist/design-presets.d.ts +86 -0
  21. package/dist/design-presets.js +642 -0
  22. package/dist/design-scaffold.d.ts +76 -0
  23. package/dist/design-scaffold.js +220 -0
  24. package/dist/index.d.ts +2 -1
  25. package/dist/index.js +4 -1
  26. package/dist/legacy-nav-renderers.d.ts +45 -0
  27. package/dist/legacy-nav-renderers.js +545 -0
  28. package/dist/lib/auth-secret-file.d.ts +19 -0
  29. package/dist/lib/auth-secret-file.js +112 -0
  30. package/dist/lib/design-checks.d.ts +78 -0
  31. package/dist/lib/design-checks.js +174 -0
  32. package/dist/lib/extract-module-bundle.js +5 -1
  33. package/dist/lib/license-key-files.d.ts +9 -0
  34. package/dist/lib/license-key-files.js +2 -2
  35. package/dist/lib/module-license-gate.d.ts +89 -0
  36. package/dist/lib/module-license-gate.js +171 -12
  37. package/dist/lib/module-tier.d.ts +45 -16
  38. package/dist/lib/module-tier.js +76 -0
  39. package/dist/lib/pro-callout.js +10 -2
  40. package/dist/lib/render-verdict.d.ts +52 -0
  41. package/dist/lib/render-verdict.js +77 -0
  42. package/dist/lib/scaffold-render-sweep.d.ts +105 -0
  43. package/dist/lib/scaffold-render-sweep.js +236 -0
  44. package/dist/module-architecture.d.ts +19 -16
  45. package/dist/module-architecture.js +64 -21
  46. package/dist/parse-args.d.ts +88 -10
  47. package/dist/parse-args.js +272 -21
  48. package/dist/programmatic.d.ts +66 -12
  49. package/dist/programmatic.js +361 -34
  50. package/dist/site-chrome.d.ts +19 -9
  51. package/dist/site-chrome.js +23 -13
  52. package/dist/site-manifest.d.ts +25 -0
  53. package/dist/site-manifest.js +254 -0
  54. package/dist/site-roster.d.ts +41 -0
  55. package/dist/site-roster.js +149 -11
  56. package/dist/starter-grid.d.ts +232 -0
  57. package/dist/starter-grid.js +545 -0
  58. package/dist/surfaces.d.ts +41 -9
  59. package/dist/surfaces.js +268 -92
  60. package/dist/template-manifest.d.ts +65 -29
  61. package/dist/template-manifest.js +161 -382
  62. package/dist/template-names.d.ts +14 -12
  63. package/dist/template-names.js +13 -17
  64. package/package.json +5 -4
  65. package/site-templates/_base/content/en/Feature/feature-1.mdx +69 -0
  66. package/site-templates/_base/content/en/Feature/feature-2.mdx +69 -0
  67. package/site-templates/_base/content/en/Feature/feature-3.mdx +69 -0
  68. package/site-templates/_base/content/en/FeaturesList/features.mdx +91 -0
  69. package/site-templates/_base/content/en/GlossaryList/glossary.mdx +0 -1
  70. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +0 -1
  71. package/site-templates/_base/content/en/PortfolioItem/sample-four.mdx +105 -0
  72. package/site-templates/_base/content/en/PortfolioItem/sample-one.mdx +34 -0
  73. package/site-templates/_base/content/en/PortfolioItem/sample-three.mdx +94 -0
  74. package/site-templates/_base/content/en/PortfolioItem/sample-two.mdx +45 -0
  75. package/site-templates/_base/content/en/PortfolioList/portfolio.mdx +169 -0
  76. package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +15 -0
  77. package/site-templates/_base/content/en/UseCase/persona-1.mdx +70 -0
  78. package/site-templates/_base/content/en/UseCase/persona-2.mdx +70 -0
  79. package/site-templates/_base/content/en/UseCase/persona-3.mdx +70 -0
  80. package/site-templates/{minimalism-site/TEMPLATE-MANIFEST.json → aliases/minimalism-site.TEMPLATE-MANIFEST.json} +72 -40
  81. package/site-templates/designs/minimalism/DESIGN.md +98 -6
  82. package/site-templates/designs/minimalism/sample/README.md +48 -0
  83. package/site-templates/designs/standard/DESIGN.md +103 -6
  84. package/site-templates/marketing-site/DESIGN.md +103 -6
  85. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +144 -47
  86. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +82 -41
  87. package/site-templates/marketing-site-free/DESIGN.md +103 -6
  88. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +150 -48
  89. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +114 -67
  90. package/site-templates/marketing-site-free/ailk-template.json +1 -1
  91. package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +59 -0
  92. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +199 -0
  93. package/site-templates/saas-product/DESIGN.md +103 -6
  94. package/site-templates/saas-product/TEMPLATE-MANIFEST.md +9 -1
  95. package/site-templates/{simple-service → shapes/local-business-service}/TEMPLATE-MANIFEST.json +210 -81
  96. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +356 -0
  97. package/site-templates/shapes/local-business-service/ailk-template.json +10 -0
  98. package/site-templates/{simple-service → shapes/local-business-service}/content/en/AboutPage/about.mdx +4 -4
  99. package/site-templates/shapes/local-business-service/content/en/HomePage/home.mdx +70 -0
  100. package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
  101. package/site-templates/shapes/local-business-service/content/en/PortfolioList/portfolio.mdx +171 -0
  102. package/site-templates/{minimalism-site → shapes/local-business-service}/content/en/PurchasePolicyPage/purchase-policy.mdx +7 -8
  103. package/site-templates/shapes/local-business-service/content/en/Service/first-job.mdx +39 -0
  104. package/site-templates/shapes/local-business-service/content/en/Service/second-job.mdx +39 -0
  105. package/site-templates/shapes/local-business-service/content/en/Service/third-job.mdx +39 -0
  106. package/site-templates/shapes/local-business-service/content/en/ServicesList/services.mdx +61 -0
  107. package/site-templates/shapes/local-business-service/provenance.json +4 -0
  108. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +471 -0
  109. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +354 -0
  110. package/site-templates/shapes/organization-service/ailk-template.json +10 -0
  111. package/site-templates/shapes/organization-service/content/en/AboutPage/about.mdx +38 -0
  112. package/site-templates/shapes/organization-service/content/en/HomePage/home.mdx +85 -0
  113. package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
  114. package/site-templates/shapes/organization-service/content/en/PortfolioList/portfolio.mdx +171 -0
  115. package/site-templates/shapes/organization-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
  116. package/site-templates/{minimalism-site → shapes/organization-service}/content/en/ServicesList/services.mdx +3 -4
  117. package/site-templates/shapes/organization-service/provenance.json +4 -0
  118. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +478 -0
  119. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +353 -0
  120. package/site-templates/shapes/person-service/ailk-template.json +10 -0
  121. package/site-templates/shapes/person-service/content/_site.mdx +85 -0
  122. package/site-templates/shapes/person-service/content/en/AboutPage/about.mdx +39 -0
  123. package/site-templates/shapes/person-service/content/en/HomePage/home.mdx +97 -0
  124. package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
  125. package/site-templates/shapes/person-service/content/en/PortfolioList/portfolio.mdx +171 -0
  126. package/site-templates/{simple-service → shapes/person-service}/content/en/PurchasePolicyPage/purchase-policy.mdx +10 -11
  127. package/site-templates/shapes/person-service/content/en/Service/first-engagement.mdx +38 -0
  128. package/site-templates/shapes/person-service/content/en/Service/second-engagement.mdx +38 -0
  129. package/site-templates/shapes/person-service/content/en/Service/third-engagement.mdx +38 -0
  130. package/site-templates/{simple-service → shapes/person-service}/content/en/ServicesList/services.mdx +16 -16
  131. package/site-templates/shapes/person-service/provenance.json +4 -0
  132. package/templates/.claude/rules/architecture.md +7 -0
  133. package/templates/.claude/skills/README.md +5 -3
  134. package/templates/.claude/skills/add-auth/references/env-vars.md +22 -15
  135. package/templates/.claude/skills/add-auth/references/oauth-app-setup.md +3 -2
  136. package/templates/.claude/skills/add-page/SKILL.md +241 -238
  137. package/templates/.claude/skills/add-page/references/page-types.md +85 -72
  138. package/templates/.claude/skills/add-page/scripts/site-pages.mjs +87 -0
  139. package/templates/.claude/skills/add-page/templates/comparison-page.mdx.tmpl +99 -0
  140. package/templates/.claude/skills/add-page/templates/content.mdx.tmpl +8 -18
  141. package/templates/.claude/skills/add-page/templates/flow-landing-page.mdx.tmpl +43 -0
  142. package/templates/.claude/skills/add-page/templates/report-page.mdx.tmpl +52 -0
  143. package/templates/.claude/skills/add-page/templates/reports-list.mdx.tmpl +38 -0
  144. package/templates/.claude/skills/check-site/SKILL.md +23 -0
  145. package/templates/.claude/skills/fill-page/SKILL.md +26 -7
  146. package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +1 -1
  147. package/templates/.claude/skills/setup-database/SKILL.md +156 -0
  148. package/templates/.claude/skills/setup-database/scripts/verify-inbox.ts +154 -0
  149. package/templates/.claude/skills/setup-deploy/SKILL.md +64 -32
  150. package/templates/.claude/skills/setup-deploy/references/env-vars-template.md +44 -36
  151. package/templates/.claude/skills/setup-deploy/references/runbook-template.md +58 -102
  152. package/templates/.claude/skills/update-page/SKILL.md +117 -0
  153. package/templates/.claude/skills/update-page/scripts/update-page.ts +640 -0
  154. package/templates/.claude/skills/update-page/templates/redirects.mjs.tmpl +31 -0
  155. package/templates/.claude/skills/upgrade-plan/SKILL.md +12 -8
  156. package/templates/.github/workflows/ci.yml +4 -0
  157. package/templates/.prettierignore +1 -0
  158. package/templates/CLAUDE.md +146 -103
  159. package/templates/DESIGN.md +103 -6
  160. package/templates/README.md +63 -43
  161. package/templates/apps/api/.env.example +2 -1
  162. package/templates/apps/api/CLAUDE.md +3 -2
  163. package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +2 -0
  164. package/templates/apps/api/src/__tests__/server.test.ts +116 -0
  165. package/templates/apps/api/src/config/__tests__/index.test.ts +115 -14
  166. package/templates/apps/api/src/config/index.ts +35 -16
  167. package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +30 -0
  168. package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +96 -0
  169. package/templates/apps/api/src/lib/lead-capture-store.ts +12 -0
  170. package/templates/apps/api/src/lib/lead-delivery.ts +52 -0
  171. package/templates/apps/api/src/lib/stripe.ts +9 -0
  172. package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +140 -0
  173. package/templates/apps/api/src/middleware/auth.ts +54 -0
  174. package/templates/apps/api/src/openapi/spec.ts +63 -12
  175. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +5 -2
  176. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +628 -0
  177. package/templates/apps/api/src/routes/checkout/sessions.ts +222 -24
  178. package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +88 -0
  179. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +100 -0
  180. package/templates/apps/api/src/routes/leads/index.ts +46 -1
  181. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +1 -1
  182. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +5 -1
  183. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +521 -20
  184. package/templates/apps/api/src/routes/webhooks/stripe.ts +124 -32
  185. package/templates/apps/api/src/server.ts +35 -0
  186. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +194 -0
  187. package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +124 -0
  188. package/templates/apps/api/src/services/__tests__/revalidation.test.ts +90 -15
  189. package/templates/apps/api/src/services/checkout-buyer.ts +72 -0
  190. package/templates/apps/api/src/services/lead-routing.ts +140 -30
  191. package/templates/apps/api/src/services/leads-inbox.ts +48 -11
  192. package/templates/apps/api/src/services/revalidation.ts +60 -15
  193. package/templates/apps/mcp/__tests__/parity.test.ts +5 -4
  194. package/templates/apps/mcp/__tests__/tools/create_project.test.ts +30 -24
  195. package/templates/apps/mcp/src/tools/create_project.ts +21 -6
  196. package/templates/apps/mcp/src/tools/purchase.ts +4 -2
  197. package/templates/apps/web/.env.example +13 -0
  198. package/templates/apps/web/CLAUDE.md +22 -28
  199. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +1 -1
  200. package/templates/apps/web/app/[locale]/(authed)/leads/__tests__/page.test.tsx +24 -0
  201. package/templates/apps/web/app/[locale]/(authed)/leads/page.tsx +17 -6
  202. package/templates/apps/web/app/[locale]/[slug]/[detail]/__tests__/page.test.tsx +110 -0
  203. package/templates/apps/web/app/[locale]/[slug]/[detail]/page.tsx +60 -0
  204. package/templates/apps/web/app/[locale]/[slug]/page.tsx +1 -1
  205. package/templates/apps/web/app/[locale]/alternatives/__tests__/page.test.tsx +161 -0
  206. package/templates/apps/web/app/[locale]/alternatives/page.tsx +104 -0
  207. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +14 -6
  208. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +10 -9
  209. package/templates/apps/web/app/[locale]/contact/__tests__/contact-form-text.test.ts +32 -0
  210. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +129 -0
  211. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +227 -84
  212. package/templates/apps/web/app/[locale]/contact/contact-form-text.ts +50 -0
  213. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +73 -20
  214. package/templates/apps/web/app/[locale]/contact/page.tsx +105 -15
  215. package/templates/apps/web/app/[locale]/contact/site-contact-email.ts +54 -0
  216. package/templates/apps/web/app/[locale]/features/[slug]/page.tsx +64 -0
  217. package/templates/apps/web/app/[locale]/features/page.tsx +52 -0
  218. package/templates/apps/web/app/[locale]/{use-cases → for}/[slug]/page.tsx +21 -5
  219. package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +73 -0
  220. package/templates/apps/web/app/[locale]/{use-cases → for}/page.tsx +8 -7
  221. package/templates/apps/web/app/[locale]/glossary/page.tsx +1 -1
  222. package/templates/apps/web/app/[locale]/layout.tsx +65 -17
  223. package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +75 -0
  224. package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +163 -0
  225. package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +66 -0
  226. package/templates/apps/web/app/[locale]/newsletter/page.tsx +106 -14
  227. package/templates/apps/web/app/[locale]/portfolio/[slug]/page.tsx +59 -0
  228. package/templates/apps/web/app/[locale]/{book-a-call → portfolio}/page.tsx +12 -13
  229. package/templates/apps/web/app/[locale]/vs/[slug]/__tests__/page.test.tsx +112 -0
  230. package/templates/apps/web/app/[locale]/vs/[slug]/page.tsx +70 -0
  231. package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +236 -44
  232. package/templates/apps/web/app/api/revalidate/route.ts +78 -36
  233. package/templates/apps/web/lib/__tests__/site-brand.test.ts +79 -0
  234. package/templates/apps/web/lib/__tests__/site-theme.test.ts +104 -11
  235. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +21 -4
  236. package/templates/apps/web/lib/locale-guard.ts +18 -0
  237. package/templates/apps/web/lib/meet-placeholder.ts +5 -0
  238. package/templates/apps/web/lib/nav-config.ts +50 -37
  239. package/templates/apps/web/lib/site-brand.tsx +13 -9
  240. package/templates/apps/web/lib/site-identity.ts +14 -0
  241. package/templates/apps/web/lib/site-logo.tsx +36 -0
  242. package/templates/apps/web/lib/site-theme.ts +46 -26
  243. package/templates/apps/web/messages/en.json +45 -5
  244. package/templates/apps/web/middleware.ts +28 -4
  245. package/templates/content/_site.mdx +4 -2
  246. package/templates/content/en/BlogList/blog.mdx +37 -16
  247. package/templates/content/en/BookCall/book-a-call.mdx +80 -0
  248. package/templates/content/en/BookCall/onsite-estimate.mdx +56 -0
  249. package/templates/content/en/BookCall/onsite-service.mdx +56 -0
  250. package/templates/content/en/BookCall/start-project.mdx +56 -0
  251. package/templates/content/en/ContactPage/contact.mdx +43 -34
  252. package/templates/content/en/HomePage/home.mdx +10 -22
  253. package/templates/content/en/LandingPage/meet.mdx +59 -0
  254. package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +0 -1
  255. package/templates/content/en/UseCasesList/{use-cases.mdx → for.mdx} +19 -18
  256. package/templates/database/CHANGELOG.md +14 -0
  257. package/templates/database/package.json +1 -1
  258. package/templates/package.json +2 -1
  259. package/templates/tools/check-claude-md.ts +128 -0
  260. package/versions.json +33 -33
  261. package/site-templates/_base/content/en/BookCall/book-call.mdx +0 -53
  262. package/site-templates/_base/content/en/UseCase/first-use-case.mdx +0 -23
  263. package/site-templates/_base/content/en/UseCase/second-use-case.mdx +0 -23
  264. package/site-templates/_base/content/en/UseCase/third-use-case.mdx +0 -23
  265. package/site-templates/minimalism-site/DESIGN.md +0 -162
  266. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +0 -470
  267. package/site-templates/minimalism-site/ailk-template.json +0 -23
  268. package/site-templates/minimalism-site/content/en/AboutPage/about.mdx +0 -110
  269. package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +0 -53
  270. package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +0 -34
  271. package/site-templates/minimalism-site/content/en/FAQPage/faq.mdx +0 -57
  272. package/site-templates/minimalism-site/content/en/LandingPage/referral.mdx +0 -77
  273. package/site-templates/minimalism-site/content/en/LandingPage/reports.mdx +0 -73
  274. package/site-templates/minimalism-site/content/en/ReportPage/annual-letter-2024.mdx +0 -52
  275. package/site-templates/minimalism-site/content/en/ReportPage/field-notes-2023.mdx +0 -52
  276. package/site-templates/minimalism-site/content/en/ReportPage/platform-shift-2025.mdx +0 -52
  277. package/site-templates/minimalism-site/content/en/ReportPage/state-of-the-market-2026.mdx +0 -52
  278. package/site-templates/minimalism-site/flows/referral.flow.json +0 -71
  279. package/site-templates/minimalism-site/provenance.json +0 -4
  280. package/site-templates/simple-service/DESIGN.md +0 -162
  281. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +0 -333
  282. package/site-templates/simple-service/ailk-template.json +0 -11
  283. package/site-templates/simple-service/content/en/BookCall/book-call.mdx +0 -53
  284. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +0 -32
  285. package/site-templates/simple-service/content/en/FAQPage/faq.mdx +0 -56
  286. package/site-templates/simple-service/content/en/HomePage/home.mdx +0 -85
  287. package/site-templates/simple-service/content/en/Service/landscape-design.mdx +0 -40
  288. package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +0 -40
  289. package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +0 -40
  290. package/site-templates/simple-service/provenance.json +0 -4
  291. package/templates/.claude/skills/add-page/templates/route.tsx.tmpl +0 -28
  292. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +0 -211
  293. package/templates/apps/web/app/[locale]/contact/__tests__/ContactForm.test.tsx +0 -96
  294. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +0 -72
  295. package/templates/content/en/SoftwareProduct/product.mdx +0 -175
  296. /package/site-templates/{minimalism-site → designs/minimalism/sample}/content/en/HomePage/home.mdx +0 -0
  297. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-1.svg +0 -0
  298. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-2.svg +0 -0
  299. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-3.svg +0 -0
  300. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-4.svg +0 -0
  301. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-5.svg +0 -0
  302. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-6.svg +0 -0
  303. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/hero-backdrop.svg +0 -0
  304. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-1.svg +0 -0
  305. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-2.svg +0 -0
  306. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-3.svg +0 -0
  307. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/wordmark-placeholder.svg +0 -0
  308. /package/site-templates/{minimalism-site → shapes/organization-service}/content/en/Service/first-engagement.mdx +0 -0
  309. /package/site-templates/{minimalism-site → shapes/organization-service}/content/en/Service/second-engagement.mdx +0 -0
  310. /package/site-templates/{minimalism-site → shapes/organization-service}/content/en/Service/third-engagement.mdx +0 -0
@@ -19,19 +19,28 @@ import { existsSync, readdirSync, readFileSync, rmdirSync, rmSync, statSync, wri
19
19
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
20
20
  import { fileURLToPath } from "node:url";
21
21
  import { AddModuleRefusedError, applyModuleToScaffold, } from "./add-module.js";
22
+ import { planDesignMd } from "./apply-design-md.js";
23
+ import { renderClaudeMd, syncClaudeMd, } from "./claude-md.js";
22
24
  import { CATALOG_TARGET_PATH, writeComponentCatalog, } from "./component-catalog.js";
23
25
  import { buildScaffoldMarkers, copyTemplate } from "./copy.js";
24
26
  import { deriveArchitecture } from "./derive-architecture.js";
27
+ import { compileDesignTheme } from "./design-compile.js";
28
+ import { applyDesignPresets, readSectionLookCatalog, shippedPageTypes, validateDesignPresets, } from "./design-presets.js";
29
+ import { installDesignSample, resolveDesign, spliceThemeBlock, } from "./design-scaffold.js";
30
+ import { writeAuthSecretFile } from "./lib/auth-secret-file.js";
25
31
  import { extractModuleBundle, installBundleTree, } from "./lib/extract-module-bundle.js";
26
32
  import { fetchTemplate } from "./lib/fetch-template.js";
27
33
  import { assertLicenseKeyShape, writeLicenseKeyFiles, } from "./lib/license-key-files.js";
28
- import { resolvePaidModules } from "./lib/module-license-gate.js";
34
+ import { assertDesignAllowed, deriveLevel, resolveModuleSet, resolvePaidModules, } from "./lib/module-license-gate.js";
29
35
  import { classifyModules } from "./lib/module-tier.js";
30
36
  import { normalizeModules } from "./module-architecture.js";
37
+ import { RECOGNIZED_SHAPES, RESERVED_SHAPES, } from "./parse-args.js";
31
38
  import { SITE_CHROME_PATH, renderSiteChrome, resolveSiteChrome, } from "./site-chrome.js";
39
+ import { planSiteManifest } from "./site-manifest.js";
40
+ import { SITE_ROSTER_PATH, planSiteManifestDesign, readSiteRoster, withMeetPlaceholderSeeds, } from "./site-roster.js";
32
41
  import { GATEABLE_SURFACES, MODULE_SURFACES, NAV_CONFIG_PATH, excludedRoutePaths, renderNavConfig, resolveSurfaces, } from "./surfaces.js";
33
42
  import { syncRoutes } from "./sync-routes.js";
34
- import { BASE_LAYER_DIR, STARTER_DESIGNS, TemplateError, mergeRoster, resolveTemplate, resolveTemplateLineage, rosterDeclares, templateDelivery, } from "./template-manifest.js";
43
+ import { BASE_LAYER_DIR, SHAPE_STARTERS, TEMPLATE_MANIFESTS, TemplateError, mergeRoster, resolveTemplate, resolveTemplateLineage, rosterDeclares, templateDelivery, templateTreeDir, } from "./template-manifest.js";
35
44
  import { validateTarget } from "./validate.js";
36
45
  const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
37
46
  const TEMPLATES_DIR = resolve(PKG_ROOT, "templates");
@@ -44,6 +53,8 @@ const TEMPLATES_DIR = resolve(PKG_ROOT, "templates");
44
53
  * `package.json` `files` instead.
45
54
  */
46
55
  const SITE_TEMPLATES_DIR = resolve(PKG_ROOT, "site-templates");
56
+ /** Where shipped design packages live (#6813 D2) — `resolveDesign`'s `designsDir`. */
57
+ const DESIGNS_DIR = join(SITE_TEMPLATES_DIR, "designs");
47
58
  const VERSIONS_FILE = resolve(PKG_ROOT, "versions.json");
48
59
  function readVersionMap() {
49
60
  if (!existsSync(VERSIONS_FILE)) {
@@ -75,6 +86,55 @@ function countFiles(dir) {
75
86
  }
76
87
  return count;
77
88
  }
89
+ /** Every `.mdx` file under `dir`, recursively, sorted (#6816 D6). */
90
+ function listMdxFiles(dir) {
91
+ if (!existsSync(dir))
92
+ return [];
93
+ const out = [];
94
+ const stack = [dir];
95
+ while (stack.length > 0) {
96
+ const current = stack.pop();
97
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
98
+ const abs = join(current, entry.name);
99
+ if (entry.isDirectory())
100
+ stack.push(abs);
101
+ else if (entry.name.endsWith(".mdx"))
102
+ out.push(abs);
103
+ }
104
+ }
105
+ return out.sort();
106
+ }
107
+ /**
108
+ * The template a `shape` scaffolds (#6754 D5, extended #6757 D6 row 1): the
109
+ * one FREE-delivery manifest of that shape — a declared `--template` alias
110
+ * (`TEMPLATE_MANIFESTS`) or a bare-shape starter with no alias of its own
111
+ * (`SHAPE_STARTERS`), the union the candidate set draws from. Reads manifest
112
+ * fields only — no roster.
113
+ *
114
+ * @throws TemplateError on a reserved or unknown shape, a shape with no Free
115
+ * starter yet (D6), or more than one (never picked silently).
116
+ */
117
+ function resolveShapeTemplate(shape) {
118
+ if (!RECOGNIZED_SHAPES.includes(shape)) {
119
+ const known = RECOGNIZED_SHAPES.join(", ");
120
+ throw new TemplateError(RESERVED_SHAPES.includes(shape)
121
+ ? `shape "${shape}" is not available yet. Choose one of: ${known}.`
122
+ : `unknown shape "${shape}". Recognized: ${known}`);
123
+ }
124
+ const ofShape = [
125
+ ...Object.values(TEMPLATE_MANIFESTS),
126
+ ...Object.values(SHAPE_STARTERS),
127
+ ].filter((manifest) => manifest.shape === shape);
128
+ const free = ofShape.filter((manifest) => templateDelivery(manifest) === "free");
129
+ if (free.length === 0) {
130
+ const names = ofShape.map((manifest) => manifest.name).join(", ");
131
+ throw new TemplateError(`shape "${shape}" has no Free starter yet — its page list lands with #6743. Templates built on this shape: ${names || "none"}.`);
132
+ }
133
+ if (free.length > 1) {
134
+ throw new TemplateError(`shape "${shape}" has more than one Free starter: ${free.map((manifest) => manifest.name).join(", ")}`);
135
+ }
136
+ return free[0];
137
+ }
78
138
  /**
79
139
  * Scaffold an AI Launch Kit starter project into targetDir (synchronous).
80
140
  *
@@ -96,6 +156,9 @@ export function scaffoldSync(opts) {
96
156
  excludePaths: excludedRoutePaths(enabledSurfaces),
97
157
  markers: buildScaffoldMarkers(projectName),
98
158
  });
159
+ // Stamp the copied CLAUDE.md's template regions with their hashes (#6904
160
+ // D2). No later layer writes a root CLAUDE.md, so this is the final file.
161
+ writeFileSync(join(targetAbs, "CLAUDE.md"), renderClaudeMd(readFileSync(join(TEMPLATES_DIR, "CLAUDE.md"), "utf8"), projectName));
99
162
  // Derive + write the module-set-driven architecture.yaml (spec #3787). This
100
163
  // is the single source of the scaffolded site's code-boundary config and
101
164
  // closes the dangling `architecture_rules: architecture.yaml` reference in
@@ -112,13 +175,18 @@ export function scaffoldSync(opts) {
112
175
  // template's `layout` block; a bare `scaffoldSync` call (every existing
113
176
  // synchronous caller) writes DEFAULT_SITE_CHROME, byte-identical to the
114
177
  // monorepo's own committed apps/web/lib/site-chrome.ts.
115
- writeFileSync(join(targetAbs, SITE_CHROME_PATH), renderSiteChrome(resolveSiteChrome(opts.layout, opts.design)));
178
+ writeFileSync(join(targetAbs, SITE_CHROME_PATH), renderSiteChrome(resolveSiteChrome(opts.layout, opts.designLayout)));
116
179
  // Opt-in only (#4636). Every write above this line is unconditional, so a
117
180
  // scaffold with `withCatalog` unset touches exactly the files it did before.
118
181
  if (opts.withCatalog === true)
119
182
  writeComponentCatalog(targetAbs);
120
183
  const filesCreated = countFiles(targetAbs);
121
- return { targetDir: targetAbs, filesCreated, surfaces: enabledSurfaces };
184
+ return {
185
+ targetDir: targetAbs,
186
+ filesCreated,
187
+ surfaces: enabledSurfaces,
188
+ modules: normalizeModules(opts.modules ?? ["marketing"]),
189
+ };
122
190
  }
123
191
  /**
124
192
  * Scaffold an AI Launch Kit starter project into targetDir (async wrapper).
@@ -138,8 +206,14 @@ export function scaffoldSync(opts) {
138
206
  * its fetched bundle or — when it requires only free modules — from the
139
207
  * `site-templates/` tree this package ships.
140
208
  *
141
- * And, ahead of all five when `opts.template` is set (0): resolve the
142
- * template and union its `requires` with `opts.modules`, so every step above
209
+ * Between 2 and 3, `assertDesignAllowed` (#6760) refuses a design the caller
210
+ * CHOSE (`opts.design`) for a level below Pro, HERE, also before anything is
211
+ * written to disk. A template's own declared design is never gated (#6778
212
+ * option (c)): the template's `requires` already decide its level.
213
+ *
214
+ * And, ahead of all five when `opts.template` (or `opts.shape`, #6754 D5) is
215
+ * set (0): resolve the template and union its `requires` with `opts.modules`,
216
+ * so every step above
143
217
  * runs over the EFFECTIVE module set rather than the requested one.
144
218
  *
145
219
  * `scaffoldSync` itself stays licensing-oblivious on purpose: every existing
@@ -155,39 +229,150 @@ export async function scaffold(opts) {
155
229
  const licenseKey = opts.license ?? opts.licenseKey;
156
230
  // Resolved first, and by the registry rather than re-validated here: the
157
231
  // name already passed `parse-args.ts`, and `resolveTemplate` throws a
158
- // `TemplateError` for a programmatic caller that skipped the parser.
159
- const template = opts.template !== undefined ? resolveTemplate(opts.template) : undefined;
160
- // #6638 — the design this scaffold renders: the caller's override, else the
161
- // template's own. Unset for a scaffold naming neither.
162
- const design = opts.design ?? template?.design;
163
- if (design !== undefined && !STARTER_DESIGNS.includes(design)) {
164
- throw new TemplateError(`unknown design "${design}". Declared: ${STARTER_DESIGNS.join(", ")}`);
232
+ // `TemplateError` for a programmatic caller that skipped the parser. A
233
+ // `shape` resolves to its one Free starter and from there is the template
234
+ // path verbatim (#6754 D5); neither leaves the bare overlay untouched.
235
+ if (opts.template !== undefined && opts.shape !== undefined) {
236
+ throw new TemplateError("pass a template or a shape, not both");
165
237
  }
238
+ const template = opts.template !== undefined
239
+ ? resolveTemplate(opts.template)
240
+ : opts.shape !== undefined
241
+ ? resolveShapeTemplate(opts.shape)
242
+ : undefined;
243
+ // #6813 D8 step 3 — the design this scaffold renders, resolved from its
244
+ // package ONCE, before any fetch or write: the caller's override, else the
245
+ // template's own. Unset for a scaffold naming neither. Replaces the
246
+ // `STARTER_DESIGNS.includes` name check: an unrecognized name or a missing
247
+ // directory throws `TemplateError` here; a malformed package throws
248
+ // `DesignPackageError` (never caught), each naming the field.
249
+ const designRef = opts.design ?? template?.design;
250
+ const resolvedDesign = designRef !== undefined
251
+ ? resolveDesign(designRef, { designsDir: DESIGNS_DIR })
252
+ : undefined;
253
+ // #6816 D6 step 3b — a design's section presets are validated here, before
254
+ // any fetch or write, against the look groups the shipped catalog carries
255
+ // (D2): a non-look field or unknown variant throws `DesignPackageError`
256
+ // naming page type, section type and field, and the target is never
257
+ // created. A design with no `sections` block executes no new read (D8).
258
+ if (resolvedDesign !== undefined &&
259
+ Object.keys(resolvedDesign.pkg.sections).length > 0) {
260
+ validateDesignPresets(resolvedDesign.pkg.sections, {
261
+ catalog: readSectionLookCatalog(join(PKG_ROOT, "component-catalog.json")),
262
+ pageTypes: shippedPageTypes(SITE_TEMPLATES_DIR),
263
+ file: join(resolvedDesign.dir, "DESIGN.md"),
264
+ });
265
+ }
266
+ // #6813 D7 — the resolved design only when the CALLER chose one (`opts.design`);
267
+ // a template's own declared design is never gated (#6778 option (c)).
268
+ const chosenDesign = opts.design !== undefined ? resolvedDesign : undefined;
166
269
  // #5618 D2 — the `extends` chain is resolved in the client registry before
167
270
  // any fetch: an unknown parent or a cycle is refused here, with the target
168
- // still non-existent, like the four refusals below.
169
- if (template !== undefined)
170
- resolveTemplateLineage(template.name);
271
+ // still non-existent, like the four refusals below. The registry is the
272
+ // union of TEMPLATE_MANIFESTS and SHAPE_STARTERS (#6757 D6 row 1): a
273
+ // `--shape` run's resolved template may be a bare-shape starter, whose name
274
+ // (the shape itself) `TEMPLATE_MANIFESTS` alone does not carry.
275
+ if (template !== undefined) {
276
+ resolveTemplateLineage(template.name, {
277
+ ...TEMPLATE_MANIFESTS,
278
+ ...SHAPE_STARTERS,
279
+ });
280
+ }
171
281
  // The effective module set: union(template.requires, parsed --modules),
172
282
  // normalized through `normalizeModules` — the single ordering authority
173
283
  // (parent record D7). Order-independence falls out rather than being
174
284
  // engineered: it returns `RECOGNIZED_MODULES.filter(...)`, declaration-
175
285
  // ordered and deduped, so the same SET in any argument order is the same
176
286
  // list.
177
- const modules = normalizeModules([
178
- ...(template?.requires ?? []),
179
- ...(opts.modules ?? ["marketing"]),
180
- ]);
181
- const { paid } = classifyModules(modules);
182
- const bundles = await resolvePaidModules({
183
- paidModules: paid,
184
- licenseKey,
185
- ...(template !== undefined ? { templateName: template.name } : {}),
186
- ...(opts.moduleRegistryUrl !== undefined
187
- ? { registryUrl: opts.moduleRegistryUrl }
188
- : {}),
189
- ...(opts.fetchImpl !== undefined ? { fetchImpl: opts.fetchImpl } : {}),
190
- });
287
+ let modules;
288
+ let bundles;
289
+ // The run's derived level (#6759), for `assertDesignAllowed` below.
290
+ // Undefined on the alias branch and on a `--modules`-keep run (D2/D4 in
291
+ // module-license-gate.ts — the level plays no part in composing either),
292
+ // so a non-`standard` design on either path derives it separately, below.
293
+ let level;
294
+ if (opts.template !== undefined) {
295
+ // Alias run — untouched (NFR-4): the union line and its
296
+ // resolvePaidModules({ …, templateName }) call stay exactly as they were.
297
+ modules = normalizeModules([
298
+ ...(template?.requires ?? []),
299
+ ...(opts.modules ?? ["marketing"]),
300
+ ]);
301
+ const { paid } = classifyModules(modules);
302
+ bundles = await resolvePaidModules({
303
+ paidModules: paid,
304
+ licenseKey,
305
+ ...(template !== undefined ? { templateName: template.name } : {}),
306
+ ...(opts.moduleRegistryUrl !== undefined
307
+ ? { registryUrl: opts.moduleRegistryUrl }
308
+ : {}),
309
+ ...(opts.fetchImpl !== undefined ? { fetchImpl: opts.fetchImpl } : {}),
310
+ });
311
+ // #6760 — `resolveModuleSet` never runs on this branch (NFR-4), so no
312
+ // level was derived above. Derive one — from the registry's per-module
313
+ // answers, never a key claim (D2) — only when the caller CHOSE a
314
+ // non-Free design alongside the template (`opts.design`, #6813 D7). An
315
+ // alias's own declared design is never gated (#6778 option (c)), so a
316
+ // plain `--template minimalism-site` run makes no extra network call and
317
+ // is never refused on its design.
318
+ if (chosenDesign !== undefined && !chosenDesign.free) {
319
+ ({ level } = await deriveLevel({
320
+ licenseKey,
321
+ ...(opts.moduleRegistryUrl !== undefined
322
+ ? { registryUrl: opts.moduleRegistryUrl }
323
+ : {}),
324
+ ...(opts.fetchImpl !== undefined ? { fetchImpl: opts.fetchImpl } : {}),
325
+ }));
326
+ }
327
+ }
328
+ else {
329
+ // Shape and bare runs — the key's level decides (#6759 D6), never a flag,
330
+ // a manifest or a key claim.
331
+ const chosen = await resolveModuleSet({
332
+ named: opts.modules,
333
+ licenseKey,
334
+ ...(opts.moduleRegistryUrl !== undefined
335
+ ? { registryUrl: opts.moduleRegistryUrl }
336
+ : {}),
337
+ ...(opts.fetchImpl !== undefined ? { fetchImpl: opts.fetchImpl } : {}),
338
+ });
339
+ bundles = chosen.bundles;
340
+ level = chosen.level;
341
+ modules =
342
+ template !== undefined
343
+ ? normalizeModules([...template.requires, ...chosen.modules])
344
+ : chosen.modules;
345
+ // A paid member of the shape's own starter `requires` not already
346
+ // fetched during the level probe (D6) — every buildable shape's starter
347
+ // requires only `marketing` today, so this is presently a no-op branch.
348
+ if (template !== undefined) {
349
+ const templatePaid = classifyModules(template.requires).paid;
350
+ const stillMissing = templatePaid.filter((m) => !bundles.has(m));
351
+ if (stillMissing.length > 0) {
352
+ const extra = await resolvePaidModules({
353
+ paidModules: stillMissing,
354
+ licenseKey,
355
+ ...(opts.moduleRegistryUrl !== undefined
356
+ ? { registryUrl: opts.moduleRegistryUrl }
357
+ : {}),
358
+ ...(opts.fetchImpl !== undefined
359
+ ? { fetchImpl: opts.fetchImpl }
360
+ : {}),
361
+ });
362
+ for (const [k, v] of extra)
363
+ bundles.set(k, v);
364
+ }
365
+ }
366
+ }
367
+ // #6760 D4 — after the level is resolved, before any fetch or write below.
368
+ // Only the caller's chosen design is gated (#6778 option (c)); a template's
369
+ // own declared design is not. `level ?? "free"` fails closed: a
370
+ // `--modules`-keep run under-reports its level by design (NFR-2), so a
371
+ // chosen non-Free design on that path is refused rather than trusted. The
372
+ // refusal names `chosen.display` — the name, or the absolute directory
373
+ // (#6813 D7) — and `chosen.free` decides Free-ness by identity (D2), never
374
+ // by `FREE_DESIGNS.includes(name)` alone (a path is never in that list).
375
+ assertDesignAllowed(chosenDesign?.display, level ?? "free", chosenDesign?.free);
191
376
  // The template's OWN bundle, fetched here — before `scaffoldSync` — so the
192
377
  // fourth refusal (the template's bundle unpublished, parent record D17 /
193
378
  // spec 5481 § 3 DD5 row 4) throws with the target still non-existent, like
@@ -241,7 +426,9 @@ export async function scaffold(opts) {
241
426
  // design's knobs (#6638), and a `standard` one to the same defaults
242
427
  // `scaffoldSync` already writes for a templateless scaffold.
243
428
  ...(template?.layout !== undefined ? { layout: template.layout } : {}),
244
- ...(design !== undefined ? { design } : {}),
429
+ // #6813 D8 step 6 — the resolved design's OWN layout (never its name):
430
+ // `resolveSiteChrome` reads a layout object now, not a `StarterDesign`.
431
+ ...(resolvedDesign !== undefined ? { designLayout: resolvedDesign.pkg.layout } : {}),
245
432
  });
246
433
  // Threaded to extractModuleBundle for `package.json` patch targets'
247
434
  // `workspace:*` substitution (spec 5418 § 3 D2) — the same version map
@@ -306,6 +493,11 @@ export async function scaffold(opts) {
306
493
  //
307
494
  // One install step, two source roots (DD7): an extracted staging dir on the
308
495
  // paid path, `site-templates/<name>/` on disk on the Free one.
496
+ // #6813 D6 — captured for the site-manifest record below, which needs the
497
+ // chrome ACTUALLY written (`installedLayout ?? template.layout`), readable
498
+ // only after this block (the bundle's own `layout`, when it declares one,
499
+ // is unreachable before install).
500
+ let installedLayout;
309
501
  if (template !== undefined) {
310
502
  // #5612 D1/D2 — the INSTALLED template's own `ailk-template.json` layout
311
503
  // (validated by `readAilkTemplateManifest`), if it declares one. Read
@@ -326,7 +518,7 @@ export async function scaffold(opts) {
326
518
  ownership,
327
519
  })
328
520
  : (() => {
329
- const sourceRoot = join(SITE_TEMPLATES_DIR, template.name);
521
+ const sourceRoot = join(SITE_TEMPLATES_DIR, templateTreeDir(template.name));
330
522
  if (!existsSync(sourceRoot)) {
331
523
  throw new Error(`template "${template.name}": ${sourceRoot} is missing — the published package must carry site-templates/ in its "files" list`);
332
524
  }
@@ -341,16 +533,133 @@ export async function scaffold(opts) {
341
533
  ownership,
342
534
  });
343
535
  })();
536
+ installedLayout = installed.layout;
344
537
  if (installed.layout !== undefined) {
345
- writeFileSync(join(result.targetDir, SITE_CHROME_PATH), renderSiteChrome(resolveSiteChrome(installed.layout, design)));
538
+ writeFileSync(join(result.targetDir, SITE_CHROME_PATH), renderSiteChrome(resolveSiteChrome(installed.layout, resolvedDesign?.pkg.layout)));
346
539
  }
540
+ // #6878 D6 — re-render the nav with the SERVICE slugs the installed tree
541
+ // just landed (`content/en/Service/*.mdx`, absent on a product shape),
542
+ // now that `content/en/Service` exists to read. `scaffoldSync`'s own
543
+ // one-argument render above already wrote a nav for `result.surfaces`
544
+ // BEFORE the template tree existed — byte-identical to this one on a
545
+ // product shape (no Service directory to read), and this is what makes a
546
+ // service shape's flyout show its own Services column instead of an
547
+ // empty offering column. Unconditional (every templated scaffold gets
548
+ // this second render, not only one that resolved a `layout`), and BEFORE
549
+ // `pruneUnrosteredSeeds` below: the read reflects every seed the install
550
+ // step actually wrote, not what a later prune leaves behind.
551
+ const serviceContentDir = join(result.targetDir, "content", "en", "Service");
552
+ const serviceSlugs = existsSync(serviceContentDir)
553
+ ? readdirSync(serviceContentDir)
554
+ .filter((f) => f.endsWith(".mdx"))
555
+ .map((f) => f.slice(0, -".mdx".length))
556
+ : [];
557
+ writeFileSync(join(result.targetDir, NAV_CONFIG_PATH), renderNavConfig(result.surfaces, { serviceSlugs }));
347
558
  pruneUnrosteredSeeds(result.targetDir, mergeRoster(template, template.requires), result.surfaces, ownership);
559
+ // spec 6892 D4 — the resolved design's own sample (the minimalism design's
560
+ // nine-slide deck home + its SVGs today), installed AFTER the template
561
+ // layer so it wins over the shape's `HomePage/home.mdx`, and ONLY when
562
+ // the resolved design is the template's OWN declared design: a caller
563
+ // combination like `--shape organization-product --design minimalism`
564
+ // installs no sample (its roster carries neither the deck's link targets
565
+ // nor, in its generated record, the deck's sections).
566
+ if (resolvedDesign !== undefined &&
567
+ resolvedDesign.recorded === template.design) {
568
+ installDesignSample({
569
+ designDir: resolvedDesign.dir,
570
+ targetDir: result.targetDir,
571
+ ownership,
572
+ });
573
+ }
348
574
  }
575
+ // #6813 D5/D8 steps 9-10 — whenever a design is resolved (the caller's,
576
+ // else the template's): the design's DESIGN.md (#6939); the compiled theme,
577
+ // spliced into the scaffold's `content/_site.mdx` LAST; then the site manifest records the APPLIED
578
+ // design, only when a template installed one (`saas-product` ships none).
579
+ // A scaffold naming neither a design nor a template (the bare overlay)
580
+ // executes no new statement.
581
+ if (resolvedDesign !== undefined) {
582
+ // #6939 — the scaffold's DESIGN.md is the design it wears, not the one
583
+ // its template layer shipped; the site's own header lines are kept.
584
+ writeFileSync(join(result.targetDir, "DESIGN.md"), planDesignMd(resolvedDesign.dir, result.targetDir));
585
+ const compiledTheme = compileDesignTheme(resolvedDesign.pkg);
586
+ const siteMdxPath = join(result.targetDir, "content", "_site.mdx");
587
+ if (existsSync(siteMdxPath)) {
588
+ const current = readFileSync(siteMdxPath, "utf8");
589
+ const spliced = spliceThemeBlock(current, compiledTheme, siteMdxPath);
590
+ if (spliced !== current)
591
+ writeFileSync(siteMdxPath, spliced);
592
+ }
593
+ if (template !== undefined) {
594
+ const rosterPath = join(result.targetDir, SITE_ROSTER_PATH);
595
+ if (existsSync(rosterPath)) {
596
+ const rosterText = readFileSync(rosterPath, "utf8");
597
+ const chromeWritten = resolveSiteChrome(installedLayout ?? template.layout, resolvedDesign.pkg.layout);
598
+ const nextManifest = planSiteManifestDesign(rosterText, { design: resolvedDesign.recorded, layout: chromeWritten, theme: compiledTheme }, rosterPath);
599
+ if (nextManifest !== undefined)
600
+ writeFileSync(rosterPath, nextManifest);
601
+ }
602
+ }
603
+ // #6816 D6 step 10b — the design's section presets, written onto the
604
+ // pages of the page types the roster of record places, once every layer
605
+ // has landed. The one `applyDesignPresets` call site. A scaffold with no
606
+ // roster (the bare overlay, `saas-product`) validated its presets at
607
+ // step 3b and applies none: nothing "the shape placed" to resolve against.
608
+ if (template !== undefined &&
609
+ Object.keys(resolvedDesign.pkg.sections).length > 0) {
610
+ const roster = readSiteRoster(result.targetDir);
611
+ if (roster !== undefined) {
612
+ const pageTypes = new Set();
613
+ for (const row of roster.rows) {
614
+ const pageType = row.value["pageType"];
615
+ if (typeof pageType === "string")
616
+ pageTypes.add(pageType);
617
+ }
618
+ const contentDir = join(result.targetDir, "content");
619
+ const pages = listMdxFiles(contentDir)
620
+ .filter((path) => path !== join(contentDir, "_site.mdx"))
621
+ .map((path) => ({ path, text: readFileSync(path, "utf8") }));
622
+ const presetResult = applyDesignPresets(pages, resolvedDesign.pkg.sections, { pageTypes });
623
+ for (const page of presetResult.pages)
624
+ writeFileSync(page.path, page.text);
625
+ }
626
+ }
627
+ }
628
+ // #6944 — the manifest records what this scaffold IS, once every layer has
629
+ // landed: the effective module set, a row for every route on disk, and the
630
+ // level only when the key path above derived one — `resolveModuleSet`, or
631
+ // `deriveLevel` for a chosen non-Free design. `level` stays undefined on a
632
+ // plain alias or `--modules`-keep run, and is never inferred from `modules`.
633
+ // A scaffold with no manifest (the bare overlay, `saas-product`) writes none.
634
+ if (template !== undefined) {
635
+ const roster = readSiteRoster(result.targetDir);
636
+ if (roster !== undefined) {
637
+ writeFileSync(join(result.targetDir, roster.path), planSiteManifest({
638
+ targetDir: result.targetDir,
639
+ roster,
640
+ modules: result.modules,
641
+ level,
642
+ siteTemplatesDir: SITE_TEMPLATES_DIR,
643
+ templatesDir: TEMPLATES_DIR,
644
+ markers,
645
+ }));
646
+ }
647
+ }
648
+ // #6107 D2 — after every layer landed, so nothing later overwrites it: a
649
+ // served scaffold needs BETTER_AUTH_SECRET (#6044), and this is its value.
650
+ writeAuthSecretFile(result.targetDir);
349
651
  // #6683 D9 — persisted only after every layer landed, and only when the
350
652
  // caller passed `license`: a scaffold without it writes nothing new.
351
653
  if (opts.license !== undefined) {
352
654
  writeLicenseKeyFiles(result.targetDir, opts.license);
353
655
  }
656
+ // #6754 D4/D5 — the resolved starter, so the CLI names what it built.
657
+ if (template !== undefined && resolvedDesign !== undefined) {
658
+ return {
659
+ ...result,
660
+ starter: { template: template.name, shape: template.shape, design: resolvedDesign.recorded },
661
+ };
662
+ }
354
663
  return result;
355
664
  }
356
665
  /**
@@ -368,12 +677,17 @@ export async function scaffold(opts) {
368
677
  * the content directory of a surface this scaffold ENABLES is kept too, so
369
678
  * an explicit `--surfaces pricing` on a template that does not roster
370
679
  * `/pricing` gets its route and its seed together.
680
+ *
681
+ * The Free `/meet` placeholder seeds count as declared wherever the roster
682
+ * claims their route, the `scheduling` module's rows included (#6942): an
683
+ * unconfigured scheduling site's /meet pages hand off to them.
371
684
  */
372
685
  function pruneUnrosteredSeeds(targetDir, roster, surfaces, ownership) {
373
686
  // `installBundleTree`'s ownership label for the base layer: it installs
374
687
  // with `templateRequires: []`, so it records as a template. The overlay
375
688
  // (the `templates/` snapshot) records no owner at all.
376
689
  const shared = new Set([undefined, `template "${BASE_LAYER_DIR}"`]);
690
+ const declared = withMeetPlaceholderSeeds(roster);
377
691
  const enabledDirs = GATEABLE_SURFACES.filter((surface) => surfaces.includes(surface.id)).flatMap((surface) => surface.content.map((dir) => `${dir.replace("<locale>", "en")}/`));
378
692
  const root = join(targetDir, "content", "en");
379
693
  if (!existsSync(root))
@@ -393,7 +707,7 @@ function pruneUnrosteredSeeds(targetDir, roster, surfaces, ownership) {
393
707
  continue;
394
708
  const path = relative(targetDir, abs).split(sep).join("/");
395
709
  if (shared.has(ownership.get(path)) &&
396
- !rosterDeclares(roster, path) &&
710
+ !rosterDeclares(declared, path) &&
397
711
  !enabledDirs.some((prefix) => path.startsWith(prefix))) {
398
712
  rmSync(abs);
399
713
  if (readdirSync(dir).length === 0)
@@ -526,6 +840,19 @@ export function syncCatalogSync(opts) {
526
840
  catalogPath: writeComponentCatalog(targetAbs),
527
841
  };
528
842
  }
843
+ /**
844
+ * Rewrite the template-owned sections of an already-scaffolded repo's root
845
+ * `CLAUDE.md` to the template this create-ailk ships (#6904 D3). The logic
846
+ * lives in `claude-md.ts` (a module jest imports directly); this wrapper
847
+ * only supplies the package's `templates/CLAUDE.md` — the `syncRoutesSync`
848
+ * split. `templateText` stands in for the shipped template in tests.
849
+ */
850
+ export function syncClaudeMdSync(opts) {
851
+ return syncClaudeMd({
852
+ targetDir: opts.targetDir,
853
+ templateText: opts.templateText ?? readFileSync(join(TEMPLATES_DIR, "CLAUDE.md"), "utf8"),
854
+ });
855
+ }
529
856
  /**
530
857
  * Compare an already-scaffolded repo's route tree with the template this
531
858
  * create-ailk ships and, with `apply`, copy in the route dirs it is missing
@@ -21,11 +21,14 @@
21
21
  * block, gets these defaults written, byte-identical to the monorepo's own
22
22
  * committed `apps/web/lib/site-chrome.ts`.
23
23
  *
24
- * A template's DESIGN supplies layout knobs too (#6638): `DESIGN_LAYOUTS` in
25
- * `template-manifest.ts`, read by `resolveSiteChrome`'s optional `design`
26
- * argument between an explicit `layout` and these defaults.
24
+ * A design supplies layout knobs too (#6638, widened #6813 D4): its OWN
25
+ * `layout` (a `DesignPackage`'s, read by `design-scaffold.ts`'s
26
+ * `resolveDesign`), passed as `resolveSiteChrome`'s optional `designLayout`
27
+ * argument between an explicit `layout` and these defaults. This module
28
+ * imports nothing from `template-manifest.ts` (the cycle that import used to
29
+ * close): an unrecognized design NAME now fails in `resolveDesign`, before
30
+ * this function ever sees one.
27
31
  */
28
- import { type StarterDesign } from "./template-manifest.js";
29
32
  /** The `nav` knob's vocabulary. `mark-only` → the layout passes Nav an empty
30
33
  * `navItems` (brand mark + the existing actions/theme/locale controls only). */
31
34
  export type SiteChromeNav = "full" | "mark-only";
@@ -75,12 +78,19 @@ export declare class TemplateLayoutError extends Error {
75
78
  */
76
79
  export declare function validateTemplateLayout(value: unknown, subject: string): TemplateLayout;
77
80
  /**
78
- * Fill every field a `TemplateLayout` leaves unset — from `design`'s
79
- * `DESIGN_LAYOUTS` entry when one is named (#6638), then from
80
- * `DEFAULT_SITE_CHROME`. The explicit `layout` wins field by field, so a
81
- * template can wear a design and still override one knob of it.
81
+ * Fill every field a `TemplateLayout` leaves unset — from `designLayout`
82
+ * (a design package's own resolved `layout`, #6638/#6813 D4) when one is
83
+ * given, then from `DEFAULT_SITE_CHROME`. The explicit `layout` wins field by
84
+ * field, so a template can wear a design and still override one knob of it.
85
+ *
86
+ * `designLayout` is typed `TemplateLayout`, but a `.mjs` caller
87
+ * (`build-template-manifest.mjs`) cannot be compile-time checked the way a
88
+ * `.ts` caller can, so the type alone cannot stop a caller passing a design
89
+ * NAME (a string) rather than its resolved layout: a runtime guard throws a
90
+ * `TypeError` naming what was given, rather than silently resolving to the
91
+ * defaults.
82
92
  */
83
- export declare function resolveSiteChrome(layout?: TemplateLayout, design?: StarterDesign): ResolvedSiteChrome;
93
+ export declare function resolveSiteChrome(layout?: TemplateLayout, designLayout?: TemplateLayout): ResolvedSiteChrome;
84
94
  /**
85
95
  * Render `apps/web/lib/site-chrome.ts` for a resolved layout — the same
86
96
  * GENERATED-header idiom `renderNavConfig` uses. `layout.tsx` imports
@@ -21,11 +21,14 @@
21
21
  * block, gets these defaults written, byte-identical to the monorepo's own
22
22
  * committed `apps/web/lib/site-chrome.ts`.
23
23
  *
24
- * A template's DESIGN supplies layout knobs too (#6638): `DESIGN_LAYOUTS` in
25
- * `template-manifest.ts`, read by `resolveSiteChrome`'s optional `design`
26
- * argument between an explicit `layout` and these defaults.
24
+ * A design supplies layout knobs too (#6638, widened #6813 D4): its OWN
25
+ * `layout` (a `DesignPackage`'s, read by `design-scaffold.ts`'s
26
+ * `resolveDesign`), passed as `resolveSiteChrome`'s optional `designLayout`
27
+ * argument between an explicit `layout` and these defaults. This module
28
+ * imports nothing from `template-manifest.ts` (the cycle that import used to
29
+ * close): an unrecognized design NAME now fails in `resolveDesign`, before
30
+ * this function ever sees one.
27
31
  */
28
- import { DESIGN_LAYOUTS, TemplateError, } from "./template-manifest.js";
29
32
  /** Defaults (#5612 D1; banner off since #6469). */
30
33
  export const DEFAULT_SITE_CHROME = {
31
34
  nav: "full",
@@ -92,17 +95,24 @@ export function validateTemplateLayout(value, subject) {
92
95
  return result;
93
96
  }
94
97
  /**
95
- * Fill every field a `TemplateLayout` leaves unset — from `design`'s
96
- * `DESIGN_LAYOUTS` entry when one is named (#6638), then from
97
- * `DEFAULT_SITE_CHROME`. The explicit `layout` wins field by field, so a
98
- * template can wear a design and still override one knob of it.
98
+ * Fill every field a `TemplateLayout` leaves unset — from `designLayout`
99
+ * (a design package's own resolved `layout`, #6638/#6813 D4) when one is
100
+ * given, then from `DEFAULT_SITE_CHROME`. The explicit `layout` wins field by
101
+ * field, so a template can wear a design and still override one knob of it.
102
+ *
103
+ * `designLayout` is typed `TemplateLayout`, but a `.mjs` caller
104
+ * (`build-template-manifest.mjs`) cannot be compile-time checked the way a
105
+ * `.ts` caller can, so the type alone cannot stop a caller passing a design
106
+ * NAME (a string) rather than its resolved layout: a runtime guard throws a
107
+ * `TypeError` naming what was given, rather than silently resolving to the
108
+ * defaults.
99
109
  */
100
- export function resolveSiteChrome(layout, design) {
101
- const fromDesign = design !== undefined ? DESIGN_LAYOUTS[design] : {};
102
- // A JS caller (or a cast) can reach here with an undeclared design.
103
- if (fromDesign === undefined) {
104
- throw new TemplateError(`unknown design "${design}". Declared: ${Object.keys(DESIGN_LAYOUTS).join(", ")}`);
110
+ export function resolveSiteChrome(layout, designLayout) {
111
+ if (designLayout !== undefined &&
112
+ (typeof designLayout !== "object" || designLayout === null || Array.isArray(designLayout))) {
113
+ throw new TypeError(`resolveSiteChrome: designLayout must be a layout object, got ${JSON.stringify(designLayout)}`);
105
114
  }
115
+ const fromDesign = designLayout ?? {};
106
116
  return {
107
117
  nav: layout?.nav ?? fromDesign.nav ?? DEFAULT_SITE_CHROME.nav,
108
118
  footer: layout?.footer ?? fromDesign.footer ?? DEFAULT_SITE_CHROME.footer,