create-ailk 0.7.0 → 0.8.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 (169) hide show
  1. package/README.md +44 -10
  2. package/component-catalog.json +520 -5
  3. package/component-catalog.md +9 -6
  4. package/dist/cli.js +22 -24
  5. package/dist/lib/extract-module-bundle.js +5 -1
  6. package/dist/lib/module-license-gate.d.ts +79 -0
  7. package/dist/lib/module-license-gate.js +160 -12
  8. package/dist/lib/module-tier.d.ts +45 -16
  9. package/dist/lib/module-tier.js +76 -0
  10. package/dist/lib/pro-callout.js +10 -2
  11. package/dist/parse-args.d.ts +78 -10
  12. package/dist/parse-args.js +204 -14
  13. package/dist/programmatic.d.ts +39 -9
  14. package/dist/programmatic.js +163 -25
  15. package/dist/site-roster.js +11 -6
  16. package/dist/starter-grid.d.ts +216 -0
  17. package/dist/starter-grid.js +449 -0
  18. package/dist/surfaces.d.ts +4 -4
  19. package/dist/surfaces.js +102 -43
  20. package/dist/template-manifest.d.ts +36 -17
  21. package/dist/template-manifest.js +121 -367
  22. package/dist/template-names.d.ts +14 -12
  23. package/dist/template-names.js +13 -17
  24. package/package.json +3 -3
  25. package/site-templates/_base/content/en/BookCall/book-call.mdx +0 -1
  26. package/site-templates/_base/content/en/Feature/feature-1.mdx +69 -0
  27. package/site-templates/_base/content/en/Feature/feature-2.mdx +69 -0
  28. package/site-templates/_base/content/en/Feature/feature-3.mdx +69 -0
  29. package/site-templates/_base/content/en/FeaturesList/features.mdx +91 -0
  30. package/site-templates/_base/content/en/GlossaryList/glossary.mdx +0 -1
  31. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +0 -1
  32. package/site-templates/_base/content/en/PortfolioItem/sample-four.mdx +105 -0
  33. package/site-templates/_base/content/en/PortfolioItem/sample-one.mdx +34 -0
  34. package/site-templates/_base/content/en/PortfolioItem/sample-three.mdx +94 -0
  35. package/site-templates/_base/content/en/PortfolioItem/sample-two.mdx +45 -0
  36. package/site-templates/_base/content/en/PortfolioList/portfolio.mdx +169 -0
  37. package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +15 -0
  38. package/site-templates/_base/content/en/UseCase/persona-1.mdx +69 -0
  39. package/site-templates/_base/content/en/UseCase/persona-2.mdx +69 -0
  40. package/site-templates/_base/content/en/UseCase/persona-3.mdx +69 -0
  41. package/site-templates/designs/standard/DESIGN.md +5 -0
  42. package/site-templates/marketing-site/DESIGN.md +5 -0
  43. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +89 -25
  44. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +35 -14
  45. package/site-templates/marketing-site-free/DESIGN.md +5 -0
  46. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +95 -29
  47. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +74 -44
  48. package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +59 -0
  49. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +199 -0
  50. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +17 -29
  51. package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +0 -1
  52. package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +0 -1
  53. package/site-templates/saas-product/DESIGN.md +5 -0
  54. package/site-templates/saas-product/TEMPLATE-MANIFEST.md +9 -1
  55. package/site-templates/{simple-service → shapes/local-business-service}/TEMPLATE-MANIFEST.json +96 -76
  56. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +302 -0
  57. package/site-templates/shapes/local-business-service/ailk-template.json +10 -0
  58. package/site-templates/{simple-service → shapes/local-business-service}/content/en/AboutPage/about.mdx +4 -4
  59. package/site-templates/shapes/local-business-service/content/en/HomePage/home.mdx +70 -0
  60. package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
  61. package/site-templates/shapes/local-business-service/content/en/PortfolioList/portfolio.mdx +171 -0
  62. package/site-templates/{simple-service → shapes/local-business-service}/content/en/PurchasePolicyPage/purchase-policy.mdx +7 -8
  63. package/site-templates/shapes/local-business-service/content/en/Service/first-job.mdx +39 -0
  64. package/site-templates/shapes/local-business-service/content/en/Service/second-job.mdx +39 -0
  65. package/site-templates/shapes/local-business-service/content/en/Service/third-job.mdx +39 -0
  66. package/site-templates/shapes/local-business-service/content/en/ServicesList/services.mdx +61 -0
  67. package/site-templates/shapes/local-business-service/provenance.json +4 -0
  68. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +362 -0
  69. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +300 -0
  70. package/site-templates/shapes/organization-service/ailk-template.json +10 -0
  71. package/site-templates/shapes/organization-service/content/en/AboutPage/about.mdx +38 -0
  72. package/site-templates/shapes/organization-service/content/en/HomePage/home.mdx +85 -0
  73. package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
  74. package/site-templates/shapes/organization-service/content/en/PortfolioList/portfolio.mdx +171 -0
  75. package/site-templates/shapes/organization-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
  76. package/site-templates/shapes/organization-service/content/en/Service/first-engagement.mdx +38 -0
  77. package/site-templates/shapes/organization-service/content/en/Service/second-engagement.mdx +38 -0
  78. package/site-templates/shapes/organization-service/content/en/Service/third-engagement.mdx +38 -0
  79. package/site-templates/shapes/organization-service/content/en/ServicesList/services.mdx +72 -0
  80. package/site-templates/shapes/organization-service/provenance.json +4 -0
  81. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +369 -0
  82. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +299 -0
  83. package/site-templates/shapes/person-service/ailk-template.json +10 -0
  84. package/site-templates/shapes/person-service/content/_site.mdx +85 -0
  85. package/site-templates/shapes/person-service/content/en/AboutPage/about.mdx +39 -0
  86. package/site-templates/shapes/person-service/content/en/HomePage/home.mdx +97 -0
  87. package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
  88. package/site-templates/shapes/person-service/content/en/PortfolioList/portfolio.mdx +169 -0
  89. package/site-templates/shapes/person-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
  90. package/site-templates/shapes/person-service/content/en/Service/first-engagement.mdx +38 -0
  91. package/site-templates/shapes/person-service/content/en/Service/second-engagement.mdx +38 -0
  92. package/site-templates/shapes/person-service/content/en/Service/third-engagement.mdx +38 -0
  93. package/site-templates/{simple-service → shapes/person-service}/content/en/ServicesList/services.mdx +16 -16
  94. package/site-templates/shapes/person-service/provenance.json +4 -0
  95. package/templates/.claude/skills/README.md +3 -2
  96. package/templates/.claude/skills/add-page/references/page-types.md +4 -0
  97. package/templates/.claude/skills/setup-database/SKILL.md +156 -0
  98. package/templates/.claude/skills/setup-database/scripts/verify-inbox.ts +154 -0
  99. package/templates/.claude/skills/setup-deploy/SKILL.md +64 -32
  100. package/templates/.claude/skills/setup-deploy/references/env-vars-template.md +19 -28
  101. package/templates/.claude/skills/setup-deploy/references/runbook-template.md +58 -102
  102. package/templates/DESIGN.md +5 -0
  103. package/templates/README.md +46 -38
  104. package/templates/apps/api/CLAUDE.md +3 -2
  105. package/templates/apps/api/src/config/__tests__/index.test.ts +115 -14
  106. package/templates/apps/api/src/config/index.ts +35 -16
  107. package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +30 -0
  108. package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +96 -0
  109. package/templates/apps/api/src/lib/lead-capture-store.ts +12 -0
  110. package/templates/apps/api/src/lib/lead-delivery.ts +52 -0
  111. package/templates/apps/api/src/openapi/spec.ts +63 -12
  112. package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +88 -0
  113. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +100 -0
  114. package/templates/apps/api/src/routes/leads/index.ts +46 -1
  115. package/templates/apps/api/src/server.ts +7 -0
  116. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +194 -0
  117. package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +124 -0
  118. package/templates/apps/api/src/services/lead-routing.ts +140 -30
  119. package/templates/apps/api/src/services/leads-inbox.ts +48 -11
  120. package/templates/apps/mcp/__tests__/parity.test.ts +5 -4
  121. package/templates/apps/mcp/src/tools/create_project.ts +21 -6
  122. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +14 -6
  123. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +10 -9
  124. package/templates/apps/web/app/[locale]/contact/__tests__/contact-form-text.test.ts +32 -0
  125. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +129 -0
  126. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +227 -84
  127. package/templates/apps/web/app/[locale]/contact/contact-form-text.ts +50 -0
  128. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +73 -20
  129. package/templates/apps/web/app/[locale]/contact/page.tsx +105 -15
  130. package/templates/apps/web/app/[locale]/contact/site-contact-email.ts +54 -0
  131. package/templates/apps/web/app/[locale]/features/[slug]/page.tsx +64 -0
  132. package/templates/apps/web/app/[locale]/features/page.tsx +52 -0
  133. package/templates/apps/web/app/[locale]/layout.tsx +37 -5
  134. package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +75 -0
  135. package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +163 -0
  136. package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +66 -0
  137. package/templates/apps/web/app/[locale]/newsletter/page.tsx +106 -14
  138. package/templates/apps/web/app/[locale]/portfolio/[slug]/page.tsx +59 -0
  139. package/templates/apps/web/app/[locale]/portfolio/page.tsx +49 -0
  140. package/templates/apps/web/app/[locale]/use-cases/[slug]/page.tsx +18 -3
  141. package/templates/apps/web/lib/nav-config.ts +48 -36
  142. package/templates/apps/web/lib/site-identity.ts +14 -0
  143. package/templates/apps/web/messages/en.json +28 -2
  144. package/templates/content/_site.mdx +4 -2
  145. package/templates/content/en/BlogList/blog.mdx +37 -16
  146. package/templates/content/en/ContactPage/contact.mdx +14 -44
  147. package/templates/content/en/HomePage/home.mdx +8 -16
  148. package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +0 -1
  149. package/templates/content/en/UseCasesList/use-cases.mdx +6 -6
  150. package/templates/database/CHANGELOG.md +7 -0
  151. package/templates/database/package.json +1 -1
  152. package/versions.json +33 -33
  153. package/site-templates/_base/content/en/UseCase/first-use-case.mdx +0 -23
  154. package/site-templates/_base/content/en/UseCase/second-use-case.mdx +0 -23
  155. package/site-templates/_base/content/en/UseCase/third-use-case.mdx +0 -23
  156. package/site-templates/simple-service/DESIGN.md +0 -162
  157. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +0 -333
  158. package/site-templates/simple-service/ailk-template.json +0 -11
  159. package/site-templates/simple-service/content/en/BookCall/book-call.mdx +0 -53
  160. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +0 -32
  161. package/site-templates/simple-service/content/en/FAQPage/faq.mdx +0 -56
  162. package/site-templates/simple-service/content/en/HomePage/home.mdx +0 -85
  163. package/site-templates/simple-service/content/en/Service/landscape-design.mdx +0 -40
  164. package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +0 -40
  165. package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +0 -40
  166. package/site-templates/simple-service/provenance.json +0 -4
  167. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +0 -211
  168. package/templates/apps/web/app/[locale]/contact/__tests__/ContactForm.test.tsx +0 -96
  169. package/templates/content/en/SoftwareProduct/product.mdx +0 -175
