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
package/dist/surfaces.js CHANGED
@@ -31,6 +31,7 @@
31
31
  * `MODULE_SURFACES`, and `__tests__/surface-gating.test.ts` asserts those keys
32
32
  * equal `RECOGNIZED_MODULES` so the two cannot drift.
33
33
  */
34
+ import { createHash } from "node:crypto";
34
35
  /** Template-relative prefix every `apps/web` route directory sits under. */
35
36
  const ROUTE_ROOT = "apps/web/app/[locale]";
36
37
  /**
@@ -114,19 +115,22 @@ export const NON_ROUTE_DIRS = new Set([
114
115
  * byte-deterministic).
115
116
  */
116
117
  export const GATEABLE_SURFACES = [
118
+ {
119
+ // #6878 D9 — the comparison hub/spoke pair (`ComparisonsList` at
120
+ // /alternatives, `ComparisonPage` at /vs/<slug>). No `navLabelKey`: no
121
+ // roster names it by default, so it excludes at scaffold time exactly
122
+ // like any other gateable surface a roster leaves off. `add-page`
123
+ // installs it (D11) the first time a site adds a comparison.
124
+ id: "alternatives",
125
+ routes: [`${ROUTE_ROOT}/alternatives`, `${ROUTE_ROOT}/vs`],
126
+ content: ["content/<locale>/ComparisonsList", "content/<locale>/ComparisonPage"],
127
+ },
117
128
  {
118
129
  id: "blog",
119
130
  routes: [`${ROUTE_ROOT}/blog`],
120
131
  content: ["apps/web/content/<locale>/blog"],
121
132
  navLabelKey: "blog",
122
133
  },
123
- {
124
- // Module-less (#6658 D5), like `pricing`: every starter's roster names
125
- // it. No `navLabelKey` — the footer links it, gated on this surface.
126
- id: "book-a-call",
127
- routes: [`${ROUTE_ROOT}/book-a-call`],
128
- content: ["content/<locale>/BookCall"],
129
- },
130
134
  {
131
135
  id: "careers",
132
136
  routes: [`${ROUTE_ROOT}/careers`],
@@ -186,6 +190,14 @@ export const GATEABLE_SURFACES = [
186
190
  routes: [`${ROUTE_ROOT}/flows`],
187
191
  content: [],
188
192
  },
193
+ {
194
+ // #6878 D3 — the persona hub, renamed from `use-cases` (route-naming
195
+ // convention #3450: /for/{persona} spokes under a /for hub). Every shape
196
+ // roster carries it now (D7), service shapes included.
197
+ id: "for",
198
+ routes: [`${ROUTE_ROOT}/for`],
199
+ content: ["content/<locale>/UseCasesList", "content/<locale>/UseCase"],
200
+ },
189
201
  {
190
202
  // Module-less (#6658 D5) — see `book-a-call`.
191
203
  id: "glossary",
@@ -303,11 +315,6 @@ export const GATEABLE_SURFACES = [
303
315
  routes: [`${ROUTE_ROOT}/testimonials`],
304
316
  content: ["content/<locale>/TestimonialsList"],
305
317
  },
306
- {
307
- id: "use-cases",
308
- routes: [`${ROUTE_ROOT}/use-cases`],
309
- content: ["content/<locale>/UseCasesList", "content/<locale>/UseCase"],
310
- },
311
318
  {
312
319
  id: "whitepapers",
313
320
  routes: [`${ROUTE_ROOT}/whitepapers`],
@@ -323,6 +330,9 @@ export const RECOGNIZED_SURFACES = GATEABLE_SURFACES.map((s) => s.id);
323
330
  */
324
331
  export const SURFACE_ALIASES = {
325
332
  schedule: "meet",
333
+ // #6878 D3 — one-release alias for the /use-cases -> /for rename, same
334
+ // removal trigger as `schedule` above.
335
+ "use-cases": "for",
326
336
  };
327
337
  /**
328
338
  * Surface ids that became core routes (#6658 D3: `software` — the product's
@@ -507,10 +517,6 @@ export function excludedContentPaths(enabled) {
507
517
  export const ROUTE_SINCE = {
508
518
  about: "0.0.0",
509
519
  "blog/[[...slug]]": "0.0.0",
510
- // #6658 — four genuinely new route directories (D4, D5), attaching at
511
- // today's fixed-group version, the same "attach at today's version" rule
512
- // #6084, #5977, #5678, #5479, #5371 and #5048 applied.
513
- "book-a-call": "0.26.0",
514
520
  careers: "0.0.0",
515
521
  "careers/[slug]": "0.0.0",
516
522
  "case-studies": "0.0.0",
@@ -531,6 +537,10 @@ export const ROUTE_SINCE = {
531
537
  "flows/[slug]": "0.0.0",
532
538
  "flows/[slug]/results/[payload]": "0.14.0",
533
539
  "flows/[slug]/start": "0.18.0",
540
+ // #6658 — genuinely new route directories (D4, D5), attaching at today's
541
+ // fixed-group version, the same "attach at today's version" rule #6084,
542
+ // #5977, #5678, #5479, #5371 and #5048 applied. `book-a-call` was a third
543
+ // such entry, retired by #6897 D10.
534
544
  glossary: "0.26.0",
535
545
  "how-to": "0.0.0",
536
546
  "how-to/[slug]": "0.0.0",
@@ -579,8 +589,14 @@ export const ROUTE_SINCE = {
579
589
  terms: "0.22.0",
580
590
  testimonials: "0.0.0",
581
591
  "thanks/[slug]": "0.14.0",
582
- "use-cases": "0.0.0",
583
- "use-cases/[slug]": "0.26.0",
592
+ // #6878 — the persona family, renamed from `use-cases`/`use-cases/[slug]`.
593
+ // Attaches at today's fixed-group version, the same "attach at today's
594
+ // version" rule #6084, #5977, #5678, #5479, #5371 and #5048 applied.
595
+ for: "0.28.0",
596
+ "for/[slug]": "0.28.0",
597
+ // #6878 D9 — the comparison hub/spoke pair.
598
+ alternatives: "0.28.0",
599
+ "vs/[slug]": "0.28.0",
584
600
  whitepapers: "0.0.0",
585
601
  "whitepapers/[slug]": "0.0.0",
586
602
  // #5014 — the (authed) route GROUP: layout.tsx + sign-in-gate.tsx sit
@@ -645,6 +661,10 @@ export const ROUTE_SINCE = {
645
661
  // directory, so it attaches at today's fixed-group version, the same rule
646
662
  // #5479, #5371 and #5048 applied.
647
663
  "[slug]": "0.23.0",
664
+ // #6897 D2 — the Free /meet/<slug> placeholder detail route. A genuinely
665
+ // new route directory, so it attaches at today's fixed-group version, the
666
+ // same rule applied above.
667
+ "[slug]/[detail]": "0.28.0",
648
668
  };
649
669
  /**
650
670
  * Surface id of the registry that owns `routeDir` (ROUTE_ROOT-relative): the
@@ -680,12 +700,18 @@ export function surfaceForRouteDir(routeDir) {
680
700
  */
681
701
  export const ROUTE_DEPENDENCIES = {
682
702
  // #5963 — the (auth) group's layout renders the brand mark through
683
- // `siteLogo()`, and the LOCALE layout above it branches on
684
- // `isAuthPath()` to drop the marketing chrome for these five paths: without
685
- // `auth-routes.ts` a synced group renders inside the Nav + Footer it exists
686
- // to replace. The sign-in page no longer imports `site-brand` — the mark is
687
- // the shell's since #5963 — so its own list is just the env reader.
688
- "(auth)": ["apps/web/lib/site-brand.tsx", "apps/web/lib/auth-routes.ts"],
703
+ // `siteLogo()` — which lives in `site-logo.tsx` since #6820 and reads the
704
+ // raw mark + name from `site-brand.tsx`, so BOTH files travel with the
705
+ // group — and the LOCALE layout above it branches on `isAuthPath()` to drop
706
+ // the marketing chrome for these five paths: without `auth-routes.ts` a
707
+ // synced group renders inside the Nav + Footer it exists to replace. The
708
+ // sign-in page no longer imports `site-brand` — the mark is the shell's
709
+ // since #5963 — so its own list is just the env reader.
710
+ "(auth)": [
711
+ "apps/web/lib/site-brand.tsx",
712
+ "apps/web/lib/site-logo.tsx",
713
+ "apps/web/lib/auth-routes.ts",
714
+ ],
689
715
  "(auth)/sign-in": ["apps/web/lib/auth-env.ts"],
690
716
  "(auth)/sign-up": ["apps/web/lib/auth-env.ts"],
691
717
  "(auth)/forgot-password": ["apps/web/lib/auth-env.ts"],
@@ -760,6 +786,46 @@ export const ROUTE_MESSAGE_NAMESPACES = {
760
786
  };
761
787
  /** Template-relative path of the generated site-nav registry. */
762
788
  export const NAV_CONFIG_PATH = "apps/web/lib/nav-config.ts";
789
+ /**
790
+ * `renderNavConfig`'s first-line stamp (#6908 D1): `// ailk:generated
791
+ * sha256=<64 lowercase hex>`, recognised whether it ends in `\n` or `\r\n`.
792
+ * `<hex>` is the SHA-256 of every byte after the stamp line's terminator,
793
+ * CRLF folded to LF first — the same fold-and-hash rule `claude-md.ts`
794
+ * `hashBody` uses for the scaffold's `CLAUDE.md` regions (#6904), given its
795
+ * own definition here (N1) rather than an import, since the two files hash
796
+ * unrelated bodies.
797
+ */
798
+ const NAV_STAMP_RE = /^\/\/ ailk:generated sha256=([0-9a-f]{64})\r?\n/;
799
+ /** SHA-256, lowercase hex, of a nav body with every CRLF folded to LF (D1, N1). */
800
+ function hashNavBody(body) {
801
+ return createHash("sha256")
802
+ .update(body.replace(/\r\n/g, "\n"), "utf8")
803
+ .digest("hex");
804
+ }
805
+ /**
806
+ * Verify a rendered nav's self-hashing stamp (#6908 D1/F1–F2): `"valid"` when
807
+ * the first line's hash matches every byte after it, `"invalid"` when a
808
+ * stamp line is present but the hash fails (an edited, still-stamped nav),
809
+ * `"absent"` when there is no stamp line at all. A stamp that verifies proves
810
+ * the file unedited whatever generator wrote it — the check needs only the
811
+ * file, never the generator's code.
812
+ */
813
+ export function readNavConfigStamp(text) {
814
+ const match = NAV_STAMP_RE.exec(text);
815
+ if (match === null)
816
+ return "absent";
817
+ const body = text.slice(match[0].length);
818
+ return match[1] === hashNavBody(body) ? "valid" : "invalid";
819
+ }
820
+ /**
821
+ * Every byte after a nav's stamp line, or `text` unchanged when it carries
822
+ * none. The one place the D1 stamp-line shape is matched outside
823
+ * `readNavConfigStamp` itself, so a test simulating an edit below the stamp
824
+ * (or a bare pre-D1 body) never re-derives `NAV_STAMP_RE`'s pattern by hand.
825
+ */
826
+ export function stripNavConfigStamp(text) {
827
+ return text.replace(NAV_STAMP_RE, "");
828
+ }
763
829
  /**
764
830
  * Render `apps/web/lib/nav-config.ts` for an enabled surface set.
765
831
  *
@@ -770,14 +836,19 @@ export const NAV_CONFIG_PATH = "apps/web/lib/nav-config.ts";
770
836
  * surface set for the same reason the route tree does.
771
837
  *
772
838
  * The base items are the Free marketing-site chrome (#5469 D5, the approved
773
- * chrome diff recorded on that card, re-pointed by #6658 D8):
774
- * a Product flyout in two columns (Features · Use Cases, #6721), then Blog,
775
- * Pricing and About. The feature and use-case links ride the `features` and
776
- * `use-cases` surfaces; `/blog` is always linked (the teaser or the docs-blog
777
- * module serves it). Pricing is gateable
778
- * surfaces: each item is emitted only when its surface is enabled (a template
779
- * roster enables it, #5469 D10), so the nav still never links a page the
780
- * scaffold does not carry.
839
+ * chrome diff recorded on that card, re-pointed by #6658 D8, then by #6878 D6):
840
+ * ONE flyout rule for every shape — an offering column (Features on product
841
+ * shapes, Services on service shapes) beside a Customers column linking the
842
+ * /for persona pages — then Blog, Pricing and About. `/blog` is always linked
843
+ * (the teaser or the docs-blog module serves it). Pricing and every other
844
+ * gateable surface: each item is emitted only when its surface is enabled (a
845
+ * template roster enables it, #5469 D10), so the nav still never links a page
846
+ * the scaffold does not carry. Portfolio is never a flyout link (D6): on a
847
+ * service shape it is a plain top-level link directly after the Services
848
+ * flyout, and every shape's footer Site pages column links it (#6895), both
849
+ * under the `portfolio` surface's gate. No empty flyout is ever emitted: with
850
+ * neither column non-empty, no flyout item is emitted at all (closes #6766's
851
+ * AC-1).
781
852
  *
782
853
  * Only gateable surfaces carrying a `navLabelKey` are linked after the base
783
854
  * items, because a nav entry needs a translated label. Emitting an entry whose
@@ -791,23 +862,20 @@ export const NAV_CONFIG_PATH = "apps/web/lib/nav-config.ts";
791
862
  * Deterministic: gateable entries follow GATEABLE_SURFACES order (alphabetical
792
863
  * by id), after the fixed base items.
793
864
  */
794
- export function renderNavConfig(enabled) {
865
+ export function renderNavConfig(enabled, options) {
795
866
  const on = new Set(enabled);
796
867
  // `blog` is linked unconditionally below (operator ruling 2026-09-23), so
797
- // it is excluded here; `use-cases` is the Free roster's own page (a
798
- // module-less surface the roster enables, like `pricing`), linked in its
799
- // fixed positions below rather than appended.
800
- const linked = GATEABLE_SURFACES.filter((s) => s.navLabelKey !== undefined &&
801
- on.has(s.id) &&
802
- s.id !== "blog" &&
803
- s.id !== "use-cases");
804
- const useCases = on.has("use-cases");
868
+ // it is excluded here; `for` is the Customers flyout/footer column below,
869
+ // linked in its own fixed positions rather than appended.
870
+ const linked = GATEABLE_SURFACES.filter((s) => s.navLabelKey !== undefined && on.has(s.id) && s.id !== "blog");
805
871
  const features = on.has("features");
806
- // #6721 (operator ruling 2026-09-25): the Product flyout is two columns —
807
- // Features and Use Cases — each a heading over three label-only links. The
808
- // `groups` render the desktop panel; `features` carries the same links for
809
- // the mobile disclosure and the active-path match. "Use cases" left the top
810
- // nav (it lives in the flyout and its own footer column).
872
+ const services = on.has("services");
873
+ const forEnabled = on.has("for");
874
+ const portfolio = on.has("portfolio");
875
+ const serviceSlugs = [...(options?.serviceSlugs ?? [])].sort().slice(0, 3);
876
+ // #6878 D6 (operator ruling 2026-09-27): one flyout rule for every shape.
877
+ // The `groups` render the desktop panel; `features` carries the same links
878
+ // for the mobile disclosure and the active-path match.
811
879
  const featureLinks = features
812
880
  ? [
813
881
  ' { labelKey: "feature1", path: "/features/feature-1" },',
@@ -815,11 +883,14 @@ export function renderNavConfig(enabled) {
815
883
  ' { labelKey: "feature3", path: "/features/feature-3" },',
816
884
  ]
817
885
  : [];
818
- const useCaseLinks = useCases
886
+ const serviceLinks = !features && services
887
+ ? serviceSlugs.map((slug, i) => ` { labelKey: "service${i + 1}", path: "/services/${slug}" },`)
888
+ : [];
889
+ const customerLinks = forEnabled
819
890
  ? [
820
- ' { labelKey: "useCase1", path: "/use-cases/persona-1" },',
821
- ' { labelKey: "useCase2", path: "/use-cases/persona-2" },',
822
- ' { labelKey: "useCase3", path: "/use-cases/persona-3" },',
891
+ ' { labelKey: "customer1", path: "/for/persona-1" },',
892
+ ' { labelKey: "customer2", path: "/for/persona-2" },',
893
+ ' { labelKey: "customer3", path: "/for/persona-3" },',
823
894
  ]
824
895
  : [];
825
896
  const indent = (line) => ` ${line}`;
@@ -833,24 +904,56 @@ export function renderNavConfig(enabled) {
833
904
  " ],",
834
905
  " },",
835
906
  ];
907
+ // Offering column: Features on a product shape, Services on a service
908
+ // shape. `features` wins when both surfaces are somehow on at once — the
909
+ // Free roster never enables both.
910
+ const offeringLinks = features ? featureLinks : serviceLinks;
911
+ const offeringLabelKey = features ? "product" : "services";
912
+ const offeringHeadingKey = features
913
+ ? "flyoutFeaturesHeading"
914
+ : "flyoutServicesHeading";
915
+ const flyoutGroups = [
916
+ ...group(offeringHeadingKey, offeringLinks),
917
+ ...group("flyoutCustomersHeading", customerLinks),
918
+ ];
919
+ // #6942 — the Services flyout's action bar links the /services hub. The
920
+ // hub rides the `services` surface's gate: its route directory serves both
921
+ // /services and /services/[slug], so every roster that enables the surface
922
+ // carries the hub. Emitted only inside a flyout that has a column (never
923
+ // an action-only flyout). A flyout action requires an icon
924
+ // (`NavFlyoutAction`), imported below only when this link is emitted.
925
+ const servicesHub = !features && services && flyoutGroups.length > 0;
836
926
  const entries = [
837
- " {",
838
- ' labelKey: "product",',
839
- " flyout: {",
840
- // SOFT_LOCK_OK: #6508 — width "panel" (D4/D5): a fixed 640px panel.
841
- ' width: "panel",',
842
- " features: [",
843
- ...featureLinks,
844
- ...useCaseLinks,
845
- " ],",
846
- " groups: [",
847
- ...group("flyoutFeaturesHeading", featureLinks),
848
- ...group("flyoutUseCasesHeading", useCaseLinks),
849
- " ],",
850
- " actions: [],",
851
- " },",
852
- " },",
853
- // Operator ruling 2026-09-25: Product · Blog · Pricing · About.
927
+ // No empty flyout (D6): emitted only when at least one column has links.
928
+ ...(flyoutGroups.length === 0
929
+ ? []
930
+ : [
931
+ " {",
932
+ ` labelKey: "${offeringLabelKey}",`,
933
+ " flyout: {",
934
+ // SOFT_LOCK_OK: #6508 — width "panel" (D4/D5): a fixed 640px panel.
935
+ ' width: "panel",',
936
+ " features: [",
937
+ ...offeringLinks,
938
+ ...customerLinks,
939
+ " ],",
940
+ " groups: [",
941
+ ...flyoutGroups,
942
+ " ],",
943
+ servicesHub
944
+ ? ' actions: [{ labelKey: "services", icon: ArrowRight, path: "/services" }],'
945
+ : " actions: [],",
946
+ " },",
947
+ " },",
948
+ ]),
949
+ // #6895 (operator decision 2026-09-27): a service shape's header reads
950
+ // Services ▾ · Portfolio · Blog · About — Portfolio a plain top-level
951
+ // link, never a flyout link (D6), and the first item when no flyout is
952
+ // emitted. Product shapes keep their header.
953
+ ...(!features && services && portfolio
954
+ ? [' { labelKey: "portfolio", path: "/portfolio" },']
955
+ : []),
956
+ // Operator ruling 2026-09-25: [flyout ·] Blog · Pricing · About.
854
957
  // `/blog` is always linked — the `blog` surface serves the real route, and
855
958
  // with it off the `[slug]` route serves the overlay's module page.
856
959
  ' { labelKey: "blog", path: "/blog" },',
@@ -858,13 +961,17 @@ export function renderNavConfig(enabled) {
858
961
  ' { labelKey: "about", path: "/about" },',
859
962
  ...linked.map((s) => ` { labelKey: "${s.navLabelKey}", path: "/${s.id}" },`),
860
963
  ];
861
- // The footer's column groups (operator rulings 2026-09-23 + 2026-09-25):
862
- // Site pages, Features, Use Cases, Resources. Pricing rides the same
863
- // `pricing` gate as the header item; `/blog` is always linked (above). The
864
- // Features column links the three feature pages and the /features hub; the
865
- // Use Cases column the three use cases and the /use-cases hub — each rides
866
- // its surface's gate. Book a call, Glossary and Newsletter (#6658 D8) each
867
- // ride their own surface's gate.
964
+ // The footer's column groups (operator rulings 2026-09-23 + 2026-09-25,
965
+ // #6878 D6). Site pages, Features, Customers, Resources. Pricing rides the
966
+ // same `pricing` gate as the header item; `/blog` is always linked (above).
967
+ // The Features column links the three feature pages and the /features hub;
968
+ // the Customers column the three personas and the /for hub — each rides its
969
+ // surface's gate. Glossary and Newsletter (#6658 D8) each ride their own
970
+ // surface's gate. Book a call is always linked (#6897 D9), the same /blog
971
+ // precedent above: it retargets to /meet/book-a-call, served as a Free
972
+ // placeholder or the live scheduling page depending on what the site
973
+ // carries. No other footer column changes (D6) — there is deliberately no
974
+ // footer Services column.
868
975
  const footerEntries = [
869
976
  " {",
870
977
  ' headingKey: "footerSitePagesHeading",',
@@ -873,11 +980,13 @@ export function renderNavConfig(enabled) {
873
980
  ...(on.has("pricing")
874
981
  ? [' { labelKey: "pricing", path: "/pricing" },']
875
982
  : []),
983
+ // #6895 — every shape's Site pages column links Portfolio, gated.
984
+ ...(portfolio
985
+ ? [' { labelKey: "portfolio", path: "/portfolio" },']
986
+ : []),
876
987
  ' { labelKey: "about", path: "/about" },',
877
988
  ' { labelKey: "contact", path: "/contact" },',
878
- ...(on.has("book-a-call")
879
- ? [' { labelKey: "bookACall", path: "/book-a-call" },']
880
- : []),
989
+ ' { labelKey: "bookACall", path: "/meet/book-a-call" },',
881
990
  " ],",
882
991
  " },",
883
992
  ...(features
@@ -891,13 +1000,13 @@ export function renderNavConfig(enabled) {
891
1000
  " },",
892
1001
  ]
893
1002
  : []),
894
- ...(useCases
1003
+ ...(forEnabled
895
1004
  ? [
896
1005
  " {",
897
- ' headingKey: "footerUseCasesHeading",',
1006
+ ' headingKey: "footerCustomersHeading",',
898
1007
  " items: [",
899
- ...useCaseLinks.map((l) => l.slice(2)),
900
- ' { labelKey: "allUseCases", path: "/use-cases" },',
1008
+ ...customerLinks.map((l) => l.slice(2)),
1009
+ ' { labelKey: "allCustomers", path: "/for" },',
901
1010
  " ],",
902
1011
  " },",
903
1012
  ]
@@ -927,18 +1036,23 @@ export function renderNavConfig(enabled) {
927
1036
  "from ",
928
1037
  '"@working-theory/ui";',
929
1038
  ].join("");
930
- return `// nav-config.ts — site-nav registry.
1039
+ const body = `// nav-config.ts — site-nav registry.
931
1040
  //
932
1041
  // GENERATED by create-ailk from the declared route surfaces. Every entry here
933
1042
  // points at a route this site actually carries — the nav is regenerated with
934
1043
  // the surface set so it can never link a page that was not scaffolded.
935
1044
  //
1045
+ // The first line hashes the rest of this file: while it matches, \`create-ailk
1046
+ // add-module\` regenerates this nav when a module adds pages; once you edit
1047
+ // below it, the nav is yours and add-module reports the change it wanted
1048
+ // instead.
1049
+ //
936
1050
  // Adding a link: scaffold the page (the \`add-page\` skill), then add an
937
1051
  // entry below with a \`labelKey\` that exists in the \`nav\` namespace of
938
1052
  // \`apps/web/messages/<locale>.json\`. Paths are locale-agnostic — Nav prefixes
939
1053
  // them with the active locale at render.
940
1054
  ${navItemImport}
941
-
1055
+ ${servicesHub ? 'import { ArrowRight } from "lucide-react";\n' : ""}
942
1056
  export const navItems: NavItem[] = [
943
1057
  ${entries.join("\n")}
944
1058
  ];
@@ -954,4 +1068,7 @@ export const footerColumns: FooterColumnConfig[] = [
954
1068
  ${footerEntries.join("\n")}
955
1069
  ];
956
1070
  `;
1071
+ // D1 — a first-line stamp holding the SHA-256 of the rest of the file
1072
+ // (`body`), so an unedited nav proves itself whatever generator wrote it.
1073
+ return `// ailk:generated sha256=${hashNavBody(body)}\n${body}`;
957
1074
  }
@@ -31,21 +31,14 @@ export declare const STARTER_SHAPES: readonly ["organization-product", "organiza
31
31
  export type StarterShape = (typeof STARTER_SHAPES)[number];
32
32
  /**
33
33
  * The closed **design** enum (same contract section) — the look applied to
34
- * any shape; orthogonal to it by construction.
34
+ * any shape; orthogonal to it by construction. Each name RESOLVES to a
35
+ * package at `site-templates/designs/<name>/` (#6813 D2/D3, D11 pins this set
36
+ * equal to that directory listing): `resolveDesign` (`design-scaffold.ts`)
37
+ * reads the package's own `layout`, `colors` and treatments — no design fact
38
+ * lives in this module, or anywhere in `packages/create-ailk/src`, any more.
35
39
  */
36
40
  export declare const STARTER_DESIGNS: readonly ["standard", "minimalism"];
37
41
  export type StarterDesign = (typeof STARTER_DESIGNS)[number];
38
- /**
39
- * Each design's layout knobs (#6638) — the part of a design that is chrome,
40
- * so a template names `design:` instead of re-typing a `layout` block, and any
41
- * shape can wear any design. `resolveSiteChrome(layout, design)` reads it:
42
- * an explicit `TemplateManifest.layout` wins field by field, then this, then
43
- * `DEFAULT_SITE_CHROME`. `standard` declares nothing — it IS the defaults, so
44
- * spelling them out here would be a second copy of them to drift. A plain
45
- * object, not a file: this module touches no disk. The design's documented
46
- * look lives in `site-templates/designs/<design>/DESIGN.md`.
47
- */
48
- export declare const DESIGN_LAYOUTS: Readonly<Record<StarterDesign, TemplateLayout>>;
49
42
  /**
50
43
  * One site template: its fixed module list and the page rows it brings itself.
51
44
  *
@@ -182,9 +175,33 @@ export declare const SHAPE_STARTERS: Readonly<Partial<Record<BuildableShape, Tem
182
175
  * Where a starter's files live under `site-templates/` (#6757 D5) — the one
183
176
  * place that says so. A `SHAPE_STARTERS` key (a bare-shape starter with no
184
177
  * `--template` alias) lives at `shapes/<name>`, out of the template showcase
185
- * (§ 1.6); every other name (a declared `--template` alias) is unchanged.
178
+ * (§ 1.6). A declared `--template` alias whose `ownTree` is `false` (spec
179
+ * 6892 D1) ships no directory of its own: its seed layer is its shape's tree.
180
+ * Every other name (a declared alias with its own tree) is unchanged.
186
181
  */
187
182
  export declare function templateTreeDir(name: string): string;
183
+ /**
184
+ * Whether `name` ships its own `site-templates/<name>/` directory (spec 6892
185
+ * D1) — `false` only for a declared `--template` alias whose `ownTree` is
186
+ * `false` (none today). A `SHAPE_STARTERS` key owns its
187
+ * `shapes/<name>` tree outright, so it is never treeless by this predicate
188
+ * even though `templateTreeDir` also renames its directory.
189
+ *
190
+ * The one place a caller outside this module (or `template-names.ts`) asks
191
+ * "does this name have its own tree" without needing `ALIASES` itself —
192
+ * `starter-grid.ts`'s own header restricts its importers to this file and
193
+ * `template-names.ts`, so a producer script asking this question goes
194
+ * through this predicate rather than importing `ALIASES` directly.
195
+ */
196
+ export declare function isOwnTree(name: string): boolean;
197
+ /**
198
+ * Where a starter's GENERATED roster record lives (spec 6892 D1) — the
199
+ * companion to {@link templateTreeDir}. A treeless alias (`ownTree: false`)
200
+ * has no directory of its own to hold one, so its record lives under
201
+ * `site-templates/aliases/` instead, keyed by its name; every other name's
202
+ * record lives beside its tree, as it always has.
203
+ */
204
+ export declare function shippedManifestPath(name: string): string;
188
205
  /**
189
206
  * Look up a declared template — the one exported function that yields a
190
207
  * template's roster (#6753 AC-1); `TEMPLATE_MANIFESTS`, the frozen record it
@@ -19,28 +19,13 @@ export const STARTER_SHAPES = [
19
19
  ];
20
20
  /**
21
21
  * The closed **design** enum (same contract section) — the look applied to
22
- * any shape; orthogonal to it by construction.
22
+ * any shape; orthogonal to it by construction. Each name RESOLVES to a
23
+ * package at `site-templates/designs/<name>/` (#6813 D2/D3, D11 pins this set
24
+ * equal to that directory listing): `resolveDesign` (`design-scaffold.ts`)
25
+ * reads the package's own `layout`, `colors` and treatments — no design fact
26
+ * lives in this module, or anywhere in `packages/create-ailk/src`, any more.
23
27
  */
24
28
  export const STARTER_DESIGNS = ["standard", "minimalism"];
25
- /**
26
- * Each design's layout knobs (#6638) — the part of a design that is chrome,
27
- * so a template names `design:` instead of re-typing a `layout` block, and any
28
- * shape can wear any design. `resolveSiteChrome(layout, design)` reads it:
29
- * an explicit `TemplateManifest.layout` wins field by field, then this, then
30
- * `DEFAULT_SITE_CHROME`. `standard` declares nothing — it IS the defaults, so
31
- * spelling them out here would be a second copy of them to drift. A plain
32
- * object, not a file: this module touches no disk. The design's documented
33
- * look lives in `site-templates/designs/<design>/DESIGN.md`.
34
- */
35
- export const DESIGN_LAYOUTS = {
36
- standard: {},
37
- minimalism: {
38
- nav: "mark-only",
39
- footer: "centered",
40
- announcementBanner: false,
41
- home: "deck",
42
- },
43
- };
44
29
  /**
45
30
  * The base layer's directory under `site-templates/` (#5618 D1) — the seed
46
31
  * files every template reaches by declaring `extends`, installed by
@@ -75,10 +60,10 @@ export class TemplateError extends Error {
75
60
  * shape's standard roster, minus the alias's `drop` routes, with its `add`
76
61
  * rows inserted before the `before` route (appended when there is none),
77
62
  * then each `insert` group's rows spliced before ITS OWN `before` route, in
78
- * array order — the one alias with more than one anchor point
79
- * (`minimalism-site`) uses this to interleave its own rows into the shape's
80
- * row order. Rows are pushed by reference, never cloned, so
81
- * `manifest.roster.includes(row)` identity checks downstream keep working.
63
+ * array order — for an alias that interleaves its own rows into the shape's
64
+ * row order at more than one anchor point. Rows are pushed by reference,
65
+ * never cloned, so `manifest.roster.includes(row)` identity checks
66
+ * downstream keep working.
82
67
  * Every refusal names the alias, so a malformed table fails at load — the
83
68
  * shipped one — or in the test that injected it.
84
69
  *
@@ -176,12 +161,52 @@ export const SHAPE_STARTERS = Object.freeze(Object.fromEntries(Object.entries(SH
176
161
  * Where a starter's files live under `site-templates/` (#6757 D5) — the one
177
162
  * place that says so. A `SHAPE_STARTERS` key (a bare-shape starter with no
178
163
  * `--template` alias) lives at `shapes/<name>`, out of the template showcase
179
- * (§ 1.6); every other name (a declared `--template` alias) is unchanged.
164
+ * (§ 1.6). A declared `--template` alias whose `ownTree` is `false` (spec
165
+ * 6892 D1) ships no directory of its own: its seed layer is its shape's tree.
166
+ * Every other name (a declared alias with its own tree) is unchanged.
180
167
  */
181
168
  export function templateTreeDir(name) {
182
- return Object.prototype.hasOwnProperty.call(SHAPE_STARTERS, name)
183
- ? `shapes/${name}`
184
- : name;
169
+ if (Object.prototype.hasOwnProperty.call(SHAPE_STARTERS, name)) {
170
+ return `shapes/${name}`;
171
+ }
172
+ const alias = ALIASES[name];
173
+ if (alias?.ownTree === false) {
174
+ return `shapes/${alias.shape}`;
175
+ }
176
+ return name;
177
+ }
178
+ /**
179
+ * Whether `name` ships its own `site-templates/<name>/` directory (spec 6892
180
+ * D1) — `false` only for a declared `--template` alias whose `ownTree` is
181
+ * `false` (none today). A `SHAPE_STARTERS` key owns its
182
+ * `shapes/<name>` tree outright, so it is never treeless by this predicate
183
+ * even though `templateTreeDir` also renames its directory.
184
+ *
185
+ * The one place a caller outside this module (or `template-names.ts`) asks
186
+ * "does this name have its own tree" without needing `ALIASES` itself —
187
+ * `starter-grid.ts`'s own header restricts its importers to this file and
188
+ * `template-names.ts`, so a producer script asking this question goes
189
+ * through this predicate rather than importing `ALIASES` directly.
190
+ */
191
+ export function isOwnTree(name) {
192
+ if (Object.prototype.hasOwnProperty.call(SHAPE_STARTERS, name))
193
+ return true;
194
+ const alias = ALIASES[name];
195
+ return alias?.ownTree !== false;
196
+ }
197
+ /**
198
+ * Where a starter's GENERATED roster record lives (spec 6892 D1) — the
199
+ * companion to {@link templateTreeDir}. A treeless alias (`ownTree: false`)
200
+ * has no directory of its own to hold one, so its record lives under
201
+ * `site-templates/aliases/` instead, keyed by its name; every other name's
202
+ * record lives beside its tree, as it always has.
203
+ */
204
+ export function shippedManifestPath(name) {
205
+ const alias = ALIASES[name];
206
+ if (alias?.ownTree === false) {
207
+ return `aliases/${name}.TEMPLATE-MANIFEST.json`;
208
+ }
209
+ return `${templateTreeDir(name)}/TEMPLATE-MANIFEST.json`;
185
210
  }
186
211
  /**
187
212
  * Look up a declared template — the one exported function that yields a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ailk",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "Scaffolder for AI Launch Kit — `pnpm create ailk@latest <dir>` produces a starter repo consuming @working-theory/* from npm (per ADR 0007).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -10,9 +10,11 @@
10
10
  },
11
11
  "files": [
12
12
  "dist",
13
+ "!dist/modules",
13
14
  "templates",
14
15
  "site-templates",
15
16
  "versions.json",
17
+ "release-scope.json",
16
18
  "component-catalog.json",
17
19
  "component-catalog.md",
18
20
  "README.md",
@@ -25,8 +27,8 @@
25
27
  "tar": "^7.5.21"
26
28
  },
27
29
  "devDependencies": {
28
- "@working-theory/templates": "0.28.0",
29
- "@working-theory/schema": "0.28.0"
30
+ "@working-theory/schema": "0.29.0",
31
+ "@working-theory/templates": "0.29.0"
30
32
  },
31
33
  "repository": {
32
34
  "type": "git",
@@ -59,6 +61,7 @@
59
61
  "check-catalog": "node scripts/build-component-catalog.mjs --check",
60
62
  "manifest:build": "node scripts/build-template-manifest.mjs --template marketing-site-free",
61
63
  "manifest:check": "node scripts/build-template-manifest.mjs --template marketing-site-free --check && node scripts/build-template-manifest.mjs --template marketing-site --check",
64
+ "design:gen": "tsc -b && node scripts/build-design-md.mjs",
62
65
  "page-types:build": "tsx scripts/generate-page-types-reference.ts",
63
66
  "page-types:check": "tsx scripts/generate-page-types-reference.ts --check"
64
67
  }