create-ailk 0.4.0 → 0.6.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 (396) hide show
  1. package/README.md +55 -8
  2. package/component-catalog.json +12248 -0
  3. package/component-catalog.md +193 -68
  4. package/dist/cli.js +29 -1
  5. package/dist/derive-architecture.js +5 -2
  6. package/dist/lib/api-boot-assertion.d.ts +46 -0
  7. package/dist/lib/api-boot-assertion.js +70 -0
  8. package/dist/lib/extract-module-bundle.d.ts +50 -2
  9. package/dist/lib/extract-module-bundle.js +223 -15
  10. package/dist/lib/fetch-guard.d.ts +28 -0
  11. package/dist/lib/fetch-guard.js +101 -0
  12. package/dist/lib/fetch-module.js +4 -0
  13. package/dist/lib/fetch-template.js +4 -0
  14. package/dist/lib/import-graph.d.ts +29 -0
  15. package/dist/lib/import-graph.js +140 -0
  16. package/dist/module-architecture.d.ts +60 -8
  17. package/dist/module-architecture.js +587 -242
  18. package/dist/parse-args.js +6 -2
  19. package/dist/programmatic.d.ts +10 -0
  20. package/dist/programmatic.js +134 -19
  21. package/dist/site-chrome.d.ts +80 -0
  22. package/dist/site-chrome.js +135 -0
  23. package/dist/surfaces.d.ts +21 -6
  24. package/dist/surfaces.js +209 -36
  25. package/dist/template-manifest.d.ts +88 -3
  26. package/dist/template-manifest.js +281 -29
  27. package/dist/template-names.d.ts +1 -1
  28. package/dist/template-names.js +2 -0
  29. package/package.json +10 -2
  30. package/site-templates/_base/ailk-template.json +6 -0
  31. package/site-templates/_base/content/en/Pricing/pricing.mdx +169 -0
  32. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +351 -0
  33. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +400 -0
  34. package/site-templates/marketing-site/ailk-template.json +11 -0
  35. package/site-templates/marketing-site/content/en/blog/05-this-vc-morning-routine-will-make-you-super-annoying.mdx +36 -0
  36. package/site-templates/marketing-site/provenance.json +4 -0
  37. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +288 -0
  38. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +286 -0
  39. package/site-templates/marketing-site-free/ailk-template.json +2 -1
  40. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +279 -0
  41. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +336 -0
  42. package/site-templates/minimalism-site/ailk-template.json +22 -0
  43. package/site-templates/minimalism-site/assets/avatar-placeholder-1.svg +7 -0
  44. package/site-templates/minimalism-site/assets/avatar-placeholder-2.svg +7 -0
  45. package/site-templates/minimalism-site/assets/avatar-placeholder-3.svg +7 -0
  46. package/site-templates/minimalism-site/assets/avatar-placeholder-4.svg +7 -0
  47. package/site-templates/minimalism-site/assets/avatar-placeholder-5.svg +7 -0
  48. package/site-templates/minimalism-site/assets/avatar-placeholder-6.svg +7 -0
  49. package/site-templates/minimalism-site/assets/hero-backdrop.svg +17 -0
  50. package/site-templates/minimalism-site/assets/logo-placeholder-1.svg +6 -0
  51. package/site-templates/minimalism-site/assets/logo-placeholder-2.svg +6 -0
  52. package/site-templates/minimalism-site/assets/logo-placeholder-3.svg +6 -0
  53. package/site-templates/minimalism-site/assets/wordmark-placeholder.svg +28 -0
  54. package/site-templates/minimalism-site/content/en/AboutPage/about.mdx +110 -0
  55. package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +68 -0
  56. package/site-templates/minimalism-site/content/en/FAQPage/faq.mdx +57 -0
  57. package/site-templates/minimalism-site/content/en/HomePage/home.mdx +244 -0
  58. package/site-templates/minimalism-site/content/en/LandingPage/referral.mdx +77 -0
  59. package/site-templates/minimalism-site/content/en/LandingPage/reports.mdx +73 -0
  60. package/site-templates/minimalism-site/content/en/ReportPage/annual-letter-2024.mdx +52 -0
  61. package/site-templates/minimalism-site/content/en/ReportPage/field-notes-2023.mdx +52 -0
  62. package/site-templates/minimalism-site/content/en/ReportPage/platform-shift-2025.mdx +52 -0
  63. package/site-templates/minimalism-site/content/en/ReportPage/state-of-the-market-2026.mdx +52 -0
  64. package/site-templates/minimalism-site/content/en/Service/first-engagement.mdx +38 -0
  65. package/site-templates/minimalism-site/content/en/Service/second-engagement.mdx +38 -0
  66. package/site-templates/minimalism-site/content/en/Service/third-engagement.mdx +38 -0
  67. package/site-templates/minimalism-site/content/en/ServicesList/services.mdx +73 -0
  68. package/site-templates/minimalism-site/flows/referral.flow.json +71 -0
  69. package/site-templates/minimalism-site/provenance.json +4 -0
  70. package/site-templates/saas-product/TEMPLATE-MANIFEST.md +61 -0
  71. package/site-templates/saas-product/ailk-template.json +16 -0
  72. package/site-templates/saas-product/provenance.json +10 -0
  73. package/site-templates/simple-service/TEMPLATE-MANIFEST.json +240 -0
  74. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +278 -0
  75. package/site-templates/simple-service/ailk-template.json +10 -0
  76. package/site-templates/simple-service/content/en/AboutPage/about.mdx +38 -0
  77. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +67 -0
  78. package/site-templates/simple-service/content/en/FAQPage/faq.mdx +56 -0
  79. package/site-templates/simple-service/content/en/HomePage/home.mdx +85 -0
  80. package/site-templates/simple-service/content/en/Service/landscape-design.mdx +40 -0
  81. package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +40 -0
  82. package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +40 -0
  83. package/site-templates/simple-service/content/en/ServicesList/services.mdx +72 -0
  84. package/site-templates/simple-service/provenance.json +4 -0
  85. package/templates/.claude/skills/README.md +16 -25
  86. package/templates/.env.example +102 -12
  87. package/templates/.github/workflows/ci.yml +48 -0
  88. package/templates/README.md +3 -1
  89. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +8 -0
  90. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +12 -0
  91. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +12 -0
  92. package/templates/apps/api/.env.example +13 -0
  93. package/templates/apps/api/CLAUDE.md +29 -2
  94. package/templates/apps/api/eslint.config.mjs +69 -0
  95. package/templates/apps/api/jest.config.cjs +3 -0
  96. package/templates/apps/api/package.json +7 -2
  97. package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +407 -0
  98. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +89 -9
  99. package/templates/apps/api/src/__tests__/review-surface-registration.test.ts +167 -0
  100. package/templates/apps/api/src/__tests__/server.test.ts +471 -0
  101. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +45 -0
  102. package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +10 -1
  103. package/templates/apps/api/src/config/index.ts +62 -0
  104. package/templates/apps/api/src/config/modules.ts +7 -3
  105. package/templates/apps/api/src/lib/__mocks__/prisma.ts +241 -2
  106. package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +143 -0
  107. package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +23 -0
  108. package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +125 -0
  109. package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +20 -0
  110. package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +317 -0
  111. package/templates/apps/api/src/lib/__tests__/site.test.ts +238 -0
  112. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +75 -0
  113. package/templates/apps/api/src/lib/auth-email-budget.ts +160 -0
  114. package/templates/apps/api/src/lib/content-adapter.ts +57 -31
  115. package/templates/apps/api/src/lib/deliverable-asset-store.ts +76 -0
  116. package/templates/apps/api/src/lib/error-serializer.ts +23 -3
  117. package/templates/apps/api/src/lib/outbound-http.ts +7 -2
  118. package/templates/apps/api/src/lib/require-commerce-claim.ts +250 -0
  119. package/templates/apps/api/src/lib/site.ts +208 -9
  120. package/templates/apps/api/src/lib/stripe.ts +66 -0
  121. package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +521 -0
  122. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +150 -0
  123. package/templates/apps/api/src/middleware/default-tenant.ts +136 -0
  124. package/templates/apps/api/src/middleware/rate-limit.ts +7 -1
  125. package/templates/apps/api/src/openapi/spec.ts +543 -2
  126. package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +48 -0
  127. package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +309 -0
  128. package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +123 -0
  129. package/templates/apps/api/src/routes/auth/transactional-email.ts +273 -0
  130. package/templates/apps/api/src/routes/auth/user.ts +61 -0
  131. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +434 -0
  132. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +888 -0
  133. package/templates/apps/api/src/routes/checkout/index.ts +34 -0
  134. package/templates/apps/api/src/routes/checkout/session.ts +139 -0
  135. package/templates/apps/api/src/routes/checkout/sessions.ts +197 -0
  136. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +554 -0
  137. package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +108 -0
  138. package/templates/apps/api/src/routes/content/__tests__/search.test.ts +450 -0
  139. package/templates/apps/api/src/routes/content/get-page.ts +94 -12
  140. package/templates/apps/api/src/routes/content/index.ts +13 -73
  141. package/templates/apps/api/src/routes/content/list-pages.ts +15 -3
  142. package/templates/apps/api/src/routes/content/rate-limit.ts +15 -6
  143. package/templates/apps/api/src/routes/content/search.ts +129 -0
  144. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +447 -31
  145. package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +171 -0
  146. package/templates/apps/api/src/routes/leads/index.ts +100 -13
  147. package/templates/apps/api/src/routes/leads/list.ts +104 -0
  148. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +51 -0
  149. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +351 -0
  150. package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +51 -0
  151. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -2
  152. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +4 -1
  153. package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +58 -0
  154. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +107 -6
  155. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +87 -2
  156. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +51 -0
  157. package/templates/apps/api/src/routes/project-listings/start.ts +5 -1
  158. package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +686 -0
  159. package/templates/apps/api/src/routes/requests/confirm.ts +601 -0
  160. package/templates/apps/api/src/routes/requests/index.ts +54 -0
  161. package/templates/apps/api/src/routes/webhooks/README.md +265 -0
  162. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +749 -0
  163. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +330 -0
  164. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +262 -0
  165. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +285 -0
  166. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +213 -0
  167. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +301 -0
  168. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +648 -0
  169. package/templates/apps/api/src/routes/webhooks/index.ts +89 -0
  170. package/templates/apps/api/src/routes/webhooks/stripe.ts +1399 -0
  171. package/templates/apps/api/src/server.ts +521 -55
  172. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +39 -0
  173. package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +217 -40
  174. package/templates/apps/api/src/services/__tests__/audit-log.test.ts +620 -0
  175. package/templates/apps/api/src/services/__tests__/consent.test.ts +2 -0
  176. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +300 -5
  177. package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +20 -0
  178. package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +368 -0
  179. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +408 -0
  180. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +160 -0
  181. package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +329 -0
  182. package/templates/apps/api/src/services/__tests__/listing-config.test.ts +166 -0
  183. package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +154 -0
  184. package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +137 -0
  185. package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +444 -0
  186. package/templates/apps/api/src/services/__tests__/revalidation.test.ts +238 -0
  187. package/templates/apps/api/src/services/__tests__/verified-email.test.ts +78 -0
  188. package/templates/apps/api/src/services/abandon-email.ts +8 -2
  189. package/templates/apps/api/src/services/aeo-score.ts +49 -12
  190. package/templates/apps/api/src/services/audit-log.ts +668 -0
  191. package/templates/apps/api/src/services/consent.ts +18 -0
  192. package/templates/apps/api/src/services/default-tenant.ts +300 -0
  193. package/templates/apps/api/src/services/deliverable-fulfillment.ts +141 -13
  194. package/templates/apps/api/src/services/dunning-policy.ts +294 -0
  195. package/templates/apps/api/src/services/entitlements.ts +80 -0
  196. package/templates/apps/api/src/services/lead-promotion.ts +160 -0
  197. package/templates/apps/api/src/services/lead-routing.ts +161 -2
  198. package/templates/apps/api/src/services/leads-inbox.ts +284 -0
  199. package/templates/apps/api/src/services/listing-config.ts +27 -9
  200. package/templates/apps/api/src/services/owner-workspaces.ts +132 -0
  201. package/templates/apps/api/src/services/project-listings.ts +201 -6
  202. package/templates/apps/api/src/services/public-site-resolver.ts +106 -0
  203. package/templates/apps/api/src/services/request-confirm.ts +646 -0
  204. package/templates/apps/api/src/services/revalidation.ts +173 -0
  205. package/templates/apps/api/src/services/verified-email.ts +71 -0
  206. package/templates/apps/api/src/vercel-handler.ts +75 -35
  207. package/templates/apps/mcp/.env.example +17 -0
  208. package/templates/apps/mcp/CLAUDE.md +58 -25
  209. package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +6 -0
  210. package/templates/apps/mcp/__tests__/audit.test.ts +137 -8
  211. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +79 -36
  212. package/templates/apps/mcp/__tests__/get_offerings.test.ts +111 -2
  213. package/templates/apps/mcp/__tests__/get_pricing.test.ts +82 -2
  214. package/templates/apps/mcp/__tests__/http-transport.test.ts +541 -2
  215. package/templates/apps/mcp/__tests__/index.test.ts +39 -0
  216. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +143 -0
  217. package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +111 -0
  218. package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +65 -0
  219. package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +188 -0
  220. package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +165 -0
  221. package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +124 -0
  222. package/templates/apps/mcp/__tests__/list_pages.test.ts +116 -0
  223. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +7 -1
  224. package/templates/apps/mcp/__tests__/parity.test.ts +12 -9
  225. package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +89 -22
  226. package/templates/apps/mcp/__tests__/read_content.test.ts +322 -1
  227. package/templates/apps/mcp/__tests__/search_content.test.ts +105 -2
  228. package/templates/apps/mcp/__tests__/server.test.ts +67 -14
  229. package/templates/apps/mcp/__tests__/start_purchase.test.ts +252 -0
  230. package/templates/apps/mcp/__tests__/tools/{scaffold.test.ts → create_project.test.ts} +23 -23
  231. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +300 -123
  232. package/templates/apps/mcp/eslint.config.mjs +69 -0
  233. package/templates/apps/mcp/jest.config.cjs +13 -0
  234. package/templates/apps/mcp/package.json +8 -2
  235. package/templates/apps/mcp/src/abuse/index.ts +159 -24
  236. package/templates/apps/mcp/src/config/modules.ts +10 -5
  237. package/templates/apps/mcp/src/http-transport.ts +102 -29
  238. package/templates/apps/mcp/src/index.ts +73 -11
  239. package/templates/apps/mcp/src/lib/audit-single-page.ts +90 -3
  240. package/templates/apps/mcp/src/lib/commerce-claim.ts +94 -0
  241. package/templates/apps/mcp/src/lib/content-adapter.ts +113 -0
  242. package/templates/apps/mcp/src/lib/empty-content-adapter.ts +139 -0
  243. package/templates/apps/mcp/src/lib/prisma.ts +39 -0
  244. package/templates/apps/mcp/src/lib/public-workspace.ts +133 -0
  245. package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +59 -0
  246. package/templates/apps/mcp/src/server.ts +61 -37
  247. package/templates/apps/mcp/src/tools/audit.ts +26 -2
  248. package/templates/apps/mcp/src/tools/{scaffold.ts → create_project.ts} +3 -3
  249. package/templates/apps/mcp/src/tools/get_business_hours.ts +4 -3
  250. package/templates/apps/mcp/src/tools/get_faq.ts +4 -3
  251. package/templates/apps/mcp/src/tools/get_locations.ts +5 -3
  252. package/templates/apps/mcp/src/tools/get_offerings.ts +16 -4
  253. package/templates/apps/mcp/src/tools/get_pricing.ts +25 -6
  254. package/templates/apps/mcp/src/tools/get_site_info.ts +4 -3
  255. package/templates/apps/mcp/src/tools/index.ts +9 -7
  256. package/templates/apps/mcp/src/tools/list_pages.ts +4 -3
  257. package/templates/apps/mcp/src/tools/purchase.ts +128 -45
  258. package/templates/apps/mcp/src/tools/read_content.ts +20 -6
  259. package/templates/apps/mcp/src/tools/search_content.ts +11 -4
  260. package/templates/apps/web/.env.example +74 -8
  261. package/templates/apps/web/CLAUDE.md +46 -0
  262. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +18 -2
  263. package/templates/apps/web/app/.well-known/mcp.json/route.ts +11 -3
  264. package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +282 -0
  265. package/templates/apps/web/app/[locale]/(auth)/__tests__/password-reset.test.tsx +515 -0
  266. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +89 -0
  267. package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +109 -0
  268. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +92 -0
  269. package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +135 -0
  270. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +109 -0
  271. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/page.test.tsx +48 -2
  272. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/page.tsx +20 -6
  273. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +125 -0
  274. package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +96 -0
  275. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +101 -0
  276. package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +102 -0
  277. package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +87 -0
  278. package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +20 -10
  279. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +84 -0
  280. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +58 -0
  281. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +67 -14
  282. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +19 -2
  283. package/templates/apps/web/app/[locale]/(authed)/leads/__tests__/page.test.tsx +181 -0
  284. package/templates/apps/web/app/[locale]/(authed)/leads/components/LeadsTable.tsx +137 -0
  285. package/templates/apps/web/app/[locale]/(authed)/leads/page.tsx +111 -0
  286. package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +1 -1
  287. package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +111 -0
  288. package/templates/apps/web/app/[locale]/[slug]/page.tsx +65 -0
  289. package/templates/apps/web/app/[locale]/cancellation/page.tsx +44 -0
  290. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +1 -1
  291. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +1 -1
  292. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +6 -5
  293. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +1 -1
  294. package/templates/apps/web/app/[locale]/layout.tsx +308 -65
  295. package/templates/apps/web/app/[locale]/purchase/__tests__/cancel.test.tsx +162 -0
  296. package/templates/apps/web/app/[locale]/purchase/__tests__/module-scope.test.ts +68 -0
  297. package/templates/apps/web/app/[locale]/purchase/__tests__/success.test.tsx +361 -0
  298. package/templates/apps/web/app/[locale]/purchase/cancel/page.tsx +115 -0
  299. package/templates/apps/web/app/[locale]/purchase/success/page.tsx +307 -0
  300. package/templates/apps/web/app/[locale]/returns/page.tsx +44 -0
  301. package/templates/apps/web/app/[locale]/shipping/page.tsx +44 -0
  302. package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +670 -0
  303. package/templates/apps/web/app/api/revalidate/route.ts +194 -0
  304. package/templates/apps/web/auth.ts +407 -8
  305. package/templates/apps/web/components/google-analytics.tsx +18 -1
  306. package/templates/apps/web/eslint.config.mjs +69 -0
  307. package/templates/apps/web/jest.config.cjs +15 -19
  308. package/templates/apps/web/lib/__tests__/auth-email.test.ts +214 -0
  309. package/templates/apps/web/lib/__tests__/auth-routes.test.ts +74 -0
  310. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +72 -0
  311. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +50 -6
  312. package/templates/apps/web/lib/auth-email.ts +318 -0
  313. package/templates/apps/web/lib/auth-env.ts +85 -0
  314. package/templates/apps/web/lib/auth-routes.ts +55 -0
  315. package/templates/apps/web/lib/meeting-slugs.ts +53 -0
  316. package/templates/apps/web/lib/nav-config.ts +27 -7
  317. package/templates/apps/web/lib/server-api.ts +1 -1
  318. package/templates/apps/web/lib/site-chrome.ts +29 -0
  319. package/templates/apps/web/lib/telemetry-scrub.ts +50 -0
  320. package/templates/apps/web/messages/en.json +216 -2
  321. package/templates/apps/web/next.config.mjs +20 -0
  322. package/templates/apps/web/package.json +8 -3
  323. package/templates/apps/web/sentry.client.config.ts +131 -0
  324. package/templates/apps/web/sentry.edge.config.ts +72 -0
  325. package/templates/apps/web/sentry.server.config.ts +72 -0
  326. package/templates/apps/web/src/lib/commerce-claim.ts +33 -0
  327. package/templates/apps/web/src/lib/commerce-sections.ts +61 -0
  328. package/templates/apps/web/src/lib/routes.ts +13 -18
  329. package/templates/brand_assets/README.md +24 -0
  330. package/templates/content/en/AboutPage/about.mdx +160 -15
  331. package/templates/content/en/CancellationPolicyPage/cancellation.mdx +108 -0
  332. package/templates/content/en/ContactPage/contact.mdx +90 -13
  333. package/templates/content/en/FAQPage/faq.mdx +62 -22
  334. package/templates/content/en/HomePage/home.mdx +244 -20
  335. package/templates/content/en/LandingPage/blog.mdx +83 -0
  336. package/templates/content/en/LandingPage/features.mdx +103 -0
  337. package/templates/content/en/LandingPage/how-it-works.mdx +143 -0
  338. package/templates/content/en/ReturnsPolicyPage/returns.mdx +108 -0
  339. package/templates/content/en/ShippingPolicyPage/shipping.mdx +108 -0
  340. package/templates/database/CHANGELOG.md +149 -0
  341. package/templates/database/README.md +10 -3
  342. package/templates/database/__tests__/appointment.db.test.ts +106 -0
  343. package/templates/database/__tests__/artifact-content-store.db.test.ts +316 -0
  344. package/templates/database/__tests__/composite-fence.db.test.ts +206 -0
  345. package/templates/database/__tests__/fence-fan-out.db.test.ts +711 -0
  346. package/templates/database/__tests__/fixtures/appointment-create-probe.ts +79 -0
  347. package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +100 -0
  348. package/templates/database/__tests__/flow-workspace-key.db.test.ts +263 -0
  349. package/templates/database/__tests__/prefixed-ids.db.test.ts +85 -9
  350. package/templates/database/__tests__/project-listing-consent-at.db.test.ts +111 -0
  351. package/templates/database/__tests__/seed.test.ts +1 -1
  352. package/templates/database/__tests__/tenancy.test.ts +141 -0
  353. package/templates/database/config/schema.prisma +79 -2
  354. package/templates/database/content/schema.prisma +87 -11
  355. package/templates/database/eslint.config.mjs +69 -0
  356. package/templates/database/inbox/schema.prisma +452 -9
  357. package/templates/database/jest.config.cjs +5 -3
  358. package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +15 -0
  359. package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +158 -0
  360. package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +40 -0
  361. package/templates/database/migrations/20260918120000_add_appointment/migration.sql +67 -0
  362. package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +70 -0
  363. package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +181 -0
  364. package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +172 -0
  365. package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +41 -0
  366. package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +185 -0
  367. package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +70 -0
  368. package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +57 -0
  369. package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +68 -0
  370. package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +1067 -0
  371. package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +210 -0
  372. package/templates/database/package.json +7 -3
  373. package/templates/database/seed.ts +1 -1
  374. package/templates/database/tenancy.ts +86 -1
  375. package/templates/database/tsconfig.json +1 -0
  376. package/templates/package.json +1 -14
  377. package/versions.json +52 -0
  378. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -103
  379. package/templates/apps/api/src/routes/content/__tests__/create.test.ts +0 -253
  380. package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +0 -215
  381. package/templates/apps/api/src/routes/content/__tests__/update.test.ts +0 -266
  382. package/templates/apps/api/src/routes/content/create.ts +0 -115
  383. package/templates/apps/api/src/routes/content/delete.ts +0 -82
  384. package/templates/apps/api/src/routes/content/update.ts +0 -112
  385. package/templates/apps/mcp/__tests__/create_page.test.ts +0 -202
  386. package/templates/apps/mcp/__tests__/delete_page.test.ts +0 -177
  387. package/templates/apps/mcp/__tests__/update_page.test.ts +0 -196
  388. package/templates/apps/mcp/src/tools/create_page.ts +0 -96
  389. package/templates/apps/mcp/src/tools/delete_page.ts +0 -74
  390. package/templates/apps/mcp/src/tools/update_page.ts +0 -92
  391. package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +0 -123
  392. /package/site-templates/{marketing-site-free → _base}/content/en/PrivacyPage/privacy.mdx +0 -0
  393. /package/site-templates/{marketing-site-free → _base}/content/en/TermsPage/terms.mdx +0 -0
  394. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/next-path.test.ts +0 -0
  395. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/next-path.ts +0 -0
  396. /package/templates/apps/web/{src/components → components}/SiteNav.tsx +0 -0