@@ -25,13 +25,14 @@ import { deriveArchitecture } from "./derive-architecture.js";
25
25
  import { extractModuleBundle, installBundleTree, } from "./lib/extract-module-bundle.js";
26
26
  import { fetchTemplate } from "./lib/fetch-template.js";
27
27
  import { assertLicenseKeyShape, writeLicenseKeyFiles, } from "./lib/license-key-files.js";
28
- import { resolvePaidModules } from "./lib/module-license-gate.js";
28
+ import { FREE_DESIGNS, assertDesignAllowed, deriveLevel, resolveModuleSet, resolvePaidModules, } from "./lib/module-license-gate.js";
29
29
  import { classifyModules } from "./lib/module-tier.js";
30
30
  import { normalizeModules } from "./module-architecture.js";
31
+ import { RECOGNIZED_SHAPES, RESERVED_SHAPES, } from "./parse-args.js";
31
32
  import { SITE_CHROME_PATH, renderSiteChrome, resolveSiteChrome, } from "./site-chrome.js";
32
33
  import { GATEABLE_SURFACES, MODULE_SURFACES, NAV_CONFIG_PATH, excludedRoutePaths, renderNavConfig, resolveSurfaces, } from "./surfaces.js";
33
34
  import { syncRoutes } from "./sync-routes.js";
