create-ailk 0.1.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 (533) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +39 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +80 -0
  5. package/dist/copy.d.ts +8 -0
  6. package/dist/copy.js +109 -0
  7. package/dist/index.d.ts +10 -0
  8. package/dist/index.js +10 -0
  9. package/dist/lib/pro-callout.d.ts +12 -0
  10. package/dist/lib/pro-callout.js +20 -0
  11. package/dist/parse-args.d.ts +13 -0
  12. package/dist/parse-args.js +81 -0
  13. package/dist/programmatic.d.ts +49 -0
  14. package/dist/programmatic.js +89 -0
  15. package/dist/substitute.d.ts +31 -0
  16. package/dist/substitute.js +45 -0
  17. package/dist/validate.d.ts +8 -0
  18. package/dist/validate.js +70 -0
  19. package/package.json +47 -0
  20. package/templates/.claude/.gitkeep +0 -0
  21. package/templates/.claude/agents/web.md +80 -0
  22. package/templates/.claude/rules/architecture.md +62 -0
  23. package/templates/.claude/rules/preserve-existing-style.md +51 -0
  24. package/templates/.claude/skills/README.md +43 -0
  25. package/templates/.claude/skills/add-schema/SKILL.md +270 -0
  26. package/templates/.claude/skills/add-schema/references/conflict-handling.md +200 -0
  27. package/templates/.claude/skills/add-schema/references/file-finding.md +123 -0
  28. package/templates/.claude/skills/add-schema/references/schema-types.md +131 -0
  29. package/templates/.claude/skills/audit-site/SKILL.md +213 -0
  30. package/templates/.claude/skills/audit-site/references/common-errors.md +113 -0
  31. package/templates/.claude/skills/audit-site/references/findings-glossary.md +138 -0
  32. package/templates/.claude/skills/audit-site/references/severity-levels.md +114 -0
  33. package/templates/.claude/skills/provision-config/SKILL.md +243 -0
  34. package/templates/.claude/skills/provision-config/references/env-vars-template.md +184 -0
  35. package/templates/.claude/skills/provision-config/references/runbook-template.md +248 -0
  36. package/templates/.claude/skills/provision-config/references/vercel-json-template.md +98 -0
  37. package/templates/.claude/skills/scaffold-auth/SKILL.md +261 -0
  38. package/templates/.claude/skills/scaffold-auth/references/env-vars.md +91 -0
  39. package/templates/.claude/skills/scaffold-auth/references/oauth-app-setup.md +171 -0
  40. package/templates/.claude/skills/scaffold-commerce/SKILL.md +310 -0
  41. package/templates/.claude/skills/scaffold-commerce/references/lookup-keys-template.md +71 -0
  42. package/templates/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md +83 -0
  43. package/templates/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl +72 -0
  44. package/templates/.claude/skills/scaffold-page/SKILL.md +243 -0
  45. package/templates/.claude/skills/scaffold-page/references/page-types.md +169 -0
  46. package/templates/.claude/skills/scaffold-page/templates/content.mdx.tmpl +28 -0
  47. package/templates/.claude/skills/scaffold-page/templates/route.tsx.tmpl +28 -0
  48. package/templates/.claude/skills/suggest-site-pages/SKILL.md +242 -0
  49. package/templates/.claude/skills/suggest-site-pages/references/categories.md +287 -0
  50. package/templates/.env.example +205 -0
  51. package/templates/.gitkeep +2 -0
  52. package/templates/CLAUDE.md +142 -0
  53. package/templates/CONVENTIONS.md +222 -0
  54. package/templates/README.md +110 -0
  55. package/templates/apps/api/.env.example +40 -0
  56. package/templates/apps/api/CLAUDE.md +63 -0
  57. package/templates/apps/api/api/index.ts +144 -0
  58. package/templates/apps/api/jest.config.cjs +74 -0
  59. package/templates/apps/api/package.json +54 -0
  60. package/templates/apps/api/src/__tests__/factories/content-fixture.ts +138 -0
  61. package/templates/apps/api/src/__tests__/factories/index.ts +70 -0
  62. package/templates/apps/api/src/__tests__/factories/lead-fixture.ts +82 -0
  63. package/templates/apps/api/src/__tests__/index.test.ts +118 -0
  64. package/templates/apps/api/src/__tests__/server.test.ts +207 -0
  65. package/templates/apps/api/src/__tests__/services/enrichment-shim.test.ts +126 -0
  66. package/templates/apps/api/src/__tests__/setup.ts +24 -0
  67. package/templates/apps/api/src/cli.ts +30 -0
  68. package/templates/apps/api/src/config/__tests__/index.test.ts +97 -0
  69. package/templates/apps/api/src/config/index.ts +56 -0
  70. package/templates/apps/api/src/index.ts +32 -0
  71. package/templates/apps/api/src/lib/__mocks__/prisma-client.js +15 -0
  72. package/templates/apps/api/src/lib/__mocks__/prisma.ts +45 -0
  73. package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +178 -0
  74. package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +100 -0
  75. package/templates/apps/api/src/lib/__tests__/errors.test.ts +94 -0
  76. package/templates/apps/api/src/lib/__tests__/file-fallback.test.ts +144 -0
  77. package/templates/apps/api/src/lib/__tests__/fixtures/HomePage/home.mdx +8 -0
  78. package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +227 -0
  79. package/templates/apps/api/src/lib/__tests__/outbound-http.test.ts +125 -0
  80. package/templates/apps/api/src/lib/__tests__/prisma.test.ts +165 -0
  81. package/templates/apps/api/src/lib/__tests__/require-paid-tier.test.ts +129 -0
  82. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +74 -0
  83. package/templates/apps/api/src/lib/content-adapter.ts +86 -0
  84. package/templates/apps/api/src/lib/error-serializer.ts +77 -0
  85. package/templates/apps/api/src/lib/errors.ts +95 -0
  86. package/templates/apps/api/src/lib/file-fallback.ts +87 -0
  87. package/templates/apps/api/src/lib/lead-capture-store.ts +110 -0
  88. package/templates/apps/api/src/lib/locales.ts +201 -0
  89. package/templates/apps/api/src/lib/outbound-http.ts +122 -0
  90. package/templates/apps/api/src/lib/prisma.ts +21 -0
  91. package/templates/apps/api/src/lib/require-paid-tier.ts +53 -0
  92. package/templates/apps/api/src/lib/stripe.ts +30 -0
  93. package/templates/apps/api/src/lib/webhook-idempotency.ts +92 -0
  94. package/templates/apps/api/src/middleware/__tests__/adversarial/bearer-token-format.test.ts +111 -0
  95. package/templates/apps/api/src/middleware/__tests__/adversarial/cookie-or-bearer-precedence.test.ts +172 -0
  96. package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-bearer-skip.test.ts +119 -0
  97. package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-cookie-path.test.ts +138 -0
  98. package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +149 -0
  99. package/templates/apps/api/src/middleware/__tests__/adversarial/session-timeout.test.ts +139 -0
  100. package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +175 -0
  101. package/templates/apps/api/src/middleware/__tests__/auth.both-present-precedence.test.ts +76 -0
  102. package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +153 -0
  103. package/templates/apps/api/src/middleware/__tests__/auth.neither-present.test.ts +72 -0
  104. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +292 -0
  105. package/templates/apps/api/src/middleware/auth.ts +246 -0
  106. package/templates/apps/api/src/middleware/rate-limit.ts +132 -0
  107. package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +129 -0
  108. package/templates/apps/api/src/openapi/index.ts +12 -0
  109. package/templates/apps/api/src/openapi/spec.ts +408 -0
  110. package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +193 -0
  111. package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +201 -0
  112. package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +294 -0
  113. package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +486 -0
  114. package/templates/apps/api/src/routes/auth/tokens.ts +199 -0
  115. package/templates/apps/api/src/routes/auth/user.ts +244 -0
  116. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +588 -0
  117. package/templates/apps/api/src/routes/checkout/index.ts +13 -0
  118. package/templates/apps/api/src/routes/checkout/sessions.ts +185 -0
  119. package/templates/apps/api/src/routes/content/__tests__/create.test.ts +239 -0
  120. package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +201 -0
  121. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +141 -0
  122. package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +122 -0
  123. package/templates/apps/api/src/routes/content/__tests__/list-types.test.ts +44 -0
  124. package/templates/apps/api/src/routes/content/__tests__/locale.test.ts +118 -0
  125. package/templates/apps/api/src/routes/content/__tests__/rate-limit.test.ts +51 -0
  126. package/templates/apps/api/src/routes/content/__tests__/update.test.ts +252 -0
  127. package/templates/apps/api/src/routes/content/create.ts +114 -0
  128. package/templates/apps/api/src/routes/content/delete.ts +81 -0
  129. package/templates/apps/api/src/routes/content/get-page.ts +98 -0
  130. package/templates/apps/api/src/routes/content/index.ts +32 -0
  131. package/templates/apps/api/src/routes/content/list-pages.ts +83 -0
  132. package/templates/apps/api/src/routes/content/list-types.ts +19 -0
  133. package/templates/apps/api/src/routes/content/rate-limit.ts +13 -0
  134. package/templates/apps/api/src/routes/content/slug.ts +9 -0
  135. package/templates/apps/api/src/routes/content/update.ts +111 -0
  136. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +415 -0
  137. package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +165 -0
  138. package/templates/apps/api/src/routes/leads/index.ts +130 -0
  139. package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +296 -0
  140. package/templates/apps/api/src/routes/test/auth/session.ts +141 -0
  141. package/templates/apps/api/src/routes/webhooks/README.md +57 -0
  142. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +247 -0
  143. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +198 -0
  144. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +633 -0
  145. package/templates/apps/api/src/routes/webhooks/index.ts +41 -0
  146. package/templates/apps/api/src/routes/webhooks/stripe.ts +253 -0
  147. package/templates/apps/api/src/server.ts +157 -0
  148. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +395 -0
  149. package/templates/apps/api/src/services/enrichment-shim.ts +53 -0
  150. package/templates/apps/api/src/services/lead-routing.ts +338 -0
  151. package/templates/apps/api/tsconfig.json +34 -0
  152. package/templates/apps/api/vercel.json +17 -0
  153. package/templates/apps/mcp/.env.example +52 -0
  154. package/templates/apps/mcp/CLAUDE.md +84 -0
  155. package/templates/apps/mcp/__tests__/abuse/daily-cap.test.ts +52 -0
  156. package/templates/apps/mcp/__tests__/abuse/email-validator.test.ts +46 -0
  157. package/templates/apps/mcp/__tests__/abuse/honeypot.test.ts +36 -0
  158. package/templates/apps/mcp/__tests__/abuse/user-agent-deny.test.ts +54 -0
  159. package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +110 -0
  160. package/templates/apps/mcp/__tests__/audit-single-page.test.ts +213 -0
  161. package/templates/apps/mcp/__tests__/audit.test.ts +217 -0
  162. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +52 -0
  163. package/templates/apps/mcp/__tests__/create_page.test.ts +201 -0
  164. package/templates/apps/mcp/__tests__/delete_page.test.ts +176 -0
  165. package/templates/apps/mcp/__tests__/get_business_hours.test.ts +82 -0
  166. package/templates/apps/mcp/__tests__/get_faq.test.ts +80 -0
  167. package/templates/apps/mcp/__tests__/get_locations.test.ts +65 -0
  168. package/templates/apps/mcp/__tests__/get_offerings.test.ts +90 -0
  169. package/templates/apps/mcp/__tests__/get_pricing.test.ts +80 -0
  170. package/templates/apps/mcp/__tests__/get_site_info.test.ts +81 -0
  171. package/templates/apps/mcp/__tests__/http-transport.test.ts +323 -0
  172. package/templates/apps/mcp/__tests__/lib/errors.test.ts +75 -0
  173. package/templates/apps/mcp/__tests__/lib/require-paid-tier.test.ts +75 -0
  174. package/templates/apps/mcp/__tests__/list_pages.test.ts +112 -0
  175. package/templates/apps/mcp/__tests__/locale.test.ts +135 -0
  176. package/templates/apps/mcp/__tests__/parity.test.ts +205 -0
  177. package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +34 -0
  178. package/templates/apps/mcp/__tests__/read_content.test.ts +153 -0
  179. package/templates/apps/mcp/__tests__/request_callback.test.ts +163 -0
  180. package/templates/apps/mcp/__tests__/search_content.test.ts +153 -0
  181. package/templates/apps/mcp/__tests__/server.test.ts +115 -0
  182. package/templates/apps/mcp/__tests__/submit_lead.test.ts +165 -0
  183. package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +161 -0
  184. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +227 -0
  185. package/templates/apps/mcp/__tests__/tools/scaffold.test.ts +237 -0
  186. package/templates/apps/mcp/__tests__/update_page.test.ts +195 -0
  187. package/templates/apps/mcp/jest.config.cjs +82 -0
  188. package/templates/apps/mcp/package.json +37 -0
  189. package/templates/apps/mcp/src/abuse/daily-cap.ts +47 -0
  190. package/templates/apps/mcp/src/abuse/email-validator.ts +25 -0
  191. package/templates/apps/mcp/src/abuse/honeypot.ts +21 -0
  192. package/templates/apps/mcp/src/abuse/index.ts +111 -0
  193. package/templates/apps/mcp/src/abuse/user-agent-deny.ts +58 -0
  194. package/templates/apps/mcp/src/http-transport.ts +303 -0
  195. package/templates/apps/mcp/src/index.ts +67 -0
  196. package/templates/apps/mcp/src/lib/audit-single-page.ts +194 -0
  197. package/templates/apps/mcp/src/lib/errors.ts +61 -0
  198. package/templates/apps/mcp/src/lib/locales.ts +171 -0
  199. package/templates/apps/mcp/src/lib/require-paid-tier.ts +44 -0
  200. package/templates/apps/mcp/src/lib/resolve-locale.ts +30 -0
  201. package/templates/apps/mcp/src/server.ts +139 -0
  202. package/templates/apps/mcp/src/tools/audit.ts +103 -0
  203. package/templates/apps/mcp/src/tools/capture_lead.ts +52 -0
  204. package/templates/apps/mcp/src/tools/create_page.ts +95 -0
  205. package/templates/apps/mcp/src/tools/delete_page.ts +73 -0
  206. package/templates/apps/mcp/src/tools/get_business_hours.ts +66 -0
  207. package/templates/apps/mcp/src/tools/get_faq.ts +69 -0
  208. package/templates/apps/mcp/src/tools/get_locations.ts +85 -0
  209. package/templates/apps/mcp/src/tools/get_offerings.ts +66 -0
  210. package/templates/apps/mcp/src/tools/get_pricing.ts +74 -0
  211. package/templates/apps/mcp/src/tools/get_site_info.ts +84 -0
  212. package/templates/apps/mcp/src/tools/index.ts +125 -0
  213. package/templates/apps/mcp/src/tools/list_pages.ts +72 -0
  214. package/templates/apps/mcp/src/tools/purchase.ts +89 -0
  215. package/templates/apps/mcp/src/tools/read_content.ts +61 -0
  216. package/templates/apps/mcp/src/tools/request_callback.ts +51 -0
  217. package/templates/apps/mcp/src/tools/scaffold.ts +119 -0
  218. package/templates/apps/mcp/src/tools/search_content.ts +59 -0
  219. package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +47 -0
  220. package/templates/apps/mcp/src/tools/update_page.ts +91 -0
  221. package/templates/apps/mcp/tsconfig.json +19 -0
  222. package/templates/apps/web/.env.example +47 -0
  223. package/templates/apps/web/CLAUDE.md +81 -0
  224. package/templates/apps/web/__tests__/auth/adversarial/cookie-attributes.test.ts +94 -0
  225. package/templates/apps/web/__tests__/auth/adversarial/oauth-state-parameter.test.ts +75 -0
  226. package/templates/apps/web/__tests__/auth/adversarial/redirect-uri-allowlist.test.ts +82 -0
  227. package/templates/apps/web/__tests__/auth/env-helpers.test.ts +168 -0
  228. package/templates/apps/web/__tests__/authority-list-routes.test.tsx +208 -0
  229. package/templates/apps/web/__tests__/better-stack.test.ts +66 -0
  230. package/templates/apps/web/__tests__/blog-dir-resolution.test.ts +87 -0
  231. package/templates/apps/web/__tests__/blog-source-populated.test.ts +130 -0
  232. package/templates/apps/web/__tests__/data-testid-coverage.test.tsx +60 -0
  233. package/templates/apps/web/__tests__/google-analytics.test.tsx +104 -0
  234. package/templates/apps/web/__tests__/i18n/middleware.test.ts +119 -0
  235. package/templates/apps/web/__tests__/i18n/routing.test.ts +114 -0
  236. package/templates/apps/web/__tests__/i18n/translations.test.ts +191 -0
  237. package/templates/apps/web/__tests__/json-ld.test.ts +98 -0
  238. package/templates/apps/web/__tests__/knowledge-events-routes.test.tsx +580 -0
  239. package/templates/apps/web/__tests__/layout-stealth.test.ts +39 -0
  240. package/templates/apps/web/__tests__/lighthouse-fixtures/improvement-image-shrink.tsx +103 -0
  241. package/templates/apps/web/__tests__/lighthouse-fixtures/override-fixture.md +114 -0
  242. package/templates/apps/web/__tests__/lighthouse-fixtures/regression-bloat.tsx +94 -0
  243. package/templates/apps/web/__tests__/page.test.tsx +230 -0
  244. package/templates/apps/web/__tests__/products-services-phase1.test.tsx +321 -0
  245. package/templates/apps/web/__tests__/projects-routes.test.tsx +392 -0
  246. package/templates/apps/web/__tests__/resources-downloads-integrations-phase2.test.tsx +475 -0
  247. package/templates/apps/web/__tests__/routes/ai-plugin-json.test.ts +53 -0
  248. package/templates/apps/web/__tests__/routes/blog-route.test.tsx +191 -0
  249. package/templates/apps/web/__tests__/routes/llms-txt.test.ts +123 -0
  250. package/templates/apps/web/__tests__/routes/robots-txt.test.ts +52 -0
  251. package/templates/apps/web/__tests__/routes/sitemap-xml.test.ts +78 -0
  252. package/templates/apps/web/__tests__/routes/well-known-mcp.test.ts +203 -0
  253. package/templates/apps/web/__tests__/section-routes.test.tsx +693 -0
  254. package/templates/apps/web/__tests__/security-headers.test.ts +201 -0
  255. package/templates/apps/web/__tests__/sentry-config.test.ts +116 -0
  256. package/templates/apps/web/__tests__/setup.ts +82 -0
  257. package/templates/apps/web/__tests__/sitemap-locales.test.ts +415 -0
  258. package/templates/apps/web/__tests__/standalone-informational-routes.test.tsx +844 -0
  259. package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +81 -0
  260. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +110 -0
  261. package/templates/apps/web/app/.well-known/mcp.json/route.ts +58 -0
  262. package/templates/apps/web/app/[locale]/__tests__/layout.test.tsx +120 -0
  263. package/templates/apps/web/app/[locale]/__tests__/page-renderer-migration.test.tsx +202 -0
  264. package/templates/apps/web/app/[locale]/about/page.tsx +31 -0
  265. package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +173 -0
  266. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +45 -0
  267. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +117 -0
  268. package/templates/apps/web/app/[locale]/careers/page.tsx +25 -0
  269. package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +44 -0
  270. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +113 -0
  271. package/templates/apps/web/app/[locale]/case-studies/page.tsx +25 -0
  272. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +63 -0
  273. package/templates/apps/web/app/[locale]/clients/page.tsx +27 -0
  274. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +130 -0
  275. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +152 -0
  276. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +92 -0
  277. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +79 -0
  278. package/templates/apps/web/app/[locale]/contact/error.tsx +24 -0
  279. package/templates/apps/web/app/[locale]/contact/page.tsx +38 -0
  280. package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +34 -0
  281. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +119 -0
  282. package/templates/apps/web/app/[locale]/courses/page.tsx +25 -0
  283. package/templates/apps/web/app/[locale]/dev/purchase/__tests__/actions.test.ts +160 -0
  284. package/templates/apps/web/app/[locale]/dev/purchase/error.tsx +25 -0
  285. package/templates/apps/web/app/[locale]/dev/purchase/page.tsx +117 -0
  286. package/templates/apps/web/app/[locale]/dev/purchase/purchase.actions.ts +58 -0
  287. package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +118 -0
  288. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +72 -0
  289. package/templates/apps/web/app/[locale]/downloads/page.tsx +25 -0
  290. package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +47 -0
  291. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +130 -0
  292. package/templates/apps/web/app/[locale]/events/page.tsx +25 -0
  293. package/templates/apps/web/app/[locale]/faq/page.tsx +36 -0
  294. package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +34 -0
  295. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +78 -0
  296. package/templates/apps/web/app/[locale]/how-to/page.tsx +40 -0
  297. package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +43 -0
  298. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +129 -0
  299. package/templates/apps/web/app/[locale]/integrations/page.tsx +25 -0
  300. package/templates/apps/web/app/[locale]/launch-service/page.tsx +31 -0
  301. package/templates/apps/web/app/[locale]/layout.tsx +241 -0
  302. package/templates/apps/web/app/[locale]/page.tsx +41 -0
  303. package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +44 -0
  304. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +113 -0
  305. package/templates/apps/web/app/[locale]/partners/page.tsx +25 -0
  306. package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +43 -0
  307. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +129 -0
  308. package/templates/apps/web/app/[locale]/products/page.tsx +25 -0
  309. package/templates/apps/web/app/[locale]/projects/[slug]/page.tsx +39 -0
  310. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +138 -0
  311. package/templates/apps/web/app/[locale]/projects/page.tsx +30 -0
  312. package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +51 -0
  313. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +117 -0
  314. package/templates/apps/web/app/[locale]/resources/page.tsx +27 -0
  315. package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +43 -0
  316. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +129 -0
  317. package/templates/apps/web/app/[locale]/services/page.tsx +25 -0
  318. package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +34 -0
  319. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +81 -0
  320. package/templates/apps/web/app/[locale]/software/page.tsx +39 -0
  321. package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +34 -0
  322. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +82 -0
  323. package/templates/apps/web/app/[locale]/tech-articles/page.tsx +39 -0
  324. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +63 -0
  325. package/templates/apps/web/app/[locale]/testimonials/page.tsx +27 -0
  326. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +63 -0
  327. package/templates/apps/web/app/[locale]/use-cases/page.tsx +27 -0
  328. package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +34 -0
  329. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +82 -0
  330. package/templates/apps/web/app/[locale]/whitepapers/page.tsx +39 -0
  331. package/templates/apps/web/app/__tests__/error-boundaries.test.tsx +152 -0
  332. package/templates/apps/web/app/api/auth/[...all]/route.ts +16 -0
  333. package/templates/apps/web/app/error-fallback.tsx +75 -0
  334. package/templates/apps/web/app/error.tsx +23 -0
  335. package/templates/apps/web/app/layout.tsx +18 -0
  336. package/templates/apps/web/app/llms.txt/route.ts +101 -0
  337. package/templates/apps/web/app/not-found.tsx +48 -0
  338. package/templates/apps/web/app/og/__tests__/route.test.ts +164 -0
  339. package/templates/apps/web/app/og/route.tsx +135 -0
  340. package/templates/apps/web/app/robots.txt/route.ts +36 -0
  341. package/templates/apps/web/app/sitemap.xml/route.ts +146 -0
  342. package/templates/apps/web/auth.ts +141 -0
  343. package/templates/apps/web/components/ThemeToggle.tsx +60 -0
  344. package/templates/apps/web/components/google-analytics.tsx +34 -0
  345. package/templates/apps/web/content/en/blog/index.mdx +15 -0
  346. package/templates/apps/web/content/en/blog/launching-ailk.mdx +20 -0
  347. package/templates/apps/web/content/en/docs/getting-started.mdx +30 -0
  348. package/templates/apps/web/i18n/request.ts +36 -0
  349. package/templates/apps/web/jest.config.cjs +100 -0
  350. package/templates/apps/web/lib/__mocks__/next-mdx-remote-rsc.js +12 -0
  351. package/templates/apps/web/lib/__mocks__/source-server.js +31 -0
  352. package/templates/apps/web/lib/__tests__/og.test.ts +65 -0
  353. package/templates/apps/web/lib/auth-env.ts +66 -0
  354. package/templates/apps/web/lib/blog-list-data.ts +95 -0
  355. package/templates/apps/web/lib/blog-post-data.ts +79 -0
  356. package/templates/apps/web/lib/nav-config.ts +13 -0
  357. package/templates/apps/web/lib/observability/better-stack.ts +27 -0
  358. package/templates/apps/web/lib/og.ts +23 -0
  359. package/templates/apps/web/lib/prisma-for-auth.ts +34 -0
  360. package/templates/apps/web/lib/source.ts +34 -0
  361. package/templates/apps/web/messages/en.json +131 -0
  362. package/templates/apps/web/middleware.ts +80 -0
  363. package/templates/apps/web/next.config.mjs +167 -0
  364. package/templates/apps/web/package.json +62 -0
  365. package/templates/apps/web/public/.well-known/security.txt +17 -0
  366. package/templates/apps/web/public/android-chrome-192x192.png +0 -0
  367. package/templates/apps/web/public/android-chrome-512x512.png +0 -0
  368. package/templates/apps/web/public/apple-touch-icon.png +0 -0
  369. package/templates/apps/web/public/favicon-16x16.png +0 -0
  370. package/templates/apps/web/public/favicon-32x32.png +0 -0
  371. package/templates/apps/web/public/favicon.ico +0 -0
  372. package/templates/apps/web/public/favicon.svg +4 -0
  373. package/templates/apps/web/public/logo-dark.svg +21 -0
  374. package/templates/apps/web/public/logo-light.svg +21 -0
  375. package/templates/apps/web/public/logomark-dark.svg +4 -0
  376. package/templates/apps/web/public/logomark-light.svg +4 -0
  377. package/templates/apps/web/public/og-default.png +0 -0
  378. package/templates/apps/web/public/site.webmanifest +19 -0
  379. package/templates/apps/web/scripts/assert-content-bundled.ts +55 -0
  380. package/templates/apps/web/security-headers.config.ts +132 -0
  381. package/templates/apps/web/sentry.client.config.ts +27 -0
  382. package/templates/apps/web/sentry.edge.config.ts +15 -0
  383. package/templates/apps/web/sentry.server.config.ts +20 -0
  384. package/templates/apps/web/source.config.ts +78 -0
  385. package/templates/apps/web/src/components/SiteNav.tsx +21 -0
  386. package/templates/apps/web/src/lib/routes.ts +37 -0
  387. package/templates/apps/web/tsconfig.json +35 -0
  388. package/templates/apps/web/types/messages.d.ts +19 -0
  389. package/templates/content/.gitkeep +0 -0
  390. package/templates/content/CLAUDE.md +85 -0
  391. package/templates/content/_site.mdx +38 -0
  392. package/templates/content/ar/HomePage/home.mdx +123 -0
  393. package/templates/content/ar/_site.mdx +35 -0
  394. package/templates/content/de/_site.mdx +29 -0
  395. package/templates/content/en/.gitkeep +0 -0
  396. package/templates/content/en/AboutPage/about.mdx +19 -0
  397. package/templates/content/en/AboutPage/about.schema.json +82 -0
  398. package/templates/content/en/BookCall/book-call.mdx +19 -0
  399. package/templates/content/en/BookCall/book-call.schema.json +121 -0
  400. package/templates/content/en/BreadcrumbList/site-breadcrumbs.mdx +20 -0
  401. package/templates/content/en/CareersList/careers.mdx +24 -0
  402. package/templates/content/en/CaseStudiesList/case-studies.mdx +22 -0
  403. package/templates/content/en/CaseStudiesList/showcase.mdx +8 -0
  404. package/templates/content/en/CaseStudy/ailk-aeo-rollout.mdx +32 -0
  405. package/templates/content/en/CaseStudy/schema-builder-migration.mdx +41 -0
  406. package/templates/content/en/ClientsList/clients.mdx +38 -0
  407. package/templates/content/en/ContactPage/contact.mdx +19 -0
  408. package/templates/content/en/ContactPage/contact.schema.json +100 -0
  409. package/templates/content/en/ContactPoint/primary.mdx +14 -0
  410. package/templates/content/en/Course/aeo-fundamentals.mdx +37 -0
  411. package/templates/content/en/Course/schema-builder-workshop.mdx +62 -0
  412. package/templates/content/en/CoursesList/courses.mdx +22 -0
  413. package/templates/content/en/DownloadsList/downloads.mdx +22 -0
  414. package/templates/content/en/Event/aeo-office-hours-may-2026.mdx +39 -0
  415. package/templates/content/en/Event/ailk-launch-day-2026.mdx +35 -0
  416. package/templates/content/en/EventsList/events.mdx +26 -0
  417. package/templates/content/en/FAQPage/faq.mdx +420 -0
  418. package/templates/content/en/FAQPage/faq.schema.json +231 -0
  419. package/templates/content/en/GlossaryList/glossary.mdx +19 -0
  420. package/templates/content/en/GlossaryList/glossary.schema.json +300 -0
  421. package/templates/content/en/HomePage/home.mdx +312 -0
  422. package/templates/content/en/HomePage/home.schema.json +88 -0
  423. package/templates/content/en/HomePage/launch-service.mdx +475 -0
  424. package/templates/content/en/HomePage/launch-service.schema.json +138 -0
  425. package/templates/content/en/HowTo/add-mdx-content.mdx +38 -0
  426. package/templates/content/en/HowTo/configure-aeo-scoring.mdx +35 -0
  427. package/templates/content/en/HowTo/set-up-json-ld.mdx +36 -0
  428. package/templates/content/en/Integration/github-actions.mdx +31 -0
  429. package/templates/content/en/Integration/openai-api.mdx +26 -0
  430. package/templates/content/en/Integration/vercel.mdx +32 -0
  431. package/templates/content/en/IntegrationsList/integrations.mdx +22 -0
  432. package/templates/content/en/JobPosting/content-strategist.mdx +46 -0
  433. package/templates/content/en/JobPosting/developer-advocate.mdx +47 -0
  434. package/templates/content/en/JobPosting/senior-fullstack-engineer.mdx +57 -0
  435. package/templates/content/en/LandingPage/aeo-audit.mdx +19 -0
  436. package/templates/content/en/LandingPage/aeo-audit.schema.json +92 -0
  437. package/templates/content/en/LandingPage/agency-waitlist.mdx +340 -0
  438. package/templates/content/en/LandingPage/agency-waitlist.schema.json +152 -0
  439. package/templates/content/en/LandingPage/developers.mdx +19 -0
  440. package/templates/content/en/LandingPage/developers.schema.json +109 -0
  441. package/templates/content/en/LandingPage/for-agencies.mdx +390 -0
  442. package/templates/content/en/LandingPage/for-agencies.schema.json +107 -0
  443. package/templates/content/en/LandingPage/for-ai-search-agencies.mdx +417 -0
  444. package/templates/content/en/LandingPage/for-ai-search-agencies.schema.json +105 -0
  445. package/templates/content/en/LandingPage/for-businesses.mdx +19 -0
  446. package/templates/content/en/LandingPage/for-businesses.schema.json +96 -0
  447. package/templates/content/en/LandingPage/platform.mdx +369 -0
  448. package/templates/content/en/LandingPage/platform.schema.json +166 -0
  449. package/templates/content/en/LandingPage/vs-ai-website-builders.mdx +19 -0
  450. package/templates/content/en/LandingPage/vs-ai-website-builders.schema.json +102 -0
  451. package/templates/content/en/LandingPage/vs-wordpress.mdx +19 -0
  452. package/templates/content/en/LandingPage/vs-wordpress.schema.json +53 -0
  453. package/templates/content/en/NewsletterSignup/newsletter.mdx +236 -0
  454. package/templates/content/en/NewsletterSignup/newsletter.schema.json +91 -0
  455. package/templates/content/en/Organization/site-owner.mdx +16 -0
  456. package/templates/content/en/Partner/prisma.mdx +31 -0
  457. package/templates/content/en/Partner/supabase.mdx +30 -0
  458. package/templates/content/en/Partner/vercel.mdx +28 -0
  459. package/templates/content/en/PartnersList/partners.mdx +22 -0
  460. package/templates/content/en/Person/founder.mdx +18 -0
  461. package/templates/content/en/Pricing/pricing.mdx +538 -0
  462. package/templates/content/en/Pricing/pricing.schema.json +209 -0
  463. package/templates/content/en/PrivacyPage/privacy.mdx +165 -0
  464. package/templates/content/en/PrivacyPage/privacy.schema.json +64 -0
  465. package/templates/content/en/Product/ailk.mdx +19 -0
  466. package/templates/content/en/Product/ailk.schema.json +94 -0
  467. package/templates/content/en/Product/api-launch-kit.mdx +26 -0
  468. package/templates/content/en/Product/launch-kit.mdx +28 -0
  469. package/templates/content/en/ProductsList/products.mdx +26 -0
  470. package/templates/content/en/Project/ailk-phase-2.mdx +54 -0
  471. package/templates/content/en/ProjectsList/projects.mdx +25 -0
  472. package/templates/content/en/ResourceDetail/aeo-scoring-guide.mdx +27 -0
  473. package/templates/content/en/ResourceDetail/content-model-whitepaper.mdx +28 -0
  474. package/templates/content/en/ResourceDetail/schema-org-quick-reference.mdx +39 -0
  475. package/templates/content/en/ResourcesHub/resources.mdx +22 -0
  476. package/templates/content/en/Service/consulting.mdx +22 -0
  477. package/templates/content/en/Service/implementation.mdx +30 -0
  478. package/templates/content/en/Service/launch-service.mdx +232 -0
  479. package/templates/content/en/Service/launch.mdx +228 -0
  480. package/templates/content/en/ServicesList/services.mdx +84 -0
  481. package/templates/content/en/SoftwareProduct/ailk-aeo.mdx +44 -0
  482. package/templates/content/en/SoftwareProduct/ailk-content-adapters.mdx +47 -0
  483. package/templates/content/en/SoftwareProduct/ailk-schema.mdx +49 -0
  484. package/templates/content/en/TeamList/team.mdx +63 -0
  485. package/templates/content/en/TeamMember/founder.mdx +72 -0
  486. package/templates/content/en/TermsPage/terms.mdx +19 -0
  487. package/templates/content/en/TermsPage/terms.schema.json +99 -0
  488. package/templates/content/en/TestimonialsList/testimonials.mdx +44 -0
  489. package/templates/content/en/UseCasesList/use-cases.mdx +30 -0
  490. package/templates/content/en/Whitepaper/ax-first-product-development.mdx +27 -0
  491. package/templates/content/en/Whitepaper/open-core-commercial-strategy.mdx +27 -0
  492. package/templates/content/en/Whitepaper/structured-data-for-answer-engines.mdx +31 -0
  493. package/templates/content/en-XA/AboutPage/about.mdx +8 -0
  494. package/templates/content/en-XA/FAQPage/faq.mdx +8 -0
  495. package/templates/content/en-XA/HomePage/home.mdx +8 -0
  496. package/templates/content/es/_site.mdx +30 -0
  497. package/templates/content/fr/_site.mdx +30 -0
  498. package/templates/content/ja/_site.mdx +30 -0
  499. package/templates/content/pt-BR/AboutPage/about.mdx +8 -0
  500. package/templates/content/pt-BR/FAQPage/faq.mdx +8 -0
  501. package/templates/content/pt-BR/HomePage/home.mdx +23 -0
  502. package/templates/database/CHANGELOG.md +5 -0
  503. package/templates/database/README.md +234 -0
  504. package/templates/database/__tests__/user-cascade.test.ts +125 -0
  505. package/templates/database/auth/.gitkeep +0 -0
  506. package/templates/database/config/.gitkeep +0 -0
  507. package/templates/database/content/.gitkeep +0 -0
  508. package/templates/database/inbox/.gitkeep +0 -0
  509. package/templates/database/inbox/README.md +3 -0
  510. package/templates/database/inbox/__tests__/schema.test.ts +40 -0
  511. package/templates/database/inbox/schema.prisma +52 -0
  512. package/templates/database/jest.config.cjs +29 -0
  513. package/templates/database/migrations/20260508000000_add_inbox_namespace/migration.sql +328 -0
  514. package/templates/database/migrations/20260513000000_better_auth_migration/migration.sql +100 -0
  515. package/templates/database/migrations/20260515000000_add_user_billing_fields/migration.sql +28 -0
  516. package/templates/database/migrations/20260516000000_add_user_tier_care_plan_seat/migration.sql +25 -0
  517. package/templates/database/migrations/20260518000000_add_telemetry_records/migration.sql +42 -0
  518. package/templates/database/migrations/20260525000000_theme_wiring/migration.sql +67 -0
  519. package/templates/database/migrations/20260710000000_retire_commercial_tier/migration.sql +21 -0
  520. package/templates/database/migrations/migration_lock.toml +3 -0
  521. package/templates/database/ops/.gitkeep +0 -0
  522. package/templates/database/package.json +45 -0
  523. package/templates/database/prisma.config.ts +20 -0
  524. package/templates/database/schema.prisma +487 -0
  525. package/templates/database/scripts/db-generate-locked.sh +80 -0
  526. package/templates/database/seed-test.ts +85 -0
  527. package/templates/database/tsconfig.build.json +16 -0
  528. package/templates/database/tsconfig.json +9 -0
  529. package/templates/package.json +125 -0
  530. package/templates/pnpm-workspace.yaml +6 -0
  531. package/templates/project.yaml +59 -0
  532. package/templates/tsconfig.base.json +18 -0
  533. package/templates/tsconfig.json +45 -0
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Lighthouse CI improvement fixture — image shrink
3
+ *
4
+ * Documentation-as-code, not loaded in production. Lives under
5
+ * `__tests__/` and is **not** imported by any file under `app/` or
6
+ * `components/` (asserted by spec §3.7 / AC bullet "None of the three
7
+ * fixtures is imported…").
8
+ *
9
+ * **Purpose**
10
+ *
11
+ * Sam's verification checklist (spec §5.1, Layer 1) calls for opening a
12
+ * PR that "improves performance" so the workflow's PR comment shows
13
+ * positive LCP/CLS deltas vs main. This fixture documents a known-good
14
+ * improvement pattern — swapping a hand-coded `<img>` for `next/image`
15
+ * with explicit dimensions and AVIF/WebP negotiation — so Sam (or any
16
+ * implementer running the verification) can apply it deterministically
17
+ * without designing a new improvement from scratch.
18
+ *
19
+ * **How to wire it in for verification**
20
+ *
21
+ * The improvement direction is "make `/` faster than baseline by enough
22
+ * that the PR comment shows a noticeable LCP improvement." The fixture
23
+ * is the *recipe*, not a runtime artifact.
24
+ *
25
+ * 1. On `main`, identify a route whose largest-contentful-paint element
26
+ * is an image — e.g., the home page hero. (Use the most recent
27
+ * `.lighthouseci/lhr-*.html` report's "Largest Contentful Paint
28
+ * element" diagnostic to find it.)
29
+ * 2. If the LCP image is currently rendered via a plain `<img>`, swap
30
+ * to `next/image` per the BEFORE/AFTER below.
31
+ * 3. If it's already `next/image`, the easier improvement is shrinking
32
+ * the source image's dimensions to match the rendered size (avoid
33
+ * serving a 2400×1600 image into a 800×533 slot).
34
+ * 4. Open a PR. The Lighthouse CI workflow's PR comment will show the
35
+ * LCP delta vs main; expect a negative delta on `/` (faster).
36
+ *
37
+ * **BEFORE — plain img**
38
+ *
39
+ * ```tsx
40
+ * <img src="/hero.png" alt="..." />
41
+ * ```
42
+ *
43
+ * Issues: no width/height (CLS risk), no responsive sources, no
44
+ * priority hint, no format negotiation.
45
+ *
46
+ * **AFTER — next/image**
47
+ *
48
+ * ```tsx
49
+ * import Image from "next/image";
50
+ *
51
+ * <Image
52
+ * src="/hero.png"
53
+ * alt="..."
54
+ * width={800}
55
+ * height={533}
56
+ * priority
57
+ * />
58
+ * ```
59
+ *
60
+ * Effects: dimensions reserve layout space (CLS → 0), `priority` skips
61
+ * lazy-load for above-the-fold content (LCP improves), Next's image
62
+ * pipeline negotiates AVIF/WebP and serves the right size.
63
+ *
64
+ * **Why this is documented as a fixture, not a real PR**
65
+ *
66
+ * The actual perf improvement is implementation-specific (which route's
67
+ * LCP element is currently a plain `<img>`?). The fixture captures the
68
+ * *recipe* so that when Sam runs the verification, the diff is
69
+ * predictable instead of one-off-discovered. The fixture ships as code
70
+ * (not just docs) so editor tooling can refactor it if `next/image`'s
71
+ * API shifts.
72
+ */
73
+
74
+ import type { ImageProps } from "next/image";
75
+
76
+ /**
77
+ * Reference props for the recommended `next/image` invocation. Not
78
+ * exported as a component; the verification PR copies this shape into
79
+ * the actual route file.
80
+ */
81
+ export const recommendedImageProps: Partial<ImageProps> = {
82
+ width: 800,
83
+ height: 533,
84
+ priority: true,
85
+ };
86
+
87
+ /**
88
+ * Reference render demonstrating the AFTER shape. Not imported by any
89
+ * route — see file header.
90
+ */
91
+ export function ImprovementImageShrinkFixture() {
92
+ // Inline `<img>` here is intentional — this fixture documents the
93
+ // anti-pattern (BEFORE) alongside the recommended pattern. Replacing
94
+ // this `<img>` is the verification step, so we keep it visible.
95
+ // eslint-disable-next-line @next/next/no-img-element, jsx-a11y/alt-text
96
+ return (
97
+ <img
98
+ data-fixture="lhci-improvement-image-shrink"
99
+ src="/hero.png"
100
+ alt="placeholder"
101
+ />
102
+ );
103
+ }
@@ -0,0 +1,114 @@
1
+ # Lighthouse CI override fixture
2
+
3
+ Documentation-only, not loaded in production. Lives under `__tests__/`
4
+ alongside the other Lighthouse fixtures. This file is markdown, not a
5
+ component — Sam's verification checklist for the override path is a
6
+ PR-process exercise, not a runtime artifact.
7
+
8
+ ## Purpose
9
+
10
+ Sam's verification checklist (`docs/internal/specs/ailk-lighthouse-ci-spec.md`
11
+ §5.1, Layer 1) calls for confirming the override mechanism: when a
12
+ performance regression is intentional, applying `perf-override` plus a
13
+ qualifying comment lets the PR ship despite the failing assertion. This
14
+ fixture is the recipe.
15
+
16
+ ## When to override
17
+
18
+ Override is for **intentional** regressions whose business value
19
+ outweighs the perf cost — e.g., adding a one-time analytics script the
20
+ team has decided to ship, or shipping a heavy demo asset for a launch.
21
+ Routine code that regresses LCP without intent should be fixed, not
22
+ overridden.
23
+
24
+ If you find yourself overriding more than once per quarter, the
25
+ thresholds are wrong. Re-run the §2.5 baseline-measurement procedure on
26
+ post-merge `main` and commit recalibrated thresholds in a follow-up PR
27
+ per `docs/perf.md` § How to update thresholds.
28
+
29
+ ## How to override (verification recipe)
30
+
31
+ Run this against the `regression-bloat.tsx` fixture or any PR with a
32
+ genuine intentional regression.
33
+
34
+ 1. Confirm the Lighthouse CI workflow has run and failed on the PR. The
35
+ PR comment posted by the workflow names the route + metric that
36
+ exceeded the threshold.
37
+ 2. Apply the `perf-override` label to the PR:
38
+
39
+ ```bash
40
+ gh pr edit <PR> --add-label perf-override
41
+ ```
42
+
43
+ 3. Post a comment whose body **starts with** the literal string
44
+ `perf-override:` (case-insensitive — the workflow `ascii_downcase`s
45
+ before checking) followed by the justification:
46
+
47
+ ```
48
+ perf-override: shipping the launch-day hero asset; will revisit
49
+ thresholds in #<follow-up-issue> after main has the new baseline.
50
+ ```
51
+
52
+ Bot comments are excluded — the comment must be from a non-bot
53
+ author. The comment must be from a non-bot login (the workflow
54
+ filters logins ending in `[bot]`).
55
+
56
+ 4. Re-trigger the workflow. The simplest trigger is to push an empty
57
+ commit:
58
+
59
+ ```bash
60
+ git commit --allow-empty -m "chore: re-run lhci with override"
61
+ git push
62
+ ```
63
+
64
+ The `pull_request: synchronize` event fires the workflow. The
65
+ `labeled` event also fires it, so just adding the label without
66
+ pushing also re-runs the gate (concurrency group will cancel the
67
+ prior in-flight run).
68
+
69
+ 5. Confirm the workflow run completes green:
70
+ - The original `Run Lighthouse CI` step still fails (this is
71
+ expected — the override does not change the assertion outcome).
72
+ - The `Evaluate override gate` step posts a comment naming the
73
+ justification: `Lighthouse CI override applied. Justification: …`.
74
+ - The final `Surface result` step exits 0 because the override
75
+ applied.
76
+
77
+ 6. Verify the audit trail by reading the PR's comment thread. The
78
+ override comment, the workflow's acknowledgement comment, and the
79
+ original Lighthouse failure summary should all be visible.
80
+
81
+ ## How the workflow evaluates the override
82
+
83
+ From `.github/workflows/lighthouse-ci.yml` `Evaluate override gate`
84
+ step:
85
+
86
+ - Reads PR JSON: `gh pr view <PR> --json labels,comments`.
87
+ - Checks `labels` for `perf-override`. If absent → no override; surface
88
+ the original failure.
89
+ - Iterates `comments`, filters out bot logins (`endswith("[bot]")`),
90
+ and finds the first comment whose body, after `ascii_downcase`,
91
+ starts with `perf-override:`. If none → posts the
92
+ "label-without-comment" instructional comment, surfaces the original
93
+ failure.
94
+ - Both present → posts the override-applied comment with the
95
+ justification quoted, sets `applied=true`. The final `Surface result`
96
+ step then exits 0.
97
+
98
+ The override is per-PR-per-run; each push or label event re-evaluates
99
+ the current state. Removing the label or deleting the comment between
100
+ runs returns the gate to enforcing.
101
+
102
+ ## Anti-patterns
103
+
104
+ - **Substring match.** A comment that mentions `perf-override` mid-body
105
+ does not count — the body must _start with_ `perf-override:`. This is
106
+ intentional (spec §3.6) so casual references in discussion don't
107
+ silently override the gate.
108
+ - **Bot-authored override.** Comments from automated reviewers (Bugbot,
109
+ claude-review, the workflow itself) are filtered out. A human-author
110
+ comment is required.
111
+ - **Override-only PRs.** If every PR that touches the LCP element ends
112
+ up overridden, recalibrate thresholds instead of overriding. The
113
+ override path is for one-off intent, not a workaround for stale
114
+ budgets.
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Lighthouse CI regression fixture — bloat
3
+ *
4
+ * Documentation-as-code, not loaded in production. Lives under
5
+ * `__tests__/` and is **not** imported by any file under `app/` or
6
+ * `components/` (asserted by spec §3.7 / AC bullet "None of the three
7
+ * fixtures is imported…").
8
+ *
9
+ * **Purpose**
10
+ *
11
+ * Sam's verification checklist (spec §5.1, Layer 1) calls for opening a
12
+ * PR that adds a "deliberately wasteful 200KB+ unused dependency" to
13
+ * `apps/web` to confirm Lighthouse CI fails on `/` with an LCP
14
+ * regression. Tree-shaking is the failure mode that makes the
15
+ * verification a no-op — if Next.js drops the import because the value
16
+ * is unused, the regression never happens. This fixture is structured
17
+ * so Next.js cannot eliminate it.
18
+ *
19
+ * **How to wire it in for verification**
20
+ *
21
+ * 1. Move this file to a temporary location inside `app/` (e.g.,
22
+ * `app/_lhci-regression-fixture.tsx`) — `__tests__/` is not part of
23
+ * the Next.js compile graph.
24
+ * 2. Add a side-effectful import to `apps/web/app/page.tsx`:
25
+ *
26
+ * ```ts
27
+ * import "./_lhci-regression-fixture";
28
+ * ```
29
+ *
30
+ * A side-effect import (no symbol, no destructure) is the strongest
31
+ * signal to bundlers that the module must be evaluated. Combined
32
+ * with the eager `moment` + locales evaluation below, the bundler
33
+ * will not drop the dependency.
34
+ * 3. Open a PR to `main` containing the move + the import line.
35
+ * 4. Wait for the `Lighthouse CI` workflow on the PR to fail with an
36
+ * `largest-contentful-paint` assertion error on `http://localhost:3000/$`.
37
+ * The PR comment will name the regressed metric and route.
38
+ * 5. Revert the changes after verification.
39
+ *
40
+ * **Why `moment` + locales**
41
+ *
42
+ * `moment` ships every locale by default (≈230 KB minified). Webpack
43
+ * tree-shaking cannot drop locales that are reachable via dynamic
44
+ * `require()` paths inside moment's source. The eager `moment().locale(...)`
45
+ * calls below force the runtime to walk the locale registry. The result
46
+ * is a deterministic ~250 KB JS payload added to the route's first-load
47
+ * bundle, which translates to a measurable LCP regression on `/` under
48
+ * the desktop preset.
49
+ *
50
+ * Note: this fixture is intentionally *not* shipped — it's a recipe for
51
+ * Sam, not a runtime artifact.
52
+ */
53
+
54
+ // eslint-disable-next-line import/no-extraneous-dependencies, @typescript-eslint/no-require-imports
55
+ const moment: any = (globalThis as any).require?.("moment") ?? null;
56
+
57
+ const NOW = moment ? moment() : null;
58
+
59
+ if (NOW) {
60
+ // Touch every locale to defeat tree-shaking. Each .locale() call walks
61
+ // moment's locale registry and pulls in the locale module.
62
+ for (const code of [
63
+ "en",
64
+ "fr",
65
+ "de",
66
+ "es",
67
+ "it",
68
+ "pt",
69
+ "nl",
70
+ "ru",
71
+ "ja",
72
+ "zh-cn",
73
+ "ko",
74
+ "ar",
75
+ "he",
76
+ "tr",
77
+ "pl",
78
+ "sv",
79
+ "da",
80
+ "fi",
81
+ "no",
82
+ "cs",
83
+ ]) {
84
+ NOW.locale(code);
85
+ }
86
+ }
87
+
88
+ export function RegressionBloatFixture() {
89
+ return (
90
+ <div data-fixture="lhci-regression-bloat">
91
+ {NOW ? NOW.format("YYYY-MM-DD") : "no-moment"}
92
+ </div>
93
+ );
94
+ }
@@ -0,0 +1,230 @@
1
+ // Home page tests. The page is an async server component; jest can render
2
+ // it by awaiting the returned ReactElement promise. getMdxSource and
3
+ // getPageSections are injected via optional props instead of mocking internal
4
+ // modules — the test is independent of the on-disk /content/ tree without
5
+ // coupling to module internals.
6
+ // next-mdx-remote/rsc is mocked (external boundary — RSC compiler not available under jsdom).
7
+
8
+ import { render, screen } from "@testing-library/react";
9
+
10
+ jest.mock("next-mdx-remote/rsc", () => ({
11
+ __esModule: true,
12
+ MDXRemote: ({ source }: { source: string }): React.ReactElement => (
13
+ <div data-testid="mdx-rendered">{source}</div>
14
+ ),
15
+ }));
16
+
17
+ // Renders a real <h1>/<p> for a leading hero section — mirroring the real
18
+ // Hero section component (packages/ui) closely enough to exercise the
19
+ // renderPage leading-hero carve-out (#3301: renderPage omits its own
20
+ // top-level <h1>/<p> when sections[0] is a hero, trusting the hero section
21
+ // to supply the page's sole heading + lead paragraph).
22
+ jest.mock("@working-theory/ui", () => ({
23
+ __esModule: true,
24
+ SectionRenderer: ({
25
+ sections,
26
+ }: {
27
+ sections: Array<{
28
+ type: string;
29
+ data?: { headline?: string; subheadline?: string };
30
+ }>;
31
+ }): React.ReactElement => (
32
+ <div data-testid="section-renderer" data-section-count={sections.length}>
33
+ {sections.map((section, i) =>
34
+ section.type === "hero" ? (
35
+ <div key={i} data-testid="section-item">
36
+ <h1>{section.data?.headline}</h1>
37
+ {section.data?.subheadline ? (
38
+ <p>{section.data.subheadline}</p>
39
+ ) : null}
40
+ </div>
41
+ ) : (
42
+ <div key={i} data-testid="section-item" />
43
+ ),
44
+ )}
45
+ </div>
46
+ ),
47
+ }));
48
+
49
+ import * as React from "react";
50
+
51
+ import Page from "../app/[locale]/page";
52
+ import type { getMdxSource, getPageSections } from "@working-theory/page-renderer";
53
+
54
+ type GetMdxSource = typeof getMdxSource;
55
+ type GetPageSections = typeof getPageSections;
56
+
57
+ async function renderAsync(element: Promise<React.ReactElement>): Promise<void> {
58
+ const resolved = await element;
59
+ render(resolved);
60
+ }
61
+
62
+ // Mirrors the real content-authoring convention (#3301): the hero's
63
+ // headline/subheadline mirror the page's frontmatter title/description.
64
+ const HERO_SECTION = {
65
+ id: "hero-1",
66
+ type: "hero" as const,
67
+ data: {
68
+ headline: "Welcome to AILK",
69
+ subheadline: "AX-first OSS template.",
70
+ primaryCta: { label: "Get started", href: "/docs" },
71
+ },
72
+ };
73
+
74
+ const FEATURE_SECTION = {
75
+ id: "feature-1",
76
+ type: "feature" as const,
77
+ data: {
78
+ headline: "Built for agents",
79
+ features: [
80
+ { title: "API-first", description: "Canonical write surface." },
81
+ ],
82
+ },
83
+ };
84
+
85
+ const ITEMS_SECTION = {
86
+ id: "items-1",
87
+ type: "items" as const,
88
+ data: {
89
+ headline: "What's inside",
90
+ items: [
91
+ { title: "apps/web", description: "Next.js App Router" },
92
+ { title: "apps/api", description: "Fastify HTTP API" },
93
+ { title: "apps/mcp", description: "MCP server" },
94
+ ],
95
+ },
96
+ };
97
+
98
+ const CTA_SECTION = {
99
+ id: "cta-1",
100
+ type: "cta" as const,
101
+ data: {
102
+ headline: "Ready to ship?",
103
+ primaryCta: { label: "Clone now", href: "https://github.com/tailwind-ai/ai-launch-kit" },
104
+ },
105
+ };
106
+
107
+ describe("apps/web — home page", () => {
108
+ describe("with HomePage MDX present and sections in frontmatter", () => {
109
+ const stubGetMdxSource = jest.fn<ReturnType<GetMdxSource>, Parameters<GetMdxSource>>().mockResolvedValue({
110
+ frontmatter: {
111
+ type: "HomePage",
112
+ slug: "home",
113
+ title: "Welcome to AILK",
114
+ description: "AX-first OSS template.",
115
+ },
116
+ body: "# Welcome\n\nBody copy.",
117
+ });
118
+ const stubGetPageSections = jest.fn<ReturnType<GetPageSections>, Parameters<GetPageSections>>().mockResolvedValue([
119
+ HERO_SECTION,
120
+ FEATURE_SECTION,
121
+ ITEMS_SECTION,
122
+ CTA_SECTION,
123
+ ]);
124
+
125
+ beforeEach(() => {
126
+ stubGetMdxSource.mockClear();
127
+ stubGetPageSections.mockClear();
128
+ });
129
+
130
+ it("renders the MDX-sourced title in the h1", async () => {
131
+ await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
132
+ const heading = screen.getByRole("heading", { level: 1 });
133
+ expect(heading).toHaveTextContent("Welcome to AILK");
134
+ });
135
+
136
+ it("renders the description via the leading hero section, not a duplicate top-level paragraph (#3301)", async () => {
137
+ await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
138
+ // renderPage's own <p data-testid="home-description"> is skipped when
139
+ // the first section is a hero — the hero renders the lead paragraph.
140
+ expect(screen.queryByTestId("home-description")).toBeNull();
141
+ expect(screen.getByText("AX-first OSS template.")).toBeInTheDocument();
142
+ });
143
+
144
+ it("renders exactly one h1 (no duplicate title from renderPage + hero, #3301)", async () => {
145
+ await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
146
+ expect(screen.getAllByRole("heading", { level: 1 })).toHaveLength(1);
147
+ });
148
+
149
+ it("emits a parseable <script type='application/ld+json'>", async () => {
150
+ await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
151
+ const script = screen.getByTestId("json-ld");
152
+ expect(script.getAttribute("type")).toBe("application/ld+json");
153
+ // The app's responsibility is emitting a parseable JSON-LD <script>.
154
+ // The emitted @graph SHAPE is pinned once in @working-theory/schema's HomePage
155
+ // builder test — re-asserting @type/mainEntity here duplicated that pin
156
+ // and turned every builder change into per-app churn (#2635).
157
+ expect(() => JSON.parse(script.innerHTML)).not.toThrow();
158
+ });
159
+
160
+ it("renders SectionRenderer with sections from frontmatter", async () => {
161
+ await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
162
+ const renderer = screen.getByTestId("section-renderer");
163
+ expect(renderer).toBeInTheDocument();
164
+ expect(renderer.dataset.sectionCount).toBe("4");
165
+ });
166
+
167
+ it("renders at least 3 section items beyond the hero", async () => {
168
+ await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
169
+ const items = screen.getAllByTestId("section-item");
170
+ expect(items.length).toBeGreaterThanOrEqual(4);
171
+ });
172
+
173
+ it("does not render the MDX body directly", async () => {
174
+ await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
175
+ expect(screen.queryByTestId("mdx-body")).toBeNull();
176
+ });
177
+ });
178
+
179
+ describe("with HomePage MDX present but no sections in frontmatter", () => {
180
+ const stubGetMdxSource = jest.fn<ReturnType<GetMdxSource>, Parameters<GetMdxSource>>().mockResolvedValue({
181
+ frontmatter: {
182
+ type: "HomePage",
183
+ slug: "home",
184
+ title: "Welcome to AILK",
185
+ description: "AX-first OSS template.",
186
+ },
187
+ body: "# Welcome\n\nBody copy.",
188
+ });
189
+ const stubGetPageSections = jest.fn<ReturnType<GetPageSections>, Parameters<GetPageSections>>().mockResolvedValue([]);
190
+
191
+ beforeEach(() => {
192
+ stubGetMdxSource.mockClear();
193
+ stubGetPageSections.mockClear();
194
+ });
195
+
196
+ it("renders the page without crashing", async () => {
197
+ await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
198
+ expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent(
199
+ "Welcome to AILK",
200
+ );
201
+ });
202
+
203
+ it("renders a SectionRenderer with empty sections (returns 200)", async () => {
204
+ await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
205
+ expect(screen.queryByTestId("section-item")).toBeNull();
206
+ });
207
+ });
208
+
209
+ describe("with no HomePage MDX (fresh clone)", () => {
210
+ const stubGetMdxSource = jest.fn<ReturnType<GetMdxSource>, Parameters<GetMdxSource>>().mockResolvedValue(null);
211
+ const stubGetPageSections = jest.fn<ReturnType<GetPageSections>, Parameters<GetPageSections>>().mockResolvedValue([]);
212
+
213
+ beforeEach(() => {
214
+ stubGetMdxSource.mockClear();
215
+ stubGetPageSections.mockClear();
216
+ });
217
+
218
+ it("falls back to the placeholder h1", async () => {
219
+ await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
220
+ const heading = screen.getByRole("heading", { level: 1 });
221
+ expect(heading).toHaveTextContent("AI Launch Kit");
222
+ });
223
+
224
+ it("renders a non-empty fallback description", async () => {
225
+ await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
226
+ const description = screen.getByTestId("home-description");
227
+ expect(description.textContent?.trim().length ?? 0).toBeGreaterThan(0);
228
+ });
229
+ });
230
+ });