create-ailk 0.8.0 → 0.10.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 (238) hide show
  1. package/README.md +71 -3
  2. package/component-catalog.json +279 -45
  3. package/dist/add-module.d.ts +20 -0
  4. package/dist/add-module.js +43 -7
  5. package/dist/add-route.d.ts +56 -0
  6. package/dist/add-route.js +286 -0
  7. package/dist/apply-design-md.d.ts +7 -0
  8. package/dist/apply-design-md.js +50 -0
  9. package/dist/apply-design.d.ts +64 -0
  10. package/dist/apply-design.js +273 -0
  11. package/dist/claude-md.d.ts +111 -0
  12. package/dist/claude-md.js +334 -0
  13. package/dist/cli.js +104 -5
  14. package/dist/derive-architecture.js +41 -36
  15. package/dist/design-compile.d.ts +71 -0
  16. package/dist/design-compile.js +98 -0
  17. package/dist/design-package.d.ts +83 -0
  18. package/dist/design-package.js +468 -0
  19. package/dist/design-presets.d.ts +86 -0
  20. package/dist/design-presets.js +642 -0
  21. package/dist/design-scaffold.d.ts +76 -0
  22. package/dist/design-scaffold.js +220 -0
  23. package/dist/index.d.ts +2 -1
  24. package/dist/index.js +4 -1
  25. package/dist/legacy-nav-renderers.d.ts +45 -0
  26. package/dist/legacy-nav-renderers.js +545 -0
  27. package/dist/lib/auth-secret-file.d.ts +19 -0
  28. package/dist/lib/auth-secret-file.js +112 -0
  29. package/dist/lib/design-checks.d.ts +78 -0
  30. package/dist/lib/design-checks.js +174 -0
  31. package/dist/lib/license-key-files.d.ts +9 -0
  32. package/dist/lib/license-key-files.js +2 -2
  33. package/dist/lib/module-license-gate.d.ts +48 -32
  34. package/dist/lib/module-license-gate.js +117 -8
  35. package/dist/lib/render-verdict.d.ts +52 -0
  36. package/dist/lib/render-verdict.js +77 -0
  37. package/dist/lib/scaffold-render-sweep.d.ts +105 -0
  38. package/dist/lib/scaffold-render-sweep.js +236 -0
  39. package/dist/module-architecture.d.ts +19 -16
  40. package/dist/module-architecture.js +67 -23
  41. package/dist/parse-args.d.ts +12 -2
  42. package/dist/parse-args.js +74 -13
  43. package/dist/programmatic.d.ts +30 -6
  44. package/dist/programmatic.js +216 -19
  45. package/dist/site-chrome.d.ts +19 -9
  46. package/dist/site-chrome.js +23 -13
  47. package/dist/site-manifest.d.ts +25 -0
  48. package/dist/site-manifest.js +254 -0
  49. package/dist/site-roster.d.ts +41 -0
  50. package/dist/site-roster.js +141 -8
  51. package/dist/starter-grid.d.ts +16 -63
  52. package/dist/starter-grid.js +102 -84
  53. package/dist/surfaces.d.ts +41 -9
  54. package/dist/surfaces.js +200 -83
  55. package/dist/template-manifest.d.ts +30 -13
  56. package/dist/template-manifest.js +53 -28
  57. package/package.json +6 -3
  58. package/release-scope.json +25 -0
  59. package/site-templates/_base/content/en/UseCase/persona-1.mdx +7 -6
  60. package/site-templates/_base/content/en/UseCase/persona-2.mdx +7 -6
  61. package/site-templates/_base/content/en/UseCase/persona-3.mdx +7 -6
  62. package/site-templates/designs/minimalism/DESIGN.md +98 -6
  63. package/site-templates/designs/minimalism/sample/README.md +48 -0
  64. package/site-templates/{minimalism-site → designs/minimalism/sample}/content/en/HomePage/home.mdx +1 -1
  65. package/site-templates/designs/standard/DESIGN.md +98 -6
  66. package/site-templates/marketing-site/DESIGN.md +98 -6
  67. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +60 -27
  68. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +50 -30
  69. package/site-templates/marketing-site-free/DESIGN.md +98 -6
  70. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +60 -24
  71. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +44 -27
  72. package/site-templates/marketing-site-free/ailk-template.json +1 -1
  73. package/site-templates/saas-product/DESIGN.md +98 -6
  74. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +127 -18
  75. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +74 -20
  76. package/site-templates/shapes/local-business-service/ailk-template.json +1 -1
  77. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +127 -18
  78. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +74 -20
  79. package/site-templates/shapes/organization-service/ailk-template.json +1 -1
  80. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +127 -18
  81. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +75 -21
  82. package/site-templates/shapes/person-service/ailk-template.json +1 -1
  83. package/site-templates/shapes/person-service/content/en/AboutPage/about.mdx +1 -1
  84. package/site-templates/shapes/person-service/content/en/HomePage/home.mdx +2 -2
  85. package/site-templates/shapes/person-service/content/en/PortfolioList/portfolio.mdx +4 -2
  86. package/site-templates/shapes/person-service/content/en/ServicesList/services.mdx +1 -1
  87. package/templates/.claude/agents/web.md +10 -8
  88. package/templates/.claude/rules/architecture.md +17 -7
  89. package/templates/.claude/skills/README.md +3 -2
  90. package/templates/.claude/skills/add-auth/SKILL.md +142 -90
  91. package/templates/.claude/skills/add-auth/references/env-vars.md +32 -17
  92. package/templates/.claude/skills/add-auth/references/oauth-app-setup.md +17 -8
  93. package/templates/.claude/skills/add-page/SKILL.md +241 -238
  94. package/templates/.claude/skills/add-page/references/page-types.md +94 -86
  95. package/templates/.claude/skills/add-page/scripts/site-pages.mjs +87 -0
  96. package/templates/.claude/skills/add-page/templates/comparison-page.mdx.tmpl +99 -0
  97. package/templates/.claude/skills/add-page/templates/content.mdx.tmpl +8 -18
  98. package/templates/.claude/skills/add-page/templates/flow-landing-page.mdx.tmpl +43 -0
  99. package/templates/.claude/skills/add-page/templates/report-page.mdx.tmpl +52 -0
  100. package/templates/.claude/skills/add-page/templates/reports-list.mdx.tmpl +38 -0
  101. package/templates/.claude/skills/check-site/SKILL.md +51 -18
  102. package/templates/.claude/skills/check-site/references/common-errors.md +12 -9
  103. package/templates/.claude/skills/check-site/references/findings-glossary.md +4 -4
  104. package/templates/.claude/skills/check-site/references/severity-levels.md +3 -3
  105. package/templates/.claude/skills/fill-page/SKILL.md +26 -7
  106. package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +1 -1
  107. package/templates/.claude/skills/setup-deploy/SKILL.md +75 -38
  108. package/templates/.claude/skills/update-page/SKILL.md +117 -0
  109. package/templates/.claude/skills/update-page/scripts/update-page.ts +640 -0
  110. package/templates/.claude/skills/update-page/templates/redirects.mjs.tmpl +31 -0
  111. package/templates/.claude/skills/upgrade-plan/SKILL.md +12 -8
  112. package/templates/.env.example +23 -2
  113. package/templates/.github/workflows/ci.yml +4 -0
  114. package/templates/.prettierignore +1 -0
  115. package/templates/CLAUDE.md +146 -103
  116. package/templates/DESIGN.md +98 -6
  117. package/templates/README.md +50 -38
  118. package/templates/apps/api/.env.example +2 -1
  119. package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +2 -0
  120. package/templates/apps/api/src/__tests__/server.test.ts +116 -0
  121. package/templates/apps/api/src/lib/stripe.ts +9 -0
  122. package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +140 -0
  123. package/templates/apps/api/src/middleware/auth.ts +54 -0
  124. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +5 -2
  125. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +628 -0
  126. package/templates/apps/api/src/routes/checkout/sessions.ts +222 -24
  127. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +1 -1
  128. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +5 -1
  129. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +521 -20
  130. package/templates/apps/api/src/routes/webhooks/stripe.ts +124 -32
  131. package/templates/apps/api/src/server.ts +28 -0
  132. package/templates/apps/api/src/services/__tests__/revalidation.test.ts +90 -15
  133. package/templates/apps/api/src/services/checkout-buyer.ts +72 -0
  134. package/templates/apps/api/src/services/revalidation.ts +60 -15
  135. package/templates/apps/mcp/__tests__/tools/create_project.test.ts +30 -24
  136. package/templates/apps/mcp/src/tools/purchase.ts +4 -2
  137. package/templates/apps/web/.env.example +13 -0
  138. package/templates/apps/web/CLAUDE.md +22 -28
  139. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +1 -1
  140. package/templates/apps/web/app/[locale]/(authed)/leads/__tests__/page.test.tsx +24 -0
  141. package/templates/apps/web/app/[locale]/(authed)/leads/page.tsx +17 -6
  142. package/templates/apps/web/app/[locale]/[slug]/[detail]/__tests__/page.test.tsx +110 -0
  143. package/templates/apps/web/app/[locale]/[slug]/[detail]/page.tsx +60 -0
  144. package/templates/apps/web/app/[locale]/[slug]/page.tsx +1 -1
  145. package/templates/apps/web/app/[locale]/alternatives/__tests__/page.test.tsx +161 -0
  146. package/templates/apps/web/app/[locale]/alternatives/page.tsx +104 -0
  147. package/templates/apps/web/app/[locale]/{use-cases → for}/[slug]/page.tsx +4 -3
  148. package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +73 -0
  149. package/templates/apps/web/app/[locale]/{use-cases → for}/page.tsx +8 -7
  150. package/templates/apps/web/app/[locale]/glossary/page.tsx +1 -1
  151. package/templates/apps/web/app/[locale]/layout.tsx +35 -19
  152. package/templates/apps/web/app/[locale]/newsletter/page.tsx +1 -1
  153. package/templates/apps/web/app/[locale]/portfolio/[slug]/page.tsx +1 -1
  154. package/templates/apps/web/app/[locale]/portfolio/page.tsx +1 -1
  155. package/templates/apps/web/app/[locale]/vs/[slug]/__tests__/page.test.tsx +112 -0
  156. package/templates/apps/web/app/[locale]/vs/[slug]/page.tsx +70 -0
  157. package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +236 -44
  158. package/templates/apps/web/app/api/revalidate/route.ts +78 -36
  159. package/templates/apps/web/lib/__tests__/site-brand.test.ts +79 -0
  160. package/templates/apps/web/lib/__tests__/site-theme.test.ts +104 -11
  161. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +21 -4
  162. package/templates/apps/web/lib/locale-guard.ts +18 -0
  163. package/templates/apps/web/lib/meet-placeholder.ts +5 -0
  164. package/templates/apps/web/lib/nav-config.ts +14 -13
  165. package/templates/apps/web/lib/site-brand.tsx +13 -9
  166. package/templates/apps/web/lib/site-logo.tsx +36 -0
  167. package/templates/apps/web/lib/site-theme.ts +46 -26
  168. package/templates/apps/web/messages/en.json +23 -9
  169. package/templates/apps/web/middleware.ts +28 -4
  170. package/templates/content/en/BookCall/book-a-call.mdx +80 -0
  171. package/templates/content/en/BookCall/onsite-estimate.mdx +56 -0
  172. package/templates/content/en/BookCall/onsite-service.mdx +56 -0
  173. package/templates/content/en/BookCall/start-project.mdx +56 -0
  174. package/templates/content/en/ContactPage/contact.mdx +40 -1
  175. package/templates/content/en/HomePage/home.mdx +2 -6
  176. package/templates/content/en/LandingPage/meet.mdx +59 -0
  177. package/templates/content/en/UseCasesList/{use-cases.mdx → for.mdx} +19 -18
  178. package/templates/database/CHANGELOG.md +7 -0
  179. package/templates/database/package.json +1 -1
  180. package/templates/package.json +4 -1
  181. package/templates/tools/check-claude-md.ts +128 -0
  182. package/versions.json +33 -33
  183. package/dist/modules/commerce/0.6.0.tar.gz +0 -0
  184. package/dist/modules/commerce/0.6.1.tar.gz +0 -0
  185. package/dist/modules/docs-blog/0.6.0.tar.gz +0 -0
  186. package/dist/modules/docs-blog/0.6.1.tar.gz +0 -0
  187. package/dist/modules/marketing-site/0.6.1.tar.gz +0 -0
  188. package/dist/modules/minimalism-site/0.6.1.tar.gz +0 -0
  189. package/dist/modules/multi-tenant/0.6.0.tar.gz +0 -0
  190. package/dist/modules/multi-tenant/0.6.1.tar.gz +0 -0
  191. package/dist/modules/saas-product/0.6.1.tar.gz +0 -0
  192. package/dist/modules/scheduling/0.6.0.tar.gz +0 -0
  193. package/dist/modules/scheduling/0.6.1.tar.gz +0 -0
  194. package/dist/modules/signal-collectors/0.6.0.tar.gz +0 -0
  195. package/dist/modules/signal-collectors/0.6.1.tar.gz +0 -0
  196. package/dist/modules/simple-service/0.6.1.tar.gz +0 -0
  197. package/dist/modules/workspace/0.6.0.tar.gz +0 -0
  198. package/dist/modules/workspace/0.6.1.tar.gz +0 -0
  199. package/site-templates/_base/content/en/BookCall/book-call.mdx +0 -52
  200. package/site-templates/minimalism-site/DESIGN.md +0 -162
  201. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +0 -506
  202. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +0 -458
  203. package/site-templates/minimalism-site/ailk-template.json +0 -23
  204. package/site-templates/minimalism-site/content/en/AboutPage/about.mdx +0 -110
  205. package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +0 -52
  206. package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +0 -34
  207. package/site-templates/minimalism-site/content/en/FAQPage/faq.mdx +0 -57
  208. package/site-templates/minimalism-site/content/en/LandingPage/referral.mdx +0 -77
  209. package/site-templates/minimalism-site/content/en/LandingPage/reports.mdx +0 -73
  210. package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +0 -123
  211. package/site-templates/minimalism-site/content/en/ReportPage/annual-letter-2024.mdx +0 -52
  212. package/site-templates/minimalism-site/content/en/ReportPage/field-notes-2023.mdx +0 -52
  213. package/site-templates/minimalism-site/content/en/ReportPage/platform-shift-2025.mdx +0 -52
  214. package/site-templates/minimalism-site/content/en/ReportPage/state-of-the-market-2026.mdx +0 -52
  215. package/site-templates/minimalism-site/content/en/Service/first-engagement.mdx +0 -38
  216. package/site-templates/minimalism-site/content/en/Service/second-engagement.mdx +0 -38
  217. package/site-templates/minimalism-site/content/en/Service/third-engagement.mdx +0 -38
  218. package/site-templates/minimalism-site/content/en/ServicesList/services.mdx +0 -73
  219. package/site-templates/minimalism-site/flows/referral.flow.json +0 -71
  220. package/site-templates/minimalism-site/provenance.json +0 -4
  221. package/templates/.claude/skills/add-page/templates/route.tsx.tmpl +0 -28
  222. package/templates/.claude/skills/setup-deploy/references/env-vars-template.md +0 -175
  223. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +0 -8
  224. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +0 -12
  225. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +0 -12
  226. package/templates/apps/web/app/[locale]/book-a-call/page.tsx +0 -50
  227. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +0 -72
  228. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-1.svg +0 -0
  229. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-2.svg +0 -0
  230. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-3.svg +0 -0
  231. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-4.svg +0 -0
  232. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-5.svg +0 -0
  233. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-6.svg +0 -0
  234. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/hero-backdrop.svg +0 -0
  235. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-1.svg +0 -0
  236. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-2.svg +0 -0
  237. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-3.svg +0 -0
  238. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/wordmark-placeholder.svg +0 -0