34
- import { BASE_LAYER_DIR, STARTER_DESIGNS, TemplateError, mergeRoster, resolveTemplate, resolveTemplateLineage, rosterDeclares, templateDelivery, } from "./template-manifest.js";
35
+ import { BASE_LAYER_DIR, SHAPE_STARTERS, STARTER_DESIGNS, TEMPLATE_MANIFESTS, TemplateError, mergeRoster, resolveTemplate, resolveTemplateLineage, rosterDeclares, templateDelivery, templateTreeDir, } from "./template-manifest.js";
35
36
  import { validateTarget } from "./validate.js";
36
37
  const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
37
38
  const TEMPLATES_DIR = resolve(PKG_ROOT, "templates");
@@ -75,6 +76,37 @@ function countFiles(dir) {
75
76
  }
76
77
  return count;
77
78
  }
79
+ /**
80
+ * The template a `shape` scaffolds (#6754 D5, extended #6757 D6 row 1): the
81
+ * one FREE-delivery manifest of that shape — a declared `--template` alias
82
+ * (`TEMPLATE_MANIFESTS`) or a bare-shape starter with no alias of its own
83
+ * (`SHAPE_STARTERS`), the union the candidate set draws from. Reads manifest
84
+ * fields only — no roster.
85
+ *
86
+ * @throws TemplateError on a reserved or unknown shape, a shape with no Free
87
+ * starter yet (D6), or more than one (never picked silently).
88
+ */
89
+ function resolveShapeTemplate(shape) {
90
+ if (!RECOGNIZED_SHAPES.includes(shape)) {
91
+ const known = RECOGNIZED_SHAPES.join(", ");
92
+ throw new TemplateError(RESERVED_SHAPES.includes(shape)
93
+ ? `shape "${shape}" is not available yet. Choose one of: ${known}.`
94
+ : `unknown shape "${shape}". Recognized: ${known}`);
95
+ }
96
+ const ofShape = [
97
+ ...Object.values(TEMPLATE_MANIFESTS),
98
+ ...Object.values(SHAPE_STARTERS),
99
+ ].filter((manifest) => manifest.shape === shape);
100
+ const free = ofShape.filter((manifest) => templateDelivery(manifest) === "free");
101
+ if (free.length === 0) {
102
+ const names = ofShape.map((manifest) => manifest.name).join(", ");
103
+ throw new TemplateError(`shape "${shape}" has no Free starter yet — its page list lands with #6743. Templates built on this shape: ${names || "none"}.`);
104
+ }
105
+ if (free.length > 1) {
106
+ throw new TemplateError(`shape "${shape}" has more than one Free starter: ${free.map((manifest) => manifest.name).join(", ")}`);
107
+ }
108
+ return free[0];
109
+ }
78
110
  /**
79
111
  * Scaffold an AI Launch Kit starter project into targetDir (synchronous).
80
112
  *
@@ -118,7 +150,12 @@ export function scaffoldSync(opts) {
118
150
  if (opts.withCatalog === true)
119
151
  writeComponentCatalog(targetAbs);
120
152
  const filesCreated = countFiles(targetAbs);
121
- return { targetDir: targetAbs, filesCreated, surfaces: enabledSurfaces };
153
+ return {
154
+ targetDir: targetAbs,
155
+ filesCreated,
156
+ surfaces: enabledSurfaces,
157
+ modules: normalizeModules(opts.modules ?? ["marketing"]),
158
+ };
122
159
  }
123
160
  /**
124
161
  * Scaffold an AI Launch Kit starter project into targetDir (async wrapper).
@@ -138,8 +175,14 @@ export function scaffoldSync(opts) {
138
175
  * its fetched bundle or — when it requires only free modules — from the
139
176
  * `site-templates/` tree this package ships.
140
177
  *
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
178
+ * Between 2 and 3, `assertDesignAllowed` (#6760) refuses a design the caller
179
+ * CHOSE (`opts.design`) for a level below Pro, HERE, also before anything is
180
+ * written to disk. A template's own declared design is never gated (#6778
181
+ * option (c)): the template's `requires` already decide its level.
182
+ *
183
+ * And, ahead of all five when `opts.template` (or `opts.shape`, #6754 D5) is
184
+ * set (0): resolve the template and union its `requires` with `opts.modules`,
185
+ * so every step above
143
186
  * runs over the EFFECTIVE module set rather than the requested one.
144
187
  *
145
188
  * `scaffoldSync` itself stays licensing-oblivious on purpose: every existing
@@ -155,8 +198,17 @@ export async function scaffold(opts) {
155
198
  const licenseKey = opts.license ?? opts.licenseKey;
156
199
  // Resolved first, and by the registry rather than re-validated here: the
157
200
  // 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;
201
+ // `TemplateError` for a programmatic caller that skipped the parser. A
202
+ // `shape` resolves to its one Free starter and from there is the template
203
+ // path verbatim (#6754 D5); neither leaves the bare overlay untouched.
204
+ if (opts.template !== undefined && opts.shape !== undefined) {
205
+ throw new TemplateError("pass a template or a shape, not both");
206
+ }
207
+ const template = opts.template !== undefined
208
+ ? resolveTemplate(opts.template)
209
+ : opts.shape !== undefined
210
+ ? resolveShapeTemplate(opts.shape)
211
+ : undefined;
160
212
  // #6638 — the design this scaffold renders: the caller's override, else the
161
213
  // template's own. Unset for a scaffold naming neither.
162
214
  const design = opts.design ?? template?.design;
@@ -165,29 +217,108 @@ export async function scaffold(opts) {
165
217
  }
166
218
  // #5618 D2 — the `extends` chain is resolved in the client registry before
167
219
  // 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);
220
+ // still non-existent, like the four refusals below. The registry is the
221
+ // union of TEMPLATE_MANIFESTS and SHAPE_STARTERS (#6757 D6 row 1): a
222
+ // `--shape` run's resolved template may be a bare-shape starter, whose name
223
+ // (the shape itself) `TEMPLATE_MANIFESTS` alone does not carry.
224
+ if (template !== undefined) {
225
+ resolveTemplateLineage(template.name, {
226
+ ...TEMPLATE_MANIFESTS,
227
+ ...SHAPE_STARTERS,
228
+ });
229
+ }
171
230
  // The effective module set: union(template.requires, parsed --modules),
172
231
  // normalized through `normalizeModules` — the single ordering authority
173
232
  // (parent record D7). Order-independence falls out rather than being
174
233
  // engineered: it returns `RECOGNIZED_MODULES.filter(...)`, declaration-
175
234
  // ordered and deduped, so the same SET in any argument order is the same
176
235
  // 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
- });
236
+ let modules;
237
+ let bundles;
238
+ // The run's derived level (#6759), for `assertDesignAllowed` below.
239
+ // Undefined on the alias branch and on a `--modules`-keep run (D2/D4 in
240
+ // module-license-gate.ts — the level plays no part in composing either),
241
+ // so a non-`standard` design on either path derives it separately, below.
242
+ let level;
243
+ if (opts.template !== undefined) {
244
+ // Alias run — untouched (NFR-4): the union line and its
245
+ // resolvePaidModules({ …, templateName }) call stay exactly as they were.
246
+ modules = normalizeModules([
247
+ ...(template?.requires ?? []),
248
+ ...(opts.modules ?? ["marketing"]),
249
+ ]);
250
+ const { paid } = classifyModules(modules);
251
+ bundles = await resolvePaidModules({
252
+ paidModules: paid,
253
+ licenseKey,
254
+ ...(template !== undefined ? { templateName: template.name } : {}),
255
+ ...(opts.moduleRegistryUrl !== undefined
256
+ ? { registryUrl: opts.moduleRegistryUrl }
257
+ : {}),
258
+ ...(opts.fetchImpl !== undefined ? { fetchImpl: opts.fetchImpl } : {}),
259
+ });
260
+ // #6760 — `resolveModuleSet` never runs on this branch (NFR-4), so no
261
+ // level was derived above. Derive one — from the registry's per-module
262
+ // answers, never a key claim (D2) — only when the caller CHOSE a
263
+ // non-`standard` design alongside the template (`opts.design`). An
264
+ // 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)) {
268
+ ({ level } = await deriveLevel({
269
+ licenseKey,
270
+ ...(opts.moduleRegistryUrl !== undefined
271
+ ? { registryUrl: opts.moduleRegistryUrl }
272
+ : {}),
273
+ ...(opts.fetchImpl !== undefined ? { fetchImpl: opts.fetchImpl } : {}),
274
+ }));
275
+ }
276
+ }
277
+ else {
278
+ // Shape and bare runs — the key's level decides (#6759 D6), never a flag,
279
+ // a manifest or a key claim.
280
+ const chosen = await resolveModuleSet({
281
+ named: opts.modules,
282
+ licenseKey,
283
+ ...(opts.moduleRegistryUrl !== undefined
284
+ ? { registryUrl: opts.moduleRegistryUrl }
285
+ : {}),
286
+ ...(opts.fetchImpl !== undefined ? { fetchImpl: opts.fetchImpl } : {}),
287
+ });
288
+ bundles = chosen.bundles;
289
+ level = chosen.level;
290
+ modules =
291
+ template !== undefined
292
+ ? normalizeModules([...template.requires, ...chosen.modules])
293
+ : chosen.modules;
294
+ // A paid member of the shape's own starter `requires` not already
295
+ // fetched during the level probe (D6) — every buildable shape's starter
296
+ // requires only `marketing` today, so this is presently a no-op branch.
297
+ if (template !== undefined) {
298
+ const templatePaid = classifyModules(template.requires).paid;
299
+ const stillMissing = templatePaid.filter((m) => !bundles.has(m));
300
+ if (stillMissing.length > 0) {
301
+ const extra = await resolvePaidModules({
302
+ paidModules: stillMissing,
303
+ licenseKey,
304
+ ...(opts.moduleRegistryUrl !== undefined
305
+ ? { registryUrl: opts.moduleRegistryUrl }
306
+ : {}),
307
+ ...(opts.fetchImpl !== undefined
308
+ ? { fetchImpl: opts.fetchImpl }
309
+ : {}),
310
+ });
311
+ for (const [k, v] of extra)
312
+ bundles.set(k, v);
313
+ }
314
+ }
315
+ }
316
+ // #6760 D4 — after the level is resolved, before any fetch or write below.
317
+ // Only the caller's chosen design is gated (#6778 option (c)); a template's
318
+ // own declared design is not. `level ?? "free"` fails closed: a
319
+ // `--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");
191
322
  // The template's OWN bundle, fetched here — before `scaffoldSync` — so the
192
323
  // fourth refusal (the template's bundle unpublished, parent record D17 /
193
324
  // spec 5481 § 3 DD5 row 4) throws with the target still non-existent, like
@@ -326,7 +457,7 @@ export async function scaffold(opts) {
326
457
  ownership,
327
458
  })
328
459
  : (() => {
329
- const sourceRoot = join(SITE_TEMPLATES_DIR, template.name);
460
+ const sourceRoot = join(SITE_TEMPLATES_DIR, templateTreeDir(template.name));
330
461
  if (!existsSync(sourceRoot)) {
331
462
  throw new Error(`template "${template.name}": ${sourceRoot} is missing — the published package must carry site-templates/ in its "files" list`);
332
463
  }
@@ -351,6 +482,13 @@ export async function scaffold(opts) {
351
482
  if (opts.license !== undefined) {
352
483
  writeLicenseKeyFiles(result.targetDir, opts.license);
353
484
  }
485
+ // #6754 D4/D5 — the resolved starter, so the CLI names what it built.
486
+ if (template !== undefined && design !== undefined) {
487
+ return {
488
+ ...result,
489
+ starter: { template: template.name, shape: template.shape, design },
490
+ };
491
+ }
354
492
  return result;
355
493
  }
356
494
  /**
@@ -30,7 +30,7 @@ import { AddModuleRefusedError, readArchitectureHeader, } from "./add-module-cor
30
30
  import { diffTwoStrings } from "./lib/extract-module-bundle.js";
31
31
  import { MODULE_SLICES, normalizeModules, } from "./module-architecture.js";
32
32
  import { substituteMarkers } from "./substitute.js";
33
- import { BASE_LAYER_DIR, TEMPLATE_MANIFESTS, resolveTemplate, rosterDeclares, } from "./template-manifest.js";
33
+ import { BASE_LAYER_DIR, SHAPE_STARTERS, TEMPLATE_MANIFESTS, resolveTemplate, rosterDeclares, templateTreeDir, } from "./template-manifest.js";
34
34
  /** The site-root file that is the roster of record (#6658 D7). */
