create-ailk 0.3.0 → 0.5.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 (155) hide show
  1. package/component-catalog.json +11939 -0
  2. package/component-catalog.md +176 -64
  3. package/dist/cli.js +48 -10
  4. package/dist/derive-architecture.js +5 -2
  5. package/dist/lib/extract-module-bundle.d.ts +88 -1
  6. package/dist/lib/extract-module-bundle.js +375 -80
  7. package/dist/lib/fetch-guard.d.ts +28 -0
  8. package/dist/lib/fetch-guard.js +101 -0
  9. package/dist/lib/fetch-module.js +4 -0
  10. package/dist/lib/fetch-template.d.ts +20 -0
  11. package/dist/lib/fetch-template.js +133 -0
  12. package/dist/lib/module-license-gate.d.ts +8 -0
  13. package/dist/lib/module-license-gate.js +12 -4
  14. package/dist/module-architecture.d.ts +116 -7
  15. package/dist/module-architecture.js +465 -94
  16. package/dist/parse-args.d.ts +8 -1
  17. package/dist/parse-args.js +49 -2
  18. package/dist/programmatic.d.ts +36 -1
  19. package/dist/programmatic.js +216 -5
  20. package/dist/site-chrome.d.ts +80 -0
  21. package/dist/site-chrome.js +135 -0
  22. package/dist/surfaces.d.ts +21 -6
  23. package/dist/surfaces.js +140 -23
  24. package/dist/template-manifest.d.ts +144 -0
  25. package/dist/template-manifest.js +337 -0
  26. package/dist/template-names.d.ts +41 -0
  27. package/dist/template-names.js +45 -0
  28. package/package.json +10 -2
  29. package/site-templates/_base/ailk-template.json +6 -0
  30. package/site-templates/_base/content/en/Pricing/pricing.mdx +169 -0
  31. package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +79 -0
  32. package/site-templates/_base/content/en/TermsPage/terms.mdx +80 -0
  33. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +331 -0
  34. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +393 -0
  35. package/site-templates/marketing-site/ailk-template.json +11 -0
  36. package/site-templates/marketing-site/content/en/blog/05-this-vc-morning-routine-will-make-you-super-annoying.mdx +36 -0
  37. package/site-templates/marketing-site/provenance.json +4 -0
  38. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +288 -0
  39. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +285 -0
  40. package/site-templates/marketing-site-free/ailk-template.json +9 -0
  41. package/site-templates/marketing-site-free/provenance.json +4 -0
  42. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +271 -0
  43. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +324 -0
  44. package/site-templates/minimalism-site/ailk-template.json +21 -0
  45. package/site-templates/minimalism-site/assets/avatar-placeholder-1.svg +7 -0
  46. package/site-templates/minimalism-site/assets/avatar-placeholder-2.svg +7 -0
  47. package/site-templates/minimalism-site/assets/avatar-placeholder-3.svg +7 -0
  48. package/site-templates/minimalism-site/assets/avatar-placeholder-4.svg +7 -0
  49. package/site-templates/minimalism-site/assets/avatar-placeholder-5.svg +7 -0
  50. package/site-templates/minimalism-site/assets/avatar-placeholder-6.svg +7 -0
  51. package/site-templates/minimalism-site/assets/hero-backdrop.svg +17 -0
  52. package/site-templates/minimalism-site/assets/logo-placeholder-1.svg +6 -0
  53. package/site-templates/minimalism-site/assets/logo-placeholder-2.svg +6 -0
  54. package/site-templates/minimalism-site/assets/logo-placeholder-3.svg +6 -0
  55. package/site-templates/minimalism-site/content/en/AboutPage/about.mdx +110 -0
  56. package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +68 -0
  57. package/site-templates/minimalism-site/content/en/FAQPage/faq.mdx +57 -0
  58. package/site-templates/minimalism-site/content/en/HomePage/home.mdx +214 -0
  59. package/site-templates/minimalism-site/content/en/LandingPage/referral.mdx +77 -0
  60. package/site-templates/minimalism-site/content/en/LandingPage/reports.mdx +73 -0
  61. package/site-templates/minimalism-site/content/en/ReportPage/annual-letter-2024.mdx +52 -0
  62. package/site-templates/minimalism-site/content/en/ReportPage/field-notes-2023.mdx +52 -0
  63. package/site-templates/minimalism-site/content/en/ReportPage/platform-shift-2025.mdx +52 -0
  64. package/site-templates/minimalism-site/content/en/ReportPage/state-of-the-market-2026.mdx +52 -0
  65. package/site-templates/minimalism-site/content/en/Service/first-engagement.mdx +38 -0
  66. package/site-templates/minimalism-site/content/en/Service/second-engagement.mdx +38 -0
  67. package/site-templates/minimalism-site/content/en/Service/third-engagement.mdx +38 -0
  68. package/site-templates/minimalism-site/content/en/ServicesList/services.mdx +73 -0
  69. package/site-templates/minimalism-site/flows/referral.flow.json +71 -0
  70. package/site-templates/minimalism-site/provenance.json +4 -0
  71. package/site-templates/saas-product/TEMPLATE-MANIFEST.md +61 -0
  72. package/site-templates/saas-product/ailk-template.json +16 -0
  73. package/site-templates/saas-product/provenance.json +10 -0
  74. package/templates/.claude/skills/README.md +16 -25
  75. package/templates/.env.example +32 -7
  76. package/templates/.github/workflows/ci.yml +48 -0
  77. package/templates/README.md +3 -1
  78. package/templates/apps/api/CLAUDE.md +4 -1
  79. package/templates/apps/api/eslint.config.mjs +69 -0
  80. package/templates/apps/api/package.json +6 -2
  81. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +6 -2
  82. package/templates/apps/api/src/__tests__/review-surface-registration.test.ts +167 -0
  83. package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +173 -0
  84. package/templates/apps/api/src/config/modules.ts +1 -1
  85. package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -0
  86. package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +339 -0
  87. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +108 -0
  88. package/templates/apps/api/src/middleware/default-tenant.ts +136 -0
  89. package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +171 -0
  90. package/templates/apps/api/src/routes/leads/list.ts +104 -0
  91. package/templates/apps/api/src/server.ts +122 -3
  92. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +408 -0
  93. package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +329 -0
  94. package/templates/apps/api/src/services/default-tenant.ts +261 -0
  95. package/templates/apps/api/src/services/lead-promotion.ts +161 -0
  96. package/templates/apps/api/src/services/leads-inbox.ts +263 -0
  97. package/templates/apps/api/src/services/owner-workspaces.ts +132 -0
  98. package/templates/apps/mcp/CLAUDE.md +1 -1
  99. package/templates/apps/mcp/__tests__/parity.test.ts +2 -2
  100. package/templates/apps/mcp/__tests__/tools/{scaffold.test.ts → create_project.test.ts} +23 -23
  101. package/templates/apps/mcp/eslint.config.mjs +69 -0
  102. package/templates/apps/mcp/package.json +6 -2
  103. package/templates/apps/mcp/src/server.ts +2 -2
  104. package/templates/apps/mcp/src/tools/{scaffold.ts → create_project.ts} +3 -3
  105. package/templates/apps/mcp/src/tools/index.ts +2 -2
  106. package/templates/apps/web/.env.example +3 -3
  107. package/templates/apps/web/CLAUDE.md +2 -0
  108. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +4 -6
  109. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +1 -1
  110. package/templates/apps/web/app/[locale]/(authed)/leads/__tests__/page.test.tsx +181 -0
  111. package/templates/apps/web/app/[locale]/(authed)/leads/components/LeadsTable.tsx +137 -0
  112. package/templates/apps/web/app/[locale]/(authed)/leads/page.tsx +111 -0
  113. package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +1 -1
  114. package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +111 -0
  115. package/templates/apps/web/app/[locale]/[slug]/page.tsx +65 -0
  116. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +1 -1
  117. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +1 -1
  118. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +6 -5
  119. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +1 -1
  120. package/templates/apps/web/app/[locale]/layout.tsx +214 -17
  121. package/templates/apps/web/app/[locale]/pricing/page.tsx +40 -0
  122. package/templates/apps/web/app/[locale]/privacy/page.tsx +40 -0
  123. package/templates/apps/web/app/[locale]/terms/page.tsx +40 -0
  124. package/templates/apps/web/components/google-analytics.tsx +18 -1
  125. package/templates/apps/web/eslint.config.mjs +69 -0
  126. package/templates/apps/web/jest.config.cjs +12 -15
  127. package/templates/apps/web/lib/__tests__/server-api.test.ts +61 -0
  128. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +32 -5
  129. package/templates/apps/web/lib/nav-config.ts +27 -7
  130. package/templates/apps/web/lib/server-api.ts +29 -13
  131. package/templates/apps/web/lib/site-chrome.ts +29 -0
  132. package/templates/apps/web/lib/telemetry-scrub.ts +46 -0
  133. package/templates/apps/web/messages/en.json +86 -1
  134. package/templates/apps/web/next.config.mjs +20 -0
  135. package/templates/apps/web/package.json +6 -2
  136. package/templates/apps/web/sentry.client.config.ts +131 -0
  137. package/templates/apps/web/sentry.edge.config.ts +72 -0
  138. package/templates/apps/web/sentry.server.config.ts +72 -0
  139. package/templates/apps/web/src/lib/routes.ts +13 -18
  140. package/templates/brand_assets/README.md +24 -0
  141. package/templates/content/en/AboutPage/about.mdx +160 -15
  142. package/templates/content/en/ContactPage/contact.mdx +90 -13
  143. package/templates/content/en/FAQPage/faq.mdx +62 -22
  144. package/templates/content/en/HomePage/home.mdx +244 -20
  145. package/templates/content/en/LandingPage/blog.mdx +83 -0
  146. package/templates/content/en/LandingPage/features.mdx +103 -0
  147. package/templates/content/en/LandingPage/how-it-works.mdx +143 -0
  148. package/templates/database/CHANGELOG.md +21 -0
  149. package/templates/database/__tests__/seed.test.ts +1 -1
  150. package/templates/database/eslint.config.mjs +69 -0
  151. package/templates/database/package.json +7 -3
  152. package/templates/database/seed.ts +1 -1
  153. package/templates/package.json +1 -14
  154. package/versions.json +52 -0
  155. /package/templates/apps/web/{src/components → components}/SiteNav.tsx +0 -0
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Site-template registry, and the roster merge that composes a template with
3
+ * the modules it takes (spec 5478, unit A of the site-templates mechanism;
4
+ * design record docs/specs/5462-site-templates-module-bundles-spec.md).
5
+ *
6
+ * A site template is DATA AND CONTENT, NOT CODE: a page roster, seed content
7
+ * and config — never a `page.tsx` (parent § D16). This file declares what a
8
+ * template IS. It declares no delivery channel, reads no licence, and touches
9
+ * no disk: the `--template` flag, the bundle fetch and the landing order are
10
+ * #5481's, and `GET /v1/templates/:name/:version` is #5480's.
11
+ *
12
+ * A CODE registry, not per-template JSON (parent § D2): in TypeScript,
13
+ * `requires` is checked against `RECOGNIZED_MODULES` at compile time, and this
14
+ * package already twice records that a fixed template FILE is the drift a
15
+ * registry removes.
16
+ */
17
+ import { classifyModules } from "./lib/module-tier.js";
18
+ import { MODULE_SLICES, normalizeModules, } from "./module-architecture.js";
19
+ /**
20
+ * The base layer's directory under `site-templates/` (#5618 D1) — the seed
21
+ * files every template reaches by declaring `extends`, installed by
22
+ * `installBundleTree` before any module bundle and only when `--template`
23
+ * is named, so a scaffold naming no template is byte-identical to before it
24
+ * existed. It is NOT a template: it has no `TEMPLATE_MANIFESTS` entry (so
25
+ * `--template _base` stays unrecognized — `RECOGNIZED_TEMPLATES` is exactly
26
+ * that object's key set) and is never published as a registry bundle. The
27
+ * leading underscore is what `build-module-bundles.mjs`'s template loop and
28
+ * the directory-scanning tests key their skip on.
29
+ */
30
+ export const BASE_LAYER_DIR = "_base";
31
+ /** Thrown on an undeclared template name or a route claimed twice. Mirrors `SurfaceError`'s shape. */
32
+ export class TemplateError extends Error {
33
+ constructor(message) {
34
+ super(message);
35
+ this.name = "TemplateError";
36
+ }
37
+ }
38
+ /**
39
+ * The ten rows every declared template brings itself — one declared page set,
40
+ * whichever mechanism scaffolds each (the Free manifest § 3, #5469 D4).
41
+ *
42
+ * Two mechanisms, one roster:
43
+ *
44
+ * - `/`, `/how-it-works`, `/features`, `/about`, `/faq`, `/contact` and
45
+ * `/sign-in` are in EVERY scaffold, template or not: their content is the
46
+ * always-copied overlay (`template-overlay/content/en/**`, copied whole by
47
+ * `build-templates.mjs`) and their routes are core (`CORE_ROUTE_DIRS` —
48
+ * the three LandingPage pages render through the generic `[slug]` route,
49
+ * #5469 D2). They are declared here anyway, so the manifest a template
50
+ * publishes (`build-template-manifest.mjs`) lists the whole page set and
51
+ * the roster is what a template IS, not merely what it adds.
52
+ * - `/pricing`, `/privacy` and `/terms` are module-less by construction
53
+ * (parent § D16): no module owns these surfaces, their route files live in
54
+ * `apps/web` and are snapshotted into the starter like every other route,
55
+ * and a template enables them by NAMING them in its roster — `scaffold()`
56
+ * selects their `GATEABLE_SURFACES` entries from the merged roster's
57
+ * routes (#5469 D10). Their seed content ships ONCE, in the base layer
58
+ * (`site-templates/_base/content/**`, #5618 D1), which every template
59
+ * reaches by declaring `extends` — installed before the module bundles
60
+ * and the template's own tree, so a template may override a base page
61
+ * by shipping the same path.
62
+ *
63
+ * `/sign-in` carries no `content`: it is a code route (the SignInCard island),
64
+ * like the API-backed rows the scheduling module contributes.
65
+ *
66
+ * `/blog` is deliberately NOT here (#5469 D3): the docs-blog module
67
+ * contributes its own `/blog` row, so a base row would make `mergeRoster`
68
+ * refuse every template that takes that module. The Free teaser row is
69
+ * declared on `marketing-site-free` alone, below.
70
+ */
71
+ const TEMPLATE_BASE_ROSTER = [
72
+ { route: "/", content: ["content/en/HomePage/home.mdx"] },
73
+ {
74
+ route: "/how-it-works",
75
+ content: ["content/en/LandingPage/how-it-works.mdx"],
76
+ },
77
+ { route: "/features", content: ["content/en/LandingPage/features.mdx"] },
78
+ { route: "/pricing", content: ["content/en/Pricing/pricing.mdx"] },
79
+ { route: "/about", content: ["content/en/AboutPage/about.mdx"] },
80
+ { route: "/faq", content: ["content/en/FAQPage/faq.mdx"] },
81
+ { route: "/contact", content: ["content/en/ContactPage/contact.mdx"] },
82
+ { route: "/sign-in" },
83
+ { route: "/privacy", content: ["content/en/PrivacyPage/privacy.mdx"] },
84
+ { route: "/terms", content: ["content/en/TermsPage/terms.mdx"] },
85
+ ];
86
+ /**
87
+ * The Free `/blog` teaser (#5469 D3) — one static LandingPage (`schema: Blog`)
88
+ * that says what the Pro docs-blog module gives you. It is served by the
89
+ * generic `[slug]` route over `content/en/LandingPage/blog.mdx`, which ships
90
+ * in the always-copied overlay. `/blog` degrades by DIRECTORY PRECEDENCE, not
91
+ * by a module gate: in a keyed docs-blog scaffold the module's own
92
+ * `blog/[[...slug]]/page.tsx` directory exists and Next.js resolves the static
93
+ * segment first, so the teaser file is inert; keyless, no `blog/` directory
94
+ * exists and the teaser renders. Declared on the Free template only — on
95
+ * `marketing-site` the docs-blog contribution claims the route.
96
+ */
97
+ const FREE_BLOG_TEASER_ROW = {
98
+ route: "/blog",
99
+ content: ["content/en/LandingPage/blog.mdx"],
100
+ };
101
+ /**
102
+ * `minimalism-site`'s own rows (#5589) — the one declared template that does
103
+ * not reuse `TEMPLATE_BASE_ROSTER`. Its page set is the issue's ROSTER, and
104
+ * the rows it does NOT carry are as deliberate as the ones it does: no
105
+ * `/how-it-works`, `/features` or `/pricing` (a reports-first site sells
106
+ * nothing from a pricing table), and no `/sign-in` (a code route every
107
+ * scaffold carries regardless — `CORE_ROUTE_DIRS` — which this template
108
+ * simply does not advertise as one of its pages).
109
+ *
110
+ * `/meet`, `/meet/[slug]`, `/meet/confirmed`, `/reports/[slug]` and the three
111
+ * `/flows/*` rows are absent here on purpose: they are the `scheduling` and
112
+ * `signal-collectors` modules' own `pageContributions`, merged in by
113
+ * `mergeRoster`. Declaring one here would make that merge refuse the
114
+ * template outright (§ D8's both-claimants refusal).
115
+ */
116
+ const MINIMALISM_ROSTER = [
117
+ { route: "/", content: ["content/en/HomePage/home.mdx"] },
118
+ { route: "/reports", content: ["content/en/LandingPage/reports.mdx"] },
119
+ { route: "/about", content: ["content/en/AboutPage/about.mdx"] },
120
+ { route: "/services", content: ["content/en/ServicesList/services.mdx"] },
121
+ { route: "/services/[slug]" },
122
+ { route: "/contact", content: ["content/en/ContactPage/contact.mdx"] },
123
+ { route: "/referral", content: ["content/en/LandingPage/referral.mdx"] },
124
+ { route: "/faq", content: ["content/en/FAQPage/faq.mdx"] },
125
+ { route: "/privacy", content: ["content/en/PrivacyPage/privacy.mdx"] },
126
+ { route: "/terms", content: ["content/en/TermsPage/terms.mdx"] },
127
+ ];
128
+ /**
129
+ * The three settled templates and their fixed `requires` lists — settled by
130
+ * Sam, 2026-09-14 (parent § D12). The plain name is the Pro one: it is the
131
+ * flagship and the demo.
132
+ *
133
+ * `workspace` appears in none of them. It is a Pro module, but an authed app
134
+ * surface contributing no PUBLIC page to a marketing site, and `requires`
135
+ * names what the roster uses (parent § D15). A Pro key unlocks it regardless
136
+ * of any template. `ModuleName` could express it either way — `workspace` is
137
+ * one of the seven `RECOGNIZED_MODULES` names; it is simply not one any
138
+ * template's fixed `requires` list names.
139
+ *
140
+ * `commerce` is likewise in none of them: it is a per-site add-on available at
141
+ * any level, so a template for it would contradict the offering model and
142
+ * multiply the set combinatorially (parent § D13). It still contributes rows
143
+ * to whichever template takes it, through `mergeRoster` below.
144
+ *
145
+ * Frozen (#5507): this registry is also the issuer's authorization allowlist
146
+ * (spec 5480 § C7) — a mutable global there could be monkey-patched at
147
+ * runtime. A caller needing a different registry for a test injects its own
148
+ * fixture rather than mutating this one.
149
+ */
150
+ export const TEMPLATE_MANIFESTS = Object.freeze({
151
+ /**
152
+ * The Free template, and the mechanism's own proof (parent § D11): it
153
+ * requires only `marketing`, so the keyless merge path is exercised by a
154
+ * template we ship anyway rather than by a throwaway fixture.
155
+ */
156
+ "marketing-site-free": {
157
+ name: "marketing-site-free",
158
+ requires: ["marketing"],
159
+ extends: BASE_LAYER_DIR,
160
+ roster: [...TEMPLATE_BASE_ROSTER, FREE_BLOG_TEASER_ROW],
161
+ },
162
+ /**
163
+ * The Pro flagship. Its module list is OFFERINGS § 4 offering 3's tiers line
164
+ * — "docs, scheduling and flows modules" — where "flows" is
165
+ * `signal-collectors` (`MODULE_SURFACES` maps it to `["flows"]`).
166
+ */
167
+ "marketing-site": {
168
+ name: "marketing-site",
169
+ requires: ["marketing", "docs-blog", "scheduling", "signal-collectors"],
170
+ extends: BASE_LAYER_DIR,
171
+ roster: TEMPLATE_BASE_ROSTER,
172
+ },
173
+ /**
174
+ * Pro Plus — the Pro list plus `multi-tenant`, which is correctly in the
175
+ * list because it DOES contribute pages (the org and workspace surfaces).
176
+ * Its `pageContributions` is nonetheless unset today, deferred to its own
177
+ * instance, so this template's merged roster carries the same contributed
178
+ * rows as `marketing-site`'s and no more. That is a module which has
179
+ * declared no rows yet — visible in the diff — not a merge that dropped
180
+ * declared rows.
181
+ */
182
+ "saas-product": {
183
+ name: "saas-product",
184
+ requires: [
185
+ "marketing",
186
+ "docs-blog",
187
+ "scheduling",
188
+ "signal-collectors",
189
+ "multi-tenant",
190
+ ],
191
+ extends: BASE_LAYER_DIR,
192
+ roster: TEMPLATE_BASE_ROSTER,
193
+ },
194
+ /**
195
+ * The second Pro template (#5589) — the reports-first, scroll-snap deck
196
+ * site. NOT `marketing-site` plus a module: its `requires` omits
197
+ * `docs-blog`, so the two Pro templates are siblings under `_base`
198
+ * (#5618 D2), never a lineage. Level still derives from `requires`:
199
+ * `scheduling` and `signal-collectors` are paid, so `templateDelivery`
200
+ * returns `"paid"` exactly as it does for `marketing-site`.
201
+ *
202
+ * It declares its OWN roster rather than reusing `TEMPLATE_BASE_ROSTER`
203
+ * (the only declared template that does): the deck home replaces the
204
+ * document-scroll home, and `/how-it-works`, `/features`, `/pricing` and
205
+ * `/sign-in` are not pages this site has. `/pricing`'s absence is not a
206
+ * missing file — the `_base` layer still lands its seed content, and the
207
+ * roster is what the template IS, so an unrostered page simply has no
208
+ * route directory copied (`rosterSurfaces`).
209
+ *
210
+ * `/reports` and `/referral` carry no `GATEABLE_SURFACES` entry and need
211
+ * none: both are single-segment `LandingPage` slugs served by the generic
212
+ * `[slug]` route (#5469 D2), the same mechanism Free's `/how-it-works`
213
+ * uses. `/reports/[slug]` (the reader) is the signal-collectors module's
214
+ * own contributed row.
215
+ *
216
+ * `/services/[slug]` declares no `content`: its seed is a whole DIRECTORY
217
+ * (`content/en/Service`, as `routes-map.ts` records it), and a roster
218
+ * row's `content` names files the manifest generator reads frontmatter
219
+ * from. The three samples ride the template tree unmapped, landing at
220
+ * that directory.
221
+ */
222
+ "minimalism-site": {
223
+ name: "minimalism-site",
224
+ requires: ["marketing", "scheduling", "signal-collectors"],
225
+ extends: BASE_LAYER_DIR,
226
+ roster: MINIMALISM_ROSTER,
227
+ layout: {
228
+ nav: "mark-only",
229
+ footer: "centered",
230
+ announcementBanner: false,
231
+ home: "deck",
232
+ },
233
+ },
234
+ });
235
+ /** Look up a declared template. @throws TemplateError on an undeclared name. */
236
+ export function resolveTemplate(name) {
237
+ const manifest = TEMPLATE_MANIFESTS[name];
238
+ if (!manifest) {
239
+ throw new TemplateError(`unknown template "${name}". Declared: ${Object.keys(TEMPLATE_MANIFESTS).join(", ")}`);
240
+ }
241
+ return manifest;
242
+ }
243
+ /**
244
+ * Resolve a template's `extends` chain — `[name, …ancestors, "_base"]` — in
245
+ * the client registry, before any fetch (#5618 D2). `_base` is the only
246
+ * valid terminus: an `extends` naming neither it nor a declared template is
247
+ * refused naming the field, the value and the recognized set, in
248
+ * `resolveTemplate`'s message shape; a cycle is refused naming the whole
249
+ * chain. The registry is a parameter for the same reason `TEMPLATE_MANIFESTS`
250
+ * is frozen (#5507): a test needing a cyclic or dangling fixture injects its
251
+ * own rather than mutating the shipped one.
252
+ *
253
+ * @throws TemplateError on an undeclared name, an unknown parent or a cycle.
254
+ */
255
+ export function resolveTemplateLineage(name, registry = TEMPLATE_MANIFESTS) {
256
+ const chain = [];
257
+ let current = name;
258
+ for (;;) {
259
+ if (chain.includes(current)) {
260
+ throw new TemplateError(`template "${name}": cyclic "extends" chain ${[...chain, current].join(" → ")}`);
261
+ }
262
+ chain.push(current);
263
+ if (current === BASE_LAYER_DIR)
264
+ return chain;
265
+ const manifest = registry[current];
266
+ if (!manifest) {
267
+ const declared = [...Object.keys(registry), BASE_LAYER_DIR].join(", ");
268
+ if (chain.length === 1) {
269
+ throw new TemplateError(`unknown template "${current}". Declared: ${declared}`);
270
+ }
271
+ throw new TemplateError(`template "${chain[chain.length - 2]}": "extends" names unknown template "${current}". Declared: ${declared}`);
272
+ }
273
+ current = manifest.extends;
274
+ }
275
+ }
276
+ /**
277
+ * Compose a template's roster: its base rows, then each taken module's
278
+ * contributed rows in `normalizeModules` order (parent § D7).
279
+ *
280
+ * `normalizeModules` is REUSED rather than re-sorted here. It already returns
281
+ * the unique set ordered by `RECOGNIZED_MODULES`, and `scaffold()` already
282
+ * calls it — a second sort at this call site would be a second ordering
283
+ * authority that could drift from the one `deriveArchitecture` depends on.
284
+ * That also makes the merge a pure function of the module SET: the same set in
285
+ * any argument order returns identical rows.
286
+ *
287
+ * The merge is pure and writes nothing. Staging a contributed row's seed
288
+ * content is the bundle producer's job (`build-module-bundles.mjs`, #5481);
289
+ * doing it here would put a filesystem dependency in a function three sibling
290
+ * units import for its return value.
291
+ *
292
+ * @param modules the modules this scaffold takes. A template's `requires` are
293
+ * taken by definition — a required module whose bundle cannot be had refuses
294
+ * the install outright (parent § D17, #5481) rather than merging short.
295
+ * @throws TemplateError when two sources claim one route (parent § D8). Both
296
+ * the base roster and every contribution are compile-time known, so the
297
+ * collision is caught by a test before a bundle exists — never resolved at
298
+ * runtime by a base-wins or module-wins precedence rule, either of which
299
+ * silently produces a site missing a page someone declared.
300
+ */
301
+ export function mergeRoster(manifest, modules) {
302
+ const merged = [];
303
+ /** route → the source that claimed it, for the collision message. */
304
+ const claimedBy = new Map();
305
+ const take = (rows, source) => {
306
+ for (const row of rows) {
307
+ const owner = claimedBy.get(row.route);
308
+ if (owner !== undefined) {
309
+ throw new TemplateError(`route "${row.route}" is claimed by both ${owner} and ${source}`);
310
+ }
311
+ claimedBy.set(row.route, source);
312
+ merged.push(row);
313
+ }
314
+ };
315
+ take(manifest.roster, `template "${manifest.name}"`);
316
+ for (const module of normalizeModules([...modules])) {
317
+ take(MODULE_SLICES[module].pageContributions ?? [], `module "${module}"`);
318
+ }
319
+ return merged;
320
+ }
321
+ /**
322
+ * Whether a template installs on the keyless path or needs a verified
323
+ * `AILK_LICENSE_KEY` — derived from `requires`, never declared (parent § D3).
324
+ *
325
+ * The derivation goes through the existing `classifyModules` rather than a
326
+ * second list of paid names, which would drift. This is deliberately the
327
+ * CLIENT half of a two-derivation split: the issuer derives the commercial
328
+ * LEVEL from the same `requires` via `TIER_SPECS` (#5480). One shared
329
+ * derivation would force this package to import
330
+ * `@working-theory/license-manifest` — a `restricted` package — breaking
331
+ * `pnpm install` for every Free consumer, not merely gating the paid path.
332
+ */
333
+ export function templateDelivery(manifest) {
334
+ return classifyModules([...manifest.requires]).paid.length > 0
335
+ ? "paid"
336
+ : "free";
337
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The `--template` flag's vocabulary, and nothing else.
3
+ *
4
+ * WHY THIS FILE EXISTS, measured rather than assumed. Spec 5481 § 3 DD1 has
5
+ * `parse-args.ts` validate `--template` against `TEMPLATE_MANIFESTS` through a
6
+ * runtime import, on the grounds that the registry's own dependency on
7
+ * `parse-args.ts` (for `ModuleName`) is `import type` and so erases at
8
+ * compile. That is true of the DIRECT edge and false of the graph:
9
+ * `template-manifest.ts` imports `lib/module-tier.js` and
10
+ * `module-architecture.js` as RUNTIME values, and BOTH of those import
11
+ * `RECOGNIZED_MODULES` — a runtime value — back from `parse-args.ts`.
12
+ * `module-tier.ts` then reads it at MODULE SCOPE to derive `PAID_MODULES`.
13
+ * So `parse-args.ts` importing the registry closes a runtime cycle whose
14
+ * first read lands in the temporal dead zone:
15
+ *
16
+ * ReferenceError: Cannot access 'RECOGNIZED_MODULES' before initialization
17
+ *
18
+ * — reproduced against the built `dist/` while implementing this unit, not
19
+ * theorised. `surfaces.ts`'s header warns about exactly this relationship
20
+ * ("Deliberately imports nothing from `parse-args.ts`: that module imports
21
+ * THIS one … so a back-import would close a cycle"), and `surfaces.ts` is the
22
+ * precedent this file follows: the flag's vocabulary lives in a module the
23
+ * parser can import because it imports nothing at all.
24
+ *
25
+ * THE REGISTRY IS STILL `template-manifest.ts` (parent record D2). This file
26
+ * declares NAMES; that one declares what a template IS — `requires`, the
27
+ * roster, the `installMap`, the free-vs-paid derivation — and it stays the
28
+ * single place a template is defined. The two cannot drift: the names here
29
+ * are asserted equal to `Object.keys(TEMPLATE_MANIFESTS)` by
30
+ * `__tests__/template-scaffold.test.ts`, so adding a template without listing
31
+ * it here (or the reverse) fails the suite rather than shipping a flag that
32
+ * accepts a name the registry does not know.
33
+ *
34
+ * Rejected: lazifying `module-tier.ts`'s `PAID_MODULES` to break the cycle at
35
+ * its source. It is the correct structural fix and it is not this unit's to
36
+ * make — `module-tier.ts` is a READ row in spec 5481 § 4, and `PAID_MODULES`
37
+ * is consumed as a plain array across the package.
38
+ */
39
+ /** The declared site templates, in `TEMPLATE_MANIFESTS` declaration order. */
40
+ export declare const RECOGNIZED_TEMPLATES: readonly ["marketing-site-free", "marketing-site", "saas-product", "minimalism-site"];
41
+ export type TemplateName = (typeof RECOGNIZED_TEMPLATES)[number];
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The `--template` flag's vocabulary, and nothing else.
3
+ *
4
+ * WHY THIS FILE EXISTS, measured rather than assumed. Spec 5481 § 3 DD1 has
5
+ * `parse-args.ts` validate `--template` against `TEMPLATE_MANIFESTS` through a
6
+ * runtime import, on the grounds that the registry's own dependency on
7
+ * `parse-args.ts` (for `ModuleName`) is `import type` and so erases at
8
+ * compile. That is true of the DIRECT edge and false of the graph:
9
+ * `template-manifest.ts` imports `lib/module-tier.js` and
10
+ * `module-architecture.js` as RUNTIME values, and BOTH of those import
11
+ * `RECOGNIZED_MODULES` — a runtime value — back from `parse-args.ts`.
12
+ * `module-tier.ts` then reads it at MODULE SCOPE to derive `PAID_MODULES`.
13
+ * So `parse-args.ts` importing the registry closes a runtime cycle whose
14
+ * first read lands in the temporal dead zone:
15
+ *
16
+ * ReferenceError: Cannot access 'RECOGNIZED_MODULES' before initialization
17
+ *
18
+ * — reproduced against the built `dist/` while implementing this unit, not
19
+ * theorised. `surfaces.ts`'s header warns about exactly this relationship
20
+ * ("Deliberately imports nothing from `parse-args.ts`: that module imports
21
+ * THIS one … so a back-import would close a cycle"), and `surfaces.ts` is the
22
+ * precedent this file follows: the flag's vocabulary lives in a module the
23
+ * parser can import because it imports nothing at all.
24
+ *
25
+ * THE REGISTRY IS STILL `template-manifest.ts` (parent record D2). This file
26
+ * declares NAMES; that one declares what a template IS — `requires`, the
27
+ * roster, the `installMap`, the free-vs-paid derivation — and it stays the
28
+ * single place a template is defined. The two cannot drift: the names here
29
+ * are asserted equal to `Object.keys(TEMPLATE_MANIFESTS)` by
30
+ * `__tests__/template-scaffold.test.ts`, so adding a template without listing
31
+ * it here (or the reverse) fails the suite rather than shipping a flag that
32
+ * accepts a name the registry does not know.
33
+ *
34
+ * Rejected: lazifying `module-tier.ts`'s `PAID_MODULES` to break the cycle at
35
+ * its source. It is the correct structural fix and it is not this unit's to
36
+ * make — `module-tier.ts` is a READ row in spec 5481 § 4, and `PAID_MODULES`
37
+ * is consumed as a plain array across the package.
38
+ */
39
+ /** The declared site templates, in `TEMPLATE_MANIFESTS` declaration order. */
40
+ export const RECOGNIZED_TEMPLATES = [
41
+ "marketing-site-free",
42
+ "marketing-site",
43
+ "saas-product",
44
+ "minimalism-site",
45
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ailk",
3
- "version": "0.3.0",
3
+ "version": "0.5.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",
@@ -11,6 +11,9 @@
11
11
  "files": [
12
12
  "dist",
13
13
  "templates",
14
+ "site-templates",
15
+ "versions.json",
16
+ "component-catalog.json",
14
17
  "component-catalog.md",
15
18
  "README.md",
16
19
  "LICENSE"
@@ -21,6 +24,9 @@
21
24
  "dependencies": {
22
25
  "tar": "^7.5.21"
23
26
  },
27
+ "devDependencies": {
28
+ "@working-theory/templates": "0.24.0"
29
+ },
24
30
  "repository": {
25
31
  "type": "git",
26
32
  "url": "git+https://github.com/working-theory-labs/ai-launch-kit.git",
@@ -49,6 +55,8 @@
49
55
  "test:coverage": "jest --coverage",
50
56
  "build-templates": "tsc -b && node scripts/build-templates.mjs",
51
57
  "build-catalog": "node scripts/build-component-catalog.mjs",
52
- "check-catalog": "node scripts/build-component-catalog.mjs --check"
58
+ "check-catalog": "node scripts/build-component-catalog.mjs --check",
59
+ "manifest:build": "node scripts/build-template-manifest.mjs --template marketing-site-free",
60
+ "manifest:check": "node scripts/build-template-manifest.mjs --template marketing-site-free --check && node scripts/build-template-manifest.mjs --template marketing-site --check"
53
61
  }
54
62
  }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "_base",
3
+ "requires": [],
4
+ "$comment": "The BASE LAYER (#5618 D1) — not a template. Every declared template reaches this tree by `extends: \"_base\"`; scaffold() installs it through installBundleTree before any module bundle and before the template's own tree, only when --template is named. It carries the seed content every template brings itself and no module owns: /pricing, /privacy and /terms (spec 5462 D16). No installMap: every file here is unmapped and lands at its own path under the scaffold root's content/, so a template overrides a base page by shipping the same path. Never published as a registry bundle; the leading underscore is the skip signal for the bundle producer and the site-templates/ directory scans.",
5
+ "postInstall": "Base layer installed: seed copy for /pricing, /privacy and /terms is in content/en/ - replace the placeholder copy before you launch."
6
+ }
@@ -0,0 +1,169 @@
1
+ ---
2
+ type: "Pricing"
3
+ slug: "pricing"
4
+ title: "Pays for itself in {tangible value or outcome}"
5
+ description: "This is a great place to communicate any important details you want everyone to know about how your pricing works."
6
+ sections:
7
+ [
8
+ {
9
+ "id": "hero",
10
+ "type": "hero",
11
+ "data":
12
+ {
13
+ "eyebrow": "Pricing",
14
+ "headline": "Pays for itself in {tangible value or outcome}",
15
+ "subheadline": "This is a great place to communicate any important details you want everyone to know about how your pricing works.",
16
+ "primaryCta": { "label": "{Primary CTA}", "href": "/contact" },
17
+ "secondaryCta":
18
+ {
19
+ "label": "{Secondary CTA}",
20
+ "href": "/how-it-works",
21
+ "variant": "outline",
22
+ },
23
+ },
24
+ },
25
+ {
26
+ "id": "plans",
27
+ "type": "pricing",
28
+ "data":
29
+ {
30
+ "variant": "three-tiers-with-toggle",
31
+ "eyebrow": "Pricing",
32
+ "headline": "Pays for itself in {tangible value or outcome}",
33
+ "description": "This is a great place to communicate any important details you want everyone to know about how your pricing works.",
34
+ "billingToggle": true,
35
+ "plans":
36
+ [
37
+ {
38
+ "name": "Basic plan",
39
+ "price": { "monthly": "$19/mo", "annual": "$180/yr" },
40
+ "features":
41
+ [
42
+ "Feature text goes here",
43
+ "Feature text goes here",
44
+ "Feature text goes here",
45
+ ],
46
+ "cta": { "label": "{Primary CTA}", "href": "/contact" },
47
+ },
48
+ {
49
+ "name": "Business plan",
50
+ "price": { "monthly": "$29/mo", "annual": "$280/yr" },
51
+ "features":
52
+ [
53
+ "New feature text goes here",
54
+ "Feature text goes here",
55
+ "Feature text goes here",
56
+ "Feature text goes here",
57
+ ],
58
+ "cta": { "label": "{Primary CTA}", "href": "/contact" },
59
+ },
60
+ {
61
+ "name": "Enterprise plan",
62
+ "price": { "monthly": "$49/mo", "annual": "$480/yr" },
63
+ "features":
64
+ [
65
+ "New feature text goes here",
66
+ "Feature text goes here",
67
+ "Feature text goes here",
68
+ "Feature text goes here",
69
+ "Feature text goes here",
70
+ ],
71
+ "cta": { "label": "{Primary CTA}", "href": "/contact" },
72
+ },
73
+ ],
74
+ },
75
+ },
76
+ {
77
+ "id": "logo-cloud",
78
+ "type": "logoCloud",
79
+ "data":
80
+ {
81
+ "headline": "Don’t just take our word for it",
82
+ "logos":
83
+ [
84
+ { "name": "Placeholder logo", "src": "/logomark.svg" },
85
+ { "name": "Placeholder logo", "src": "/logomark.svg" },
86
+ { "name": "Placeholder logo", "src": "/logomark.svg" },
87
+ { "name": "Placeholder logo", "src": "/logomark.svg" },
88
+ { "name": "Placeholder logo", "src": "/logomark.svg" },
89
+ { "name": "Placeholder logo", "src": "/logomark.svg" },
90
+ ],
91
+ "layout": "inline",
92
+ "treatment": "monochrome",
93
+ },
94
+ },
95
+ {
96
+ "id": "testimonial",
97
+ "type": "testimonials",
98
+ "data":
99
+ {
100
+ "variant": "featured",
101
+ "items":
102
+ [
103
+ {
104
+ "quote": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat.",
105
+ "author": "John Smith",
106
+ "role": "CEO, Google",
107
+ },
108
+ ],
109
+ },
110
+ },
111
+ {
112
+ "id": "switch",
113
+ "type": "cta",
114
+ "data":
115
+ {
116
+ "variant": "box",
117
+ "headline": "Switch to {your product} in {time amount} or less",
118
+ "description": "You shouldn't have to {experience a painful problem}. With {your product}, you can finally {achieve a value outcome or get a big improvement}.",
119
+ "chips":
120
+ ["No credit card required", "Cancel any time", "Live chat support"],
121
+ "primaryCta": { "label": "{Primary CTA}", "href": "/contact" },
122
+ "secondaryCta":
123
+ {
124
+ "label": "{Secondary CTA}",
125
+ "href": "/pricing",
126
+ "variant": "outline",
127
+ },
128
+ "closingLine": "{Social Proof goes here}",
129
+ },
130
+ },
131
+ {
132
+ "id": "faq",
133
+ "type": "faq",
134
+ "data":
135
+ {
136
+ "headline": "FAQs",
137
+ "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique.",
138
+ "supportLink": { "label": "Contact", "href": "/contact" },
139
+ "presentation": "accordion",
140
+ "initialOpen": "first",
141
+ "items":
142
+ [
143
+ {
144
+ "question": "Vel odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa?",
145
+ "answer": "Vel odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa.",
146
+ },
147
+ {
148
+ "question": "Dolorem consectetur quia maxime sunt ut sint. Fuga optio dolorem animi voluptatem laudantium error. Quis accusamus deleniti rer?",
149
+ "answer": "Vel odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa.",
150
+ },
151
+ {
152
+ "question": "Totam nihil esse. Itaque optio temporibus adipisci. Rerum nisi aperiam aliquid doloremque aliquam?",
153
+ "answer": "Vel odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa.",
154
+ },
155
+ {
156
+ "question": "Delectus minus officiis et. Perspiciatis illum nisi vitae. Sed consequuntur animi corrupti distinctio ipsam accusamus?",
157
+ "answer": "Vel odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa.",
158
+ },
159
+ {
160
+ "question": "Aperiam perferendis voluptatem est sit enim sed eaque perspiciatis enim. Est explicabo ea explicabo et et dignissimos aliquid?",
161
+ "answer": "Vel odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa.",
162
+ },
163
+ ],
164
+ },
165
+ },
166
+ ]
167
+ ---
168
+
169
+ {/* Rendered from frontmatter `sections[]`. Seed content — replace the plan names, prices and features above with your own before you launch. */}