create-ailk 0.27.0 → 0.28.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 (210) hide show
  1. package/README.md +84 -7
  2. package/dist/add-module-core.js +2 -1
  3. package/dist/add-module.d.ts +2 -1
  4. package/dist/add-module.js +9 -4
  5. package/dist/add-route.js +46 -19
  6. package/dist/body-heading.d.ts +56 -0
  7. package/dist/body-heading.js +394 -0
  8. package/dist/cli.js +28 -1
  9. package/dist/content-payloads.js +2 -2
  10. package/dist/derive-architecture.js +8 -2
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.js +4 -1
  13. package/dist/lib/auth-secret-file.d.ts +6 -2
  14. package/dist/lib/auth-secret-file.js +46 -20
  15. package/dist/lib/env-catalog.js +20 -0
  16. package/dist/lib/site-modules.d.ts +8 -0
  17. package/dist/lib/site-modules.js +34 -0
  18. package/dist/module-architecture.js +23 -14
  19. package/dist/paid-sections.d.ts +17 -0
  20. package/dist/paid-sections.js +39 -0
  21. package/dist/programmatic.d.ts +16 -0
  22. package/dist/programmatic.js +73 -4
  23. package/dist/remove-module.d.ts +98 -0
  24. package/dist/remove-module.js +620 -0
  25. package/dist/seed-flows.d.ts +2 -2
  26. package/dist/seed-flows.js +16 -9
  27. package/dist/site-roster.js +4 -1
  28. package/dist/starter-grid.d.ts +26 -0
  29. package/dist/starter-grid.js +34 -0
  30. package/dist/surfaces.d.ts +26 -0
  31. package/dist/surfaces.js +105 -9
  32. package/dist/sync-routes.d.ts +7 -0
  33. package/dist/sync-routes.js +53 -6
  34. package/dist/template-pages.d.ts +36 -0
  35. package/dist/template-pages.js +129 -0
  36. package/dist/topology.d.ts +2 -1
  37. package/dist/topology.js +4 -2
  38. package/dist/update.d.ts +3 -1
  39. package/dist/update.js +38 -4
  40. package/package.json +3 -3
  41. package/release-scope.json +2 -1
  42. package/site-templates/aliases/hub.TEMPLATE-MANIFEST.json +115 -0
  43. package/templates/.env.example +5 -2
  44. package/templates/apps/api/.env.example +9 -5
  45. package/templates/apps/api/CLAUDE.md +14 -9
  46. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +37 -0
  47. package/templates/apps/api/src/vercel-handler.ts +8 -1
  48. package/templates/apps/mcp/CLAUDE.md +18 -10
  49. package/templates/apps/web/.env.example +25 -11
  50. package/templates/apps/web/CLAUDE.md +45 -4
  51. package/templates/apps/web/__tests__/setup.ts +7 -0
  52. package/templates/apps/web/app/.well-known/mcp.json/route.ts +7 -7
  53. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/versions/[version]/file/route.ts +61 -2
  54. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/file-route.test.ts +81 -0
  55. package/templates/apps/web/app/[locale]/(authed)/account/connect/__tests__/page.test.tsx +197 -0
  56. package/templates/apps/web/app/[locale]/(authed)/account/connect/page.tsx +152 -0
  57. package/templates/apps/web/app/[locale]/about/__tests__/about-routes.test.tsx +2 -2
  58. package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +76 -21
  59. package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +46 -16
  60. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +20 -3
  61. package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +180 -2
  62. package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +6 -2
  63. package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +122 -0
  64. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +1 -1
  65. package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +20 -3
  66. package/templates/apps/web/app/[locale]/case-studies/__tests__/hub-children.test.tsx +65 -0
  67. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +6 -2
  68. package/templates/apps/web/app/[locale]/case-studies/page.tsx +23 -11
  69. package/templates/apps/web/app/[locale]/checkout/__tests__/thank-you-account.test.tsx +11 -1
  70. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +1 -1
  71. package/templates/apps/web/app/[locale]/consent.actions.ts +44 -6
  72. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +4 -4
  73. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +269 -42
  74. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +88 -30
  75. package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +20 -3
  76. package/templates/apps/web/app/[locale]/courses/__tests__/hub-children.test.tsx +32 -0
  77. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +6 -2
  78. package/templates/apps/web/app/[locale]/courses/page.tsx +22 -6
  79. package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +15 -2
  80. package/templates/apps/web/app/[locale]/events/__tests__/hub-children.test.tsx +32 -0
  81. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +8 -2
  82. package/templates/apps/web/app/[locale]/events/page.tsx +23 -7
  83. package/templates/apps/web/app/[locale]/features/__tests__/hub-children.test.tsx +32 -0
  84. package/templates/apps/web/app/[locale]/features/page.tsx +23 -25
  85. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +95 -0
  86. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/save.actions.test.ts +193 -0
  87. package/templates/apps/web/app/[locale]/flows/[slug]/email-moment.ts +92 -0
  88. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +10 -0
  89. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +513 -0
  90. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +122 -0
  91. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.no-commerce.test.tsx +195 -0
  92. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/landing.test.tsx +293 -0
  93. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +397 -0
  94. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/checkout-seam.ts +74 -0
  95. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +264 -0
  96. package/templates/apps/web/app/[locale]/flows/[slug]/save.actions.ts +75 -0
  97. package/templates/apps/web/app/[locale]/for/__tests__/hub-children.test.tsx +32 -0
  98. package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +5 -1
  99. package/templates/apps/web/app/[locale]/for/page.tsx +23 -25
  100. package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +20 -3
  101. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  102. package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +25 -4
  103. package/templates/apps/web/app/[locale]/integrations/__tests__/hub-children.test.tsx +38 -0
  104. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +8 -2
  105. package/templates/apps/web/app/[locale]/integrations/page.tsx +23 -11
  106. package/templates/apps/web/app/[locale]/layout.tsx +157 -139
  107. package/templates/apps/web/app/[locale]/page.tsx +51 -3
  108. package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +16 -3
  109. package/templates/apps/web/app/[locale]/partners/__tests__/hub-children.test.tsx +32 -0
  110. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +6 -2
  111. package/templates/apps/web/app/[locale]/partners/page.tsx +22 -6
  112. package/templates/apps/web/app/[locale]/portfolio/__tests__/hub-children.test.tsx +38 -0
  113. package/templates/apps/web/app/[locale]/portfolio/page.tsx +25 -24
  114. package/templates/apps/web/app/[locale]/pricing/[slug]/__tests__/page.test.tsx +321 -0
  115. package/templates/apps/web/app/[locale]/pricing/[slug]/page.tsx +87 -0
  116. package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +3 -0
  117. package/templates/apps/web/app/[locale]/pricing/__tests__/pricing-pages.test.tsx +215 -0
  118. package/templates/apps/web/app/[locale]/pricing/page.tsx +56 -341
  119. package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +21 -4
  120. package/templates/apps/web/app/[locale]/products/__tests__/hub-children.test.tsx +32 -0
  121. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +8 -2
  122. package/templates/apps/web/app/[locale]/products/page.tsx +22 -6
  123. package/templates/apps/web/app/[locale]/projects/__tests__/hub-children.test.tsx +32 -0
  124. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +4 -0
  125. package/templates/apps/web/app/[locale]/projects/page.tsx +18 -11
  126. package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +25 -4
  127. package/templates/apps/web/app/[locale]/resources/__tests__/hub-children.test.tsx +38 -0
  128. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +6 -2
  129. package/templates/apps/web/app/[locale]/resources/page.tsx +20 -10
  130. package/templates/apps/web/app/[locale]/services/[slug]/[child]/page.tsx +68 -0
  131. package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +64 -9
  132. package/templates/apps/web/app/[locale]/services/__tests__/hub-children.test.tsx +158 -0
  133. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +12 -2
  134. package/templates/apps/web/app/[locale]/services/__tests__/service-detail-sections.test.tsx +223 -0
  135. package/templates/apps/web/app/[locale]/services/__tests__/service-families.test.tsx +368 -0
  136. package/templates/apps/web/app/[locale]/services/page.tsx +45 -19
  137. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  138. package/templates/apps/web/app/[locale]/team/__tests__/hub-children.test.tsx +32 -0
  139. package/templates/apps/web/app/[locale]/team/page.tsx +25 -24
  140. package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +24 -3
  141. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  142. package/templates/apps/web/app/[locale]/templates/[slug]/__tests__/page.test.tsx +203 -0
  143. package/templates/apps/web/app/[locale]/templates/[slug]/page.tsx +81 -0
  144. package/templates/apps/web/app/[locale]/templates/__tests__/page.test.tsx +236 -0
  145. package/templates/apps/web/app/[locale]/templates/page.tsx +108 -0
  146. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +1 -1
  147. package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +24 -3
  148. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  149. package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +204 -2
  150. package/templates/apps/web/app/api/auth/hub/authorize/route.ts +151 -33
  151. package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/__tests__/route.test.ts +233 -7
  152. package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +92 -41
  153. package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +77 -39
  154. package/templates/apps/web/app/llms.txt/route.ts +41 -3
  155. package/templates/apps/web/app/robots.txt/route.ts +5 -1
  156. package/templates/apps/web/app/sitemap.xml/route.ts +45 -3
  157. package/templates/apps/web/auth.ts +102 -50
  158. package/templates/apps/web/components/signed-in-nav.tsx +8 -1
  159. package/templates/apps/web/lib/__tests__/bot-check.test.ts +254 -0
  160. package/templates/apps/web/lib/__tests__/consent-visitor.test.ts +212 -13
  161. package/templates/apps/web/lib/__tests__/deck-page.test.ts +13 -1
  162. package/templates/apps/web/lib/__tests__/docs-index.test.ts +93 -0
  163. package/templates/apps/web/lib/__tests__/docs-path.test.ts +168 -0
  164. package/templates/apps/web/lib/__tests__/hub-children.fixtures.ts +278 -0
  165. package/templates/apps/web/lib/__tests__/nav-label-keys.test.ts +138 -0
  166. package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +178 -2
  167. package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +183 -0
  168. package/templates/apps/web/lib/__tests__/page-chrome.test.ts +412 -0
  169. package/templates/apps/web/lib/__tests__/pricing-page.test.ts +142 -0
  170. package/templates/apps/web/lib/__tests__/relay.test.ts +202 -10
  171. package/templates/apps/web/lib/__tests__/route-family.test.ts +57 -0
  172. package/templates/apps/web/lib/__tests__/service-listing.test.ts +111 -0
  173. package/templates/apps/web/lib/__tests__/services-path.test.ts +96 -0
  174. package/templates/apps/web/lib/__tests__/site-header.test.ts +151 -0
  175. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +148 -6
  176. package/templates/apps/web/lib/__tests__/telemetry-scrub.test.ts +84 -0
  177. package/templates/apps/web/lib/__tests__/template-listing.test.ts +158 -0
  178. package/templates/apps/web/lib/auth-secret.ts +57 -0
  179. package/templates/apps/web/lib/bot-check.ts +138 -0
  180. package/templates/apps/web/lib/consent-visitor.ts +81 -7
  181. package/templates/apps/web/lib/deck-page.ts +6 -3
  182. package/templates/apps/web/lib/docs-index.ts +100 -0
  183. package/templates/apps/web/lib/docs-path.ts +94 -0
  184. package/templates/apps/web/lib/hub-children.tsx +318 -0
  185. package/templates/apps/web/lib/modules.generated.ts +9 -0
  186. package/templates/apps/web/lib/nav-label-check.ts +61 -0
  187. package/templates/apps/web/lib/nav-label-keys.ts +78 -0
  188. package/templates/apps/web/lib/newsletter.actions.ts +31 -6
  189. package/templates/apps/web/lib/offerings-pricing.ts +135 -6
  190. package/templates/apps/web/lib/page-chrome.ts +367 -0
  191. package/templates/apps/web/lib/posting-title.ts +26 -4
  192. package/templates/apps/web/lib/pricing-page.ts +430 -0
  193. package/templates/apps/web/lib/relay.ts +31 -106
  194. package/templates/apps/web/lib/route-family.ts +69 -0
  195. package/templates/apps/web/lib/service-listing.ts +89 -0
  196. package/templates/apps/web/lib/services-path.ts +97 -0
  197. package/templates/apps/web/lib/site-header.ts +94 -0
  198. package/templates/apps/web/lib/telemetry-scrub.ts +68 -3
  199. package/templates/apps/web/lib/template-listing.ts +169 -0
  200. package/templates/apps/web/messages/en.json +86 -30
  201. package/templates/apps/web/middleware.ts +171 -11
  202. package/templates/apps/web/next.config.mjs +211 -2
  203. package/templates/apps/web/package.json +1 -1
  204. package/templates/apps/web/scripts/assert-content-bundled.ts +28 -6
  205. package/templates/apps/web/scripts/check-nav-labels.ts +39 -0
  206. package/templates/apps/web/sentry.server.config.ts +30 -2
  207. package/templates/apps/web/src/lib/__tests__/rate-limit.test.ts +67 -9
  208. package/templates/apps/web/src/lib/rate-limit.ts +68 -27
  209. package/templates/apps/web/src/lib/routes.ts +15 -1
  210. package/versions.json +48 -48
