create-ailk 0.1.1 → 0.3.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 (284) hide show
  1. package/component-catalog.md +210 -52
  2. package/dist/cli.js +0 -0
  3. package/dist/lib/apply-module-patches.d.ts +47 -0
  4. package/dist/lib/apply-module-patches.js +370 -0
  5. package/dist/lib/extract-module-bundle.d.ts +12 -3
  6. package/dist/lib/extract-module-bundle.js +236 -24
  7. package/dist/lib/fetch-module.d.ts +3 -38
  8. package/dist/lib/fetch-module.js +26 -8
  9. package/dist/lib/module-license-gate.d.ts +2 -0
  10. package/dist/lib/module-license-gate.js +1 -0
  11. package/dist/lib/paid-paths.d.ts +16 -0
  12. package/dist/lib/paid-paths.js +90 -0
  13. package/dist/module-architecture.d.ts +139 -23
  14. package/dist/module-architecture.js +668 -25
  15. package/dist/parse-args.d.ts +2 -2
  16. package/dist/parse-args.js +3 -1
  17. package/dist/programmatic.d.ts +2 -1
  18. package/dist/programmatic.js +15 -4
  19. package/dist/surfaces.d.ts +12 -36
  20. package/dist/surfaces.js +35 -5
  21. package/dist/sync-routes.js +31 -1
  22. package/package.json +14 -15
  23. package/templates/.claude/agents/web.md +2 -3
  24. package/templates/.claude/rules/architecture.md +5 -5
  25. package/templates/.claude/skills/README.md +2 -1
  26. package/templates/.env.example +21 -9
  27. package/templates/CONVENTIONS.md +6 -7
  28. package/templates/apps/api/.env.example +9 -0
  29. package/templates/apps/api/CLAUDE.md +35 -4
  30. package/templates/apps/api/package.json +0 -1
  31. package/templates/apps/api/src/__tests__/cors.test.ts +195 -0
  32. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +60 -2
  33. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +159 -0
  34. package/templates/apps/api/src/config/__tests__/modules.test.ts +76 -0
  35. package/templates/apps/api/src/config/index.ts +19 -0
  36. package/templates/apps/api/src/config/modules.ts +12 -14
  37. package/templates/apps/api/src/lib/__mocks__/prisma.ts +13 -0
  38. package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +89 -0
  39. package/templates/apps/api/src/lib/slice-load.ts +90 -0
  40. package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +52 -32
  41. package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +182 -0
  42. package/templates/apps/api/src/openapi/__tests__/surface-drift.test.ts +293 -0
  43. package/templates/apps/api/src/openapi/index.ts +9 -3
  44. package/templates/apps/api/src/openapi/spec.ts +505 -65
  45. package/templates/apps/api/src/openapi/surface-drift.ts +195 -0
  46. package/templates/apps/api/src/routes/content/__tests__/create.test.ts +14 -0
  47. package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +14 -0
  48. package/templates/apps/api/src/routes/content/__tests__/update.test.ts +14 -0
  49. package/templates/apps/api/src/routes/content/index.ts +38 -11
  50. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +28 -0
  51. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +144 -0
  52. package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +796 -0
  53. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +478 -0
  54. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +38 -0
  55. package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +814 -0
  56. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +717 -0
  57. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +35 -0
  58. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +484 -0
  59. package/templates/apps/api/src/routes/project-listings/index.ts +52 -10
  60. package/templates/apps/api/src/routes/project-listings/me.ts +86 -0
  61. package/templates/apps/api/src/routes/project-listings/patch-draft.ts +5 -0
  62. package/templates/apps/api/src/routes/project-listings/public.ts +5 -0
  63. package/templates/apps/api/src/routes/project-listings/respond.ts +11 -0
  64. package/templates/apps/api/src/routes/project-listings/resume-token.ts +5 -0
  65. package/templates/apps/api/src/routes/project-listings/start.ts +38 -0
  66. package/templates/apps/api/src/routes/project-listings/submit.ts +5 -0
  67. package/templates/apps/api/src/routes/project-listings/verify-email.ts +150 -0
  68. package/templates/apps/api/src/server.ts +471 -133
  69. package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +65 -0
  70. package/templates/apps/api/src/services/__tests__/consent.test.ts +282 -0
  71. package/templates/apps/api/src/services/consent.ts +236 -0
  72. package/templates/apps/api/src/services/deliverable-fulfillment.ts +10 -0
  73. package/templates/apps/api/src/services/listing-config.ts +58 -0
  74. package/templates/apps/api/src/services/project-listing-resume-email.ts +326 -0
  75. package/templates/apps/api/src/services/project-listings.ts +602 -29
  76. package/templates/apps/api/src/vercel-handler.ts +60 -26
  77. package/templates/apps/mcp/.env.example +8 -0
  78. package/templates/apps/mcp/CLAUDE.md +2 -2
  79. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +21 -0
  80. package/templates/apps/mcp/__tests__/config/modules.test.ts +78 -0
  81. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +1 -1
  82. package/templates/apps/mcp/src/config/modules.ts +14 -13
  83. package/templates/apps/mcp/src/server.ts +8 -4
  84. package/templates/apps/mcp/src/tools/index.ts +7 -22
  85. package/templates/apps/web/.env.example +15 -0
  86. package/templates/apps/web/app/[locale]/(authed)/{waitlist/__tests__ → __tests__}/gate.test.tsx +4 -4
  87. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +2 -1
  88. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +2 -1
  89. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +2 -1
  90. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +1 -0
  91. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +1 -0
  92. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +2 -1
  93. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +1 -0
  94. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +1 -0
  95. package/templates/apps/web/app/[locale]/layout.tsx +13 -1
  96. package/templates/apps/web/app/llms.txt/route.ts +12 -9
  97. package/templates/apps/web/jest.config.cjs +20 -13
  98. package/templates/apps/web/lib/__tests__/site-theme.test.ts +112 -0
  99. package/templates/apps/web/lib/site-brand.tsx +4 -1
  100. package/templates/apps/web/lib/site-theme.ts +74 -0
  101. package/templates/apps/web/next.config.mjs +1 -3
  102. package/templates/apps/web/package.json +1 -6
  103. package/templates/apps/web/public/android-chrome-192x192.png +0 -0
  104. package/templates/apps/web/public/android-chrome-512x512.png +0 -0
  105. package/templates/apps/web/public/apple-touch-icon.png +0 -0
  106. package/templates/apps/web/public/favicon-16x16.png +0 -0
  107. package/templates/apps/web/public/favicon-32x32.png +0 -0
  108. package/templates/apps/web/public/favicon.ico +0 -0
  109. package/templates/apps/web/public/favicon.svg +6 -3
  110. package/templates/apps/web/public/lockup-horizontal.svg +4 -0
  111. package/templates/apps/web/public/logomark.svg +4 -0
  112. package/templates/apps/web/public/site.webmanifest +2 -2
  113. package/templates/apps/web/public/wordmark.svg +4 -0
  114. package/templates/content/_site.mdx +12 -0
  115. package/templates/database/CHANGELOG.md +94 -0
  116. package/templates/database/inbox/schema.prisma +165 -0
  117. package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +32 -0
  118. package/templates/database/migrations/20260911180000_consent_grants/migration.sql +71 -0
  119. package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +30 -0
  120. package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +49 -0
  121. package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +123 -0
  122. package/templates/database/package.json +1 -1
  123. package/templates/database/scripts/db-generate-locked.sh +0 -0
  124. package/templates/package.json +1 -1
  125. package/templates/.claude/skills/scaffold-commerce/SKILL.md +0 -807
  126. package/templates/.claude/skills/scaffold-commerce/references/lookup-keys-template.md +0 -71
  127. package/templates/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md +0 -83
  128. package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +0 -370
  129. package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +0 -376
  130. package/templates/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl +0 -72
  131. package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +0 -471
  132. package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +0 -365
  133. package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +0 -60
  134. package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +0 -446
  135. package/templates/apps/api/src/__tests__/server.test.ts +0 -253
  136. package/templates/apps/api/src/bin/deliverable-resend.ts +0 -144
  137. package/templates/apps/api/src/bin/deliverable-upload.ts +0 -114
  138. package/templates/apps/api/src/bin/followup-sweep.ts +0 -44
  139. package/templates/apps/api/src/bin/listing-csv-sweep.ts +0 -46
  140. package/templates/apps/api/src/bin/seed-presets.ts +0 -58
  141. package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +0 -104
  142. package/templates/apps/api/src/bin/session-retention-sweep.ts +0 -46
  143. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -74
  144. package/templates/apps/api/src/lib/stripe.ts +0 -52
  145. package/templates/apps/api/src/lib/tenant-db.ts +0 -225
  146. package/templates/apps/api/src/lib/ws-token.ts +0 -91
  147. package/templates/apps/api/src/middleware/tenant.ts +0 -99
  148. package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +0 -450
  149. package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +0 -262
  150. package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +0 -436
  151. package/templates/apps/api/src/routes/billing/index.ts +0 -36
  152. package/templates/apps/api/src/routes/billing/portal.ts +0 -182
  153. package/templates/apps/api/src/routes/billing/read.ts +0 -75
  154. package/templates/apps/api/src/routes/billing/usage.ts +0 -153
  155. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -687
  156. package/templates/apps/api/src/routes/checkout/index.ts +0 -13
  157. package/templates/apps/api/src/routes/checkout/sessions.ts +0 -197
  158. package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +0 -393
  159. package/templates/apps/api/src/routes/deliverables/index.ts +0 -200
  160. package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +0 -443
  161. package/templates/apps/api/src/routes/flow-checkouts/index.ts +0 -82
  162. package/templates/apps/api/src/routes/flows/README.md +0 -147
  163. package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +0 -752
  164. package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +0 -671
  165. package/templates/apps/api/src/routes/flows/index.ts +0 -202
  166. package/templates/apps/api/src/routes/flows/recommender.ts +0 -443
  167. package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +0 -815
  168. package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +0 -566
  169. package/templates/apps/api/src/routes/project-listings/get.ts +0 -70
  170. package/templates/apps/api/src/routes/project-listings/list.ts +0 -47
  171. package/templates/apps/api/src/routes/project-listings/patch.ts +0 -136
  172. package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +0 -490
  173. package/templates/apps/api/src/routes/schedule/index.ts +0 -249
  174. package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +0 -385
  175. package/templates/apps/api/src/routes/slack/actions.ts +0 -177
  176. package/templates/apps/api/src/routes/slack/index.ts +0 -38
  177. package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +0 -577
  178. package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +0 -78
  179. package/templates/apps/api/src/routes/waitlist-experiments/index.ts +0 -41
  180. package/templates/apps/api/src/routes/waitlist-experiments/list.ts +0 -64
  181. package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +0 -42
  182. package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +0 -84
  183. package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +0 -119
  184. package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +0 -238
  185. package/templates/apps/api/src/routes/waitlist-signups/index.ts +0 -101
  186. package/templates/apps/api/src/routes/webhooks/README.md +0 -80
  187. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -315
  188. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -247
  189. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -270
  190. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -198
  191. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -633
  192. package/templates/apps/api/src/routes/webhooks/index.ts +0 -41
  193. package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -481
  194. package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +0 -356
  195. package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +0 -671
  196. package/templates/apps/api/src/routes/workspaces/config.ts +0 -125
  197. package/templates/apps/api/src/routes/workspaces/index.ts +0 -194
  198. package/templates/apps/api/src/routes/workspaces/sessions.ts +0 -218
  199. package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +0 -425
  200. package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +0 -2840
  201. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +0 -393
  202. package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +0 -560
  203. package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +0 -406
  204. package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +0 -290
  205. package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +0 -736
  206. package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +0 -475
  207. package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +0 -340
  208. package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +0 -983
  209. package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +0 -71
  210. package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +0 -379
  211. package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +0 -768
  212. package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +0 -1314
  213. package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +0 -341
  214. package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +0 -567
  215. package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +0 -474
  216. package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +0 -354
  217. package/templates/apps/api/src/services/flow-aggregate.ts +0 -232
  218. package/templates/apps/api/src/services/flow-checkouts.ts +0 -123
  219. package/templates/apps/api/src/services/flow-engine.ts +0 -1278
  220. package/templates/apps/api/src/services/lead-promotion.ts +0 -176
  221. package/templates/apps/api/src/services/listing-csv-sweep.ts +0 -455
  222. package/templates/apps/api/src/services/playbook-compile.ts +0 -398
  223. package/templates/apps/api/src/services/playbook-render.ts +0 -263
  224. package/templates/apps/api/src/services/project-listing-decision.ts +0 -490
  225. package/templates/apps/api/src/services/project-listing-flow.ts +0 -426
  226. package/templates/apps/api/src/services/project-listing-issue.ts +0 -251
  227. package/templates/apps/api/src/services/recommender-capture.ts +0 -835
  228. package/templates/apps/api/src/services/scheduling/cal-provider.ts +0 -392
  229. package/templates/apps/api/src/services/scheduling/index.ts +0 -63
  230. package/templates/apps/api/src/services/scheduling/types.ts +0 -88
  231. package/templates/apps/api/src/services/tenant-context.ts +0 -451
  232. package/templates/apps/api/src/services/usage-metering.ts +0 -699
  233. package/templates/apps/api/src/services/waitlist-dashboard.ts +0 -947
  234. package/templates/apps/api/src/services/waitlist-experiments.ts +0 -213
  235. package/templates/apps/api/src/services/waitlist-followup.ts +0 -486
  236. package/templates/apps/api/src/services/waitlist-scoring.ts +0 -166
  237. package/templates/apps/api/src/services/waitlist-signups.ts +0 -165
  238. package/templates/apps/mcp/__tests__/schedule_tools.test.ts +0 -242
  239. package/templates/apps/mcp/src/tools/create_booking.ts +0 -58
  240. package/templates/apps/mcp/src/tools/get_event_meta.ts +0 -55
  241. package/templates/apps/mcp/src/tools/get_flow.ts +0 -61
  242. package/templates/apps/mcp/src/tools/list_availability.ts +0 -67
  243. package/templates/apps/mcp/src/tools/submit_flow_step.ts +0 -83
  244. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +0 -309
  245. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +0 -149
  246. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +0 -273
  247. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +0 -171
  248. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +0 -179
  249. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +0 -116
  250. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +0 -66
  251. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +0 -61
  252. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +0 -114
  253. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +0 -139
  254. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +0 -85
  255. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +0 -67
  256. package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +0 -179
  257. package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +0 -178
  258. package/templates/apps/web/app/[locale]/dev/purchase/__tests__/actions.test.ts +0 -160
  259. package/templates/apps/web/app/[locale]/dev/purchase/error.tsx +0 -25
  260. package/templates/apps/web/app/[locale]/dev/purchase/page.tsx +0 -117
  261. package/templates/apps/web/app/[locale]/dev/purchase/purchase.actions.ts +0 -58
  262. package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +0 -119
  263. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +0 -287
  264. package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +0 -254
  265. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +0 -532
  266. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +0 -121
  267. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +0 -457
  268. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +0 -396
  269. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +0 -114
  270. package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +0 -110
  271. package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +0 -96
  272. package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +0 -117
  273. package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +0 -96
  274. package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +0 -92
  275. package/templates/apps/web/app/[locale]/schedule/page.tsx +0 -82
  276. package/templates/apps/web/content/en/blog/README.txt +0 -25
  277. package/templates/apps/web/content/en/docs/index.mdx +0 -28
  278. package/templates/apps/web/lib/__mocks__/source-server.js +0 -31
  279. package/templates/apps/web/lib/blog-list-data.ts +0 -95
  280. package/templates/apps/web/lib/blog-post-data.ts +0 -79
  281. package/templates/apps/web/lib/source.ts +0 -34
  282. package/templates/apps/web/public/logomark-dark.svg +0 -4
  283. package/templates/apps/web/public/logomark-light.svg +0 -4
  284. package/templates/apps/web/source.config.ts +0 -78