@@ -7,6 +7,8 @@
7
7
  * On any parse / validation failure: throws ParseError. The orchestrator's
8
8
  * top-level catch turns this into a stderr write + non-zero exit.
9
9
  */
10
+ import { existsSync, statSync } from "node:fs";
11
+ import { join, resolve } from "node:path";
10
12
  import { createInterface } from "node:readline/promises";
11
13
  import { parseArgs } from "node:util";
12
14
  import { assertLicenseKeyShape } from "./lib/license-key-files.js";
@@ -95,8 +97,9 @@ Options:
95
97
  Installs that shape's Free starter. A run naming
96
98
  neither --shape nor --template asks for a shape on
97
99
  a terminal, and refuses without one.
98
- --design <design> The look to render the starter in (recognized:
99
- ${RECOGNIZED_DESIGNS.join(", ")}). Defaults to the
100
+ --design <design> The look to render the starter in: a design name
101
+ (recognized: ${RECOGNIZED_DESIGNS.join(", ")}) or a
102
+ directory holding a DESIGN.md. Defaults to the
100
103
  starter's own.
101
104
  --template <name> The site template to install (recognized:
102
105
  ${RECOGNIZED_TEMPLATES.join(", ")}).
@@ -120,8 +123,9 @@ Options:
120
123
  Surfaces the declared architecture does not carry are