@@ -21,8 +21,8 @@
21
21
  * `architecture.yaml` template files — a fixed template IS the drift this
22
22
  * removes.
23
23
  */
24
- import { readFileSync, readdirSync } from "node:fs";
25
- import { dirname, join, sep } from "node:path";
24
+ import { sep } from "node:path";
25
+ import { importClosure } from "./lib/import-graph.js";
26
26
  import { PAID_MODULES } from "./lib/module-tier.js";
27
27
  import { RECOGNIZED_MODULES } from "./parse-args.js";
28
28
  /**
@@ -124,6 +124,31 @@ export const ARCHITECTURE_BASE = {
124
124
  },
125
125
  ],
126
126
  };
127
+ /**
128
+ * The workspace asset store (#5854), CO-OWNED by the `workspace` and
129
+ * `multi-tenant` slices.
130
+ *
131
+ * `server.ts` registers both of its routes under
132
+ * `isModuleEnabled("workspace") || isModuleEnabled("multi-tenant")`, and a file
133
+ * behind a two-module disjunction must be deliverable by BOTH bundles — or
134
+ * whichever module lacks it throws ERR_MODULE_NOT_FOUND during `build()`. One
135
+ * array spread into both `ownedFiles` lists makes the two copies incapable of
136
+ * drifting, which matters because a drift only surfaces in a scaffolded
137
+ * customer repo rather than here.
138
+ *
139
+ * `routes/assets/index.ts` is listed although it registers nothing: the
140
+ * template-exclusion closure runs over IMPORTERS, so a shared-constants module
141
+ * that is imported BY the routes and imports neither of them is never reached
142
+ * from them, and an unlisted one would ship into the keyless Free starter.
143
+ */
144
+ const ASSET_STORE_FILES = [
145
+ "apps/api/src/routes/assets/index.ts",
146
+ "apps/api/src/routes/assets/upload.ts",
147
+ "apps/api/src/routes/assets/read.ts",
148
+ // The manage routes — list, PATCH, DELETE (#6093 D16).
149
+ "apps/api/src/routes/assets/manage.ts",
150
+ "apps/api/src/lib/asset-store.ts",
151
+ ];
127
152
  /**
128
153
  * The per-module slices. Authored for every `RECOGNIZED_MODULES` value; only
129
154
  * `marketing` (the default) is validated end-to-end against a golden.
@@ -246,6 +271,11 @@ export const MODULE_SLICES = {
246
271
  // The Fumadocs source pair — both whole, both loaders (D3).
247
272
  "apps/web/source.config.ts",
248
273
  "apps/web/lib/source.ts",
274
+ // The runtime source `lib/source.ts` resolves to under
275
+ // CONTENT_ADAPTER=database (#5878). Listed explicitly: the exclusion
276
+ // closure grows over DEPENDENTS, not dependencies, so being imported by
277
+ // `lib/source.ts` does not pull this file into the bundle by itself.
278
+ "apps/web/lib/runtime-source.ts",
249
279
  // The route adapters and the jest stub for the codegen output.
250
280
  "apps/web/lib/blog-list-data.ts",
251
281
  "apps/web/lib/blog-post-data.ts",
@@ -253,39 +283,46 @@ export const MODULE_SLICES = {
253
283
  // The module's own /llms.txt variant; the bundle installs it onto
254
284
  // app/llms.txt/route.ts (#5365 D9, install-path mapping owned by #5373).
255
285
  "apps/web/lib/slices/docs-blog/llms-route.ts",
256
- // The two shells and the six components, each off its public barrel and
257
- // reachable only from the slice barrel that leaves with them (D-45, D5).
258
- "packages/templates/src/shells/BlogShell.tsx",
259
- "packages/templates/src/shells/DocsShell.tsx",
260
- "packages/templates/src/shells/slices/docs-blog.ts",
261
- "packages/ui/src/blocks/DocsActionBar.tsx",
262
- "packages/ui/src/blocks/DocsArticleHeader.tsx",
263
- "packages/ui/src/blocks/DocsFeedback.tsx",
264
- "packages/ui/src/blocks/DocsSearch.tsx",
265
- "packages/ui/src/blocks/DocsTocButton.tsx",
266
- "packages/ui/src/chrome/DocsSidebar.tsx",
267
- "packages/ui/src/chrome/slices/docs-blog.ts",
286
+ // The two shells and the six components moved to packages-paid/ui-docs-blog
287
+ // (#5581) — that package is a physical part of the tree, not a listed
288
+ // ownedFiles entry, and is installed via this slice's `patches` block below.
268
289
  ],
269
290
  // pageContributions (spec 5478; parent § D6 shape 1) — the two route
270
291
  // surfaces this module mounts, each with the seed MDX it renders. The
271
- // three MDX files are exactly the three on the tree and cannot come from
292
+ // nine MDX files are exactly the nine on the tree and cannot come from
272
293
  // the `templates/` snapshot: `apps/web/content` is in build-templates.mjs's
273
294
  // EXCLUDE_TEMPLATE_DIRS and `template-overlay/` carries no blog or docs
274
295
  // stub. They ride this module's own bundle, consistent with the route
275
- // files above — STAGING them into it is #5481's change to
276
- // build-module-bundles.mjs, since `collectModuleFiles` unions `ownedFiles`
277
- // with `installMap` keys and nothing else. This unit only DECLARES them.
296
+ // files above: `collectModuleFiles` in build-module-bundles.mjs stages
297
+ // every `pageContributions[].content` entry alongside `ownedFiles` and
298
+ // the `installMap` keys, and `copyTree` lands each at this same path.
299
+ //
300
+ // #5618 D6.2 — the module OWNS its sample content (Rule B): the blog
301
+ // listing, four sample posts and the three docs pages moved here out of
302
+ // `site-templates/marketing-site/`, so any template requiring docs-blog
303
+ // gets the samples and a keyed `--modules docs-blog` scaffold with no
304
+ // template lands them too. `launching-ailk.mdx` stays the module's fifth
305
+ // seed post; the Pro template maps its own post 05 ONTO that path as a
306
+ // copy override (D6.2a — spec § 7 finding 9 carries the seed's fate).
278
307
  pageContributions: [
279
308
  {
280
309
  route: "/blog",
281
310
  content: [
282
311
  "apps/web/content/en/blog/index.mdx",
312
+ "apps/web/content/en/blog/01-8-things-that-prove-coding-skills-are-directly-linked-to-successfully-assembling-ikea-furniture.mdx",
313
+ "apps/web/content/en/blog/02-5-ways-to-guarantee-your-startup-goes-nowhere-fast.mdx",
314
+ "apps/web/content/en/blog/03-10-ideas-that-will-guarantee-1000x-growth.mdx",
315
+ "apps/web/content/en/blog/04-7-keys-to-saas-success-the-elite-doesnt-want-you-to-know.mdx",
283
316
  "apps/web/content/en/blog/launching-ailk.mdx",
284
317
  ],
285
318
  },
286
319
  {
287
320
  route: "/docs",
288
- content: ["apps/web/content/en/docs/getting-started.mdx"],
321
+ content: [
322
+ "apps/web/content/en/docs/getting-started.mdx",
323
+ "apps/web/content/en/docs/configure.mdx",
324
+ "apps/web/content/en/docs/deploy.mdx",
325
+ ],
289
326
  },
290
327
  ],
291
328
  // patches (spec 5418 § 3 D1, Group G) — every Group G row that edits a
@@ -330,6 +367,8 @@ export const MODULE_SLICES = {
330
367
  "fumadocs-core": "^16.10.4",
331
368
  "fumadocs-mdx": "^15.0.12",
332
369
  "@working-theory/fumadocs-schema": "workspace:*",
370
+ // #5581 — the docs-blog module's paid component package (D5).
371
+ "@working-theory/ui-docs-blog": "workspace:*",
333
372
  },
334
373
  },
335
374
  {
@@ -396,13 +435,13 @@ export const MODULE_SLICES = {
396
435
  //
397
436
  // The FEAT-042 Organization → Workspace → Membership tenancy layer:
398
437
  // schema (auth/content namespaces) + workspace-scoped API (apps/api) +
399
- // the authed workspace app surface (apps/workspace). Single-tenant sites
438
+ // the authed Admin app surface (apps/admin). Single-tenant sites
400
439
  // (module absent) ship none of this — same base apps/web, fixed scope.
401
440
  "multi-tenant": {
402
441
  apps: {
403
- workspace: {
404
- path: "apps/workspace",
405
- description: "Next.js — authed workspace app (Organization → Workspace → Membership). Hosts the organization-plugin Better Auth handlers; imports the database only via lib/prisma-for-auth.ts (auth-adapter exception); all domain data via apps/api's workspace-scoped routes over HTTP.",
442
+ admin: {
443
+ path: "apps/admin",
444
+ description: "Next.js — authed Admin app (Organization → Workspace → Membership). Hosts the organization-plugin Better Auth handlers; imports the database only via lib/prisma-for-auth.ts (auth-adapter exception); all domain data via apps/api's workspace-scoped routes over HTTP.",
406
445
  may_import_from: [
407
446
  "database",
408
447
  "packages/templates",
@@ -411,10 +450,10 @@ export const MODULE_SLICES = {
411
450
  "packages/validation",
412
451
  // The `workspace` module's shared settings components (#5371 req. 6)
413
452
  // — the scaffolded-site twin of the root architecture.yaml grant on
414
- // `apps_paid.workspace`. Pro Plus inherits Pro's module set, so a
453
+ // `apps_paid.admin`. Pro Plus inherits Pro's module set, so a
415
454
  // saas-product scaffold carries both modules and this app composes
416
455
  // the identical panels apps/web does, with no forked copy.
417
- "packages/workspace-ui",
456
+ "packages/admin-ui",
418
457
  ],
419
458
  may_not_import_from: [
420
459
  "apps/api",
@@ -486,12 +525,12 @@ export const MODULE_SLICES = {
486
525
  description: "Stripe SDK wrapper + webhook signature verification",
487
526
  may_import_from: ["packages/validation"],
488
527
  },
489
- // apps/workspace presentation deps. templates is net-new here; theme/ui/
528
+ // apps/admin presentation deps. templates is net-new here; theme/ui/
490
529
  // validation duplicate the marketing slice's definitions VERBATIM so the
491
530
  // Object.assign merge in deriveArchitecture is order-independent (a
492
531
  // multi-tenant-only selection stays self-consistent).
493
532
  templates: {
494
- description: "Shell/Chrome composition — AppShell (tier-5 shell); WorkspaceShell + ArtifactEditorShell ship inside this module itself, not the public package (D-45)",
533
+ description: "Shell/Chrome composition — AppShell (tier-5 shell); AdminShell + ArtifactEditorShell ship inside this module itself, not the public package (D-45)",
495
534
  may_import_from: ["packages/theme", "packages/ui", "packages/validation"],
496
535
  },
497
536
  theme: {
@@ -553,11 +592,12 @@ export const MODULE_SLICES = {
553
592
  // ownedFiles (S1, #5088). Verified against the current filesystem and
554
593
  // server.ts's module-conditional registration (isModuleEnabled('multi-tenant')).
555
594
  //
556
- // apps-paid/workspace/** is a single glob — the whole app (43 files as of
595
+ // apps-paid/admin/** is a single glob — the whole app (43 files as of
557
596
  // this pass, excluding __tests__/node_modules), just moved here from
558
- // apps/workspace by a parallel workstream on this same branch. Unlike
559
- // every other entry below, this one is intentionally a glob, not an
560
- // enumeration.
597
+ // apps/workspace by a parallel workstream on this same branch. Later
598
+ // renamed in place from its former Workspace-prefixed directory name by
599
+ // #5637, the Admin-vocabulary rename. Unlike every other entry below,
600
+ // this one is intentionally a glob, not an enumeration.
561
601
  //
562
602
  // apps/api/src/routes/project-listings/ is MIXED (free anonymous surface
563
603
  // + tenant dashboard sharing one directory) — only the tenant-guarded
@@ -646,6 +686,11 @@ export const MODULE_SLICES = {
646
686
  "apps/api/src/routes/workspaces/index.ts",
647
687
  "apps/api/src/routes/workspaces/sessions.ts",
648
688
  "apps/api/src/routes/workspaces/config.ts",
689
+ // Co-owned with `workspace` (#5853): the content-write scope is gated on
690
+ // content-write AND (workspace || multi-tenant) and this file imports
691
+ // lib/tenant-db.ts, so each tenancy slice must deliver it.
692
+ "apps/api/src/routes/workspaces/content.ts",
693
+ ...ASSET_STORE_FILES,
649
694
  "apps/api/src/routes/billing/index.ts",
650
695
  "apps/api/src/routes/billing/portal.ts",
651
696
  "apps/api/src/routes/billing/read.ts",
@@ -662,8 +707,42 @@ export const MODULE_SLICES = {
662
707
  "apps/api/src/middleware/tenant.ts",
663
708
  "apps/api/src/services/tenant-context.ts",
664
709
  "apps/api/src/services/waitlist-dashboard.ts",
710
+ // #6258 — the file waitlist-dashboard.ts imports for the pinned
711
+ // platform-operator read scope. It rides BOTH slices for the same
712
+ // reason its importer does: an unlisted import target breaks a
713
+ // signal-collectors-without-multi-tenant scaffold at build() time.
714
+ "apps/api/src/services/waitlist-operator-reads.ts",
715
+ // #6204/#6280 CI fix — `listUnhandledMessages`/`markMessageHandled`
716
+ // (the "Needs-you" message read/write over `tenantDb(tenant).leads`)
717
+ // moved here off `services/leads-inbox.ts`, a BASE-tier file every
718
+ // scaffold ships. Importing `lib/tenant-db.ts` from that base file
719
+ // walked `paidModuleTemplateExclusions()`'s companion closure
720
+ // (`lib/import-graph.ts`) BACKWARD into `leads-inbox.ts` itself and
721
+ // then into every one of `apps/api/src/{server,cli,index,
722
+ // vercel-handler}.ts` — all of which reach it through
723
+ // `routes/leads/list.ts` — dropping them from a keyless scaffold
724
+ // (`__tests__/paid-module-exclusions.test.ts`'s `MUST_SHIP` guard,
725
+ // #5678's cascade shape recurring a third time).
726
+ //
727
+ // #6216 — now reached: `routes/workspaces/messages.ts` (below)
728
+ // consumes `getMessage` alongside the two functions above. That route
729
+ // registers under `isModuleEnabled("workspace") ||
730
+ // isModuleEnabled("multi-tenant")` — the SAME gate
731
+ // `workspaces/general.ts`/`members.ts` register under, NOT the
732
+ // multi-tenant-only family `workspaces/meetings.ts`/`config.ts` sit
733
+ // in — so this file and `routes/workspaces/messages.ts` are now
734
+ // CO-OWNED with `workspace` too (see that slice's entry below), the
735
+ // asset-store precedent: a file behind an OR gate must be
736
+ // deliverable by BOTH bundles.
737
+ "apps/api/src/services/inbox-messages.ts",
738
+ "apps/api/src/routes/workspaces/messages.ts",
665
739
  "apps/api/src/lib/tenant-db.ts",
666
740
  "apps/api/src/lib/ws-token.ts",
741
+ // #6157 D16 — the magnet Admin authoring surface, co-owned with
742
+ // `signal-collectors` below for the SAME reason #5677 co-owns the
743
+ // review surface under both: its scope is registered under the
744
+ // widened `multi-tenant || signal-collectors` gate.
745
+ "apps/api/src/routes/magnets/index.ts",
667
746
  "apps/web/app/[locale]/(authed)/waitlist/page.tsx",
668
747
  "apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx",
669
748
  "apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx",
@@ -674,15 +753,36 @@ export const MODULE_SLICES = {
674
753
  "apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx",
675
754
  "apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx",
676
755
  "apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx",
677
- "apps-paid/workspace/**",
756
+ "apps-paid/admin/**",
678
757
  ],
758
+ // The whole-app glob above lands its files at their REPO-relative path
759
+ // unless something re-roots them (`installBundleTree`'s `copyTree`
760
+ // excludes only `installMap` KEYS from its verbatim copy), so without a
761
+ // rename rule a keyed install left a stray source-tree-relative
762
+ // directory in the scaffold instead of the working `apps/admin` app
763
+ // this slice's own `apps.admin.path` (and `paid-paths.ts`'s
764
+ // `introducedAppPaths()`) already declares (#5626 D3/D4). The fix lives
765
+ // here, on the module, so `--modules multi-tenant` alone is fixed too —
766
+ // never as a per-template workaround.
767
+ //
768
+ // `installRoot`, not a literal `installMap` (#5644 D1): a committed
769
+ // 58-entry literal map — one line per staged file's proprietary source
770
+ // path — put every one of those paths into this OSS-shipped file's
771
+ // SOURCE TEXT, the exact leak class `scripts/check-oss-strip.sh` exists
772
+ // to catch. `build-module-bundles.mjs`'s `deriveGlobInstallMap` derives
773
+ // the equivalent per-file `installMap` at bundle-build time instead —
774
+ // one entry per file `expandEntry` yields for the glob entry above,
775
+ // byte-identical to the literal map it replaces — and the mock registry
776
+ // fixture calls the same derivation so it cannot drift from the
777
+ // producer's. See `installRoot`'s own doc on `ModuleSlice` above.
778
+ installRoot: "apps/admin/",
679
779
  },
680
780
  // ── workspace — Pro module: the single-workspace settings shell (D-27/D-49)
681
781
  //
682
- // `/workspace` in apps/web's `(authed)` segment — General (rename), Site
782
+ // `/admin` in apps/web's `(authed)` segment — General (rename), Site
683
783
  // (identity / SEO / theme, over the already-shipped apps/api config routes)
684
784
  // and Team (a read-only roster). The three panels live in the shared
685
- // `packages/workspace-ui` package so the multi-tenant workspace app (the
785
+ // `packages/admin-ui` package so the multi-tenant Admin app (the
686
786
  // Pro Plus app) composes the identical components rather than a fork.
687
787
  //
688
788
  // No `apps` / `namespaces` / `forbiddenPatterns` block: this module
@@ -692,14 +792,14 @@ export const MODULE_SLICES = {
692
792
  // A PERMISSION, not a physical slice (#5371 D1a) — the identical shape
693
793
  // docs-blog uses for `packages/fumadocs-schema`. `packages/` is never in
694
794
  // SNAPSHOT_DIRS, so the package reaches a buyer as an npm/bundle
695
- // dependency under `node_modules/`, never as a `packages/workspace-ui`
795
+ // dependency under `node_modules/`, never as a `packages/admin-ui`
696
796
  // source sibling; the `patches` entry below is what puts it in the
697
797
  // buyer's apps/web/package.json.
698
- webImports: ["packages/workspace-ui"],
798
+ webImports: ["packages/admin-ui"],
699
799
  packages: {
700
- "workspace-ui": {
701
- description: "Shared workspace-settings components — the settings shell plus the General / Site / Team panels, presentational only (data + save callbacks as props)",
702
- // packages/templates is required because WorkspaceSettingsShell
800
+ "admin-ui": {
801
+ description: "Shared Admin-settings components — the settings shell plus the General / Site / Team panels, presentational only (data + save callbacks as props)",
802
+ // packages/templates is required because AdminSettingsShell
703
803
  // composes AppShell from @working-theory/templates/shells — the same
704
804
  // composition apps/web's own account page uses.
705
805
  may_import_from: [
@@ -713,7 +813,7 @@ export const MODULE_SLICES = {
713
813
  // SOURCE files only, and only the ones this module physically owns: the
714
814
  // three apps/web route files and the two apps/api route files.
715
815
  //
716
- // `packages/workspace-ui/**` is deliberately NOT listed (D1a). It is a
816
+ // `packages/admin-ui/**` is deliberately NOT listed (D1a). It is a
717
817
  // whole package that is paid end to end, whose strip disposition is
718
818
  // carried by architecture.yaml's `commercial_tier_moves_to` field (its
719
819
  // future paid-tree home) rather than by a per-file inventory —
@@ -733,11 +833,21 @@ export const MODULE_SLICES = {
733
833
  // Test companions (`__tests__/`) are never listed — the SOURCE-files-only
734
834
  // rule above.
735
835
  ownedFiles: [
736
- "apps/web/app/[locale]/(authed)/workspace/page.tsx",
737
- "apps/web/app/[locale]/(authed)/workspace/site/page.tsx",
738
- "apps/web/app/[locale]/(authed)/workspace/team/page.tsx",
836
+ "apps/web/app/[locale]/(authed)/admin/page.tsx",
837
+ "apps/web/app/[locale]/(authed)/admin/site/page.tsx",
838
+ "apps/web/app/[locale]/(authed)/admin/team/page.tsx",
739
839
  "apps/api/src/routes/workspaces/general.ts",
740
840
  "apps/api/src/routes/workspaces/members.ts",
841
+ // Co-owned with `multi-tenant` (#6216) — see that slice's entry: the
842
+ // route registers under the SAME workspace||multi-tenant gate as the
843
+ // two lines above, and its service function imports `lib/tenant-db.ts`
844
+ // (a multi-tenant-owned file), so both must be deliverable by either
845
+ // bundle alone.
846
+ "apps/api/src/routes/workspaces/messages.ts",
847
+ "apps/api/src/services/inbox-messages.ts",
848
+ // Co-owned with `multi-tenant` (#5853) — see that slice's entry.
849
+ "apps/api/src/routes/workspaces/content.ts",
850
+ ...ASSET_STORE_FILES,
741
851
  ],
742
852
  // patches — the dependency add that delivers the shared package to the
743
853
  // buyer's apps/web, mirroring docs-blog's own fumadocs-schema merge-keys
@@ -754,7 +864,7 @@ export const MODULE_SLICES = {
754
864
  op: "merge-keys",
755
865
  path: ["dependencies"],
756
866
  entries: {
757
- "@working-theory/workspace-ui": "workspace:*",
867
+ "@working-theory/admin-ui": "workspace:*",
758
868
  },
759
869
  },
760
870
  ],
@@ -770,8 +880,10 @@ export const MODULE_SLICES = {
770
880
  // the ONLY holder of the scheduling credential.
771
881
  // api apps/api's own /v1/schedule/availability + /v1/schedule/book.
772
882
  // presentation the `booker` section (packages/ui, no iframe) rendered by
773
- // the `schedule` route surface, plus the module-owned
774
- // confirmation page at /schedule/confirmed.
883
+ // the `meet` route surface, plus the per-booking-type page
884
+ // and the module-owned confirmation page at /meet/:slug and
885
+ // /meet/confirmed (renamed from the schedule/ directory by
886
+ // #5563).
775
887
  //
776
888
  // The four forbidden patterns below are the module's whole security
777
889
  // argument, restated as boundary rules rather than left in a comment: the
@@ -864,16 +976,19 @@ export const MODULE_SLICES = {
864
976
  // consumer — unlike packages/validation below, a strip tool CAN delete
865
977
  // exactly these files without touching anything another module needs.
866
978
  //
867
- // BOTH apps/web/app/[locale]/schedule pages are listed (#5182 Q9, disposed
868
- // 2026-09-11). confirmed/page.tsx says outright in its own doc comment that
869
- // "The module OWNS this page... a scaffold without the module has no such
870
- // route", and its sibling page.tsx is the SOLE production constructor of a
871
- // `booker` section: it builds the section literally in TypeScript
872
- // (page.tsx:60, `type: "booker"`) rather than reading it from content, and
873
- // its own header calls the route API-backed with "no content file that
874
- // could disagree with the calendar". The create-ailk scaffolder already
875
- // gates the whole `schedule` route surface (src/surfaces.ts), so the two
876
- // pages arrive and depart together.
979
+ // ALL THREE apps/web/app/[locale]/meet pages are listed (#5182 Q9, disposed
980
+ // 2026-09-11; directory renamed from schedule/ to meet/ by #5563).
981
+ // confirmed/page.tsx says outright in its own doc comment that "The module
982
+ // OWNS this page... a scaffold without the module has no such route", and
983
+ // its sibling page.tsx is the SOLE production constructor of a `booker`
984
+ // section: it builds the section literally in TypeScript (page.tsx:60,
985
+ // `type: "booker"`) rather than reading it from content, and its own
986
+ // header calls the route API-backed with "no content file that could
987
+ // disagree with the calendar". [slug]/page.tsx is the third — the
988
+ // per-booking-type page #5563 added, composing its calLink from the same
989
+ // configured owner rather than reading one of its own. The create-ailk
990
+ // scaffolder already gates the whole `meet` route surface (src/surfaces.ts),
991
+ // so all three pages arrive and depart together.
877
992
  //
878
993
  // packages/sections-registry/src/registry.ts (the `booker:` entry) and
879
994
  // packages/validation/src/schemas/page-sections.ts (BookerSectionDataSchema,
@@ -902,39 +1017,111 @@ export const MODULE_SLICES = {
902
1017
  // scheduling-specific. This is a judgment call, not covered by this
903
1018
  // card's explicit checklist: flagged for reviewer confirmation.
904
1019
  //
905
- // `packages/ui/src/sections/slices/scheduling.ts` (#5214) is the slice's own
906
- // registration entry point: it statically imports Booker and registers it as
907
- // the `booker` renderer, so it is the one file inside packages/ui that may
908
- // reach a scheduling component — because it strips alongside them. The three
909
- // files that path added on the OSS side — `SectionRenderer.tsx`,
910
- // `slice-renderers.ts` and `ModuleAbsent.tsx` — are deliberately NOT listed
911
- // here: they ship, and listing one would take the whole dispatch table with it.
1020
+ // The Booker components and the slice's registration entry point (#5214)
1021
+ // moved to packages-paid/ui-scheduling (#5581 U3); that package is a
1022
+ // physical part of the tree, not a listed ownedFiles entry, and reaches a
1023
+ // keyed scaffold through this slice's `patches` block below (D5). The
1024
+ // three files the old in-packages/ui path added on the OSS side —
1025
+ // `SectionRenderer.tsx`, `slice-renderers.ts` and `ModuleAbsent.tsx` — are
1026
+ // deliberately NOT listed here: they ship, and listing one would take the
1027
+ // whole dispatch table with it.
912
1028
  ownedFiles: [
913
1029
  "apps/api/src/routes/schedule/index.ts",
914
1030
  "apps/api/src/services/scheduling/index.ts",
915
1031
  "apps/api/src/services/scheduling/cal-provider.ts",
916
1032
  "apps/api/src/services/scheduling/types.ts",
1033
+ // #6182 (S9a) — the Cal adapter's own suite. Individually, never as a
1034
+ // glob (§ D12 above, property 1: a glob entry is a silent no-op, so
1035
+ // `__tests__/**` would ship no test at all to a keyed scaffold).
1036
+ "apps/api/src/services/scheduling/__tests__/cal-provider.test.ts",
917
1037
  "apps/mcp/src/tools/list_availability.ts",
918
1038
  "apps/mcp/src/tools/get_event_meta.ts",
919
1039
  "apps/mcp/src/tools/create_booking.ts",
920
- "packages/ui/src/sections/Booker.tsx",
921
- "packages/ui/src/sections/BookerIsland.tsx",
922
- "packages/ui/src/blocks/BookerPanel.tsx",
923
- "packages/ui/src/blocks/BookerSlotGrid.tsx",
924
- "packages/ui/src/blocks/BookerAttendeeFields.tsx",
925
- "packages/ui/src/blocks/BookerEventHeader.tsx",
926
- "packages/ui/src/blocks/BookerDateStrip.tsx",
927
- "packages/ui/src/sections/slices/scheduling.ts",
928
- "apps/web/app/[locale]/schedule/page.tsx",
929
- "apps/web/app/[locale]/schedule/confirmed/page.tsx",
1040
+ "apps/web/app/[locale]/meet/page.tsx",
1041
+ "apps/web/app/[locale]/meet/[slug]/page.tsx",
1042
+ "apps/web/app/[locale]/meet/confirmed/page.tsx",
1043
+ // #6085 (brief H7) — the six files the webhook receiver + the
1044
+ // tenant-guarded appointments read add. Individually, never as a
1045
+ // glob (§ D12 / this card's own comment above, property 1: a glob
1046
+ // entry is a silent no-op, so __tests__/** would ship no test at all
1047
+ // to a keyed scaffold). `apps/api/src/routes/webhooks/index.ts` is
1048
+ // NOT added — it is the mixed barrel that also registers the Stripe
1049
+ // receiver, the same "mixed barrel is never listed" precedent this
1050
+ // slice already records for `routes/project-listings/index.ts`.
1051
+ // `apps/api/src/routes/schedule/index.ts` is already listed above and
1052
+ // stays (it re-exports `listAppointmentsRoute` alongside the three
1053
+ // anonymous routes it already re-exports).
1054
+ "apps/api/src/routes/webhooks/calcom.ts",
1055
+ "apps/api/src/routes/webhooks/__tests__/calcom.test.ts",
1056
+ "apps/api/src/services/scheduling/appointments.ts",
1057
+ "apps/api/src/services/scheduling/__tests__/appointments.test.ts",
1058
+ "apps/api/src/routes/schedule/appointments.ts",
1059
+ "apps/api/src/routes/schedule/__tests__/appointments.test.ts",
1060
+ // #6086 (brief H7) — the SITE's appointments page, the visible half of
1061
+ // the read above. The co-located suite is listed EXPLICITLY alongside
1062
+ // its page: `EXCLUDE_TEMPLATE_DIRS` below drops only the three
1063
+ // top-level `apps/web/**/__tests__` directories, and the import-graph
1064
+ // closure walks FORWARD from an owned file — a test that imports
1065
+ // `../page` is a dependent, not a companion, so it is never swept
1066
+ // along. Without the second entry a keyless scaffold would carry a
1067
+ // test for a page it does not have.
1068
+ "apps/web/app/[locale]/(authed)/appointments/page.tsx",
1069
+ "apps/web/app/[locale]/(authed)/appointments/__tests__/page.test.tsx",
1070
+ // #6183 S9b — the enabled-meeting-slugs gate. `meetings.ts` is its own
1071
+ // file (not folded into `routes/workspaces/config.ts`, which is
1072
+ // `workspace`/`multi-tenant`-owned) precisely because its GET reaches
1073
+ // `services/scheduling/` — the SAME reason `routes/schedule/
1074
+ // appointments.ts` above is its own file rather than a row on an
1075
+ // existing route. `lib/public-meetings-config.ts` is the public-read
1076
+ // boundary `routes/schedule/meeting-types.ts` (the anonymous read,
1077
+ // already reached via the already-listed `routes/schedule/index.ts`)
1078
+ // calls into.
1079
+ "apps/api/src/routes/workspaces/meetings.ts",
1080
+ "apps/api/src/routes/workspaces/__tests__/meetings.test.ts",
1081
+ "apps/api/src/lib/public-meetings-config.ts",
1082
+ "apps/api/src/routes/schedule/meeting-types.ts",
1083
+ "apps/api/src/routes/schedule/__tests__/meeting-types.test.ts",
1084
+ // meetings.ts is the FIRST scheduling-gated route to use the generic
1085
+ // tenantDb() scope layer directly (its sibling, appointments.ts, does
1086
+ // its own scoped read in services/scheduling/appointments.ts instead)
1087
+ // — and `workspaceMeetings` is now in architecture.yaml's tenant-scope
1088
+ // forbidden_pattern (#6183), so meetings.ts cannot bypass tenantDb()
1089
+ // with a raw prisma call. Co-owned with `multi-tenant` below (the
1090
+ // SAME two files, same reason `content.ts`'s own comment there
1091
+ // records: "each tenancy slice must deliver it").
1092
+ "apps/api/src/lib/tenant-db.ts",
1093
+ "apps/api/src/services/tenant-context.ts",
930
1094
  ],