@@ -21,6 +21,9 @@
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";
26
+ import { PAID_MODULES } from "./lib/module-tier.js";
24
27
  import { RECOGNIZED_MODULES } from "./parse-args.js";
25
28
  /**
26
29
  * The standalone-site "no upstream-monorepo import" forbidden pattern, assembled
@@ -65,6 +68,19 @@ const PUBLIC_SCHEDULING_VAR_PATTERN = [
65
68
  "CAL",
66
69
  "_|CALCOM|CALENDAR|SCHEDUL|BOOKING)",
67
70
  ].join("");
71
+ /**
72
+ * The commerce module's one credential-boundary pattern: a live-mode Stripe
73
+ * key shape (`sk_`/`pk_`/`rk_` + the live-mode infix), assembled from
74
+ * fragments for the same reason as CAL_KEY_PATTERN above — a literal
75
+ * live-key-shaped token in this file's source would itself trip the repo's
76
+ * own live-key hard block (`rules/environment-boundaries.md`). `apps/api` is
77
+ * excluded because `STRIPE_SECRET_KEY` legitimately lives there, gated by
78
+ * `assertSandboxKey`'s sandbox-only allow-list at request time and by
79
+ * `assertCommerceSandboxKey` at boot (server.ts) — this pattern catches a
80
+ * live-shaped key leaking anywhere ELSE (a fixture, a client component, a
81
+ * `NEXT_PUBLIC_` var).
82
+ */
83
+ const STRIPE_LIVE_KEY_PATTERN = ["(sk|pk|rk)_li", "ve_[A-Za-z0-9]"].join("");
68
84
  /**
69
85
  * The module-independent base: `apps/web` (the Next.js presentation shell every
70
86
  * AILK site has) plus the standalone forbidden-pattern floor. `may_import_from`
@@ -193,8 +209,8 @@ export const MODULE_SLICES = {
193
209
  },
194
210
  },
195
211
  },
196
- // ── docs — mountable presentation surface (fumadocs) ─────────────────────
197
- docs: {
212
+ // ── docs-blogPro module: both fumadocs surfaces (D-46) ────────────────
213
+ "docs-blog": {
198
214
  webImports: ["packages/fumadocs-schema"],
199
215
  packages: {
200
216
  "fumadocs-schema": {
@@ -202,27 +218,157 @@ export const MODULE_SLICES = {
202
218
  may_import_from: ["packages/schema", "packages/validation"],
203
219
  },
204
220
  },
205
- // ownedFiles (S1, #5088). packages/fumadocs-schema/** is deliberately NOT
206
- // listed: it is a JSON-LD bridge over the oss `packages/schema`, consumed
207
- // by the Free `/blog` route, and was tiered `oss` in architecture.yaml
208
- // (Sam, 2026-09-10, REL-015 #5182 Q3). The docs module owns its ROUTES,
209
- // not that helper the strip keeps the package in the public tree.
210
- // (`webImports` above still names it, so a docs scaffold still installs it.)
221
+ // ownedFiles (S1, #5088; widened by #5365). The standing reason to list
222
+ // only the `/docs` route was that `/blog` was a FREE route, which made
223
+ // `lib/source.ts` and `source.config.ts` genuinely mixed files a strip
224
+ // tool could not touch without breaking every OSS build. D-46 removed
225
+ // that premise: docs and blog are ONE Pro module and Free has no blog, so
226
+ // both files are wholly module-owned and move whole (#5365 D3). Nothing
227
+ // in a Free `apps/web` resolves a Fumadocs collection afterwards, which is
228
+ // also why `apps/web/content/<locale>/{blog,docs}` no longer has to exist
229
+ // in every scaffold — see `programmatic.ts`'s note and #5365 D3.
230
+ //
231
+ // `packages/fumadocs-schema/**` is still deliberately NOT listed. It is a
232
+ // permission, not a physical slice: `webImports` above names it so a
233
+ // scaffold that installs this module declares the import, while the
234
+ // package itself stays in the tree for this repo's own marketing site. Its
235
+ // tier, licence and npm access flip together under #5363 (D-43) — after
236
+ // this extraction, because architecture.yaml's recorded reason for the
237
+ // current `oss` tier is the Free `/blog` route this card just retired.
238
+ //
239
+ // SOURCE files only (the rule above): the three `content/en/**` MDX files
240
+ // and every test/story companion carry the `oss: false` marker but are not
241
+ // listed here.
242
+ ownedFiles: [
243
+ // The two route surfaces the module mounts.
244
+ "apps/web/app/[locale]/blog/[[...slug]]/page.tsx",
245
+ "apps/web/app/[locale]/docs/[[...slug]]/page.tsx",
246
+ // The Fumadocs source pair — both whole, both loaders (D3).
247
+ "apps/web/source.config.ts",
248
+ "apps/web/lib/source.ts",
249
+ // The route adapters and the jest stub for the codegen output.
250
+ "apps/web/lib/blog-list-data.ts",
251
+ "apps/web/lib/blog-post-data.ts",
252
+ "apps/web/lib/__mocks__/source-server.js",
253
+ // The module's own /llms.txt variant; the bundle installs it onto
254
+ // app/llms.txt/route.ts (#5365 D9, install-path mapping owned by #5373).
255
+ "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",
268
+ ],
269
+ // patches (spec 5418 § 3 D1, Group G) — every Group G row that edits a
270
+ // file the buyer's scaffold already has. Values transcribed verbatim
271
+ // from docs/specs/5365-docs-blog-module-spec.md § 4 Group G via
272
+ // docs/specs/5418-module-bundle-patch-set-spec.md § 3 D1's table; the
273
+ // fumadocs-core/fumadocs-mdx version strings are read from this repo's
274
+ // own apps/web/package.json at HEAD.
275
+ //
276
+ // Group G's tsconfig.json row (D1 row 8 — restoring the
277
+ // `../../packages/fumadocs-schema` project reference) is deliberately
278
+ // NOT included here. Re-anchoring D1 against a real keyless scaffold
279
+ // (5418 build, Deliverable 0) found `apps/web/tsconfig.json` carries no
280
+ // `references` key at all in ANY scaffold, regardless of `--modules`
281
+ // selection: `build-templates.mjs`'s `stripDanglingProjectReferences()`
282
+ // runs once over the fixed templates/ snapshot, which never contains a
283
+ // `packages/` directory, so every one of the monorepo's 15 references
284
+ // (including `fumadocs-schema`, position 6) resolves to a nonexistent
285
+ // path and the whole property is dropped. `@working-theory/fumadocs-schema`
286
+ // is delivered to a scaffold as an npm/bundle dependency under
287
+ // `node_modules/`, never as a `packages/fumadocs-schema` source sibling
288
+ // (see this slice's own `ownedFiles` comment above), so restoring the
289
+ // reference would immediately be dangling again in every scaffold — the
290
+ // exact condition `stripDanglingProjectReferences()` exists to prevent,
291
+ // which breaks `next build`'s TypeScript pass (#4090 item 2). This is
292
+ // the same reasoning the commerce row (5418 D1 row 9) already applies to
293
+ // its own tsconfig.json omission; applied here for consistency.
211
294
  //
212
- // apps/web's own `/docs` catch-all route IS module-owned and IS listed
213
- // (verified against the filesystemit exists today, contrary to this
214
- // slice's header comment suggesting docs may have "zero apps/web files
215
- // today"). Its two closest neighbors are deliberately NOT listed, because
216
- // both are genuinely mixed with the base `/blog` route rather than
217
- // docs-exclusive (checked by reading each file in full):
218
- // - apps/web/lib/source.ts — exports BOTH `blogSource` and
219
- // `docsSource` from one file; `/blog` (base) needs the former.
220
- // - apps/web/source.config.ts — defines BOTH the `blog` and `docs`
221
- // Fumadocs collections in one `defineDocs` pair.
222
- // A strip tool cannot delete either without breaking `/blog`, which ships
223
- // in every OSS build regardless of the docs module. apps/web/content/en/docs/
224
- // (the MDX content itself, not source) is also excluded — content, not code.
225
- ownedFiles: ["apps/web/app/[locale]/docs/[[...slug]]/page.tsx"],
295
+ // Group G's fourth jest.config.cjs item (the `import.meta` transform
296
+ // entry) needs no patch either the scaffold already ships it
297
+ // untouched (5418 D1 row 7b) so `add-transform` has no concrete use
298
+ // here.
299
+ patches: [
300
+ {
301
+ target: "apps/web/package.json",
302
+ kind: "json",
303
+ ops: [
304
+ {
305
+ op: "merge-keys",
306
+ path: ["dependencies"],
307
+ entries: {
308
+ "fumadocs-core": "^16.10.4",
309
+ "fumadocs-mdx": "^15.0.12",
310
+ "@working-theory/fumadocs-schema": "workspace:*",
311
+ },
312
+ },
313
+ {
314
+ op: "merge-keys",
315
+ path: ["scripts"],
316
+ entries: {
317
+ prebuild: "fumadocs-mdx",
318
+ pretypecheck: "fumadocs-mdx",
319
+ pretest: "fumadocs-mdx",
320
+ },
321
+ },
322
+ ],
323
+ },
324
+ {
325
+ target: "apps/web/next.config.mjs",
326
+ kind: "next-config",
327
+ ops: [
328
+ {
329
+ op: "add-transpile-package",
330
+ package: "@working-theory/fumadocs-schema",
331
+ },
332
+ {
333
+ op: "wrap-export-with",
334
+ importLine: 'import { createMDX } from "fumadocs-mdx/next";\n',
335
+ find: "withNextIntl(nextConfig)",
336
+ replaceWith: "withNextIntl(createMDX()(nextConfig))",
337
+ },
338
+ ],
339
+ },
340
+ {
341
+ target: "apps/web/jest.config.cjs",
342
+ kind: "jest-config",
343
+ ops: [
344
+ {
345
+ op: "add-transform-ignore-pattern",
346
+ find: '"/node_modules/(?!(\\\\.pnpm/)?@working-theory(?:\\\\+[^/]*?@?[^/]*?|/[^/]+)(/|$))"',
347
+ replaceWith: '"/node_modules/(?!(\\\\.pnpm/)?(fumadocs-[^/]*?@?[^/]*?|@working-theory(?:\\\\+[^/]*?@?[^/]*?|/[^/]+))(/|$))"',
348
+ },
349
+ {
350
+ op: "add-module-name-mapper",
351
+ pattern: "^@/\\\\.source/server$",
352
+ value: "<rootDir>/lib/__mocks__/source-server.js",
353
+ },
354
+ {
355
+ op: "add-module-name-mapper",
356
+ pattern: "^@/\\\\.source$",
357
+ value: "<rootDir>/.source/index.ts",
358
+ },
359
+ {
360
+ op: "add-module-name-mapper",
361
+ pattern: "^@working-theory/fumadocs-schema$",
362
+ value: "<rootDir>/node_modules/@working-theory/fumadocs-schema/dist/index.js",
363
+ },
364
+ ],
365
+ },
366
+ ],
367
+ // postInstall shrinks to what patches cannot express (D5): the patch
368
+ // step edits package.json but never runs a package manager on the
369
+ // buyer's behalf, so a fresh pnpm-lock.yaml resolution is still the
370
+ // buyer's own next step.
371
+ postInstall: "run pnpm install",
226
372
  },
227
373
  // ── multi-tenant — full-stack module (data + api + presentation) ─────────
228
374
  //
@@ -252,7 +398,7 @@ export const MODULE_SLICES = {
252
398
  },
253
399
  api: {
254
400
  path: "apps/api",
255
- description: "Public HTTP API — routes + services (Fastify). Owns webhooks, checkout, email, server-side analytics.",
401
+ description: "Public HTTP API — routes + services (Fastify). Owns webhooks, email, server-side analytics — checkout moved to the commerce module (D-47).",
256
402
  may_import_from: [
257
403
  "database",
258
404
  "packages/analytics",
@@ -317,7 +463,7 @@ export const MODULE_SLICES = {
317
463
  // Object.assign merge in deriveArchitecture is order-independent (a
318
464
  // multi-tenant-only selection stays self-consistent).
319
465
  templates: {
320
- description: "Shell/Chrome composition — WorkspaceShell + ArtifactEditorShell (tier-5 shells)",
466
+ description: "Shell/Chrome composition — AppShell (tier-5 shell); WorkspaceShell + ArtifactEditorShell ship inside this module itself, not the public package (D-45)",
321
467
  may_import_from: ["packages/theme", "packages/ui", "packages/validation"],
322
468
  },
323
469
  theme: {
@@ -435,6 +581,24 @@ export const MODULE_SLICES = {
435
581
  // not a documented module boundary — flagged for reviewer judgment
436
582
  // rather than asserted here.
437
583
  //
584
+ // apps/web/app/[locale]/(authed)/waitlist/** (#5367 FR4, D8): the operator
585
+ // waitlist-experiments dashboard UI. Reads GET /v1/waitlist-experiments,
586
+ // whose API half is already listed above. No prior module claimed
587
+ // apps/web at all for multi-tenant — #5182 §8 flagged this gap and left
588
+ // it for this card. Presentation follows its API (D-48: "Operator review
589
+ // dashboards stay in Pro Plus").
590
+ //
591
+ // ESCALATION (§5.3, posted to Epic #5378): the spec's §4.3 file list names
592
+ // 12 files including format.ts and components/ForbiddenState.tsx as
593
+ // multi-tenant-owned. Both are imported by the FREE
594
+ // apps/web/app/[locale]/(authed)/account/page.tsx (lines 55-56) — D-49
595
+ // keeps that page in Free. Listing them here would remove a Free
596
+ // consumer's dependency from the starter (§5.3's stop condition). Treated
597
+ // the same as this slice's own project-listings/index.ts + respond.ts
598
+ // precedent immediately above: a file shared between the free and paid
599
+ // surfaces stays OUT of ownedFiles. Only the other 10 of the 12 are
600
+ // listed below.
601
+ //
438
602
  // database/auth/ and database/content/ — investigated, NOT listed.
439
603
  // Both namespaces DO have their own schema.prisma file (a real
440
604
  // filesystem split via Prisma's multi-file schema feature), but each
@@ -472,6 +636,16 @@ export const MODULE_SLICES = {
472
636
  "apps/api/src/services/waitlist-dashboard.ts",
473
637
  "apps/api/src/lib/tenant-db.ts",
474
638
  "apps/api/src/lib/ws-token.ts",
639
+ "apps/web/app/[locale]/(authed)/waitlist/page.tsx",
640
+ "apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx",
641
+ "apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx",
642
+ "apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx",
643
+ "apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx",
644
+ "apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx",
645
+ "apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx",
646
+ "apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx",
647
+ "apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx",
648
+ "apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx",
475
649
  "apps-paid/workspace/**",
476
650
  ],
477
651
  },
@@ -511,7 +685,7 @@ export const MODULE_SLICES = {
511
685
  apps: {
512
686
  api: {
513
687
  path: "apps/api",
514
- description: "Public HTTP API — routes + services (Fastify). Owns webhooks, checkout, email, server-side analytics.",
688
+ description: "Public HTTP API — routes + services (Fastify). Owns webhooks, email, server-side analytics — checkout moved to the commerce module (D-47).",
515
689
  may_import_from: [
516
690
  "database",
517
691
  "packages/analytics",
@@ -643,6 +817,337 @@ export const MODULE_SLICES = {
643
817
  "apps/web/app/[locale]/schedule/confirmed/page.tsx",
644
818
  ],
645
819
  },
820
+ // ── signal-collectors — flows, deliverables, project listings, waitlist
821
+ // signups (D-48, #5366) ───────────────────────────────────────────────
822
+ //
823
+ // ownedFiles verified against the current filesystem, server.ts's
824
+ // module-conditional registration (isModuleEnabled('signal-collectors')),
825
+ // and a repo-wide grep of every importer of each candidate file (an
826
+ // independent re-grep, not carried over from the spec's own table).
827
+ //
828
+ // apps/api/src/routes/project-listings/index.ts is MIXED (D4): it holds
829
+ // this module's five anonymous/founder-read re-exports (lines 1-55) AND
830
+ // multi-tenant's TENANT_HANDLERS table + projectListingTenantRoutes
831
+ // (lines 57-139) in one file. NOT listed here — never deleted/edited on
832
+ // its own, exactly like the scheduling/multi-tenant precedent for a mixed
833
+ // barrel above.
834
+ //
835
+ // apps/api/src/routes/project-listings/respond.ts (`sendValidated`) IS
836
+ // listed, even though it is ALSO imported directly by the tenant routes
837
+ // get.ts/list.ts/patch.ts/patch-copy.ts (multi-tenant, Pro Plus territory
838
+ // — grep-verified, four direct importers outside this module). Same D6
839
+ // reasoning as the services below: every non-territory consumer is a
840
+ // strictly higher (Pro Plus) level, never Free, so this module owns the
841
+ // file and #5367 records the prerequisite.
842
+ //
843
+ // Ten services below are reached from Pro Plus's project-listings tenant
844
+ // routes (get.ts/list.ts/patch.ts/patch-copy.ts) or commerce's recommender,
845
+ // directly or transitively through project-listing-decision.ts /
846
+ // project-listing-flow.ts — grep-verified, not carried over from the
847
+ // spec's table (which independently reaches the same eight; this pass adds
848
+ // waitlist-scoring.ts and playbook-compile.ts, both of which the finalize
849
+ // scorecard already flagged as under-described). D6's rule: this module
850
+ // owns the shared file because every OTHER consumer is a superset level
851
+ // (OFFERINGS.md §4a Table 1 — Pro Plus contains Pro; the commerce add-on is
852
+ // never sold at Free); #5364/#5367 each record this module as a
853
+ // prerequisite of the routes that reach it rather than re-homing the file.
854
+ // - flow-engine.ts — pro-plus, via project-listing-flow.ts:49 ←
855
+ // project-listing-decision.ts:81 ← patch.ts:38
856
+ // - waitlist-scoring.ts — pro-plus, transitively (sole importer is
857
+ // flow-engine.ts:56, which is itself shared)
858
+ // - playbook-compile.ts — pro-plus, transitively (imported by
859
+ // waitlist-scoring.ts:33 and, in-module, by
860
+ // playbook-render.ts:62 and
861
+ // routes/deliverables/index.ts:44)
862
+ // - project-listings.ts, listing-config.ts, project-listing-decision.ts,
863
+ // project-listing-flow.ts, project-listing-issue.ts,
864
+ // waitlist-experiments.ts — pro-plus, direct or transitive via the
865
+ // decision/flow chain above
866
+ // - deliverable-fulfillment.ts — commerce, via recommender-capture.ts:70.
867
+ // NOT ADDED to ownedFiles below, unlike the rest of this list — spec
868
+ // §5.4's escalation boundary fires for this ONE file: D6's premise is
869
+ // "every OTHER consumer is a superset level, never Free," but
870
+ // recommender-capture.ts (and recommender.ts above it, and server.ts
871
+ // above THAT via a plain static import) is UNCONDITIONAL today — #5364
872
+ // has not landed to gate it behind isModuleEnabled("commerce"). Adding
873
+ // this file to ownedFiles made `paidModuleTemplateExclusions()`'s
874
+ // import-graph closure exclude recommender.ts, then server.ts itself,
875
+ // from every keyless scaffold (empirically found, #5366 resume pass) —
876
+ // a base-breaking regression a template snapshot must never produce.
877
+ // Revert this line once #5364 lands and gates the recommender routes;
878
+ // until then the file ships in every scaffold regardless of module
879
+ // selection, exactly as it did before this card (a narrower, temporary
880
+ // miss than the one D6 accepts on purpose for the other nine).
881
+ //
882
+ // apps/api/src/services/listing-promotion.ts has ZERO production
883
+ // importers anywhere in the repo (only its own test) — confirmed dead code
884
+ // (spec §7 Q5). Listed anyway: it is listing code by subject, D-48 places
885
+ // the instrument here, and a reachability grep cannot place an unreached
886
+ // file — this is a recorded pre-existing defect, not fixed by this card.
887
+ //
888
+ // apps/api/src/bin/deliverable-upload.ts imports only `prisma` (no moved
889
+ // service) but is a deliverables CLI a Free starter should not ship —
890
+ // listed by subject, not by the import-criterion the other six bins meet.
891
+ //
892
+ // apps/web: FlowStepperClient.tsx, ResultsClient.tsx and
893
+ // ListingApplicationClient.tsx are this module's OWN files (below), so
894
+ // their packages/ui imports (FlowStepper, FlowSidePanel,
895
+ // ListingApplicationFlow) are in-module edges, not Free ones, despite
896
+ // living under apps/web rather than apps-paid.
897
+ //
898
+ // packages/ui: eleven components (D8) plus the NEW slice registration
899
+ // entry point (D8a, mirroring sections/slices/scheduling.ts). Four of the
900
+ // eleven (ProjectListingShelves, ListingPrivacyNotice, ProjectListingCard,
901
+ // ResultsReveal) have no consumer anywhere in the repo today — grep-
902
+ // confirmed orphaned exports, not a module-boundary fact; D8 places them
903
+ // here by subject (project-listing / results-reveal UI) rather than by
904
+ // reachability, since reachability cannot place an unreached file either.
905
+ "signal-collectors": {
906
+ ownedFiles: [
907
+ "apps/api/src/routes/flows/index.ts",
908
+ // README.md documents the flows rate-limit rationale (#4730) — not a
909
+ // test/generated file, and its absence is exactly what left a stray
910
+ // apps/api/src/routes/flows/ directory in a keyless templates/ snapshot
911
+ // (empirically found, #5366) since the import-graph closure below only
912
+ // scans source extensions, never markdown.
913
+ "apps/api/src/routes/flows/README.md",
914
+ "apps/api/src/routes/deliverables/index.ts",
915
+ "apps/api/src/routes/slack/index.ts",
916
+ "apps/api/src/routes/slack/actions.ts",
917
+ "apps/api/src/routes/waitlist-signups/index.ts",
918
+ // apps/api/src/routes/project-listings/{start,patch-draft,submit,public,
919
+ // me,respond,resume-token}.ts and services/{project-listings,
920
+ // listing-config}.ts deliberately NOT listed — see the comment above
921
+ // this array (D-48/#5366 escalation: the mixed-barrel `index.ts` in
922
+ // that directory is reached from server.ts by a plain, literal
923
+ // `import()` for multi-tenant's tenant routes, and statically imports
924
+ // all nine of these files itself, so `paidModuleTemplateExclusions()`'s
925
+ // import-graph closure excluded `index.ts`, then `server.ts` itself,
926
+ // from every keyless scaffold (empirically found, #5366 resume pass,
927
+ // same failure mode as the deliverable-fulfillment.ts case below but a
928
+ // permanent structural one rather than a temporal gap pending another
929
+ // PR). This is also the ORIGINAL multi-tenant/#5088 card's own
930
+ // conclusion about these same nine files (lines 556-571 above) —
931
+ // D-48 moves their tier ownership to signal-collectors but not their
932
+ // must-ship-unconditionally constraint. Registration of every route
933
+ // that reaches them stays gated by `isModuleEnabled("signal-collectors")`
934
+ // in server.ts; only the source files themselves ship regardless.
935
+ "apps/api/src/services/flow-engine.ts",
936
+ "apps/api/src/services/flow-aggregate.ts",
937
+ "apps/api/src/services/waitlist-signups.ts",
938
+ "apps/api/src/services/waitlist-scoring.ts",
939
+ "apps/api/src/services/playbook-compile.ts",
940
+ "apps/api/src/services/playbook-render.ts",
941
+ "apps/api/src/services/listing-csv-sweep.ts",
942
+ "apps/api/src/services/waitlist-followup.ts",
943
+ "apps/api/src/services/listing-promotion.ts",
944
+ "apps/api/src/services/project-listing-decision.ts",
945
+ "apps/api/src/services/project-listing-flow.ts",
946
+ "apps/api/src/services/project-listing-issue.ts",
947
+ "apps/api/src/services/waitlist-experiments.ts",
948
+ // deliverable-fulfillment.ts deliberately NOT listed — see the comment
949
+ // above this array (spec §5.4 escalation boundary, temporary pending #5364).
950
+ "apps/api/src/bin/deliverable-resend.ts",
951
+ "apps/api/src/bin/deliverable-upload.ts",
952
+ "apps/api/src/bin/session-retention-sweep.ts",
953
+ "apps/api/src/bin/seed-presets.ts",
954
+ "apps/api/src/bin/listing-csv-sweep.ts",
955
+ "apps/api/src/bin/followup-sweep.ts",
956
+ "apps/api/src/bin/seed-waitlist-experiments.ts",
957
+ "apps/mcp/src/tools/get_flow.ts",
958
+ "apps/mcp/src/tools/submit_flow_step.ts",
959
+ // apps/web/app/[locale]/flows/** deliberately NOT listed here (unlike
960
+ // the docs-blog precedent's own `blog`/`docs` page.tsx entries above),
961
+ // even though every file under it is signal-collectors territory and
962
+ // still carries its own `oss: false` marker for the OSS-strip. Every
963
+ // one of these files is ALREADY excluded from a keyless scaffold by the
964
+ // pre-existing, independent `MODULE_SURFACES["signal-collectors"] =
965
+ // ["flows"]` → `excludedRoutePaths()` mechanism (#4154), which removes
966
+ // the WHOLE `apps/web/app/[locale]/flows` directory at copy time
967
+ // whenever the "flows" surface isn't selected — and `resolveSurfaces`
968
+ // refuses `--surfaces flows` without the module (#5365 FR5/D12), so
969
+ // there is no way to reach "flows carried, signal-collectors absent".
970
+ // Adding them to `ownedFiles` ALSO excludes them from `templates/`
971
+ // itself (build-templates.mjs), which broke `sync-routes`'s pre-#5366
972
+ // upgrade-path fixture (`flows/[slug]/results/[payload]`, #4891/#4986)
973
+ // and — more importantly — would silently strip a REAL consumer's
974
+ // `sync-routes` tool of the ability to ever restore these files after
975
+ // scaffolding, since that tool diffs against `templates/` alone and has
976
+ // no module/licence concept of its own (empirically found, #5366 resume
977
+ // pass). `projects/apply/ListingApplicationClient.tsx` below is
978
+ // different in kind: it sits under the UNRELATED "projects" content
979
+ // surface's URL segment, which `excludedRoutePaths` does not gate on
980
+ // signal-collectors at all, so it genuinely needs `ownedFiles` to be
981
+ // excluded from a keyless scaffold — see `sync-routes.ts`'s
982
+ // `NESTED_FOREIGN_MODULE_ENTRIES` for the companion fix that keeps its
983
+ // presence from falsely reading as "projects carried".
984
+ "apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx",
985
+ "packages/ui/src/sections/ListingApplicationFlow.tsx",
986
+ "packages/ui/src/sections/ProjectListingShelves.tsx",
987
+ "packages/ui/src/sections/FlowStepper.tsx",
988
+ "packages/ui/src/blocks/FlowSidePanel.tsx",
989
+ "packages/ui/src/blocks/ListingFullPreview.tsx",
990
+ "packages/ui/src/blocks/ListingQuestionReview.tsx",
991
+ "packages/ui/src/blocks/ListingTilePreview.tsx",
992
+ "packages/ui/src/blocks/ListingPrivacyNotice.tsx",
993
+ "packages/ui/src/blocks/ProjectListingCard.tsx",
994
+ "packages/ui/src/blocks/ResponseChart.tsx",
995
+ "packages/ui/src/blocks/ResultsReveal.tsx",
996
+ "packages/ui/src/sections/slices/signal-collectors.ts",
997
+ ],
998
+ },
999
+ // ── commerce — per-site paid add-on (D-47, amending D-26) ─────────────────
1000
+ //
1001
+ // Checkout, the Stripe webhook receiver, flow checkouts and the recommender
1002
+ // (D-47's disposition sentence, verbatim). The base API keeps only
1003
+ // `isModuleEnabled("commerce")`; every registration loads through
1004
+ // `SLICE_PLUGINS` (server.ts). `apps.api.may_import_from` names
1005
+ // `packages/stripe-client` — the module's own package (D5) — mirroring the
1006
+ // `multi-tenant`/`scheduling` slices' identical entry, which already
1007
+ // document "checkout moved to the commerce module (D-47)" for the same
1008
+ // reason those two slices state theirs: `deriveArchitecture` merges `api`
1009
+ // blocks with `Object.assign`, so every slice declaring `api` must agree.
1010
+ //
1011
+ // Q1 (spec §7): a `multi-tenant` deployment using the Stripe billing portal
1012
+ // or org-scoped webhook routing (`routes/billing/portal.ts`,
1013
+ // `routes/webhooks/stripe.ts`'s org-billing branch) also depends on
1014
+ // `lib/stripe.ts` and the webhook receiver this module owns — declared
1015
+ // here, not engineered around.
1016
+ commerce: {
1017
+ apps: {
1018
+ api: {
1019
+ path: "apps/api",
1020
+ description: "Public HTTP API — routes + services (Fastify). Owns checkout, the Stripe webhook receiver, flow checkouts and the recommender (D-47) — the Stripe setup and stripe-client's sole apps/api reach.",
1021
+ may_import_from: [
1022
+ "database",
1023
+ "packages/analytics",
1024
+ "packages/email",
1025
+ "packages/license",
1026
+ "packages/schema",
1027
+ "packages/site-config",
1028
+ "packages/stripe-client",
1029
+ "packages/validation",
1030
+ ],
1031
+ may_not_import_from: [
1032
+ "apps/mcp",
1033
+ "apps/web",
1034
+ "packages/theme",
1035
+ "packages/ui",
1036
+ ],
1037
+ },
1038
+ },
1039
+ packages: {
1040
+ // Duplicated VERBATIM from the multi-tenant slice's own "stripe-client"
1041
+ // entry (§7 Q1 — a multi-tenant deployment using the Stripe billing
1042
+ // portal or org-billing webhook routing also installs commerce, so
1043
+ // both slices can be selected together) — the same order-independence
1044
+ // reason the multi-tenant/scheduling slices state for `validation`
1045
+ // below: `deriveArchitecture` merges `packages` blocks with
1046
+ // `Object.assign`, so two slices declaring this key differently would
1047
+ // make the derivation depend on module order.
1048
+ "stripe-client": {
1049
+ description: "Stripe SDK wrapper + webhook signature verification",
1050
+ may_import_from: ["packages/validation"],
1051
+ },
1052
+ // Duplicated VERBATIM from the marketing slice for the same
1053
+ // order-independence reason the multi-tenant/scheduling slices state.
1054
+ validation: {
1055
+ description: "Zod schemas + shared types",
1056
+ may_import_from: [],
1057
+ },
1058
+ },
1059
+ // webImports omitted (§4.4): no commerce component reaches apps/web
1060
+ // through a package apps/web does not already import — packages/ui is
1061
+ // base, and architecture.yaml already forbids apps/web -> stripe-client.
1062
+ forbiddenPatterns: [
1063
+ {
1064
+ pattern: STRIPE_LIVE_KEY_PATTERN,
1065
+ paths_excluded: ["apps/api/**"],
1066
+ reason: "The commerce credential belongs to apps/api, behind assertSandboxKey's sandbox-only allow-list. A live-mode-shaped key anywhere else is a leak, not a config choice",
1067
+ },
1068
+ ],
1069
+ // ownedFiles (S1, #5088). SOURCE files only (#5182 D6) — no __tests__/,
1070
+ // no .stories.tsx, no .text.ts... except .text.ts siblings ARE source
1071
+ // (they ship as runtime copy modules, not test fixtures), so the ten
1072
+ // annotated below are listed. 42 in all: 9 apps/api + 26 packages/ui
1073
+ // (15 components + 10 .text.ts siblings + this slice's own
1074
+ // sections/slices/commerce.ts) + 7 apps/web.
1075
+ ownedFiles: [
1076
+ "apps/api/src/lib/stripe.ts",
1077
+ "apps/api/src/routes/checkout/index.ts",
1078
+ "apps/api/src/routes/checkout/sessions.ts",
1079
+ "apps/api/src/routes/webhooks/index.ts",
1080
+ "apps/api/src/routes/webhooks/stripe.ts",
1081
+ // README.md documents the Stripe webhook receiver — not a test/
1082
+ // generated file, and its absence is exactly what left a stray
1083
+ // apps/api/src/routes/webhooks/ directory in a keyless templates/
1084
+ // snapshot (empirically found, #5364 build pass): the import-graph
1085
+ // closure only scans source extensions, never markdown, so once
1086
+ // index.ts and stripe.ts are excluded this is the one file left
1087
+ // behind. Same defect class #5366 found and fixed the same way for
1088
+ // routes/flows/README.md — see that entry above.
1089
+ "apps/api/src/routes/webhooks/README.md",
1090
+ "apps/api/src/routes/flow-checkouts/index.ts",
1091
+ "apps/api/src/routes/flows/recommender.ts",
1092
+ "apps/api/src/services/recommender-capture.ts",
1093
+ "apps/api/src/services/flow-checkouts.ts",
1094
+ "packages/ui/src/blocks/CheckoutForm.tsx",
1095
+ "packages/ui/src/blocks/CheckoutForm.text.ts",
1096
+ "packages/ui/src/blocks/CheckoutPriceCard.tsx",
1097
+ "packages/ui/src/blocks/PaymentLinkCard.tsx",
1098
+ "packages/ui/src/blocks/PaymentLinkCard.text.ts",
1099
+ "packages/ui/src/blocks/PaymentLinkForm.tsx",
1100
+ "packages/ui/src/blocks/PaymentLinkForm.text.ts",
1101
+ "packages/ui/src/blocks/PaymentLinkList.tsx",
1102
+ "packages/ui/src/blocks/InvoiceForm.tsx",
1103
+ "packages/ui/src/blocks/InvoiceForm.text.ts",
1104
+ "packages/ui/src/blocks/InvoiceList.tsx",
1105
+ "packages/ui/src/blocks/InvoiceList.text.ts",
1106
+ "packages/ui/src/blocks/SubscriptionStatus.tsx",
1107
+ "packages/ui/src/blocks/SubscriptionStatus.text.ts",
1108
+ "packages/ui/src/blocks/StripeActionButton.tsx",
1109
+ "packages/ui/src/blocks/PaymentStatusBadge.tsx",
1110
+ "packages/ui/src/blocks/PaymentStatusBadge.text.ts",
1111
+ "packages/ui/src/blocks/PaymentResult.tsx",
1112
+ "packages/ui/src/blocks/PaymentResult.text.ts",
1113
+ "packages/ui/src/blocks/PortalEntry.tsx",
1114
+ "packages/ui/src/panels/InvoiceDetail.tsx",
1115
+ "packages/ui/src/panels/InvoiceDetail.text.ts",
1116
+ "packages/ui/src/sections/PaymentReceipt.tsx",
1117
+ "packages/ui/src/sections/PricingTable.tsx",
1118
+ "packages/ui/src/sections/PricingTable.text.ts",
1119
+ "packages/ui/src/sections/slices/commerce.ts",
1120
+ "apps/web/app/[locale]/flows/[slug]/checkout.actions.ts",
1121
+ "apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx",
1122
+ "apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx",
1123
+ "apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx",
1124
+ "apps/web/app/[locale]/dev/purchase/page.tsx",
1125
+ "apps/web/app/[locale]/dev/purchase/error.tsx",
1126
+ "apps/web/app/[locale]/dev/purchase/purchase.actions.ts",
1127
+ ],
1128
+ // The scaffold-commerce authoring skill (D-47, #5449): moved by `git mv`
1129
+ // out of the Free starter's `templates/.claude/skills/` (never stripped —
1130
+ // it sits outside build-templates.mjs's SNAPSHOT_DIRS/overlay, so
1131
+ // ownedFiles/paidModuleTemplateExclusions cannot reach it) to
1132
+ // `module-templates/commerce/.claude/skills/scaffold-commerce/`, and
1133
+ // delivered into a keyed scaffold by `installMap` instead. Ten literal
1134
+ // entries, not one directory entry: `extractModuleBundle`'s
1135
+ // `validateInstallMap` requires every source to be a FILE the bundle
1136
+ // produced (`extract-module-bundle.ts`'s `!sourceStat.isFile()` check) —
1137
+ // a directory source is rejected outright.
1138
+ installMap: {
1139
+ "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/SKILL.md": ".claude/skills/scaffold-commerce/SKILL.md",
1140
+ "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/references/lookup-keys-template.md": ".claude/skills/scaffold-commerce/references/lookup-keys-template.md",
1141
+ "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md": ".claude/skills/scaffold-commerce/references/marketplace-brand-pages.md",
1142
+ "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts": ".claude/skills/scaffold-commerce/templates/checkout-route.template.ts",
1143
+ "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts": ".claude/skills/scaffold-commerce/templates/invoice-route.template.ts",
1144
+ "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl": ".claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl",
1145
+ "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts": ".claude/skills/scaffold-commerce/templates/payment-link-route.template.ts",
1146
+ "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/portal-route.template.ts": ".claude/skills/scaffold-commerce/templates/portal-route.template.ts",
1147
+ "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts": ".claude/skills/scaffold-commerce/templates/stripe-config.template.ts",
1148
+ "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts": ".claude/skills/scaffold-commerce/templates/subscription-route.template.ts",
1149
+ },
1150
+ },
646
1151
  };
647
1152
  /**
648
1153
  * Normalize a requested module list to the unique set, ordered by the canonical
@@ -661,3 +1166,141 @@ export function normalizeModules(modules) {
661
1166
  }
662
1167
  return RECOGNIZED_MODULES.filter((m) => requested.has(m));
663
1168
  }
1169
+ /**
1170
+ * Template-relative paths that must never be written into `templates/` for a
1171
+ * keyless starter: every PAID module's `ownedFiles` entry that resolves under
1172
+ * a `build-templates` snapshot root, plus every file under those roots that
1173
+ * imports one (transitively), which would otherwise ship dangling.
1174
+ *
1175
+ * This is the SINGLE starter-removal input for all four R1 extractions
1176
+ * (#5364 commerce, #5365 docs + blog, #5366 signal-collectors, #5367 Pro Plus
1177
+ * + scheduling). A module joins by gaining `ownedFiles` entries — never by
1178
+ * adding a path to a list here.
1179
+ *
1180
+ * The input is `ownedFiles`, NOT the `oss: false` marker set. The two answer
1181
+ * different questions: the strip's authority is their union (see the
1182
+ * `ownedFiles` doc above), while the STARTER's authority is `ownedFiles`
1183
+ * alone. The marker set includes the content-write trio, which D-44 keeps in
1184
+ * public code licence-checked at runtime — deriving the starter exclusion from
1185
+ * markers would remove a surface D-44 says must ship.
1186
+ *
1187
+ * Entries outside the snapshot roots — every `packages/**` path, and the paid
1188
+ * app roots — are not snapshotted at all and are filtered here rather than
1189
+ * special-cased at the call site. Today every glob entry is also out-of-root,
1190
+ * so globs never reach the literal-path matcher below.
1191
+ *
1192
+ * @param snapshotRoots repo-relative roots `build-templates.mjs` snapshots.
1193
+ * @param repoRoot absolute path to the monorepo root, for the import scan.
1194
+ * Passed in rather than derived from `import.meta.url`, which ts-jest cannot
1195
+ * load — the same reason `sync-routes.ts` documents for staying pure.
1196
+ */
1197
+ export function paidModuleTemplateExclusions(snapshotRoots, repoRoot) {
1198
+ const roots = new Set(snapshotRoots);
1199
+ const excluded = new Set();
1200
+ for (const moduleName of PAID_MODULES) {
1201
+ for (const owned of MODULE_SLICES[moduleName]?.ownedFiles ?? []) {
1202
+ const rel = owned.split(sep).join("/");
1203
+ if (!roots.has(rel.split("/")[0] ?? ""))
1204
+ continue;
1205
+ if (rel.includes("*"))
1206
+ continue;
1207
+ excluded.add(rel);
1208
+ }
1209
+ }
1210
+ // Companion closure by import graph, to a fixed point — never a filename
1211
+ // stem rule (#5182 D7), which misses eleven files and over-marks three.
1212
+ const candidates = listFilesUnder(repoRoot, [...roots]);
1213
+ for (;;) {
1214
+ let grew = false;
1215
+ for (const file of candidates) {
1216
+ if (excluded.has(file))
1217
+ continue;
1218
+ const imports = relativeImportsOf(join(repoRoot, file));
1219
+ for (const spec of imports) {
1220
+ const target = resolveRelativeImport(file, spec, candidates);
1221
+ if (target !== undefined && excluded.has(target)) {
1222
+ excluded.add(file);
1223
+ grew = true;
1224
+ break;
1225
+ }
1226
+ }
1227
+ }
1228
+ if (!grew)
1229
+ break;
1230
+ }
1231
+ return [...excluded].sort();
1232
+ }
1233
+ const SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
1234
+ /** Every source file under the given repo-relative roots, as "/"-joined paths. */
1235
+ function listFilesUnder(repoRoot, roots) {
1236
+ const out = new Set();
1237
+ const skip = new Set(["node_modules", ".next", "dist", ".turbo", ".source"]);
1238
+ const stack = roots.map((r) => r);
1239
+ while (stack.length > 0) {
1240
+ const rel = stack.pop();
1241
+ const abs = join(repoRoot, rel);
1242
+ let entries;
1243
+ try {
1244
+ entries = readdirSync(abs, { withFileTypes: true });
1245
+ }
1246
+ catch {
1247
+ continue;
1248
+ }
1249
+ for (const entry of entries) {
1250
+ if (skip.has(entry.name))
1251
+ continue;
1252
+ const childRel = `${rel}/${entry.name}`;
1253
+ if (entry.isDirectory())
1254
+ stack.push(childRel);
1255
+ else if (SOURCE_EXTS.some((e) => entry.name.endsWith(e)))
1256
+ out.add(childRel);
1257
+ }
1258
+ }
1259
+ return out;
1260
+ }
1261
+ /** The relative import specifiers a source file names (static imports + re-exports). */
1262
+ function relativeImportsOf(absPath) {
1263
+ let src;
1264
+ try {
1265
+ src = readFileSync(absPath, "utf8");
1266
+ }
1267
+ catch {
1268
+ return [];
1269
+ }
1270
+ const out = [];
1271
+ for (const m of src.matchAll(/(?:^|\n)\s*(?:import|export)[\s\S]{0,400}?from\s*["'](\.[^"']*)["']/g))
1272
+ out.push(m[1]);
1273
+ for (const m of src.matchAll(/\bimport\s*\(\s*["'](\.[^"']*)["']\s*\)/g))
1274
+ out.push(m[1]);
1275
+ return out;
1276
+ }
1277
+ /**
1278
+ * Resolve a relative specifier against the candidate set. TypeScript ESM writes
1279
+ * `./x.js` for `./x.ts`, so the `.js` → `.ts`/`.tsx` rewrite is the common case.
1280
+ */
1281
+ function resolveRelativeImport(fromFile, spec, candidates) {
1282
+ const base = posixJoin(dirname(fromFile.split("/").join(sep)), spec);
1283
+ const stem = base.replace(/\.(js|jsx|mjs|cjs)$/, "");
1284
+ for (const cand of [
1285
+ base,
1286
+ ...SOURCE_EXTS.map((e) => stem + e),
1287
+ ...SOURCE_EXTS.map((e) => `${stem}/index${e}`),
1288
+ ])
1289
+ if (candidates.has(cand))
1290
+ return cand;
1291
+ return undefined;
1292
+ }
1293
+ /** Join and normalize a "/"-separated path, resolving `.` and `..` segments. */
1294
+ function posixJoin(dir, spec) {
1295
+ const parts = `${dir.split(sep).join("/")}/${spec}`.split("/");
1296
+ const out = [];
1297
+ for (const part of parts) {
1298
+ if (part === "" || part === ".")
1299
+ continue;
1300
+ if (part === "..")
1301
+ out.pop();
1302
+ else
1303
+ out.push(part);
1304
+ }
1305
+ return out.join("/");
1306
+ }