121
124
  NOT scaffolded — their routes are absent, so a fresh
122
125
  site never serves a stock page with placeholder
123
- content. Add one later with the \`add-page\`
124
- skill, or re-run with the surface named here.
126
+ content. Add one later with
127
+ \`create-ailk add-route <surface>\`, or re-run with
128
+ the surface named here.
125
129
  --sync-architecture <csv> Regenerate architecture.yaml in place in an
126
130
  already-scaffolded repo from the given module set
127
131
  (recognized: ${RECOGNIZED_MODULES.join(", ")}), then
@@ -140,6 +144,12 @@ Options:
140
144
  file, at the target dir (positional arg, else cwd).
141
145
  Bypasses the scaffold path — copies no template, runs
142
146
  no install. Re-run after upgrading create-ailk.
147
+ --sync-claude-md Rewrite the AI Launch Kit sections of CLAUDE.md in
148
+ an already-scaffolded repo to the template this
149
+ create-ailk ships, then exit. Only CLAUDE.md
150
+ changes; your Site notes and anything between the
151
+ sections are kept byte for byte. Target dir is the
152
+ positional arg, else cwd.
143
153
  --license <key> Your licence key: fetch paid modules with it and
144
154
  write it into the scaffold (apps/api/.env and a
145
155
  gitignored ailk.key, both owner-only).
@@ -180,6 +190,7 @@ export function parseCliArgs(argv, env = { interactive: false }) {
180
190
  surfaces: { type: "string" },
181
191
  "sync-architecture": { type: "string" },
182
192
  "sync-catalog": { type: "boolean", default: false },
193
+ "sync-claude-md": { type: "boolean", default: false },
183
194
  "with-catalog": { type: "boolean", default: false },
184
195
  install: { type: "boolean", default: false },
185
196
  help: { type: "boolean", default: false, short: "h" },
@@ -203,12 +214,18 @@ export function parseCliArgs(argv, env = { interactive: false }) {
203
214
  const surfaces = parseSurfaceList(parsed.values.surfaces);
204
215
  const syncArchitecture = parsed.values["sync-architecture"];
205
216
  const syncCatalog = parsed.values["sync-catalog"] === true;
217
+ const syncClaudeMd = parsed.values["sync-claude-md"] === true;
206
218
  const withCatalog = parsed.values["with-catalog"] === true;
207
219
  const license = parseLicense(parsed.values.license);
208
- // The two sync modes each exit after rewriting their one file, so asking for
209
- // both in one invocation has no coherent meaning.
210
- if (syncCatalog && syncArchitecture !== undefined) {
211
- throw new ParseError("--sync-catalog and --sync-architecture cannot be combined; run them one at a time");
220
+ // The sync modes each exit after rewriting their one file, so asking for
221
+ // more than one in one invocation has no coherent meaning.
222
+ const syncFlags = [
223
+ syncCatalog ? "--sync-catalog" : undefined,
224
+ syncArchitecture !== undefined ? "--sync-architecture" : undefined,
225
+ syncClaudeMd ? "--sync-claude-md" : undefined,
226
+ ].filter((f) => f !== undefined);
227
+ if (syncFlags.length > 1) {
228
+ throw new ParseError(`${syncFlags.join(" and ")} cannot be combined; run them one at a time`);
212
229
  }
213
230
  // Validate the sync-architecture value now, through the SAME parseModules
214
231
  // path `--modules` uses, so an unknown module throws ParseError at parse time
@@ -228,12 +245,13 @@ export function parseCliArgs(argv, env = { interactive: false }) {
228
245
  modules,
229
246
  surfaces,
230
247
  syncCatalog: false,
248
+ syncClaudeMd: false,
231
249
  withCatalog: false,
232
250
  syncArchitecture: undefined,
233
251
  };
234
252
  }
235
253
  // Sync modes: the target dir is optional (positional if given, else cwd).
236
- if (syncArchitecture !== undefined || syncCatalog) {
254
+ if (syncArchitecture !== undefined || syncCatalog || syncClaudeMd) {
237
255
  if (parsed.positionals.length > 1) {
238
256
  throw new ParseError(`expected at most one target directory, got ${parsed.positionals.length}`);
239
257
  }
@@ -247,6 +265,7 @@ export function parseCliArgs(argv, env = { interactive: false }) {
247
265
  modules,
248
266
  surfaces,
249
267
  syncCatalog,
268
+ syncClaudeMd,
250
269
  withCatalog,
251
270
  syncArchitecture,
252
271
  };
@@ -284,6 +303,7 @@ export function parseCliArgs(argv, env = { interactive: false }) {
284
303
  modules,
285
304
  surfaces,
286
305
  syncCatalog,
306
+ syncClaudeMd,
287
307
  withCatalog,
288
308
  syncArchitecture: undefined,
289
309
  ...(license !== undefined ? { license } : {}),
@@ -305,15 +325,56 @@ function parseShape(raw) {
305
325
  }
306
326
  return name;
307
327
  }
308
- /** Validate `--design` (#6754 D2 step 2): trimmed and lowercased. */
328
+ /** `path` is an existing directory. */
329
+ function isExistingDir(path) {
330
+ try {
331
+ return statSync(path).isDirectory();
332
+ }
333
+ catch {
334
+ return false;
335
+ }
336
+ }
337
+ /**
338
+ * `dir` (resolved from `raw`) as a design path: an existing directory
339
+ * holding a `DESIGN.md`, else `ParseError` naming `raw` (#6813 D1 rule 2).
340
+ */
341
+ function validateDesignDir(dir, raw) {
342
+ if (!isExistingDir(dir)) {
343
+ throw new ParseError(`design directory "${raw}" does not exist`);
344
+ }
345
+ if (!existsSync(join(dir, "DESIGN.md"))) {
346
+ throw new ParseError(`design directory "${raw}" holds no DESIGN.md`);
347
+ }
348
+ return dir;
349
+ }
350
+ /**
351
+ * Validate `--design` (#6754 D2 step 2, widened #6813 D1): a recognized
352
+ * name, or a directory holding a `DESIGN.md`. A value with no `/` or `\` is
353
+ * classified as a name first (trimmed and lowercased, filesystem never
354
+ * consulted) — falling back to a path only when it names an existing
355
+ * directory in `process.cwd()`, so `--design mydesign` and `--design
356
+ * ./mydesign` can differ. A value with a separator is always a path,
357
+ * resolved against `process.cwd()`, case preserved. The package's CONTENT is
358
+ * validated later, in `scaffold()` (`resolveDesign`) — this parser checks
359
+ * presence only.
360
+ */
309
361
  function parseDesign(raw) {
310
362
  if (raw === undefined)
311
363
  return undefined;
312
- const name = raw.trim().toLowerCase();
313
- if (!RECOGNIZED_DESIGNS.includes(name)) {
364
+ const trimmed = raw.trim();
365
+ const hasSeparator = trimmed.includes("/") || trimmed.includes("\\");
366
+ if (!hasSeparator) {
367
+ const name = trimmed.toLowerCase();
368
+ if (RECOGNIZED_DESIGNS.includes(name)) {
369
+ return name;
370
+ }
371
+ const dir = resolve(process.cwd(), trimmed);
372
+ if (isExistingDir(dir)) {
373
+ return validateDesignDir(dir, raw);
374
+ }
314
375
  throw new ParseError(`unknown design "${raw}". Recognized: ${RECOGNIZED_DESIGNS.join(", ")}`);
315
376
  }
316
- return name;
377
+ return validateDesignDir(resolve(process.cwd(), trimmed), raw);
317
378
  }
318
379
  /** How many answers the prompt takes before it gives up. */
319
380
  const PROMPT_ATTEMPTS = 3;
@@ -16,11 +16,12 @@
16
16
  * const result = scaffoldSync({ targetDir: '/tmp/my-site', projectName: 'my-site' });
17
17
  */
18
18
  import { type AddModuleResult } from "./add-module.js";
19
+ import { type SyncClaudeMdResult } from "./claude-md.js";
19
20
  import { CATALOG_TARGET_PATH } from "./component-catalog.js";
20
21
  import { type ModuleName } from "./parse-args.js";
21
22
  import { type TemplateLayout } from "./site-chrome.js";
22
23
  import { type SyncRoutesResult } from "./sync-routes.js";
23
- import { type StarterDesign, type StarterShape } from "./template-manifest.js";
24
+ import { type StarterShape } from "./template-manifest.js";
24
25
  export interface ScaffoldOptions {
25
26
  /** Absolute or relative path to the target directory. Must not exist or be empty. */
26
27
  targetDir: string;
@@ -118,15 +119,23 @@ export interface ScaffoldOptions {
118
119
  */
119
120
  layout?: TemplateLayout;
120
121
  /**
121
- * The design whose layout knobs fill what `layout` leaves unset (#6638,
122
- * `DESIGN_LAYOUTS`). `scaffold()` sets it from the resolved template's
122
+ * A design name or a directory holding a DESIGN.md (#6638; widened to a
123
+ * path #6813 D1/D3). `scaffold()` sets it from the resolved template's
123
124
  * `design`; a caller may name one to OVERRIDE the template's — the
124
125
  * shape × design pairing `verify-scaffold.mjs --design` renders (#6638
125
126
  * DD-4), and the CLI's `--design` beside `--shape` (#6754). Unset (every
126
127
  * existing `scaffoldSync` caller) resolves exactly as before. Gated
127
- * (#6760): a design outside `FREE_DESIGNS` needs a Pro or Pro Plus level.
128
+ * (#6760): a non-Free design needs a Pro or Pro Plus level.
128
129
  */
129
- design?: StarterDesign;
130
+ design?: string;
131
+ /**
132
+ * INTERNAL — the resolved design's own layout knobs (#6813 D2/D4/D8),
133
+ * populated by `scaffold()` from `resolveDesign(...).pkg.layout` before
134
+ * calling `scaffoldSync`. Never set by a CLI/MCP caller. Replaces the
135
+ * design-NAME lookup `scaffoldSync` used to do internally through the
136
+ * code registry that has since been deleted (#6638's per-name table).
137
+ */
138
+ designLayout?: TemplateLayout;
130
139
  }
131
140
  export interface ScaffoldResult {
132
141
  /** Resolved absolute path to the scaffolded directory. */
@@ -138,11 +147,13 @@ export interface ScaffoldResult {
138
147
  /**
139
148
  * The starter this scaffold resolved (#6754 D5) — set whenever a template
140
149
  * was resolved, from `template` or from `shape`; absent for the bare overlay.
150
+ * `design` is the applied design's recorded name (#6813 D6): the shipped
151
+ * registry name, or the package's own `name` for an unrecognized one.
141
152
  */
142
153
  starter?: {
143
154
  template: string;
144
155
  shape: StarterShape;
145
- design: StarterDesign;
156
+ design: string;
146
157
  };
147
158
  /**
148
159
  * The effective module set this scaffold installed (#6759 D6) — what
@@ -258,6 +269,19 @@ export interface SyncCatalogResult {
258
269
  export declare function syncCatalogSync(opts: {
259
270
  targetDir: string;
260
271
  }): SyncCatalogResult;
272
+ /**
273
+ * Rewrite the template-owned sections of an already-scaffolded repo's root
274
+ * `CLAUDE.md` to the template this create-ailk ships (#6904 D3). The logic
275
+ * lives in `claude-md.ts` (a module jest imports directly); this wrapper
276
+ * only supplies the package's `templates/CLAUDE.md` — the `syncRoutesSync`
277
+ * split. `templateText` stands in for the shipped template in tests.
278
+ */
279
+ export declare function syncClaudeMdSync(opts: {
280
+ targetDir: string;
281
+ templateText?: string;
282
+ }): SyncClaudeMdResult;
283
+ /** Re-exported beside the other sync results so consumers see one surface. */
284
+ export type { SyncClaudeMdResult } from "./claude-md.js";
261
285
  export interface SyncRoutesSyncOptions {
262
286
  /** The already-scaffolded repo to sync (absolute or relative). */
263
287
  targetDir: string;
@@ -19,20 +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 { FREE_DESIGNS, assertDesignAllowed, deriveLevel, resolveModuleSet, resolvePaidModules, } from "./lib/module-license-gate.js";
34
+ import { assertDesignAllowed, assertReleased, 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";
31
37
  import { RECOGNIZED_SHAPES, RESERVED_SHAPES, } from "./parse-args.js";
32
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";
33
41
  import { GATEABLE_SURFACES, MODULE_SURFACES, NAV_CONFIG_PATH, excludedRoutePaths, renderNavConfig, resolveSurfaces, } from "./surfaces.js";
34
42
  import { syncRoutes } from "./sync-routes.js";
35
- import { BASE_LAYER_DIR, SHAPE_STARTERS, STARTER_DESIGNS, TEMPLATE_MANIFESTS, TemplateError, mergeRoster, resolveTemplate, resolveTemplateLineage, rosterDeclares, templateDelivery, templateTreeDir, } from "./template-manifest.js";
43
+ import { BASE_LAYER_DIR, SHAPE_STARTERS, TEMPLATE_MANIFESTS, TemplateError, mergeRoster, resolveTemplate, resolveTemplateLineage, rosterDeclares, templateDelivery, templateTreeDir, } from "./template-manifest.js";
36
44
  import { validateTarget } from "./validate.js";
37
45
  const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
38
46
  const TEMPLATES_DIR = resolve(PKG_ROOT, "templates");
@@ -45,6 +53,8 @@ const TEMPLATES_DIR = resolve(PKG_ROOT, "templates");
45
53
  * `package.json` `files` instead.
46
54
  */
47
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");
48
58
  const VERSIONS_FILE = resolve(PKG_ROOT, "versions.json");
49
59
  function readVersionMap() {
50
60
  if (!existsSync(VERSIONS_FILE)) {
@@ -76,6 +86,24 @@ function countFiles(dir) {
76
86
  }
77
87
  return count;
78
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
+ }
79
107
  /**
80
108
  * The template a `shape` scaffolds (#6754 D5, extended #6757 D6 row 1): the
81
109
  * one FREE-delivery manifest of that shape — a declared `--template` alias
@@ -128,6 +156,9 @@ export function scaffoldSync(opts) {
128
156
  excludePaths: excludedRoutePaths(enabledSurfaces),
129
157
  markers: buildScaffoldMarkers(projectName),
130
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));
131
162
  // Derive + write the module-set-driven architecture.yaml (spec #3787). This
132
163
  // is the single source of the scaffolded site's code-boundary config and
133
164
  // closes the dangling `architecture_rules: architecture.yaml` reference in
@@ -144,7 +175,7 @@ export function scaffoldSync(opts) {
144
175
  // template's `layout` block; a bare `scaffoldSync` call (every existing
145
176
  // synchronous caller) writes DEFAULT_SITE_CHROME, byte-identical to the
146
177
  // monorepo's own committed apps/web/lib/site-chrome.ts.
147
- 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)));
148
179
  // Opt-in only (#4636). Every write above this line is unconditional, so a
149
180
  // scaffold with `withCatalog` unset touches exactly the files it did before.
150
181
  if (opts.withCatalog === true)
@@ -204,17 +235,44 @@ export async function scaffold(opts) {
204
235
  if (opts.template !== undefined && opts.shape !== undefined) {
205
236
  throw new TemplateError("pass a template or a shape, not both");
206
237
  }
238
+ // #6984 — a surface `release-scope.json` does not release is refused here,
239
+ // after parsing and before the licence gate, fetch or write (spec 6977 D3).
240
+ assertReleased("templates", [opts.template]);
241
+ assertReleased("shapes", [opts.shape]);
207
242
  const template = opts.template !== undefined
208
243
  ? resolveTemplate(opts.template)
209
244
  : opts.shape !== undefined
210
245
  ? resolveShapeTemplate(opts.shape)
211
246
  : undefined;
212
- // #6638 — the design this scaffold renders: the caller's override, else the
213
- // template's own. Unset for a scaffold naming neither.
214
- const design = opts.design ?? template?.design;
215
- if (design !== undefined && !STARTER_DESIGNS.includes(design)) {
216
- throw new TemplateError(`unknown design "${design}". Declared: ${STARTER_DESIGNS.join(", ")}`);
247
+ assertReleased("modules", [...(opts.modules ?? []), ...(template?.requires ?? [])]);
248
+ // #6813 D8 step 3 — the design this scaffold renders, resolved from its
249
+ // package ONCE, before any fetch or write: the caller's override, else the
250
+ // template's own. Unset for a scaffold naming neither. Replaces the
251
+ // `STARTER_DESIGNS.includes` name check: an unrecognized name or a missing
252
+ // directory throws `TemplateError` here; a malformed package throws
253
+ // `DesignPackageError` (never caught), each naming the field.
254
+ const designRef = opts.design ?? template?.design;
255
+ const resolvedDesign = designRef !== undefined
256
+ ? resolveDesign(designRef, { designsDir: DESIGNS_DIR })
257
+ : undefined;
258
+ // By registry name, so a byte-identical copy of a gated design is refused too.
259
+ assertReleased("designs", [resolvedDesign?.recorded]);
260
+ // #6816 D6 step 3b — a design's section presets are validated here, before
261
+ // any fetch or write, against the look groups the shipped catalog carries
262
+ // (D2): a non-look field or unknown variant throws `DesignPackageError`
263
+ // naming page type, section type and field, and the target is never
264
+ // created. A design with no `sections` block executes no new read (D8).
265
+ if (resolvedDesign !== undefined &&
266
+ Object.keys(resolvedDesign.pkg.sections).length > 0) {
267
+ validateDesignPresets(resolvedDesign.pkg.sections, {
268
+ catalog: readSectionLookCatalog(join(PKG_ROOT, "component-catalog.json")),
269
+ pageTypes: shippedPageTypes(SITE_TEMPLATES_DIR),
270
+ file: join(resolvedDesign.dir, "DESIGN.md"),
271
+ });
217
272
  }
273
+ // #6813 D7 — the resolved design only when the CALLER chose one (`opts.design`);
274
+ // a template's own declared design is never gated (#6778 option (c)).
275
+ const chosenDesign = opts.design !== undefined ? resolvedDesign : undefined;
218
276
  // #5618 D2 — the `extends` chain is resolved in the client registry before
219
277
  // any fetch: an unknown parent or a cycle is refused here, with the target
220
278
  // still non-existent, like the four refusals below. The registry is the
@@ -260,11 +318,11 @@ export async function scaffold(opts) {
260
318
  // #6760 — `resolveModuleSet` never runs on this branch (NFR-4), so no
261
319
  // level was derived above. Derive one — from the registry's per-module
262
320
  // answers, never a key claim (D2) — only when the caller CHOSE a
263
- // non-`standard` design alongside the template (`opts.design`). An
321
+ // non-Free design alongside the template (`opts.design`, #6813 D7). An
264
322
  // alias's own declared design is never gated (#6778 option (c)), so a
265
- // plain `--template minimalism-site` run makes no extra network call and
266
- // is never refused on its design.
267
- if (opts.design !== undefined && !FREE_DESIGNS.includes(opts.design)) {
323
+ // plain `--template <alias>` run makes no extra network call and is
324
+ // never refused on its design.
325
+ if (chosenDesign !== undefined && !chosenDesign.free) {
268
326
  ({ level } = await deriveLevel({
269
327
  licenseKey,
270
328
  ...(opts.moduleRegistryUrl !== undefined
@@ -317,8 +375,11 @@ export async function scaffold(opts) {
317
375
  // Only the caller's chosen design is gated (#6778 option (c)); a template's
318
376
  // own declared design is not. `level ?? "free"` fails closed: a
319
377
  // `--modules`-keep run under-reports its level by design (NFR-2), so a
320
- // chosen non-`standard` design on that path is refused rather than trusted.
321
- assertDesignAllowed(opts.design, level ?? "free");
378
+ // chosen non-Free design on that path is refused rather than trusted. The
379
+ // refusal names `chosen.display` — the name, or the absolute directory
380
+ // (#6813 D7) — and `chosen.free` decides Free-ness by identity (D2), never
381
+ // by `FREE_DESIGNS.includes(name)` alone (a path is never in that list).
382
+ assertDesignAllowed(chosenDesign?.display, level ?? "free", chosenDesign?.free);
322
383
  // The template's OWN bundle, fetched here — before `scaffoldSync` — so the
323
384
  // fourth refusal (the template's bundle unpublished, parent record D17 /
324
385
  // spec 5481 § 3 DD5 row 4) throws with the target still non-existent, like
@@ -372,7 +433,9 @@ export async function scaffold(opts) {
372
433
  // design's knobs (#6638), and a `standard` one to the same defaults
373
434
  // `scaffoldSync` already writes for a templateless scaffold.
374
435
  ...(template?.layout !== undefined ? { layout: template.layout } : {}),
375
- ...(design !== undefined ? { design } : {}),
436
+ // #6813 D8 step 6 — the resolved design's OWN layout (never its name):
437
+ // `resolveSiteChrome` reads a layout object now, not a `StarterDesign`.
438
+ ...(resolvedDesign !== undefined ? { designLayout: resolvedDesign.pkg.layout } : {}),
376
439
  });
377
440
  // Threaded to extractModuleBundle for `package.json` patch targets'
378
441
  // `workspace:*` substitution (spec 5418 § 3 D2) — the same version map
@@ -437,6 +500,11 @@ export async function scaffold(opts) {
437
500
  //
438
501
  // One install step, two source roots (DD7): an extracted staging dir on the
439
502
  // paid path, `site-templates/<name>/` on disk on the Free one.
503
+ // #6813 D6 — captured for the site-manifest record below, which needs the
504
+ // chrome ACTUALLY written (`installedLayout ?? template.layout`), readable
505
+ // only after this block (the bundle's own `layout`, when it declares one,
506
+ // is unreachable before install).
507
+ let installedLayout;
440
508
  if (template !== undefined) {
441
509
  // #5612 D1/D2 — the INSTALLED template's own `ailk-template.json` layout
442
510
  // (validated by `readAilkTemplateManifest`), if it declares one. Read
@@ -472,21 +540,132 @@ export async function scaffold(opts) {
472
540
  ownership,
473
541
  });
474
542
  })();
543
+ installedLayout = installed.layout;
475
544
  if (installed.layout !== undefined) {
476
- writeFileSync(join(result.targetDir, SITE_CHROME_PATH), renderSiteChrome(resolveSiteChrome(installed.layout, design)));
545
+ writeFileSync(join(result.targetDir, SITE_CHROME_PATH), renderSiteChrome(resolveSiteChrome(installed.layout, resolvedDesign?.pkg.layout)));
477
546
  }
547
+ // #6878 D6 — re-render the nav with the SERVICE slugs the installed tree
548
+ // just landed (`content/en/Service/*.mdx`, absent on a product shape),
549
+ // now that `content/en/Service` exists to read. `scaffoldSync`'s own
550
+ // one-argument render above already wrote a nav for `result.surfaces`
551
+ // BEFORE the template tree existed — byte-identical to this one on a
552
+ // product shape (no Service directory to read), and this is what makes a
553
+ // service shape's flyout show its own Services column instead of an
554
+ // empty offering column. Unconditional (every templated scaffold gets
555
+ // this second render, not only one that resolved a `layout`), and BEFORE
556
+ // `pruneUnrosteredSeeds` below: the read reflects every seed the install
557
+ // step actually wrote, not what a later prune leaves behind.
558
+ const serviceContentDir = join(result.targetDir, "content", "en", "Service");
559
+ const serviceSlugs = existsSync(serviceContentDir)
560
+ ? readdirSync(serviceContentDir)
561
+ .filter((f) => f.endsWith(".mdx"))
562
+ .map((f) => f.slice(0, -".mdx".length))
563
+ : [];
564
+ writeFileSync(join(result.targetDir, NAV_CONFIG_PATH), renderNavConfig(result.surfaces, { serviceSlugs }));
478
565
  pruneUnrosteredSeeds(result.targetDir, mergeRoster(template, template.requires), result.surfaces, ownership);
566
+ // spec 6892 D4 — the resolved design's own sample (the minimalism design's
567
+ // nine-slide deck home + its SVGs; no template declares `minimalism`
568
+ // since #6976, so none installs it today), installed AFTER the template
569
+ // layer so it wins over the shape's `HomePage/home.mdx`, and ONLY when
570
+ // the resolved design is the template's OWN declared design: a caller
571
+ // combination like `--shape organization-product --design minimalism`
572
+ // installs no sample (its roster carries neither the deck's link targets
573
+ // nor, in its generated record, the deck's sections).
574
+ if (resolvedDesign !== undefined &&
575
+ resolvedDesign.recorded === template.design) {
576
+ installDesignSample({
577
+ designDir: resolvedDesign.dir,
578
+ targetDir: result.targetDir,
579
+ ownership,
580
+ });
581
+ }
479
582
  }
583
+ // #6813 D5/D8 steps 9-10 — whenever a design is resolved (the caller's,
584
+ // else the template's): the design's DESIGN.md (#6939); the compiled theme,
585
+ // spliced into the scaffold's `content/_site.mdx` LAST; then the site manifest records the APPLIED
586
+ // design, only when a template installed one (`saas-product` ships none).
587
+ // A scaffold naming neither a design nor a template (the bare overlay)
588
+ // executes no new statement.
589
+ if (resolvedDesign !== undefined) {
590
+ // #6939 — the scaffold's DESIGN.md is the design it wears, not the one
591
+ // its template layer shipped; the site's own header lines are kept.
592
+ writeFileSync(join(result.targetDir, "DESIGN.md"), planDesignMd(resolvedDesign.dir, result.targetDir));
593
+ const compiledTheme = compileDesignTheme(resolvedDesign.pkg);
594
+ const siteMdxPath = join(result.targetDir, "content", "_site.mdx");
595
+ if (existsSync(siteMdxPath)) {
596
+ const current = readFileSync(siteMdxPath, "utf8");
597
+ const spliced = spliceThemeBlock(current, compiledTheme, siteMdxPath);
598
+ if (spliced !== current)
599
+ writeFileSync(siteMdxPath, spliced);
600
+ }
601
+ if (template !== undefined) {
602
+ const rosterPath = join(result.targetDir, SITE_ROSTER_PATH);
603
+ if (existsSync(rosterPath)) {
604
+ const rosterText = readFileSync(rosterPath, "utf8");
605
+ const chromeWritten = resolveSiteChrome(installedLayout ?? template.layout, resolvedDesign.pkg.layout);
606
+ const nextManifest = planSiteManifestDesign(rosterText, { design: resolvedDesign.recorded, layout: chromeWritten, theme: compiledTheme }, rosterPath);
607
+ if (nextManifest !== undefined)
608
+ writeFileSync(rosterPath, nextManifest);
609
+ }
610
+ }
611
+ // #6816 D6 step 10b — the design's section presets, written onto the
612
+ // pages of the page types the roster of record places, once every layer
613
+ // has landed. The one `applyDesignPresets` call site. A scaffold with no
614
+ // roster (the bare overlay, `saas-product`) validated its presets at
615
+ // step 3b and applies none: nothing "the shape placed" to resolve against.
616
+ if (template !== undefined &&
617
+ Object.keys(resolvedDesign.pkg.sections).length > 0) {
618
+ const roster = readSiteRoster(result.targetDir);
619
+ if (roster !== undefined) {
620
+ const pageTypes = new Set();
621
+ for (const row of roster.rows) {
622
+ const pageType = row.value["pageType"];
623
+ if (typeof pageType === "string")
624
+ pageTypes.add(pageType);
625
+ }
626
+ const contentDir = join(result.targetDir, "content");
627
+ const pages = listMdxFiles(contentDir)
628
+ .filter((path) => path !== join(contentDir, "_site.mdx"))
629
+ .map((path) => ({ path, text: readFileSync(path, "utf8") }));
630
+ const presetResult = applyDesignPresets(pages, resolvedDesign.pkg.sections, { pageTypes });
631
+ for (const page of presetResult.pages)
632
+ writeFileSync(page.path, page.text);
633
+ }
634
+ }
635
+ }
636
+ // #6944 — the manifest records what this scaffold IS, once every layer has
637
+ // landed: the effective module set, a row for every route on disk, and the
638
+ // level only when the key path above derived one — `resolveModuleSet`, or
639
+ // `deriveLevel` for a chosen non-Free design. `level` stays undefined on a
640
+ // plain alias or `--modules`-keep run, and is never inferred from `modules`.
641
+ // A scaffold with no manifest (the bare overlay, `saas-product`) writes none.
642
+ if (template !== undefined) {
643
+ const roster = readSiteRoster(result.targetDir);
644
+ if (roster !== undefined) {
645
+ writeFileSync(join(result.targetDir, roster.path), planSiteManifest({
646
+ targetDir: result.targetDir,
647
+ roster,
648
+ modules: result.modules,
649
+ level,
650
+ siteTemplatesDir: SITE_TEMPLATES_DIR,
651
+ templatesDir: TEMPLATES_DIR,
652
+ markers,
653
+ }));
654
+ }
655
+ }
656
+ // #6107 D2 — after every layer landed, so nothing later overwrites it: a
657
+ // served scaffold needs BETTER_AUTH_SECRET (#6044), and this is its value.
658
+ writeAuthSecretFile(result.targetDir);
480
659
  // #6683 D9 — persisted only after every layer landed, and only when the
481
660
  // caller passed `license`: a scaffold without it writes nothing new.
482
661
  if (opts.license !== undefined) {
483
662
  writeLicenseKeyFiles(result.targetDir, opts.license);
484
663
  }
485
664
  // #6754 D4/D5 — the resolved starter, so the CLI names what it built.
486
- if (template !== undefined && design !== undefined) {
665
+ if (template !== undefined && resolvedDesign !== undefined) {
487
666
  return {
488
667
  ...result,
489
- starter: { template: template.name, shape: template.shape, design },
668
+ starter: { template: template.name, shape: template.shape, design: resolvedDesign.recorded },
490
669
  };
491
670
  }
492
671
  return result;
@@ -506,12 +685,17 @@ export async function scaffold(opts) {
506
685
  * the content directory of a surface this scaffold ENABLES is kept too, so
507
686
  * an explicit `--surfaces pricing` on a template that does not roster
508
687
  * `/pricing` gets its route and its seed together.
688
+ *
689
+ * The Free `/meet` placeholder seeds count as declared wherever the roster
690
+ * claims their route, the `scheduling` module's rows included (#6942): an
691
+ * unconfigured scheduling site's /meet pages hand off to them.
509
692
  */