931
- // pageContributions (spec 5478; parent § D6 shape 1) — the booker and its
932
- // confirmation page, whose route files `ownedFiles` above already carries.
933
- // Neither row names seed content: both render from the /v1/schedule/*
934
- // routes, not from an MDX file.
1095
+ // pageContributions (spec 5478; parent § D6 shape 1) — the booker, the
1096
+ // per-booking-type page and the confirmation page, whose route files
1097
+ // `ownedFiles` above already carries. No row names seed content: all
1098
+ // three render from the /v1/schedule/* routes, not from an MDX file.
935
1099
  pageContributions: [
936
- { route: "/schedule" },
937
- { route: "/schedule/confirmed" },
1100
+ { route: "/meet" },
1101
+ { route: "/meet/[slug]" },
1102
+ { route: "/meet/confirmed" },
1103
+ // #6086 — the authed appointments page. Bracket (roster) grammar and no
1104
+ // `content` key, for the same reason the three /meet rows carry none:
1105
+ // it renders from `/v1/schedule/appointments`, not from an MDX file.
1106
+ { route: "/appointments" },
1107
+ ],
1108
+ // patches (#5581 U3, D5) — delivers the extracted component package to a
1109
+ // keyed scaffold's apps/web, mirroring the signal-collectors slice's
1110
+ // identical merge-keys op below.
1111
+ patches: [
1112
+ {
1113
+ target: "apps/web/package.json",
1114
+ kind: "json",
1115
+ ops: [
1116
+ {
1117
+ op: "merge-keys",
1118
+ path: ["dependencies"],
1119
+ entries: {
1120
+ "@working-theory/ui-scheduling": "workspace:*",
1121
+ },
1122
+ },
1123
+ ],
1124
+ },
938
1125
  ],