35
35
  export const SITE_ROSTER_PATH = ".ailk-site-manifest.json";
36
36
  /**
@@ -168,10 +168,15 @@ export function readSiteRoster(targetDir) {
168
168
  if (path === undefined)
169
169
  return undefined;
170
170
  const file = parseRosterFile(readFileSync(join(targetDir, path), "utf8"), path);
171
- const name = file.doc["template"];
171
+ const name = String(file.doc["template"]);
172
172
  let template;
173
173
  try {
174
- template = resolveTemplate(String(name));
174
+ // #6757 D6 — a bare-shape starter's name (the shape itself) resolves
175
+ // through SHAPE_STARTERS first; `resolveTemplate` alone never sees one
176
+ // (NFR-3).
177
+ template = Object.prototype.hasOwnProperty.call(SHAPE_STARTERS, name)
178
+ ? SHAPE_STARTERS[name]
179
+ : resolveTemplate(name);
175
180
  }
176
181
  catch (err) {
177
182
  throw new AddModuleRefusedError(`${path} names no declared template (${err.message}), so the page roster is unknown`);
@@ -279,7 +284,7 @@ function expectedRow(ref, targetDir) {
279
284
  /** The bytes a scaffold installed at `path` from the template, base or overlay layer. */
280
285
  function installedSeed(opts, path) {
281
286
  const layers = [
282
- join(opts.siteTemplatesDir, opts.roster.template.name),
287
+ join(opts.siteTemplatesDir, templateTreeDir(opts.roster.template.name)),
283
288
  join(opts.siteTemplatesDir, BASE_LAYER_DIR),
284
289
  opts.templatesDir,
285
290
  ];
@@ -308,7 +313,7 @@ export function planRosterAppend(opts) {
308
313
  const inSet = new Set(opts.modules);
309
314
  const refs = opts.references.filter((ref) => inSet.has(ref.module));
310
315
  const contributed = new Set(refs.map((ref) => ref.contribution.route));
311
- const shipped = readShippedManifest(opts.siteTemplatesDir, roster.template.name);
316
+ const shipped = readShippedManifest(opts.siteTemplatesDir, templateTreeDir(roster.template.name));
312
317
  const templateRows = [];
313
318
  const moduleRows = new Map();
314
319
  const superseded = [];
@@ -393,7 +398,7 @@ function layerInstalledSeeds(template, modules, opts) {
393
398
  }
394
399
  // The template's own tree, as the generator's `ownedContent` reads it: an
395
400
  // `installMap` source lands at its mapped destination instead.
396
- const ownRoot = join(opts.siteTemplatesDir, template.name);
401
+ const ownRoot = join(opts.siteTemplatesDir, templateTreeDir(template.name));
397
402
  const ownManifest = join(ownRoot, "ailk-template.json");
398
403
  const ownMap = existsSync(ownManifest)
399
404
  ? (JSON.parse(readFileSync(ownManifest, "utf8")).installMap ?? {})
@@ -0,0 +1,216 @@
1
+ /**
2
+ * The starter grid's data (#6753): the shape rosters and the alias table
3
+ * every `--template` name resolves through. A starter is one shape rendered
4
+ * in one design with a module list (starter-template contract § The three
5
+ * axes); an alias names those coordinates plus how its own rows differ from
6
+ * the shape's standard roster.
7
+ *
8
+ * DATA ONLY, AND IMPORT-FREE AT RUNTIME. Every import below is `import type`,
9
+ * erased at emit. `template-names.ts` derives `RECOGNIZED_TEMPLATES` from
10
+ * `ALIASES` and `parse-args.ts` imports that at runtime, so a runtime import
11
+ * here could reach `lib/module-tier.ts` and re-open the `RECOGNIZED_MODULES`
12
+ * temporal-dead-zone cycle `template-names.ts`'s header records (spec 6753
13
+ * D1). The composition itself — shape roster, minus `drop`, plus `add` —
14
+ * lives in `template-manifest.ts`, whose `resolveTemplate` is the one reader
15
+ * that yields a template's roster. Nothing but those two files imports this one.
16
+ */
17
+ import type { CategorySlug } from "@working-theory/schema/categories";
18
+ import type { ModuleName } from "./parse-args.js";
19
+ import type { TemplateLayout } from "./site-chrome.js";
20
+ import type { StarterDesign, StarterShape, TemplateRosterRow } from "./template-manifest.js";
21
+ /** The shapes a starter can be built from — every StarterShape but the reserved one. */
22
+ export type BuildableShape = Exclude<StarterShape, "local-business-product">;
23
+ /** How an alias's own rows differ from its shape's standard roster. */
24
+ export interface AliasDifferences {
25
+ /** Shape-roster routes this alias does not carry. */
26
+ readonly drop?: readonly string[];
27
+ /** Rows this alias carries that its shape roster does not. */
28
+ readonly add?: readonly TemplateRosterRow[];
29
+ /** The retained shape route `add` is inserted before; absent → appended. */
30
+ readonly before?: string;
31
+ /**
32
+ * Further positioned groups (#6757 D7): each group's rows go before its
33
+ * own `before` route, applied group by group in array order, AFTER `add`
34
+ * is placed. The one alias that needs more than one anchor
35
+ * (`minimalism-site`, which interleaves rows at two separate points in the
36
+ * shape's row order) uses this; every other alias leaves it unset.
37
+ */
38
+ readonly insert?: readonly {
39
+ readonly before: string;
40
+ readonly rows: readonly TemplateRosterRow[];
41
+ }[];
42
+ }
43
+ /**
44
+ * One `--template` name: the grid coordinates it stands for.
45
+ *
46
+ * No `tier`, `level`, `price` or optional-module member, for the reason
47
+ * `TemplateManifest` carries none (parent § D4, D12): the level is derived
48
+ * from `modules`, never declared, and the type is what makes declaring one a
49
+ * compile error.
50
+ */
51
+ export interface TemplateAlias {
52
+ readonly shape: BuildableShape;
53
+ readonly design: StarterDesign;
54
+ /** Becomes the manifest's `requires`. */
55
+ readonly modules: readonly ModuleName[];
56
+ readonly categories: readonly CategorySlug[];
57
+ readonly layout?: TemplateLayout;
58
+ readonly differences?: AliasDifferences;
59
+ }
60
+ /**
61
+ * Each buildable shape's standard roster (#6753 D3) — the rows an alias starts
62
+ * from before its `differences` apply.
63
+ *
64
+ * `organization-product` is today's base roster, the `/blog` teaser and the
65
+ * portfolio pair. `organization-service`, `local-business-service` (#6757)
66
+ * and `person-service` (#6758, roadmap § 6 Phase 2) are each their own rows,
67
+ * the `/blog` teaser and the portfolio pair.
68
+ */
69
+ export declare const SHAPE_ROSTERS: Readonly<Record<BuildableShape, readonly TemplateRosterRow[]>>;
70
+ /**
71
+ * The `--template` names and the grid coordinates each stands for (#6753) —
72
+ * the one table `TEMPLATE_MANIFESTS` and `RECOGNIZED_TEMPLATES` derive from,
73
+ * in the declaration order both keep. The plain name is the Pro one: it is
74
+ * the flagship and the demo (Sam, 2026-09-14, parent § D12).
75
+ *
76
+ * `workspace` appears in no module list. It is a Pro module, but an authed app
77
+ * surface contributing no PUBLIC page to a marketing site, and `modules` names
78
+ * what the roster uses (parent § D15). A Pro key unlocks it regardless of any
79
+ * template.
80
+ *
81
+ * `commerce` is a per-site add-on available at any level, so no alias exists
82
+ * FOR it (parent § D13); it still contributes rows to whichever alias takes
83
+ * it, through `mergeRoster`.
84
+ *
85
+ * Frozen (#5507): the registry composed from this table is also the issuer's
86
+ * authorization allowlist (spec 5480 § C7). A test needing a different table
87
+ * passes its own to `resolveTemplate(name, aliases)` rather than mutating
88
+ * this one.
89
+ */
90
+ export declare const ALIASES: Readonly<{
91
+ /**
92
+ * The Free template, and the mechanism's own proof (parent § D11): it
93
+ * requires only `marketing`, so the keyless merge path is exercised by a
94
+ * template we ship anyway rather than by a throwaway fixture. The bare
95
+ * `organization-product` shape — no differences at all.
96
+ */
97
+ readonly "marketing-site-free": {
98
+ readonly shape: "organization-product";
99
+ readonly design: "standard";
100
+ readonly modules: readonly ["marketing"];
101
+ readonly categories: readonly ["accounting", "legal", "technology"];
102
+ readonly layout: {
103
+ readonly announcementBanner: true;
104
+ };
105
+ };
106
+ /**
107
+ * The Pro flagship. Its module list is OFFERINGS § 4 offering 3's tiers line
108
+ * — "docs, scheduling and flows modules" — where "flows" is
109
+ * `signal-collectors` (`MODULE_SURFACES` maps it to `["flows"]`). It drops
110
+ * the shape's `/blog` teaser because docs-blog contributes the real `/blog`,
111
+ * and adds the signal-collectors sample rows ahead of the portfolio pair.
112
+ */
113
+ readonly "marketing-site": {
114
+ readonly shape: "organization-product";
115
+ readonly design: "standard";
116
+ readonly modules: readonly ["marketing", "docs-blog", "scheduling", "signal-collectors"];
117
+ readonly categories: readonly ["accounting", "legal", "consulting", "hvac", "roofing", "plumbing", "electrical", "landscaping", "auto-repair", "real-estate", "technology", "medical-specialty"];
118
+ readonly differences: {
119
+ readonly drop: readonly ["/blog"];
120
+ readonly add: readonly import("./module-architecture.js").PageRosterRow[];
121
+ readonly before: "/portfolio";
122
+ };
123
+ };
124
+ /**
125
+ * Pro Plus — the Pro list plus `multi-tenant`, which is correctly in the
126
+ * list because it DOES contribute pages (the org and workspace surfaces).
127
+ * Its `pageContributions` is nonetheless unset today, deferred to its own
128
+ * instance, so this template's merged roster carries the same contributed
129
+ * rows as `marketing-site`'s and no more. That is a module which has
130
+ * declared no rows yet — visible in the diff — not a merge that dropped
131
+ * declared rows.
132
+ */
133
+ readonly "saas-product": {
134
+ readonly shape: "organization-product";
135
+ readonly design: "standard";
136
+ readonly modules: readonly ["marketing", "docs-blog", "scheduling", "signal-collectors", "multi-tenant"];
137
+ readonly categories: readonly ["technology", "medical-specialty"];
138
+ readonly differences: {
139
+ readonly drop: readonly ["/blog"];
140
+ readonly add: readonly import("./module-architecture.js").PageRosterRow[];
141
+ readonly before: "/portfolio";
142
+ };
143
+ };
144
+ /**
145
+ * The second Pro template (#5589) — the reports-first, scroll-snap deck
146
+ * site. NOT `marketing-site` plus a module: its `modules` omit `docs-blog`,
147
+ * so the two Pro templates are siblings under `_base` (#5618 D2), never a
148
+ * lineage. Level still derives from `modules`: `scheduling` and
149
+ * `signal-collectors` are paid, so `templateDelivery` returns `"paid"`
150
+ * exactly as it does for `marketing-site`.
151
+ *
152
+ * It composes the `organization-service` shape roster (#6757) rather than
153
+ * the organization-product one: the deck home replaces the document-scroll
154
+ * home, and `/how-it-works`, the product page, `/pricing` and `/sign-in`
155
+ * are not pages this site has. The roster is what the
156
+ * template IS, so an unrostered page has neither a route directory
157
+ * (`rosterSurfaces`) nor its `_base`/overlay seed (#6646 D1).
158
+ *
159
+ * It declares NO `layout` block (#6638): the deck home, mark-only nav and
160
+ * centered footer are the `minimalism` design's, supplied by
161
+ * `DESIGN_LAYOUTS` from `design:` — the look any shape can wear. The
162
+ * roster rows the `organization-service` shape does not derive
163
+ * (`/reports`, `/referral`) are recorded with their reasons in this
164
+ * template's TEMPLATE-MANIFEST.md § 3a.
165
+ *
166
+ * `/reports` and `/referral` carry no `GATEABLE_SURFACES` entry and need
167
+ * none: both are single-segment `LandingPage` slugs served by the generic
168
+ * `[slug]` route (#5469 D2), the same mechanism Free's `/how-it-works`
169
+ * uses. `/reports/[slug]` (the reader) is the signal-collectors module's
170
+ * own contributed row.
171
+ *
172
+ * `/services/[slug]` declares its seed DIRECTORY as `contentDir`
173
+ * (`content/en/Service`, as `routes-map.ts` records it). The three
174
+ * samples ride the template tree unmapped, landing at that directory.
175
+ *
176
+ * Since #6757, `organization-service` carries a real standard roster
177
+ * (D1), so this alias's `differences` is additions and drops only: it
178
+ * drops the `/blog` teaser (a Pro alias does not upsell its own level,
179
+ * D8) and the portfolio pair (the reports are the proof, not a
180
+ * portfolio), adds the signal-collectors samples, and INSERTS its two own
181
+ * rows — `/reports` before `/about`, `/referral` before `/faq` (D7) — to
182
+ * reproduce its pre-#6757 row order (§ 1.2) exactly.
183
+ */
184
+ readonly "minimalism-site": {
185
+ readonly shape: "organization-service";
186
+ readonly design: "minimalism";
187
+ readonly modules: readonly ["marketing", "scheduling", "signal-collectors"];
188
+ readonly categories: readonly ["real-estate", "technology", "legal"];
189
+ readonly differences: {
190
+ readonly drop: readonly ["/blog", "/portfolio", "/portfolio/[slug]"];
191
+ readonly add: readonly import("./module-architecture.js").PageRosterRow[];
192
+ readonly insert: readonly [{
193
+ readonly before: "/about";
194
+ readonly rows: readonly [import("./module-architecture.js").PageRosterRow];
195
+ }, {
196
+ readonly before: "/faq";
197
+ readonly rows: readonly [import("./module-architecture.js").PageRosterRow];
198
+ }];
199
+ };
200
+ };
201
+ }>;
202
+ /**
203
+ * The coordinates a keyless `--shape S` builds (#6757 D5) — the composed
204
+ * FREE manifest per shape, for a shape with no `--template` alias of its
205
+ * own. Frozen and `Partial`; `organization-product` needs none — its
206
+ * keyless tree is `marketing-site-free`'s (#6754 AC-2).
207
+ *
208
+ * No `differences` (a bare shape is its standard roster, D1) and no
209
+ * `layout`. Every category is a leaf of `packages/schema/src/categories.ts`,
210
+ * as `TemplateManifest.categories` requires. `person-service`'s single
211
+ * `consulting` category is the operator's disposition (2026-09-26,
212
+ * ai-launch-kit-internal#6758): the leaf did not exist when #6758's spec was
213
+ * written, and adding it replaced that spec's `["accounting", "legal",
214
+ * "technology"]` author-pick stand-in.
215
+ */
216
+ export declare const SHAPE_STARTER_ALIASES: Readonly<Partial<Record<BuildableShape, TemplateAlias>>>;