@@ -23,6 +23,14 @@ export const ENV_CATALOG = {
23
23
  description: "This site's public origin as the MCP server sees it: the same-origin check for audits and the purchase return URLs.",
24
24
  secret: false,
25
25
  },
26
+ AILK_BOOKING_BOT_CHECK: {
27
+ description: 'Set to "off" to turn off the booking relay\'s Vercel BotID check (#9889) — the booking alone; AILK_BOT_CHECK=off turns every check off. It runs only on Vercel; unset, empty or any other value leaves it on.',
28
+ secret: false,
29
+ },
30
+ AILK_BOT_CHECK: {
31
+ description: 'Set to "off" to turn off the site\'s Vercel BotID check on its public writes — lead, newsletter, callback, flow, checkout and booking (#9255). On by default; it runs only on Vercel.',
32
+ secret: false,
33
+ },
26
34
  AILK_CONTENT_ROOT: {
27
35
  description: "Overrides the on-disk content root (absolute, or relative to the working directory). Unset, the MCP server tries <cwd>/content, then <cwd>/../../content; the web app defaults to <repo-root>/content.",
28
36
  secret: false,
@@ -107,6 +115,10 @@ export const ENV_CATALOG = {
107
115
  description: "Better Stack heartbeat URL pinged on cron and startup; unset turns the heartbeat off.",
108
116
  secret: true,
109
117
  },
118
+ CONTENT_ADAPTER: {
119
+ description: 'Where pages are read from: unset reads the MDX files under content/; "database" reads the database. The build checks Service categories (parent:) only for MDX content.',
120
+ secret: false,
121
+ },
110
122
  CONTENT_REVALIDATE_SECRET: {
111
123
  description: "The shared secret /api/revalidate checks the API's publish webhook against; the same value as the API's.",
112
124
  secret: true,
@@ -163,6 +175,14 @@ export const ENV_CATALOG = {
163
175
  description: "The API origin the browser reads on a hub. A spoke never sets it: its hub origin is server-only (AILK_API_URL).",
164
176
  secret: false,
165
177
  },
178
+ NEXT_PUBLIC_AILK_DOCS_PATH: {
179
+ description: "The docs' public path, baked at build from content/_site.mdx's docsPath; do not set by hand.",
180
+ secret: false,
181
+ },
182
+ NEXT_PUBLIC_AILK_SERVICES_PATH: {
183
+ description: "The services' public base, baked at build from content/_site.mdx's services.path; do not set by hand.",
184
+ secret: false,
185
+ },
166
186
  NEXT_PUBLIC_AILK_LISTING_SITE_KEY: {
167
187
  description: "The site's public listing key, inlined at build; it switches the listicle directory on. Public by design.",
168
188
  secret: false,
@@ -0,0 +1,8 @@
1
+ /** `.ailk-site-manifest.json`'s `modules`, or undefined when the site has no manifest or it records none. */
2
+ export declare function readSiteManifestModules(targetDir: string): string[] | undefined;
3
+ /**
4
+ * Whether the site has dropped the `marketing` module: its manifest records a
5
+ * module list without it. A site with no manifest, or whose manifest records
6
+ * no list, keeps its public tree (today's behaviour).
7
+ */
8
+ export declare function publicTreeRemoved(targetDir: string): boolean;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The site's own module list, as `.ailk-site-manifest.json` records it
3
+ * (#10040 D10). The one reader `sync-routes`, `update`, the module flags and
4
+ * the roster check share to answer "has this site dropped the marketing
5
+ * module?" — never the architecture header alone, because a bare
6
+ * `--modules <paid>` keep run writes a header without `marketing` while its
7
+ * public pages are all on disk, and such a site has no manifest.
8
+ */
9
+ import { existsSync, readFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ /** `.ailk-site-manifest.json`'s `modules`, or undefined when the site has no manifest or it records none. */
12
+ export function readSiteManifestModules(targetDir) {
13
+ const path = join(targetDir, ".ailk-site-manifest.json");
14
+ if (!existsSync(path))
15
+ return undefined;
16
+ try {
17
+ const doc = JSON.parse(readFileSync(path, "utf8"));
18
+ return Array.isArray(doc.modules) && doc.modules.every((m) => typeof m === "string")
19
+ ? doc.modules
20
+ : undefined;
21
+ }
22
+ catch {
23
+ return undefined;
24
+ }
25
+ }
26
+ /**
27
+ * Whether the site has dropped the `marketing` module: its manifest records a
28
+ * module list without it. A site with no manifest, or whose manifest records
29
+ * no list, keeps its public tree (today's behaviour).
30
+ */
31
+ export function publicTreeRemoved(targetDir) {
32
+ const modules = readSiteManifestModules(targetDir);
33
+ return modules !== undefined && !modules.includes("marketing");
34
+ }
@@ -1362,16 +1362,15 @@ export const MODULE_SLICES = {
1362
1362
  // routes, named exactly as `surfaces.ts`'s ROUTE_SINCE keys name them.
1363
1363
  // ROWS AND SEED ONLY: unlike docs-blog and scheduling above, this module's
1364
1364
  // route code is deliberately NOT in `ownedFiles` (see the comment inside
1365
- // that array). The first two rows' code reaches a scaffold from the
1366
- // `templates/` snapshot, retained because the `flows` surface is enabled;
1367
- // the results row's code is `commerce`'s (`commerce.ownedFiles` carries
1368
- // `results/[payload]/page.tsx` + its two client files below, and
1369
- // `paidModuleTemplateExclusions` strips every paid module's owned files
1370
- // from the snapshot unconditionally), so that one row rides a DIFFERENT
1371
- // module's bundle — recorded in spec 5478 § 8 item 1, whose delivery
1372
- // consequence is #5481's and #5464's. Declaring the rows here changes none
1373
- // of it: a contribution is data, and which delivery shape carries the code
1374
- // is a property of the module, not of the contribution.
1365
+ // that array). All three rows' code reaches a scaffold from the
1366
+ // `templates/` snapshot, retained because the `flows` surface is enabled.
1367
+ // The results row's code was `commerce`'s until #6786, so a Pro scaffold
1368
+ // without commerce 404'd a finished recommending flow; it now ships here,
1369
+ // and commerce's bundle adds only the checkout half
1370
+ // (`commerce.ownedFiles`: `results/[payload]/layout.tsx` and
1371
+ // `checkout.actions.ts`). Declaring the rows here changes none of it: a
1372
+ // contribution is data, and which delivery shape carries the code is a
1373
+ // property of the module, not of the contribution.
1375
1374
  pageContributions: [
1376
1375
  { route: "/flows/[slug]" },
1377
1376
  { route: "/flows/[slug]/start" },
@@ -1500,7 +1499,8 @@ export const MODULE_SLICES = {
1500
1499
  // filter (`isSpokeExcluded`), which drops every destination under
1501
1500
  // apps/api or database and the web files that call the hub's own
1502
1501
  // checkout (`SPOKE_HUB_CHECKOUT_SEEDS`: flows/[slug]/checkout.actions.ts,
1503
- // the flow results pages, dev/purchase — spoke-intake Rule 3). What lands
1502
+ // the flow results directory — the results layout among it — and
1503
+ // dev/purchase, spoke-intake Rule 3). What lands
1504
1504
  // is the scaffold-commerce skill, the commerce-boundary rule and the
1505
1505
  // @working-theory/ui-commerce dependency (`patches` below). A spoke sells
1506
1506
  // through its hub's Offerings buy route (#9142); its remaining checkout
@@ -1583,10 +1583,13 @@ export const MODULE_SLICES = {
1583
1583
  // + the slice's own re-export entry point) moved to
1584
1584
  // packages-paid/ui-commerce; the package is delivered to a keyed
1585
1585
  // scaffold via `patches` below, not via `ownedFiles`/`installMap` (D5).
1586
+ // #6786 — the flow results page is signal-collectors' (it rides the
1587
+ // `flows` surface's directory, see that slice); commerce owns only its
1588
+ // checkout half: Pay's action and the results layout that provides the
1589
+ // price card + Pay through the base tree's `checkout-seam.ts`. Without
1590
+ // them the page renders, offering Book a Call for every fit.
1586
1591
  "apps/web/app/[locale]/flows/[slug]/checkout.actions.ts",
1587
- "apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx",
1588
- "apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx",
1589
- "apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx",
1592
+ "apps/web/app/[locale]/flows/[slug]/results/[payload]/layout.tsx",
1590
1593
  "apps/web/app/[locale]/dev/purchase/page.tsx",
1591
1594
  "apps/web/app/[locale]/dev/purchase/error.tsx",
1592
1595
  "apps/web/app/[locale]/dev/purchase/purchase.actions.ts",
@@ -1823,6 +1826,11 @@ export function coOwnedModuleDestinations() {
1823
1826
  // named by one file. A scaffold is not a git repository. Only those two
1824
1827
  // cases moved there; `connection.test.ts` and
1825
1828
  // `checkout-session-record.test.ts` keep the stores' coverage and ship.
1829
+ // - the content hubs' `lib/__tests__/hub-children.test.tsx` (#10121) — it
1830
+ // imports the hub page of thirteen route families across surfaces, and a
1831
+ // scaffold carries only the families its surfaces select, so a gated
1832
+ // scaffold would ship it with dangling imports. Its coverage of the
1833
+ // generated child cards runs in this monorepo only.
1826
1834
  export const EXCLUDE_TEMPLATE_DIRS = [
1827
1835
  "apps/web/__tests__",
1828
1836
  "apps/web/app/__tests__",
@@ -1847,6 +1855,7 @@ export const EXCLUDE_TEMPLATE_DIRS = [
1847
1855
  "apps/mcp/__tests__/parity.test.ts",
1848
1856
  "apps/web/app/[locale]/pricing/__tests__/monorepo-only.test.tsx",
1849
1857
  "apps/web/app/[locale]/pricing/__tests__/__snapshots__/monorepo-only.test.tsx.snap",
1858
+ "apps/web/lib/__tests__/hub-children.test.tsx",
1850
1859
  ];
1851
1860
  export function paidModuleTemplateExclusions(snapshotRoots, repoRoot) {
1852
1861
  const roots = new Set(snapshotRoots);
@@ -26,7 +26,24 @@ export declare function renderPaidSections(present: ReadonlySet<string>): string
26
26
  */
27
27
  export declare function renderModuleFlags(flags: {
28
28
  schedulingPages: boolean;
29
+ /** The `marketing` module is in the architecture header (#10040 D6). Absent → `true`. */
30
+ marketingPages?: boolean;
31
+ /** The `docs-blog` module is in the architecture header (#10040 D6). Absent → `false`. */
32
+ docsBlogPages?: boolean;
29
33
  }): string;
34
+ /**
35
+ * The `marketing` / `docs-blog` presence the flags render (#10040 D6).
36
+ * `marketing` is read from the site's manifest (`lib/site-modules.ts`): a
37
+ * site with no manifest keeps its public tree, whatever its header says — a
38
+ * bare `--modules <paid>` keep run writes a header without `marketing` while
39
+ * every public page is on disk. `docs-blog` is read from the architecture
40
+ * header, which lists it on every site that carries it; an unreadable header
41
+ * reads as absent.
42
+ */
43
+ export declare function readPublicPageFlags(targetDir: string): {
44
+ marketingPages: boolean;
45
+ docsBlogPages: boolean;
46
+ };
30
47
  /**
31
48
  * Write the module-presence flags into a scaffold from its own
32
49
  * `apps/web/package.json` (#8843 D10) — never copied from a template, so a
@@ -25,6 +25,8 @@
25
25
  */
26
26
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
27
27
  import { dirname, join } from "node:path";
28
+ import { readArchitectureHeader } from "./add-module-core.js";
29
+ import { readSiteManifestModules } from "./lib/site-modules.js";
28
30
  import { MODULE_SLICES } from "./module-architecture.js";
29
31
  import { RECOGNIZED_MODULES } from "./parse-args.js";
30
32
  /** Scaffold-relative path of the generated registry. */
@@ -119,15 +121,51 @@ const MODULE_FLAGS_HEADER = `// modules.generated.ts — which module pages this
119
121
  * tree does not carry.
120
122
  */
121
123
  export function renderModuleFlags(flags) {
124
+ const marketingPages = flags.marketingPages ?? true;
125
+ const docsBlogPages = flags.docsBlogPages ?? false;
122
126
  const lines = [];
127
+ // The line-1 marker stays keyed on the scheduling pages alone (#10040 D6).
123
128
  if (flags.schedulingPages)
124
129
  lines.push("// oss: false");
125
130
  lines.push(MODULE_FLAGS_HEADER.trimEnd());
126
131
  lines.push("");
127
132
  lines.push("/** The scheduling module's booking pages (/meet, /private) exist. */");
128
133
  lines.push(`export const SCHEDULING_PAGES: boolean = ${String(flags.schedulingPages)};`);
134
+ lines.push("");
135
+ lines.push("/** The marketing module's public pages exist (#10040): false on a hub. */");
136
+ lines.push(`export const MARKETING_PAGES: boolean = ${String(marketingPages)};`);
137
+ lines.push("");
138
+ lines.push("/** The docs-blog module's /docs and /blog pages exist (#10040). */");
139
+ lines.push(`export const DOCS_BLOG_PAGES: boolean = ${String(docsBlogPages)};`);
140
+ lines.push("");
141
+ lines.push("/** Any public page exists: robots, sitemap, llms.txt and the noindex header read this (#10040 D6/D7). */");
142
+ lines.push(`export const PUBLIC_PAGES: boolean = ${String(marketingPages || docsBlogPages)};`);
129
143
  return `${lines.join("\n")}\n`;
130
144
  }
145
+ /**
146
+ * The `marketing` / `docs-blog` presence the flags render (#10040 D6).
147
+ * `marketing` is read from the site's manifest (`lib/site-modules.ts`): a
148
+ * site with no manifest keeps its public tree, whatever its header says — a
149
+ * bare `--modules <paid>` keep run writes a header without `marketing` while
150
+ * every public page is on disk. `docs-blog` is read from the architecture
151
+ * header, which lists it on every site that carries it; an unreadable header
152
+ * reads as absent.
153
+ */
154
+ export function readPublicPageFlags(targetDir) {
155
+ const siteModules = readSiteManifestModules(targetDir);
156
+ const marketingPages = siteModules === undefined || siteModules.includes("marketing");
157
+ const archPath = join(targetDir, "architecture.yaml");
158
+ let docsBlogPages = false;
159
+ if (existsSync(archPath)) {
160
+ try {
161
+ docsBlogPages = readArchitectureHeader(readFileSync(archPath, "utf8")).modules.includes("docs-blog");
162
+ }
163
+ catch {
164
+ docsBlogPages = false;
165
+ }
166
+ }
167
+ return { marketingPages, docsBlogPages };
168
+ }
131
169
  /** The web app's `dependencies`, or `undefined` for a scaffold with no web app. */
132
170
  function webDependencies(targetDir, writing) {
133
171
  const pkgPath = join(targetDir, "apps/web/package.json");
@@ -158,6 +196,7 @@ export function writeModuleFlags(targetDir) {
158
196
  const before = existsSync(path) ? readFileSync(path, "utf8") : undefined;
159
197
  const next = renderModuleFlags({
160
198
  schedulingPages: Object.hasOwn(deps, SCHEDULING_PAGES_PACKAGE),
199
+ ...readPublicPageFlags(targetDir),
161
200
  });
162
201
  if (before === next)
163
202
  return "unchanged";
@@ -19,6 +19,7 @@ import { type AddModuleResult } from "./add-module.js";
19
19
  import { type SyncClaudeMdResult } from "./claude-md.js";
20
20
  import { CATALOG_TARGET_PATH } from "./component-catalog.js";
21
21
  import { type ModuleName } from "./parse-args.js";
22
+ import { type RemoveModuleResult } from "./remove-module.js";
22
23
  import { type TemplateLayout } from "./site-chrome.js";
23
24
  import { type SyncRoutesResult } from "./sync-routes.js";
24
25
  import { type StarterShape } from "./template-manifest.js";
@@ -367,6 +368,21 @@ export interface AddModuleOptions {
367
368
  * 4. the fetched bundle is merged under the plus-config policy.
368
369
  */
369
370
  export declare function addModule(opts: AddModuleOptions): Promise<AddModuleResult>;
371
+ export interface RemoveModuleCliOptions {
372
+ /** The already-scaffolded site (absolute or relative). */
373
+ targetDir: string;
374
+ /** The module to remove (validated upstream by `parseRemoveModuleArgs`). */
375
+ moduleName: ModuleName;
376
+ /** Delete the site's own edits in the removal set too. */
377
+ force?: boolean;
378
+ }
379
+ /**
380
+ * Take a module's public pages off an already-scaffolded site in place
381
+ * (`create-ailk remove-module`, #10040) — the thin package-rooted wrapper
382
+ * around `remove-module.ts`'s `removeModuleFromScaffold`, the `addModule`
383
+ * split. Every refusal is the core's and throws before the first write.
384
+ */
385
+ export declare function removeModule(opts: RemoveModuleCliOptions): RemoveModuleResult;
370
386
  /** Re-exported so the CLI can name the file it wrote without duplicating it. */
371
387
  export { CATALOG_TARGET_PATH };
372
388
  /**
@@ -39,6 +39,7 @@ import { classifyModules, isAddition } from "./lib/module-tier.js";
39
39
  import { normalizeModules } from "./module-architecture.js";
40
40
  import { writePaidSections } from "./paid-sections.js";
41
41
  import { RECOGNIZED_SHAPES, RESERVED_SHAPES, } from "./parse-args.js";
42
+ import { RemoveModuleRefusedError, removeModuleFromScaffold, } from "./remove-module.js";
42
43
  import { writeSpokeFlowsReadme } from "./seed-flows.js";
43
44
  import { SITE_CHROME_PATH, renderSiteChrome, resolveSiteChrome, } from "./site-chrome.js";
44
45
  import { SITE_FONTS_PATH, renderSiteFonts } from "./site-fonts.js";
@@ -47,7 +48,7 @@ import { SITE_ROSTER_PATH, planHomeRecord, planSiteManifestDesign, readSiteRoste
47
48
  import { SPOKE_CHECKOUT_TEMPLATE_PATHS, dropSpokeCheckoutDependency, spokeCheckoutSite, spokeEnvRowOptions, writeSpokeCheckoutSlice, } from "./spoke-checkout.js";
48
49
  import { GATEABLE_SURFACES, MODULE_SURFACES, NAV_CONFIG_PATH, excludedRoutePaths, isUnservedSurfaceContent, renderNavConfig, resolveSurfaces, } from "./surfaces.js";
49
50
  import { syncRoutes } from "./sync-routes.js";
50
- import { BASE_LAYER_DIR, SHAPE_STARTERS, TEMPLATE_MANIFESTS, TemplateError, mergeRoster, resolveTemplate, resolveTemplateLineage, rosterDeclares, templateDelivery, templateTreeDir, } from "./template-manifest.js";
51
+ import { BASE_LAYER_DIR, SHAPE_STARTERS, isOwnTree, shippedManifestPath, TEMPLATE_MANIFESTS, TemplateError, mergeRoster, resolveTemplate, resolveTemplateLineage, rosterDeclares, templateDelivery, templateTreeDir, } from "./template-manifest.js";
51
52
  import { TEMPLATE_RECORD_PATH, isRecordable, planTemplateRecord, serializeTemplateRecord, sha256Hex, } from "./template-record.js";
52
53
  import { SPOKE_ONLY_FILES, TopologyError, applySpokeTransforms, isSpokeExcluded, normalizeHubSignInUrl, normalizeHubUrl, readSiteTopology, spokeExclusions, spokeDestination, spokeModuleRefusal, spokeModules, writeHubOriginEnv, writeHubSignInEnv, } from "./topology.js";
53
54
  import { validateTarget } from "./validate.js";
@@ -134,7 +135,9 @@ function resolveShapeTemplate(shape) {
134
135
  ...Object.values(TEMPLATE_MANIFESTS),
135
136
  ...Object.values(SHAPE_STARTERS),
136
137
  ].filter((manifest) => manifest.shape === shape);
137
- const free = ofShape.filter((manifest) => templateDelivery(manifest) === "free");
138
+ // #10040 D8 — a shape's Free starter is its public site: an alias with no
139
+ // `marketing` in `requires` (the hub starter) is never a shape's starter.
140
+ const free = ofShape.filter((manifest) => templateDelivery(manifest) === "free" && manifest.requires.includes("marketing"));
138
141
  if (free.length === 0) {
139
142
  const names = ofShape.map((manifest) => manifest.name).join(", ");
140
143
  throw new TemplateError(`shape "${shape}" has no Free starter yet — its page list lands with #6743. Templates built on this shape: ${names || "none"}.`);
@@ -373,9 +376,12 @@ export async function scaffold(opts) {
373
376
  if (opts.template !== undefined) {
374
377
  // Alias run — untouched (NFR-4): the union line and its
375
378
  // resolvePaidModules({ …, templateName }) call stay exactly as they were.
379
+ // #10040 D8 — the template's `requires` is unioned in, so the only
380
+ // alias a `["marketing"]` default changed was one that deliberately
381
+ // lacks it (the hub starter); every existing alias lists `marketing`.
376
382
  modules = normalizeModules([
377
383
  ...(template?.requires ?? []),
378
- ...(opts.modules ?? ["marketing"]),
384
+ ...(opts.modules ?? []),
379
385
  ]);
380
386
  const { paid } = classifyModules(modules);
381
387
  bundles = await resolvePaidModules({
@@ -656,7 +662,11 @@ export async function scaffold(opts) {
656
662
  sourceRoot,
657
663
  targetDir: result.targetDir,
658
664
  name: template.name,
659
- templateRequires: template.requires,
665
+ // #10040 D8 — a treeless alias installs its SHAPE's tree, whose
666
+ // ailk-template.json declares the shape starter's requires.
667
+ templateRequires: isOwnTree(template.name)
668
+ ? template.requires
669
+ : (SHAPE_STARTERS[template.shape]?.requires ?? template.requires),
660
670
  templateExtends: template.extends,
661
671
  versions,
662
672
  markers,
@@ -698,6 +708,13 @@ export async function scaffold(opts) {
698
708
  // #6939 — the scaffold's DESIGN.md is the design it wears, not the one
699
709
  // its template layer shipped; the site's own header lines are kept.
700
710
  writeFileSync(join(result.targetDir, "DESIGN.md"), planDesignMd(resolvedDesign.dir, result.targetDir));
711
+ // #10040 D8 — a treeless alias (`ownTree: false`) ships no DESIGN.md of
712
+ // its own, so the kept line would still name the overlay's `_base`;
713
+ // the scaffold's provenance is the alias.
714
+ if (template !== undefined && !isOwnTree(template.name)) {
715
+ const designPath = join(result.targetDir, "DESIGN.md");
716
+ writeFileSync(designPath, readFileSync(designPath, "utf8").replace(/^([ \t]*)Source template:.*$/m, (_m, indent) => `${indent}Source template: ${template.name}`));
717
+ }
701
718
  // #7720 D4.1 — the faces the design sources, beside site-chrome.ts. A
702
719
  // design that sources none renders the overlay's committed default, so
703
720
  // the file is left as the template tree wrote it.
@@ -828,6 +845,11 @@ export async function scaffold(opts) {
828
845
  // plain alias or `--modules`-keep run, and is never inferred from `modules`.
829
846
  // A scaffold with no manifest (the bare overlay, `saas-product`) writes none.
830
847
  if (template !== undefined) {
848
+ // #10040 D8 — a treeless alias installed its SHAPE's tree, whose record
849
+ // names the shape; the site's roster of record is the alias's own.
850
+ if (!isOwnTree(template.name)) {
851
+ writeFileSync(join(result.targetDir, SITE_ROSTER_PATH), readFileSync(join(SITE_TEMPLATES_DIR, shippedManifestPath(template.name)), "utf8"));
852
+ }
831
853
  const roster = readSiteRoster(result.targetDir);
832
854
  if (roster !== undefined) {
833
855
  writeFileSync(join(result.targetDir, roster.path), planSiteManifest({
@@ -858,6 +880,23 @@ export async function scaffold(opts) {
858
880
  // #7536 D3 — the registry follows `apps/web/package.json`, which a module
859
881
  // bundle's `patches` (and the template layer) just finished editing.
860
882
  writePaidSections(result.targetDir);
883
+ // #10040 D8 — a template whose resolved module set lacks `marketing` (the
884
+ // hub starter) is the same scaffold with the public tree removed: one code
885
+ // path, the command's. Runs after the record and the manifest are written,
886
+ // so the removed paths retire exactly as they would on a live site.
887
+ if (template !== undefined && !modules.includes("marketing")) {
888
+ removeModuleFromScaffold({
889
+ targetDir: result.targetDir,
890
+ moduleName: "marketing",
891
+ force: true,
892
+ fromScaffold: true,
893
+ templatesDir: TEMPLATES_DIR,
894
+ siteTemplatesDir: SITE_TEMPLATES_DIR,
895
+ versions,
896
+ markers,
897
+ version: markers["CREATE_AILK_VERSION"] ?? "",
898
+ });
899
+ }
861
900
  // #7874 D9 — a spoke's rows again, now the module bundles and the template
862
901
  // layer are in the scan; it appends only what those layers added. A
863
902
  // commerce spoke also declares what the checkout package reads (#7838 D9).
@@ -1234,6 +1273,36 @@ export async function addModule(opts) {
1234
1273
  siteTemplatesDir: SITE_TEMPLATES_DIR,
1235
1274
  });
1236
1275
  }
1276
+ /**
1277
+ * Take a module's public pages off an already-scaffolded site in place
1278
+ * (`create-ailk remove-module`, #10040) — the thin package-rooted wrapper
1279
+ * around `remove-module.ts`'s `removeModuleFromScaffold`, the `addModule`
1280
+ * split. Every refusal is the core's and throws before the first write.
1281
+ */
1282
+ export function removeModule(opts) {
1283
+ const targetAbs = resolve(opts.targetDir);
1284
+ if (!existsSync(targetAbs)) {
1285
+ throw new Error(`remove-module target does not exist: ${targetAbs}. Point remove-module at an already-scaffolded repo.`);
1286
+ }
1287
+ if (!statSync(targetAbs).isDirectory()) {
1288
+ throw new Error(`remove-module target is not a directory: ${targetAbs}`);
1289
+ }
1290
+ const markers = buildScaffoldMarkers(basename(targetAbs));
1291
+ const version = markers["CREATE_AILK_VERSION"];
1292
+ if (version === undefined || version === "") {
1293
+ throw new RemoveModuleRefusedError("CREATE_AILK_VERSION is unset: a retired record row needs the running version.");
1294
+ }
1295
+ return removeModuleFromScaffold({
1296
+ targetDir: targetAbs,
1297
+ moduleName: opts.moduleName,
1298
+ ...(opts.force !== undefined ? { force: opts.force } : {}),
1299
+ templatesDir: TEMPLATES_DIR,
1300
+ siteTemplatesDir: SITE_TEMPLATES_DIR,
1301
+ versions: readVersionMap(),
1302
+ markers,
1303
+ version,
1304
+ });
1305
+ }
1237
1306
  /** Re-exported so the CLI can name the file it wrote without duplicating it. */
1238
1307
  export { CATALOG_TARGET_PATH };
1239
1308
  /**
@@ -0,0 +1,98 @@
1
+ import type { InstallReportRow } from "./lib/extract-module-bundle.js";
2
+ import { type ModuleName } from "./parse-args.js";
3
+ import type { MarkerMap, WorkspaceVersionMap } from "./substitute.js";
4
+ /** The modules `remove-module` takes (decision 6). */
5
+ export declare const REMOVABLE_MODULES: readonly ModuleName[];
6
+ /**
7
+ * The `content/<locale>/<type>/` directories a site keeps when it drops
8
+ * `marketing`: the seeds of the kept legal pages (`HUB_KEPT_ROUTES`).
9
+ */
10
+ export declare const HUB_KEPT_CONTENT_TYPES: readonly string[];
11
+ /** A refusal the CLI exits 2 on — the same shape `add-module` throws. */
12
+ export declare class RemoveModuleRefusedError extends Error {
13
+ readonly exitCode = 2;
14
+ constructor(message: string);
15
+ }
16
+ export interface RemoveModuleOptions {
17
+ /** The already-scaffolded site. */
18
+ targetDir: string;
19
+ moduleName: ModuleName;
20
+ /** Delete the site's own edits too (decision 5). */
21
+ force?: boolean;
22
+ /** The package's `templates/` snapshot — the record's template copies and the overlay seeds. */
23
+ templatesDir: string;
24
+ /** The package's `site-templates/` dir — the base layer and the template tree's seeds. */
25
+ siteTemplatesDir: string;
26
+ /** Workspace version map, for rendering a template copy the way `copyTemplate` did. */
27
+ versions: WorkspaceVersionMap;
28
+ /** The scaffold's marker map (`buildScaffoldMarkers`). */
29
+ markers: MarkerMap;
30
+ /** The running create-ailk version — the `version` a new record row carries. */
31
+ version: string;
32
+ /**
33
+ * `scaffold()`'s own call (#10040 D8): the header already lacks the module
34
+ * because the resolved set never had it, so the carry check is skipped.
35
+ */
36
+ fromScaffold?: boolean;
37
+ }
38
+ export interface RemoveModuleResult {
39
+ targetDir: string;
40
+ moduleName: ModuleName;
41
+ /** The module set `architecture.yaml` now declares. */
42
+ modules: ModuleName[];
43
+ /** Every path deleted, site-relative, sorted. */
44
+ removed: string[];
45
+ /** The deleted paths now carried as retired rows in the record (D1). */
46
+ retired: string[];
47
+ /** The site's own files the run deleted under `--force`. */
48
+ forced: string[];
49
+ /** Removal-set files kept because a kept file imports them, with the importer. */
50
+ keptImports: Array<{
51
+ path: string;
52
+ importedBy: string;
53
+ }>;
54
+ /** Config patches a removed module left in place, for the owner to reverse by hand (D7). */
55
+ review: Array<{
56
+ path: string;
57
+ reason: string;
58
+ }>;
59
+ /** Generated files rewritten (nav, flags, registries) or left for review (an edited nav). */
60
+ report: InstallReportRow[];
61
+ roster: {
62
+ state: "trimmed" | "unchanged" | "none";
63
+ dropped: string[];
64
+ path: string;
65
+ };
66
+ }
67
+ /** The directories `remove-module marketing` removes: the public route tree and the non-legal seeds. */
68
+ export declare function marketingRemovalDirs(targetDir: string): string[];
69
+ /**
70
+ * The removal-set files a KEPT file imports — kept with their own imports,
71
+ * to a fixed point (#10040). `apps/web/app/[locale]/contact/` holds helpers
72
+ * the locale layout and `lib/in-page-magnet.ts` import beside its page;
73
+ * deleting them with the page breaks the kept tree's type-check. Only
74
+ * `apps/web` code is scanned; a kept file is any `.ts`/`.tsx` outside the
75
+ * removal set (node_modules, .next and dist excluded).
76
+ */
77
+ export declare function importedByKept(targetDir: string, removal: string[]): Map<string, string>;
78
+ /** The paths a module's removal deletes, site-relative, only those on disk. */
79
+ export declare function removalSet(targetDir: string, moduleName: ModuleName): string[];
80
+ /**
81
+ * Removes one module from a scaffolded site. Every refusal is raised before
82
+ * the first write; the writes that follow (delete → record → architecture →
83
+ * manifest → generated files) are not atomic, but each is idempotent on its
84
+ * own inputs, so a run interrupted mid-way converges on the next invocation —
85
+ * `--force` is then needed only if the first run needed it.
86
+ */
87
+ export declare function removeModuleFromScaffold(opts: RemoveModuleOptions): RemoveModuleResult;
88
+ /** The report `remove-module` prints. */
89
+ export declare function formatRemoveModuleReport(result: RemoveModuleResult): string;
90
+ export interface RemoveModuleArgs {
91
+ module: ModuleName | undefined;
92
+ target: string | undefined;
93
+ force: boolean;
94
+ help: boolean;
95
+ }
96
+ export declare const REMOVE_MODULE_HELP = "create-ailk remove-module \u2014 take a module's public pages off an existing site.\n\nUsage:\n create-ailk remove-module <module> [<target-dir>] [--force]\n\nArguments:\n <module> marketing or docs-blog. Nothing else can be removed.\n <target-dir> An already-scaffolded repo (contains\n architecture.yaml). Defaults to the current dir.\n\nOptions:\n --force Also delete files the site has edited or authored\n inside the module's pages. Without it, such files\n are listed and nothing is removed.\n --help, -h Print this message.\n\nDeletes the module's route directories and seed content, drops the module\nfrom .ailk-site-manifest.json and architecture.yaml, and records every removed\napp file as retired in .ailk-template-files.json, so create-ailk update and\nsync-routes never bring it back. A site without marketing redirects / to\n/account, serves robots.txt disallowing everything, sends noindex on every page\nand answers 404 at sitemap.xml and llms.txt. Removing marketing while docs-blog\nis installed is refused: remove docs-blog first.\n";
97
+ /** Parse the argv AFTER the `remove-module` word, the `parseAddModuleArgs` shape. */
98
+ export declare function parseRemoveModuleArgs(argv: string[]): RemoveModuleArgs;