510
693
  function pruneUnrosteredSeeds(targetDir, roster, surfaces, ownership) {
511
694
  // `installBundleTree`'s ownership label for the base layer: it installs
512
695
  // with `templateRequires: []`, so it records as a template. The overlay
513
696
  // (the `templates/` snapshot) records no owner at all.
514
697
  const shared = new Set([undefined, `template "${BASE_LAYER_DIR}"`]);
698
+ const declared = withMeetPlaceholderSeeds(roster);
515
699
  const enabledDirs = GATEABLE_SURFACES.filter((surface) => surfaces.includes(surface.id)).flatMap((surface) => surface.content.map((dir) => `${dir.replace("<locale>", "en")}/`));
516
700
  const root = join(targetDir, "content", "en");
517
701
  if (!existsSync(root))
@@ -531,7 +715,7 @@ function pruneUnrosteredSeeds(targetDir, roster, surfaces, ownership) {
531
715
  continue;
532
716
  const path = relative(targetDir, abs).split(sep).join("/");
533
717
  if (shared.has(ownership.get(path)) &&
534
- !rosterDeclares(roster, path) &&
718
+ !rosterDeclares(declared, path) &&
535
719
  !enabledDirs.some((prefix) => path.startsWith(prefix))) {
536
720
  rmSync(abs);
537
721
  if (readdirSync(dir).length === 0)
@@ -664,6 +848,19 @@ export function syncCatalogSync(opts) {
664
848
  catalogPath: writeComponentCatalog(targetAbs),
665
849
  };
666
850
  }
851
+ /**
852
+ * Rewrite the template-owned sections of an already-scaffolded repo's root
853
+ * `CLAUDE.md` to the template this create-ailk ships (#6904 D3). The logic
854
+ * lives in `claude-md.ts` (a module jest imports directly); this wrapper
855
+ * only supplies the package's `templates/CLAUDE.md` — the `syncRoutesSync`
856
+ * split. `templateText` stands in for the shipped template in tests.
857
+ */
858
+ export function syncClaudeMdSync(opts) {
859
+ return syncClaudeMd({
860
+ targetDir: opts.targetDir,
861
+ templateText: opts.templateText ?? readFileSync(join(TEMPLATES_DIR, "CLAUDE.md"), "utf8"),
862
+ });
863
+ }
667
864
  /**
668
865
  * Compare an already-scaffolded repo's route tree with the template this
669
866
  * create-ailk ships and, with `apply`, copy in the route dirs it is missing