939
1126
  },
940
1127
  // ── signal-collectors — flows, deliverables, project listings, waitlist
@@ -1089,6 +1276,94 @@ export const MODULE_SLICES = {
1089
1276
  "apps/api/src/bin/seed-waitlist-experiments.ts",
1090
1277
  "apps/mcp/src/tools/get_flow.ts",
1091
1278
  "apps/mcp/src/tools/submit_flow_step.ts",
1279
+ // ── The review surface (#5677) — CO-OWNED with multi-tenant ──────────
1280
+ //
1281
+ // Every path below is ALSO a multi-tenant `ownedFiles` entry and stays
1282
+ // one. Listing a file under two modules is additive, not a transfer:
1283
+ // `paidModuleTemplateExclusions()` unions every paid module's entries,
1284
+ // so the `templates/` snapshot is unchanged, and a multi-tenant scaffold
1285
+ // keeps receiving these from the multi-tenant bundle exactly as before.
1286
+ // What changes is that a signal-collectors bundle now carries them too.
1287
+ //
1288
+ // WHY THEY MUST BE HERE. #5677 widened `server.ts`'s review-surface
1289
+ // registration from `multi-tenant` to `multi-tenant || signal-collectors`,
1290
+ // because signal-collectors is the module that SELLS the instruments
1291
+ // these routes read back. Registration and DELIVERY are separate layers:
1292
+ // the widened gate calls `sliceRoutePlugin("waitlistExperimentRoutes")`
1293
+ // and imports `projectListingTenantRoutes`, so on a scaffold whose
1294
+ // `AILK_ENABLED_MODULES` names signal-collectors but whose tree lacks
1295
+ // these files, `loadSlice` throws ERR_MODULE_NOT_FOUND during `build()`
1296
+ // — construction, so EVERY route 500s rather than one. That is not a
1297
+ // feature gap; it is the outage shape `lib/slice-load.ts` exists to
1298
+ // diagnose.
1299
+ //
1300
+ // It is not hypothetical: `marketing-site` — the Pro flagship in
1301
+ // `template-manifest.ts` — declares
1302
+ // `requires: ["marketing", "docs-blog", "scheduling", "signal-collectors"]`,
1303
+ // i.e. signal-collectors WITHOUT multi-tenant. That is exactly the
1304
+ // configuration that would crash, which is why the gate and the delivery
1305
+ // land together rather than the delivery being deferred.
1306
+ //
1307
+ // The closure is tight, verified by grep rather than assumed: none of
1308
+ // these files imports `lib/tenant-db.ts` or `services/tenant-context.ts`
1309
+ // (both multi-tenant-only). #5677 retargeted `waitlist-dashboard.ts`'s
1310
+ // type import of `tenant-context.js` to the base `services/default-tenant.ts`,
1311
+ // which severed the one cross-slice edge that existed.
1312
+ //
1313
+ // `routes/project-listings/{start,patch-draft,submit,public,me,respond,
1314
+ // resume-token,patch-copy}.ts` are NOT listed — they are the free
1315
+ // anonymous surface and the mixed barrel, covered by the note above this
1316
+ // array, and they already ship unconditionally.
1317
+ "apps/api/src/routes/waitlist-experiments/index.ts",
1318
+ "apps/api/src/routes/waitlist-experiments/list.ts",
1319
+ "apps/api/src/routes/waitlist-experiments/comparison.ts",
1320
+ "apps/api/src/routes/waitlist-experiments/signup.ts",
1321
+ "apps/api/src/routes/waitlist-experiments/signups.ts",
1322
+ "apps/api/src/routes/waitlist-experiments/respond.ts",
1323
+ "apps/api/src/services/waitlist-dashboard.ts",
1324
+ // #6258 — the file waitlist-dashboard.ts imports for the pinned
1325
+ // platform-operator read scope. It rides BOTH slices for the same
1326
+ // reason its importer does: an unlisted import target breaks a
1327
+ // signal-collectors-without-multi-tenant scaffold at build() time.
1328
+ "apps/api/src/services/waitlist-operator-reads.ts",
1329
+ // #6157 D14/D16 — the magnet Admin authoring surface's own sibling
1330
+ // tenant scope is gated `multi-tenant || signal-collectors` (D4,
1331
+ // the SAME expression this review-surface block co-owns files
1332
+ // under), so both its route file AND `lib/tenant-db.ts` (the ONE
1333
+ // data path it reads/writes through, FR3) must be delivered here
1334
+ // too. `lib/tenant-db.ts` is ALSO listed under `multi-tenant` above
1335
+ // — additive, not a transfer (the same reasoning this array's own
1336
+ // header comment already states for every other file in it).
1337
+ //
1338
+ // The closure stays tight WITHOUT dragging in `tenant-context.ts` or
1339
+ // the multi-tenant guard assembly behind it, because `tenant-db.ts`'s
1340
+ // own type import was retargeted from `tenant-context.js` to the
1341
+ // BASE-tree `services/default-tenant.js` in this same unit (D15) —
1342
+ // the identical one-line class of move #5677 made for
1343
+ // `waitlist-dashboard.ts` two paragraphs above this one.
1344
+ "apps/api/src/routes/magnets/index.ts",
1345
+ "apps/api/src/lib/tenant-db.ts",
1346
+ "apps/api/src/routes/project-listings/get.ts",
1347
+ "apps/api/src/routes/project-listings/list.ts",
1348
+ "apps/api/src/routes/project-listings/patch.ts",
1349
+ // The dashboard itself. Without these the Pro scaffold would boot with
1350
+ // reachable review routes and no UI to read them through, and the two
1351
+ // files that DO already ship into every scaffold
1352
+ // (`waitlist/format.ts`, `waitlist/components/ForbiddenState.tsx` — never
1353
+ // owned by any module) would stay orphaned. They import only
1354
+ // `@working-theory/api-client`, `@working-theory/ui` and
1355
+ // `@working-theory/validation`, all base packages, so this pulls in no
1356
+ // multi-tenant dependency.
1357
+ "apps/web/app/[locale]/(authed)/waitlist/page.tsx",
1358
+ "apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx",
1359
+ "apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx",
1360
+ "apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx",
1361
+ "apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx",
1362
+ "apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx",
1363
+ "apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx",
1364
+ "apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx",
1365
+ "apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx",
1366
+ "apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx",
1092
1367
  // apps/web/app/[locale]/flows/** deliberately NOT listed here (unlike
1093
1368
  // the docs-blog precedent's own `blog`/`docs` page.tsx entries above),
1094
1369
  // even though every file under it is signal-collectors territory and
@@ -1115,18 +1390,29 @@ export const MODULE_SLICES = {
1115
1390
  // `NESTED_FOREIGN_MODULE_ENTRIES` for the companion fix that keeps its
1116
1391
  // presence from falsely reading as "projects carried".
1117
1392
  "apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx",
1118
- "packages/ui/src/sections/ListingApplicationFlow.tsx",
1119
- "packages/ui/src/sections/ProjectListingShelves.tsx",
1120
- "packages/ui/src/sections/FlowStepper.tsx",
1121
- "packages/ui/src/blocks/FlowSidePanel.tsx",
1122
- "packages/ui/src/blocks/ListingFullPreview.tsx",
1123
- "packages/ui/src/blocks/ListingQuestionReview.tsx",
1124
- "packages/ui/src/blocks/ListingTilePreview.tsx",
1125
- "packages/ui/src/blocks/ListingPrivacyNotice.tsx",
1126
- "packages/ui/src/blocks/ProjectListingCard.tsx",
1127
- "packages/ui/src/blocks/ResponseChart.tsx",
1128
- "packages/ui/src/blocks/ResultsReveal.tsx",
1129
- "packages/ui/src/sections/slices/signal-collectors.ts",
1393
+ // #5581 U4 — the eleven signal-collectors components + the slice's own
1394
+ // re-export entry point moved to packages-paid/ui-signal-collectors;
1395
+ // the package is delivered to a keyed scaffold via `patches` below,
1396
+ // not via `ownedFiles`/`installMap` (D5).
1397
+ // #5562 — the report reader at `/reports/<slug>`. UNLIKE the flow routes
1398
+ // above, this one DOES belong in `ownedFiles`, and for the same reason
1399
+ // `projects/apply/ListingApplicationClient.tsx` does: `/reports` is a URL
1400
+ // segment `MODULE_SURFACES["signal-collectors"] = ["flows"]` does not gate,
1401
+ // so `excludedRoutePaths()` would leave it in a keyless scaffold. It has no
1402
+ // upgrade-path fixture to protect either — it is net-new here, so nothing
1403
+ // in `templates/` can regress by its absence.
1404
+ "apps/web/app/[locale]/reports/[slug]/page.tsx",
1405
+ //
1406
+ // The two sections that route composes — `report-header` and
1407
+ // `reports-list` — are deliberately NOT owned here. They are
1408
+ // `SectionRenderer` DISPATCH entries with static imports, so stripping
1409
+ // them would leave the renderer with dangling imports in a keyless
1410
+ // scaffold; the escape hatch for a slice-owned dispatch entry is the
1411
+ // `ModuleAbsent` placeholder (`booker`, `payment-receipt`), which is
1412
+ // machinery neither of these needs. `document-viewer` (#5270) — the
1413
+ // reader this route is built around, and the section with the strongest
1414
+ // claim to being deliverables-only — is likewise unowned base
1415
+ // `packages/ui`. A section is vocabulary; the ROUTE is the module.
1130
1416
  ],
1131
1417
  // pageContributions (spec 5478; parent § D6 shape 2) — the three flow
1132
1418
  // routes, named exactly as `surfaces.ts`'s ROUTE_SINCE keys name them.
@@ -1146,6 +1432,61 @@ export const MODULE_SLICES = {
1146
1432
  { route: "/flows/[slug]" },
1147
1433
  { route: "/flows/[slug]/start" },
1148
1434
  { route: "/flows/[slug]/results/[payload]" },
1435
+ // #5562 — the report reader. Unlike the three rows above, this row's
1436
+ // code IS this module's own `ownedFiles` (see the note beside it), so
1437
+ // the contribution and the delivery shape carrying it agree here.
1438
+ { route: "/reports/[slug]" },
1439
+ ],
1440
+ // installMap (#5618 D6.3) — the module's ten sample files, moved here out
1441
+ // of `site-templates/marketing-site/` so the module OWNS its samples
1442
+ // (Rule B) and a keyed `--modules signal-collectors` scaffold with no
1443
+ // template lands them. Their in-repo home is
1444
+ // `packages/create-ailk/module-templates/signal-collectors/**`, mirroring
1445
+ // `commerce.installMap` below — the only other slice-level map — because
1446
+ // an `installMap` KEY is a repo-relative source the producer copies
1447
+ // literally, and `content/en/ProjectsList/projects.mdx` is ALSO this
1448
+ // repo's own live marketing copy (a different file), which
1449
+ // build-templates.mjs keeps out of every scaffold: under
1450
+ // `module-templates/` the two never meet.
1451
+ //
1452
+ // Four entries are real renames (the flow and waitlist seeds the API
1453
+ // reads, and the report PDF under apps/web/public/); the six MDX entries
1454
+ // are self-addressed because no `pageContributions` row declares
1455
+ // `/projects`, `/projects/[slug]` or `/thanks/[slug]` yet (spec § 7
1456
+ // finding 7 — declaring them changes the enabled-surface set and so the
1457
+ // Pro scaffold's file set, a separate decision). Once those rows exist
1458
+ // the six move to `pageContributions[].content` with no further
1459
+ // migration. `generate-sample-report.py` keeps its root-level
1460
+ // destination (finding 1 — AC7 forbids changing the Pro file set).
1461
+ installMap: {
1462
+ "packages/create-ailk/module-templates/signal-collectors/content/en/ProjectsList/projects.mdx": "content/en/ProjectsList/projects.mdx",
1463
+ "packages/create-ailk/module-templates/signal-collectors/content/en/Project/fit-check-funnel.mdx": "content/en/Project/fit-check-funnel.mdx",
1464
+ "packages/create-ailk/module-templates/signal-collectors/content/en/Project/launch-site-in-fourteen-days.mdx": "content/en/Project/launch-site-in-fourteen-days.mdx",
1465
+ "packages/create-ailk/module-templates/signal-collectors/content/en/ThankYouPage/contact.mdx": "content/en/ThankYouPage/contact.mdx",
1466
+ "packages/create-ailk/module-templates/signal-collectors/content/en/ThankYouPage/newsletter.mdx": "content/en/ThankYouPage/newsletter.mdx",
1467
+ "packages/create-ailk/module-templates/signal-collectors/content/en/ThankYouPage/apply.mdx": "content/en/ThankYouPage/apply.mdx",
1468
+ "packages/create-ailk/module-templates/signal-collectors/flows/fit-check.flow.json": "database/flows/fit-check.flow.json",
1469
+ "packages/create-ailk/module-templates/signal-collectors/waitlist/launch-waitlist.experiment.json": "database/waitlist/launch-waitlist.experiment.json",
1470
+ "packages/create-ailk/module-templates/signal-collectors/reports/sample-report.pdf": "apps/web/public/reports/sample-report.pdf",
1471
+ "packages/create-ailk/module-templates/signal-collectors/reports/generate-sample-report.py": "reports/generate-sample-report.py",
1472
+ },
1473
+ // patches (#5581 U4, D5) — delivers the extracted component package to a
1474
+ // keyed scaffold's apps/web, mirroring the workspace slice's identical
1475
+ // merge-keys op above.
1476
+ patches: [
1477
+ {
1478
+ target: "apps/web/package.json",
1479
+ kind: "json",
1480
+ ops: [
1481
+ {
1482
+ op: "merge-keys",
1483
+ path: ["dependencies"],
1484
+ entries: {
1485
+ "@working-theory/ui-signal-collectors": "workspace:*",
1486
+ },
1487
+ },
1488
+ ],
1489
+ },
1149
1490
  ],
1150
1491
  },
1151
1492
  // ── commerce — per-site paid add-on (D-47, amending D-26) ─────────────────
@@ -1189,18 +1530,14 @@ export const MODULE_SLICES = {
1189
1530
  },
1190
1531
  },
1191
1532
  packages: {
1192
- // Duplicated VERBATIM from the multi-tenant slice's own "stripe-client"
1193
- // entry (§7 Q1 — a multi-tenant deployment using the Stripe billing
1194
- // portal or org-billing webhook routing also installs commerce, so
1195
- // both slices can be selected together) — the same order-independence
1196
- // reason the multi-tenant/scheduling slices state for `validation`
1197
- // below: `deriveArchitecture` merges `packages` blocks with
1198
- // `Object.assign`, so two slices declaring this key differently would
1199
- // make the derivation depend on module order.
1200
- "stripe-client": {
1201
- description: "Stripe SDK wrapper + webhook signature verification",
1202
- may_import_from: ["packages/validation"],
1203
- },
1533
+ // `stripe-client` is deliberately NOT declared here (#5823). It was,
1534
+ // duplicated verbatim from the multi-tenant slice, while the package was
1535
+ // paid and commerce-owned. Since #5810 it is `commercial_tier: oss`,
1536
+ // published public, and since #5812 its sole `apps/api` importer
1537
+ // (`lib/stripe.ts`) is a base file — so the package is base, not a
1538
+ // commerce slice member. The `packages/stripe-client` entries in the
1539
+ // `may_import_from` lists above and in the base/multi-tenant slices are
1540
+ // unaffected: those declare who may reach the package, not who owns it.
1204
1541
  // Duplicated VERBATIM from the marketing slice for the same
1205
1542
  // order-independence reason the multi-tenant/scheduling slices state.
1206
1543
  validation: {
@@ -1221,54 +1558,40 @@ export const MODULE_SLICES = {
1221
1558
  // ownedFiles (S1, #5088). SOURCE files only (#5182 D6) — no __tests__/,
1222
1559
  // no .stories.tsx, no .text.ts... except .text.ts siblings ARE source
1223
1560
  // (they ship as runtime copy modules, not test fixtures), so the ten
1224
- // annotated below are listed. 42 in all: 9 apps/api + 26 packages/ui
1561
+ // annotated below are listed. 36 in all: 4 apps/api + 26 packages/ui
1225
1562
  // (15 components + 10 .text.ts siblings + this slice's own
1226
1563
  // sections/slices/commerce.ts) + 7 apps/web.
1564
+ //
1565
+ // THE PRIMITIVES LEFT THIS SLICE (D-64, epic #5770, #5796). The checkout
1566
+ // launcher, the Stripe webhook receiver, customer mapping and the
1567
+ // `lib/stripe.ts` shim are BASE TREE now: `apps/api/src/server.ts`
1568
+ // registers them unconditionally and gates them at CALL time on the JWT
1569
+ // `commerce` claim (`apps/api/src/lib/require-commerce-claim.ts`), so a
1570
+ // keyless scaffold must CARRY them. Claiming them here is what deleted
1571
+ // them from that scaffold while the server still loaded them —
1572
+ // ERR_MODULE_NOT_FOUND inside build(), every route 500ing, which is the
1573
+ // exact defect `tools/lint-slice-registration.ts` (#5701) exists to
1574
+ // catch. The six that left: lib/stripe.ts,
1575
+ // routes/checkout/{index,sessions}.ts, routes/webhooks/{index,stripe}.ts
1576
+ // and routes/webhooks/README.md. The four below STAY — they are commerce
1577
+ // OVERLAY, still registered behind `isModuleEnabled("commerce")` and
1578
+ // still carrying their `// oss: false` markers.
1227
1579
  ownedFiles: [
1228
- "apps/api/src/lib/stripe.ts",
1229
- "apps/api/src/routes/checkout/index.ts",
1230
- "apps/api/src/routes/checkout/sessions.ts",
1231
- "apps/api/src/routes/webhooks/index.ts",
1232
- "apps/api/src/routes/webhooks/stripe.ts",
1233
- // README.md documents the Stripe webhook receiver — not a test/
1234
- // generated file, and its absence is exactly what left a stray
1235
- // apps/api/src/routes/webhooks/ directory in a keyless templates/
1236
- // snapshot (empirically found, #5364 build pass): the import-graph
1237
- // closure only scans source extensions, never markdown, so once
1238
- // index.ts and stripe.ts are excluded this is the one file left
1239
- // behind. Same defect class #5366 found and fixed the same way for
1240
- // routes/flows/README.md — see that entry above.
1241
- "apps/api/src/routes/webhooks/README.md",
1242
1580
  "apps/api/src/routes/flow-checkouts/index.ts",
1243
1581
  "apps/api/src/routes/flows/recommender.ts",
1244
1582
  "apps/api/src/services/recommender-capture.ts",
1245
1583
  "apps/api/src/services/flow-checkouts.ts",
1246
- "packages/ui/src/blocks/CheckoutForm.tsx",
1247
- "packages/ui/src/blocks/CheckoutForm.text.ts",
1248
- "packages/ui/src/blocks/CheckoutPriceCard.tsx",
1249
- "packages/ui/src/blocks/PaymentLinkCard.tsx",
1250
- "packages/ui/src/blocks/PaymentLinkCard.text.ts",
1251
- "packages/ui/src/blocks/PaymentLinkForm.tsx",
1252
- "packages/ui/src/blocks/PaymentLinkForm.text.ts",
1253
- "packages/ui/src/blocks/PaymentLinkList.tsx",
1254
- "packages/ui/src/blocks/InvoiceForm.tsx",
1255
- "packages/ui/src/blocks/InvoiceForm.text.ts",
1256
- "packages/ui/src/blocks/InvoiceList.tsx",
1257
- "packages/ui/src/blocks/InvoiceList.text.ts",
1258
- "packages/ui/src/blocks/SubscriptionStatus.tsx",
1259
- "packages/ui/src/blocks/SubscriptionStatus.text.ts",
1260
- "packages/ui/src/blocks/StripeActionButton.tsx",
1261
- "packages/ui/src/blocks/PaymentStatusBadge.tsx",
1262
- "packages/ui/src/blocks/PaymentStatusBadge.text.ts",
1263
- "packages/ui/src/blocks/PaymentResult.tsx",
1264
- "packages/ui/src/blocks/PaymentResult.text.ts",
1265
- "packages/ui/src/blocks/PortalEntry.tsx",
1266
- "packages/ui/src/panels/InvoiceDetail.tsx",
1267
- "packages/ui/src/panels/InvoiceDetail.text.ts",
1268
- "packages/ui/src/sections/PaymentReceipt.tsx",
1269
- "packages/ui/src/sections/PricingTable.tsx",
1270
- "packages/ui/src/sections/PricingTable.text.ts",
1271
- "packages/ui/src/sections/slices/commerce.ts",
1584
+ // #6038 — the buyer-scoped subscription-status route pair (the
1585
+ // update-your-card banner's read + portal handoff). Commerce OVERLAY,
1586
+ // same posture as the four flow-checkouts/recommender entries above.
1587
+ "apps/api/src/routes/subscription/index.ts",
1588
+ "apps/api/src/routes/subscription/me.ts",
1589
+ "apps/api/src/routes/subscription/portal.ts",
1590
+ "apps/api/src/services/subscription.ts",
1591
+ // #5581 U5 — the fifteen commerce components (+ ten .text.ts extractors
1592
+ // + the slice's own re-export entry point) moved to
1593
+ // packages-paid/ui-commerce; the package is delivered to a keyed
1594
+ // scaffold via `patches` below, not via `ownedFiles`/`installMap` (D5).
1272
1595
  "apps/web/app/[locale]/flows/[slug]/checkout.actions.ts",
1273
1596
  "apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx",
1274
1597
  "apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx",
@@ -1298,7 +1621,32 @@ export const MODULE_SLICES = {
1298
1621
  "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/portal-route.template.ts": ".claude/skills/scaffold-commerce/templates/portal-route.template.ts",
1299
1622
  "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts": ".claude/skills/scaffold-commerce/templates/stripe-config.template.ts",
1300
1623
  "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts": ".claude/skills/scaffold-commerce/templates/subscription-route.template.ts",
1624
+ // The commerce-boundary rule (#6039/#6070, this entry #6041): ships to
1625
+ // a commerce-keyed scaffold's project root so an agent working there
1626
+ // reads the money/live-mode/personal-data classification before it
1627
+ // acts. One more literal entry alongside the ten scaffold-commerce
1628
+ // skill entries above, for the same reason — a directory source is
1629
+ // rejected by `validateInstallMap`.
1630
+ "packages/create-ailk/module-templates/commerce/.claude/rules/commerce-boundary.md": ".claude/rules/commerce-boundary.md",
1301
1631
  },
1632
+ // patches (#5581 U5, D5) — delivers the extracted component package to a
1633
+ // keyed scaffold's apps/web, mirroring the workspace slice's identical
1634
+ // merge-keys op above.
1635
+ patches: [
1636
+ {
1637
+ target: "apps/web/package.json",
1638
+ kind: "json",
1639
+ ops: [
1640
+ {
1641
+ op: "merge-keys",
1642
+ path: ["dependencies"],
1643
+ entries: {
1644
+ "@working-theory/ui-commerce": "workspace:*",
1645
+ },
1646
+ },
1647
+ ],
1648
+ },
1649
+ ],
1302
1650
  },
1303
1651
  };
1304
1652
  /**
@@ -1346,9 +1694,98 @@ export function normalizeModules(modules) {
1346
1694
  * Passed in rather than derived from `import.meta.url`, which ts-jest cannot
1347
1695
  * load — the same reason `sync-routes.ts` documents for staying pure.
1348
1696
  */
1697
+ /**
1698
+ * Scaffold destinations that MORE THAN ONE module's `ownedFiles` declares —
1699
+ * the co-ownership the `#5618 D4` install-time ownership registry must treat
1700
+ * as intentional rather than as an overwrite (#5702 follow-up).
1701
+ *
1702
+ * Co-ownership is how a file reaches a scaffold under either of two modules
1703
+ * without being transferred away from the other. #5677 needed it for the
1704
+ * submissions review surface: the routes register under
1705
+ * `multi-tenant || signal-collectors`, so BOTH bundles must be able to
1706
+ * deliver them, or the module that lacks them throws
1707
+ * `ERR_MODULE_NOT_FOUND` during `build()`.
1708
+ *
1709
+ * `validateLayerOwnership` (`lib/extract-module-bundle.ts`) refuses a
1710
+ * destination an earlier layer already wrote, because a silent
1711
+ * last-writer-wins between two modules that disagree is the bug `#5618 D4`
1712
+ * exists to catch. Two modules that both DECLARE the same repo file are not
1713
+ * that case: they stage the same bytes from the same source, so whichever
1714
+ * lands second writes what is already there. Without this allowance a
1715
+ * scaffold selecting both modules — `saas-product`, whose `requires` names
1716
+ * `signal-collectors` AND `multi-tenant` — aborts mid-install, after the
1717
+ * first module's files are on disk.
1718
+ *
1719
+ * Only a DECLARED overlap is allowed. A destination two layers collide on
1720
+ * without both naming it in `ownedFiles` is still refused, which is the
1721
+ * whole invariant.
1722
+ */
1723
+ export function coOwnedModuleDestinations() {
1724
+ const seen = new Map();
1725
+ for (const slice of Object.values(MODULE_SLICES)) {
1726
+ // A module's own list is deduplicated first: repetition within one slice
1727
+ // is not co-ownership and must not license an overlap on its own.
1728
+ for (const file of new Set(slice.ownedFiles ?? [])) {
1729
+ seen.set(file, (seen.get(file) ?? 0) + 1);
1730
+ }
1731
+ }
1732
+ const coOwned = new Set();
1733
+ for (const [file, count] of seen) {
1734
+ if (count > 1)
1735
+ coOwned.add(file);
1736
+ }
1737
+ return coOwned;
1738
+ }
1739
+ // Template-relative directories excluded from the snapshot outright (#4154).
1740
+ // - apps/web/__tests__ — this repo's own harness tests. Several import route
1741
+ // modules across multiple surfaces (resources-downloads-integrations-phase2,
1742
+ // standalone-informational-routes, routes/blog-route, blog-dir-resolution),
1743
+ // so they fail outright in a scaffold whose surfaces are gated off. The
1744
+ // co-located app/[locale]/<surface>/__tests__ suites travel with their
1745
+ // surface and are unaffected.
1746
+ // - apps/web/app/[locale]/__tests__ — same class: page-renderer-migration
1747
+ // reaches across surfaces (`../launch-service/page`), so it breaks in a
1748
+ // scaffold that gated launch-service off. The PER-SURFACE co-located
1749
+ // suites (app/[locale]/<surface>/__tests__) import only their own route and
1750
+ // travel with their surface, so they are kept.
1751
+ // - apps/web/content — the blog/docs sample posts (the launching-ailk class).
1752
+ // Replaced by neutral stubs from template-overlay/.
1753
+ // - apps/web/app/[locale]/(authed)/edit — the Pro content host's editor
1754
+ // routes (#5977; added to apps/web by #5957, spec 5872 FR1/FR2). Both
1755
+ // pages are `// oss: false` and the [type]/[slug] page imports
1756
+ // `@working-theory/admin-ui`, which a keyless (Free) scaffold does not
1757
+ // carry — so shipping them puts two files importing an absent package
1758
+ // into the scaffold and fails both co-located `__tests__` suites under
1759
+ // verify-scaffold's `apps/web` jest run. The directory is taken whole,
1760
+ // `__tests__` with it, since a test of an excluded route has nothing to
1761
+ // test.
1762
+ //
1763
+ // WHY AN EXCLUSION AND NOT A MODULE SLICE. Spec 5872 D11 settled the two
1764
+ // constraints that rule out the alternatives, quoted rather than
1765
+ // re-derived:
1766
+ // 1. "content-write cannot be a MODULE_SLICES key"
1767
+ // (`module-architecture.ts:214-222` — a key would make
1768
+ // `--modules content-write` a valid selection).
1769
+ // 2. "the workspace slice is the wrong owner" (#5853 §4.2/D14 — it
1770
+ // would bind the Pro editor to a Pro Plus bundle).
1771
+ // So there is no correct `ownedFiles` owner for these files today. This
1772
+ // entry is the INTERIM: a flat exclusion that keeps the keyless scaffold
1773
+ // honest until a content-write delivery path exists, at which point the
1774
+ // routes move to that path's owner and this entry comes back out. The
1775
+ // routes stay registered in `ROUTE_SINCE` (src/surfaces.ts) meanwhile —
1776
+ // see that entry, and sync-routes' AC-5 coverage test, which reads this
1777
+ // list so the resulting ROUTE_SINCE-vs-template absence is justified
1778
+ // rather than reported stale.
1779
+ export const EXCLUDE_TEMPLATE_DIRS = [
1780
+ "apps/web/__tests__",
1781
+ "apps/web/app/__tests__",
1782
+ "apps/web/app/[locale]/__tests__",
1783
+ "apps/web/app/[locale]/(authed)/edit",
1784
+ "apps/web/content",
1785
+ ];
1349
1786
  export function paidModuleTemplateExclusions(snapshotRoots, repoRoot) {
1350
1787
  const roots = new Set(snapshotRoots);
1351
- const excluded = new Set();
1788
+ const seeds = new Set();
1352
1789
  for (const moduleName of PAID_MODULES) {
1353
1790
  for (const owned of MODULE_SLICES[moduleName]?.ownedFiles ?? []) {
1354
1791
  const rel = owned.split(sep).join("/");
@@ -1356,103 +1793,11 @@ export function paidModuleTemplateExclusions(snapshotRoots, repoRoot) {
1356
1793
  continue;
1357
1794
  if (rel.includes("*"))
1358
1795
  continue;
1359
- excluded.add(rel);
1360
- }
1361
- }
1362
- // Companion closure by import graph, to a fixed point — never a filename
1363
- // stem rule (#5182 D7), which misses eleven files and over-marks three.
1364
- const candidates = listFilesUnder(repoRoot, [...roots]);
1365
- for (;;) {
1366
- let grew = false;
1367
- for (const file of candidates) {
1368
- if (excluded.has(file))
1369
- continue;
1370
- const imports = relativeImportsOf(join(repoRoot, file));
1371
- for (const spec of imports) {
1372
- const target = resolveRelativeImport(file, spec, candidates);
1373
- if (target !== undefined && excluded.has(target)) {
1374
- excluded.add(file);
1375
- grew = true;
1376
- break;
1377
- }
1378
- }
1379
- }
1380
- if (!grew)
1381
- break;
1382
- }
1383
- return [...excluded].sort();
1384
- }
1385
- const SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
1386
- /** Every source file under the given repo-relative roots, as "/"-joined paths. */
1387
- function listFilesUnder(repoRoot, roots) {
1388
- const out = new Set();
1389
- const skip = new Set(["node_modules", ".next", "dist", ".turbo", ".source"]);
1390
- const stack = roots.map((r) => r);
1391
- while (stack.length > 0) {
1392
- const rel = stack.pop();
1393
- const abs = join(repoRoot, rel);
1394
- let entries;
1395
- try {
1396
- entries = readdirSync(abs, { withFileTypes: true });
1397
- }
1398
- catch {
1399
- continue;
1400
- }
1401
- for (const entry of entries) {
1402
- if (skip.has(entry.name))
1403
- continue;
1404
- const childRel = `${rel}/${entry.name}`;
1405
- if (entry.isDirectory())
1406
- stack.push(childRel);
1407
- else if (SOURCE_EXTS.some((e) => entry.name.endsWith(e)))
1408
- out.add(childRel);
1796
+ seeds.add(rel);
1409
1797
  }
1410
1798
  }
1411
- return out;
1412
- }
1413
- /** The relative import specifiers a source file names (static imports + re-exports). */
1414
- function relativeImportsOf(absPath) {
1415
- let src;
1416
- try {
1417
- src = readFileSync(absPath, "utf8");
1418
- }
1419
- catch {
1420
- return [];
1421
- }
1422
- const out = [];
1423
- for (const m of src.matchAll(/(?:^|\n)\s*(?:import|export)[\s\S]{0,400}?from\s*["'](\.[^"']*)["']/g))
1424
- out.push(m[1]);
1425
- for (const m of src.matchAll(/\bimport\s*\(\s*["'](\.[^"']*)["']\s*\)/g))
1426
- out.push(m[1]);
1427
- return out;
1428
- }
1429
- /**
1430
- * Resolve a relative specifier against the candidate set. TypeScript ESM writes
1431
- * `./x.js` for `./x.ts`, so the `.js` → `.ts`/`.tsx` rewrite is the common case.
1432
- */
1433
- function resolveRelativeImport(fromFile, spec, candidates) {
1434
- const base = posixJoin(dirname(fromFile.split("/").join(sep)), spec);
1435
- const stem = base.replace(/\.(js|jsx|mjs|cjs)$/, "");
1436
- for (const cand of [
1437
- base,
1438
- ...SOURCE_EXTS.map((e) => stem + e),
1439
- ...SOURCE_EXTS.map((e) => `${stem}/index${e}`),
1440
- ])
1441
- if (candidates.has(cand))
1442
- return cand;
1443
- return undefined;
1444
- }
1445
- /** Join and normalize a "/"-separated path, resolving `.` and `..` segments. */
1446
- function posixJoin(dir, spec) {
1447
- const parts = `${dir.split(sep).join("/")}/${spec}`.split("/");
1448
- const out = [];
1449
- for (const part of parts) {
1450
- if (part === "" || part === ".")
1451
- continue;
1452
- if (part === "..")
1453
- out.pop();
1454
- else
1455
- out.push(part);
1456
- }
1457
- return out.join("/");
1799
+ // Companion closure by import graph, to a fixed point — the shared walker
1800
+ // (`lib/import-graph.ts`), so `tools/lint-slice-registration.ts` asks the
1801
+ // question with the same resolution this answer was computed with (#5701).
1802
+ return [...importClosure(repoRoot, [...roots], seeds)].sort();
1458
1803
  }