popeye-cli 2.2.0 → 2.7.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 (323) hide show
  1. package/dist/adapters/gemini.d.ts +14 -0
  2. package/dist/adapters/gemini.d.ts.map +1 -1
  3. package/dist/adapters/gemini.js +41 -6
  4. package/dist/adapters/gemini.js.map +1 -1
  5. package/dist/adapters/grok.d.ts +14 -0
  6. package/dist/adapters/grok.d.ts.map +1 -1
  7. package/dist/adapters/grok.js +42 -6
  8. package/dist/adapters/grok.js.map +1 -1
  9. package/dist/adapters/openai.d.ts +10 -0
  10. package/dist/adapters/openai.d.ts.map +1 -1
  11. package/dist/adapters/openai.js +44 -5
  12. package/dist/adapters/openai.js.map +1 -1
  13. package/dist/cli/commands/create.js +1 -1
  14. package/dist/cli/commands/create.js.map +1 -1
  15. package/dist/cli/interactive.d.ts.map +1 -1
  16. package/dist/cli/interactive.js +324 -20
  17. package/dist/cli/interactive.js.map +1 -1
  18. package/dist/generators/all.d.ts.map +1 -1
  19. package/dist/generators/all.js +3 -2
  20. package/dist/generators/all.js.map +1 -1
  21. package/dist/generators/doc-parser.d.ts +21 -6
  22. package/dist/generators/doc-parser.d.ts.map +1 -1
  23. package/dist/generators/doc-parser.js +55 -4
  24. package/dist/generators/doc-parser.js.map +1 -1
  25. package/dist/generators/templates/fullstack.js +1 -1
  26. package/dist/generators/templates/website-components.js +1 -1
  27. package/dist/generators/templates/website-components.js.map +1 -1
  28. package/dist/generators/templates/website-config.d.ts +4 -1
  29. package/dist/generators/templates/website-config.d.ts.map +1 -1
  30. package/dist/generators/templates/website-config.js +17 -11
  31. package/dist/generators/templates/website-config.js.map +1 -1
  32. package/dist/generators/templates/website-conversion.js +1 -1
  33. package/dist/generators/templates/website-conversion.js.map +1 -1
  34. package/dist/generators/templates/website-landing.js +1 -1
  35. package/dist/generators/templates/website-landing.js.map +1 -1
  36. package/dist/generators/templates/website-layout.d.ts +36 -4
  37. package/dist/generators/templates/website-layout.d.ts.map +1 -1
  38. package/dist/generators/templates/website-layout.js +466 -23
  39. package/dist/generators/templates/website-layout.js.map +1 -1
  40. package/dist/generators/templates/website-pricing.js +1 -1
  41. package/dist/generators/templates/website-pricing.js.map +1 -1
  42. package/dist/generators/templates/website-sections.js +1 -1
  43. package/dist/generators/templates/website-sections.js.map +1 -1
  44. package/dist/generators/templates/website-seo.d.ts.map +1 -1
  45. package/dist/generators/templates/website-seo.js +4 -1
  46. package/dist/generators/templates/website-seo.js.map +1 -1
  47. package/dist/generators/templates/website.d.ts +1 -1
  48. package/dist/generators/templates/website.d.ts.map +1 -1
  49. package/dist/generators/templates/website.js +1 -1
  50. package/dist/generators/templates/website.js.map +1 -1
  51. package/dist/generators/website-content-ai.d.ts +52 -0
  52. package/dist/generators/website-content-ai.d.ts.map +1 -0
  53. package/dist/generators/website-content-ai.js +141 -0
  54. package/dist/generators/website-content-ai.js.map +1 -0
  55. package/dist/generators/website-content-scanner.d.ts +1 -1
  56. package/dist/generators/website-content-scanner.d.ts.map +1 -1
  57. package/dist/generators/website-content-scanner.js +98 -1
  58. package/dist/generators/website-content-scanner.js.map +1 -1
  59. package/dist/generators/website-context.d.ts +34 -1
  60. package/dist/generators/website-context.d.ts.map +1 -1
  61. package/dist/generators/website-context.js +131 -9
  62. package/dist/generators/website-context.js.map +1 -1
  63. package/dist/generators/website-debug.d.ts +12 -0
  64. package/dist/generators/website-debug.d.ts.map +1 -1
  65. package/dist/generators/website-debug.js +16 -0
  66. package/dist/generators/website-debug.js.map +1 -1
  67. package/dist/generators/website.d.ts.map +1 -1
  68. package/dist/generators/website.js +26 -4
  69. package/dist/generators/website.js.map +1 -1
  70. package/dist/pipeline/auto-recovery.d.ts +56 -0
  71. package/dist/pipeline/auto-recovery.d.ts.map +1 -0
  72. package/dist/pipeline/auto-recovery.js +185 -0
  73. package/dist/pipeline/auto-recovery.js.map +1 -0
  74. package/dist/pipeline/change-request.d.ts +39 -0
  75. package/dist/pipeline/change-request.d.ts.map +1 -1
  76. package/dist/pipeline/change-request.js +40 -1
  77. package/dist/pipeline/change-request.js.map +1 -1
  78. package/dist/pipeline/check-runner.d.ts +30 -1
  79. package/dist/pipeline/check-runner.d.ts.map +1 -1
  80. package/dist/pipeline/check-runner.js +122 -1
  81. package/dist/pipeline/check-runner.js.map +1 -1
  82. package/dist/pipeline/command-resolver.d.ts.map +1 -1
  83. package/dist/pipeline/command-resolver.js +33 -2
  84. package/dist/pipeline/command-resolver.js.map +1 -1
  85. package/dist/pipeline/consensus/arbitrator-query.d.ts +22 -0
  86. package/dist/pipeline/consensus/arbitrator-query.d.ts.map +1 -0
  87. package/dist/pipeline/consensus/arbitrator-query.js +70 -0
  88. package/dist/pipeline/consensus/arbitrator-query.js.map +1 -0
  89. package/dist/pipeline/consensus/consensus-runner.d.ts +131 -7
  90. package/dist/pipeline/consensus/consensus-runner.d.ts.map +1 -1
  91. package/dist/pipeline/consensus/consensus-runner.js +809 -35
  92. package/dist/pipeline/consensus/consensus-runner.js.map +1 -1
  93. package/dist/pipeline/cr-lifecycle.d.ts +42 -0
  94. package/dist/pipeline/cr-lifecycle.d.ts.map +1 -0
  95. package/dist/pipeline/cr-lifecycle.js +89 -0
  96. package/dist/pipeline/cr-lifecycle.js.map +1 -0
  97. package/dist/pipeline/gate-engine.d.ts +1 -0
  98. package/dist/pipeline/gate-engine.d.ts.map +1 -1
  99. package/dist/pipeline/gate-engine.js +26 -7
  100. package/dist/pipeline/gate-engine.js.map +1 -1
  101. package/dist/pipeline/orchestrator.d.ts +1 -1
  102. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  103. package/dist/pipeline/orchestrator.js +306 -16
  104. package/dist/pipeline/orchestrator.js.map +1 -1
  105. package/dist/pipeline/packets/consensus-packet-builder.d.ts +15 -4
  106. package/dist/pipeline/packets/consensus-packet-builder.d.ts.map +1 -1
  107. package/dist/pipeline/packets/consensus-packet-builder.js +29 -17
  108. package/dist/pipeline/packets/consensus-packet-builder.js.map +1 -1
  109. package/dist/pipeline/phases/architecture.d.ts.map +1 -1
  110. package/dist/pipeline/phases/architecture.js +5 -3
  111. package/dist/pipeline/phases/architecture.js.map +1 -1
  112. package/dist/pipeline/phases/audit.d.ts.map +1 -1
  113. package/dist/pipeline/phases/audit.js +5 -3
  114. package/dist/pipeline/phases/audit.js.map +1 -1
  115. package/dist/pipeline/phases/consensus-architecture.d.ts.map +1 -1
  116. package/dist/pipeline/phases/consensus-architecture.js +10 -1
  117. package/dist/pipeline/phases/consensus-architecture.js.map +1 -1
  118. package/dist/pipeline/phases/consensus-master-plan.d.ts.map +1 -1
  119. package/dist/pipeline/phases/consensus-master-plan.js +10 -3
  120. package/dist/pipeline/phases/consensus-master-plan.js.map +1 -1
  121. package/dist/pipeline/phases/consensus-role-plans.d.ts.map +1 -1
  122. package/dist/pipeline/phases/consensus-role-plans.js +10 -1
  123. package/dist/pipeline/phases/consensus-role-plans.js.map +1 -1
  124. package/dist/pipeline/phases/done.d.ts.map +1 -1
  125. package/dist/pipeline/phases/done.js +9 -4
  126. package/dist/pipeline/phases/done.js.map +1 -1
  127. package/dist/pipeline/phases/intake.d.ts.map +1 -1
  128. package/dist/pipeline/phases/intake.js +7 -3
  129. package/dist/pipeline/phases/intake.js.map +1 -1
  130. package/dist/pipeline/phases/phase-context.d.ts +2 -0
  131. package/dist/pipeline/phases/phase-context.d.ts.map +1 -1
  132. package/dist/pipeline/phases/phase-context.js +3 -1
  133. package/dist/pipeline/phases/phase-context.js.map +1 -1
  134. package/dist/pipeline/phases/production-gate.d.ts.map +1 -1
  135. package/dist/pipeline/phases/production-gate.js +28 -3
  136. package/dist/pipeline/phases/production-gate.js.map +1 -1
  137. package/dist/pipeline/phases/qa-validation.d.ts.map +1 -1
  138. package/dist/pipeline/phases/qa-validation.js +38 -5
  139. package/dist/pipeline/phases/qa-validation.js.map +1 -1
  140. package/dist/pipeline/phases/recovery-loop.d.ts +2 -0
  141. package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -1
  142. package/dist/pipeline/phases/recovery-loop.js +200 -6
  143. package/dist/pipeline/phases/recovery-loop.js.map +1 -1
  144. package/dist/pipeline/phases/review.d.ts.map +1 -1
  145. package/dist/pipeline/phases/review.js +58 -28
  146. package/dist/pipeline/phases/review.js.map +1 -1
  147. package/dist/pipeline/phases/role-planning.d.ts.map +1 -1
  148. package/dist/pipeline/phases/role-planning.js +18 -2
  149. package/dist/pipeline/phases/role-planning.js.map +1 -1
  150. package/dist/pipeline/phases/stuck.d.ts.map +1 -1
  151. package/dist/pipeline/phases/stuck.js +10 -0
  152. package/dist/pipeline/phases/stuck.js.map +1 -1
  153. package/dist/pipeline/repo-snapshot.d.ts.map +1 -1
  154. package/dist/pipeline/repo-snapshot.js +3 -0
  155. package/dist/pipeline/repo-snapshot.js.map +1 -1
  156. package/dist/pipeline/role-execution-adapter.d.ts +2 -1
  157. package/dist/pipeline/role-execution-adapter.d.ts.map +1 -1
  158. package/dist/pipeline/role-execution-adapter.js +22 -7
  159. package/dist/pipeline/role-execution-adapter.js.map +1 -1
  160. package/dist/pipeline/skill-loader.d.ts +19 -0
  161. package/dist/pipeline/skill-loader.d.ts.map +1 -1
  162. package/dist/pipeline/skill-loader.js +22 -0
  163. package/dist/pipeline/skill-loader.js.map +1 -1
  164. package/dist/pipeline/skills/coverage-gate.d.ts +44 -0
  165. package/dist/pipeline/skills/coverage-gate.d.ts.map +1 -0
  166. package/dist/pipeline/skills/coverage-gate.js +143 -0
  167. package/dist/pipeline/skills/coverage-gate.js.map +1 -0
  168. package/dist/pipeline/skills/usage-registry.d.ts +48 -0
  169. package/dist/pipeline/skills/usage-registry.d.ts.map +1 -0
  170. package/dist/pipeline/skills/usage-registry.js +55 -0
  171. package/dist/pipeline/skills/usage-registry.js.map +1 -0
  172. package/dist/pipeline/strategy-context.d.ts +20 -0
  173. package/dist/pipeline/strategy-context.d.ts.map +1 -0
  174. package/dist/pipeline/strategy-context.js +55 -0
  175. package/dist/pipeline/strategy-context.js.map +1 -0
  176. package/dist/pipeline/type-defs/artifacts.d.ts +25 -5
  177. package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -1
  178. package/dist/pipeline/type-defs/artifacts.js +4 -0
  179. package/dist/pipeline/type-defs/artifacts.js.map +1 -1
  180. package/dist/pipeline/type-defs/audit.d.ts +25 -13
  181. package/dist/pipeline/type-defs/audit.d.ts.map +1 -1
  182. package/dist/pipeline/type-defs/checks.d.ts +18 -8
  183. package/dist/pipeline/type-defs/checks.d.ts.map +1 -1
  184. package/dist/pipeline/type-defs/checks.js +4 -0
  185. package/dist/pipeline/type-defs/checks.js.map +1 -1
  186. package/dist/pipeline/type-defs/packets.d.ts +104 -18
  187. package/dist/pipeline/type-defs/packets.d.ts.map +1 -1
  188. package/dist/pipeline/type-defs/packets.js +17 -1
  189. package/dist/pipeline/type-defs/packets.js.map +1 -1
  190. package/dist/pipeline/type-defs/state.d.ts +160 -16
  191. package/dist/pipeline/type-defs/state.d.ts.map +1 -1
  192. package/dist/pipeline/type-defs/state.js +26 -1
  193. package/dist/pipeline/type-defs/state.js.map +1 -1
  194. package/dist/shared/text-utils.d.ts +23 -0
  195. package/dist/shared/text-utils.d.ts.map +1 -0
  196. package/dist/shared/text-utils.js +66 -0
  197. package/dist/shared/text-utils.js.map +1 -0
  198. package/dist/shared/website-strategy-format.d.ts +18 -0
  199. package/dist/shared/website-strategy-format.d.ts.map +1 -0
  200. package/dist/shared/website-strategy-format.js +47 -0
  201. package/dist/shared/website-strategy-format.js.map +1 -0
  202. package/dist/state/index.d.ts +2 -0
  203. package/dist/state/index.d.ts.map +1 -1
  204. package/dist/state/index.js +57 -8
  205. package/dist/state/index.js.map +1 -1
  206. package/dist/types/consensus.d.ts +1 -0
  207. package/dist/types/consensus.d.ts.map +1 -1
  208. package/dist/types/consensus.js.map +1 -1
  209. package/dist/types/website-strategy.d.ts +1 -1
  210. package/dist/types/workflow.d.ts +447 -0
  211. package/dist/types/workflow.d.ts.map +1 -1
  212. package/dist/types/workflow.js +3 -0
  213. package/dist/types/workflow.js.map +1 -1
  214. package/dist/upgrade/handlers.d.ts.map +1 -1
  215. package/dist/upgrade/handlers.js +6 -3
  216. package/dist/upgrade/handlers.js.map +1 -1
  217. package/dist/workflow/consensus.d.ts.map +1 -1
  218. package/dist/workflow/consensus.js +1 -0
  219. package/dist/workflow/consensus.js.map +1 -1
  220. package/dist/workflow/website-strategy.d.ts.map +1 -1
  221. package/dist/workflow/website-strategy.js +2 -29
  222. package/dist/workflow/website-strategy.js.map +1 -1
  223. package/dist/workflow/website-updater.d.ts.map +1 -1
  224. package/dist/workflow/website-updater.js +3 -2
  225. package/dist/workflow/website-updater.js.map +1 -1
  226. package/package.json +1 -1
  227. package/src/adapters/gemini.ts +51 -6
  228. package/src/adapters/grok.ts +51 -6
  229. package/src/adapters/openai.ts +53 -5
  230. package/src/cli/commands/create.ts +1 -1
  231. package/src/cli/interactive.ts +333 -19
  232. package/src/generators/all.ts +3 -2
  233. package/src/generators/doc-parser.ts +75 -15
  234. package/src/generators/templates/fullstack.ts +1 -1
  235. package/src/generators/templates/website-components.ts +1 -1
  236. package/src/generators/templates/website-config.ts +23 -11
  237. package/src/generators/templates/website-conversion.ts +1 -1
  238. package/src/generators/templates/website-landing.ts +1 -1
  239. package/src/generators/templates/website-layout.ts +491 -23
  240. package/src/generators/templates/website-pricing.ts +1 -1
  241. package/src/generators/templates/website-sections.ts +1 -1
  242. package/src/generators/templates/website-seo.ts +4 -1
  243. package/src/generators/templates/website.ts +3 -0
  244. package/src/generators/website-content-ai.ts +186 -0
  245. package/src/generators/website-content-scanner.ts +113 -1
  246. package/src/generators/website-context.ts +151 -12
  247. package/src/generators/website-debug.ts +26 -0
  248. package/src/generators/website.ts +28 -3
  249. package/src/pipeline/auto-recovery.ts +283 -0
  250. package/src/pipeline/change-request.ts +63 -1
  251. package/src/pipeline/check-runner.ts +141 -2
  252. package/src/pipeline/command-resolver.ts +34 -2
  253. package/src/pipeline/consensus/arbitrator-query.ts +101 -0
  254. package/src/pipeline/consensus/consensus-runner.ts +1099 -42
  255. package/src/pipeline/cr-lifecycle.ts +103 -0
  256. package/src/pipeline/gate-engine.ts +35 -7
  257. package/src/pipeline/orchestrator.ts +361 -16
  258. package/src/pipeline/packets/consensus-packet-builder.ts +44 -18
  259. package/src/pipeline/phases/architecture.ts +6 -3
  260. package/src/pipeline/phases/audit.ts +6 -3
  261. package/src/pipeline/phases/consensus-architecture.ts +10 -1
  262. package/src/pipeline/phases/consensus-master-plan.ts +10 -3
  263. package/src/pipeline/phases/consensus-role-plans.ts +10 -1
  264. package/src/pipeline/phases/done.ts +15 -4
  265. package/src/pipeline/phases/intake.ts +7 -3
  266. package/src/pipeline/phases/phase-context.ts +6 -1
  267. package/src/pipeline/phases/production-gate.ts +41 -3
  268. package/src/pipeline/phases/qa-validation.ts +51 -5
  269. package/src/pipeline/phases/recovery-loop.ts +229 -7
  270. package/src/pipeline/phases/review.ts +73 -30
  271. package/src/pipeline/phases/role-planning.ts +21 -2
  272. package/src/pipeline/phases/stuck.ts +10 -0
  273. package/src/pipeline/repo-snapshot.ts +3 -0
  274. package/src/pipeline/role-execution-adapter.ts +30 -4
  275. package/src/pipeline/skill-loader.ts +33 -0
  276. package/src/pipeline/skills/coverage-gate.ts +199 -0
  277. package/src/pipeline/skills/usage-registry.ts +87 -0
  278. package/src/pipeline/strategy-context.ts +60 -0
  279. package/src/pipeline/type-defs/artifacts.ts +4 -0
  280. package/src/pipeline/type-defs/checks.ts +4 -0
  281. package/src/pipeline/type-defs/packets.ts +18 -1
  282. package/src/pipeline/type-defs/state.ts +26 -1
  283. package/src/shared/text-utils.ts +70 -0
  284. package/src/shared/website-strategy-format.ts +56 -0
  285. package/src/state/index.ts +60 -8
  286. package/src/types/consensus.ts +1 -0
  287. package/src/types/workflow.ts +6 -0
  288. package/src/upgrade/handlers.ts +9 -3
  289. package/src/workflow/consensus.ts +1 -0
  290. package/src/workflow/website-strategy.ts +2 -36
  291. package/src/workflow/website-updater.ts +4 -2
  292. package/tests/adapters/gemini.test.ts +165 -0
  293. package/tests/adapters/grok.test.ts +137 -0
  294. package/tests/adapters/openai.test.ts +128 -0
  295. package/tests/generators/doc-parser.test.ts +88 -9
  296. package/tests/generators/quality-gate.test.ts +19 -3
  297. package/tests/generators/website-components.test.ts +34 -0
  298. package/tests/generators/website-content-ai.test.ts +308 -0
  299. package/tests/generators/website-content-scanner.test.ts +86 -0
  300. package/tests/generators/website-context.test.ts +3 -2
  301. package/tests/integration/smokestack-scaffold.test.ts +385 -0
  302. package/tests/pipeline/auto-recovery.test.ts +337 -0
  303. package/tests/pipeline/change-request.test.ts +70 -0
  304. package/tests/pipeline/command-resolver.test.ts +42 -0
  305. package/tests/pipeline/consensus/arbitrator-query.test.ts +107 -0
  306. package/tests/pipeline/consensus-runner.test.ts +1333 -10
  307. package/tests/pipeline/consensus-scoring.test.ts +602 -18
  308. package/tests/pipeline/gate-engine.test.ts +34 -0
  309. package/tests/pipeline/install-check.test.ts +261 -0
  310. package/tests/pipeline/orchestrator.test.ts +1506 -15
  311. package/tests/pipeline/packets/builders.test.ts +29 -6
  312. package/tests/pipeline/phases/role-planning.strategy.test.ts +204 -0
  313. package/tests/pipeline/pipeline-persistence.test.ts +230 -0
  314. package/tests/pipeline/recovery-loop-guidance.test.ts +280 -0
  315. package/tests/pipeline/role-execution-adapter.test.ts +88 -0
  316. package/tests/pipeline/skills/coverage-gate.test.ts +370 -0
  317. package/tests/pipeline/skills/usage-registry.test.ts +114 -0
  318. package/tests/pipeline/strategy-context.test.ts +148 -0
  319. package/tests/shared/text-utils.test.ts +155 -0
  320. package/tests/state/progress-analysis.test.ts +375 -0
  321. package/tests/upgrade/handlers.test.ts +33 -2
  322. package/tests/workflow/consensus.test.ts +6 -0
  323. package/tsconfig.json +1 -1
@@ -0,0 +1,186 @@
1
+ /**
2
+ * AI Website Content Generator — fills missing website content
3
+ * when doc parser fails to extract tagline/description/features/pricing.
4
+ *
5
+ * Uses OpenAI gpt-4.1 with Zod-validated JSON response.
6
+ * Includes pricing hallucination guard to prevent inventing prices
7
+ * without evidence in source docs.
8
+ */
9
+
10
+ import { z } from 'zod';
11
+
12
+ // ─── Types ───────────────────────────────────────────────
13
+
14
+ export interface AIContentResult {
15
+ tagline?: string;
16
+ description?: string;
17
+ features?: Array<{ title: string; description: string; icon?: string }>;
18
+ pricing?: Array<{
19
+ name: string;
20
+ price: string;
21
+ period?: string;
22
+ description: string;
23
+ features: string[];
24
+ cta: string;
25
+ featured?: boolean;
26
+ }>;
27
+ }
28
+
29
+ // ─── Response Schema ─────────────────────────────────────
30
+
31
+ const AIContentResponseSchema = z.object({
32
+ tagline: z.string().optional(),
33
+ description: z.string().optional(),
34
+ features: z.array(z.object({
35
+ title: z.string(),
36
+ description: z.string(),
37
+ icon: z.string().optional(),
38
+ })).optional(),
39
+ pricing: z.array(z.object({
40
+ name: z.string(),
41
+ price: z.string(),
42
+ period: z.string().optional(),
43
+ description: z.string(),
44
+ features: z.array(z.string()),
45
+ cta: z.string().default('Get Started'),
46
+ featured: z.boolean().optional(),
47
+ })).optional(),
48
+ });
49
+
50
+ // ─── Pricing Hallucination Guard ─────────────────────────
51
+
52
+ /** Broad pricing evidence detection */
53
+ const PRICING_EVIDENCE = /\$\d|€\d|£\d|USD\s*\d|\d+\s*\/\s*mo|per\s*month|monthly|yearly|\/yr|per\s*year|pricing\s*(?:plan|tier|model)|subscription|free\s*(?:plan|tier)/i;
54
+
55
+ /** Pattern that detects numeric pricing in AI output */
56
+ const NUMERIC_PRICING_PATTERN = /[\$€£]\d|USD\s*\d|\d+\s*\/\s*mo/;
57
+
58
+ /**
59
+ * Check if source docs contain evidence of pricing information.
60
+ *
61
+ * Args:
62
+ * rawDocs: Combined documentation content.
63
+ * specification: Expanded specification text.
64
+ *
65
+ * Returns:
66
+ * true if pricing evidence is found in either source.
67
+ */
68
+ export function hasPricingEvidence(rawDocs: string, specification: string): boolean {
69
+ return PRICING_EVIDENCE.test(rawDocs) || PRICING_EVIDENCE.test(specification);
70
+ }
71
+
72
+ /**
73
+ * Post-validate AI-generated pricing to prevent hallucinated prices.
74
+ * If no pricing evidence exists in source docs, reject any numeric prices.
75
+ * "Free" is always allowed (low risk).
76
+ *
77
+ * Args:
78
+ * pricing: AI-generated pricing array.
79
+ * hasEvidence: Whether pricing evidence was found in source docs.
80
+ *
81
+ * Returns:
82
+ * Sanitized pricing array (empty if hallucinated prices detected).
83
+ */
84
+ function sanitizePricing(
85
+ pricing: NonNullable<AIContentResult['pricing']>,
86
+ hasEvidence: boolean,
87
+ ): AIContentResult['pricing'] {
88
+ if (hasEvidence) return pricing;
89
+
90
+ // No evidence — check if AI invented numeric prices
91
+ const hasNumericPrices = pricing.some(
92
+ (tier) => NUMERIC_PRICING_PATTERN.test(tier.price),
93
+ );
94
+
95
+ if (hasNumericPrices) {
96
+ // Reject entire pricing array — AI hallucinated prices
97
+ return undefined;
98
+ }
99
+
100
+ // Allow "Free", "TBD", "Contact us" etc.
101
+ return pricing;
102
+ }
103
+
104
+ // ─── AI Content Generation ──────────────────────────────
105
+
106
+ /**
107
+ * Generate missing website content using AI.
108
+ * Single OpenAI gpt-4.1 call with Zod-validated JSON response.
109
+ * Non-blocking: returns empty {} on any failure.
110
+ *
111
+ * Args:
112
+ * productName: The product name.
113
+ * specification: Expanded specification text.
114
+ * rawDocs: Combined documentation content.
115
+ *
116
+ * Returns:
117
+ * AIContentResult with any fields the AI could generate.
118
+ */
119
+ export async function generateMissingWebsiteContent(
120
+ productName: string,
121
+ specification: string,
122
+ rawDocs: string,
123
+ ): Promise<AIContentResult> {
124
+ try {
125
+ const hasEvidence = hasPricingEvidence(rawDocs, specification);
126
+
127
+ const pricingInstruction = hasEvidence
128
+ ? 'Infer pricing tiers from the documentation. Use exact prices if mentioned.'
129
+ : 'Set all prices to "Contact us" or "TBD". Do not invent numeric pricing.';
130
+
131
+ const systemPrompt = [
132
+ 'You are a marketing content expert. Generate website content for a software product.',
133
+ 'Return a JSON object with these fields (all optional):',
134
+ '- tagline: short marketing tagline (max 80 chars)',
135
+ '- description: product description (max 200 chars)',
136
+ '- features: array of {title, description, icon?} (3-6 features, icon is a lucide icon name)',
137
+ '- pricing: array of {name, price, period?, description, features[], cta, featured?} (2-4 tiers, cta is the button text like "Get Started")',
138
+ '',
139
+ `PRICING RULES: ${pricingInstruction}`,
140
+ '',
141
+ 'Base all content on the product documentation provided. Do not invent facts.',
142
+ 'Return ONLY valid JSON, no markdown or explanation.',
143
+ ].join('\n');
144
+
145
+ const userPrompt = [
146
+ `Product: ${productName}`,
147
+ '',
148
+ specification ? `Specification:\n${specification.slice(0, 3000)}` : '',
149
+ '',
150
+ rawDocs ? `Documentation:\n${rawDocs.slice(0, 5000)}` : '',
151
+ ].filter(Boolean).join('\n');
152
+
153
+ const { createClient } = await import('../adapters/openai.js');
154
+ const client = await createClient();
155
+ const completion = await client.chat.completions.create({
156
+ model: 'gpt-4.1',
157
+ messages: [
158
+ { role: 'system', content: systemPrompt },
159
+ { role: 'user', content: userPrompt },
160
+ ],
161
+ temperature: 0.3,
162
+ response_format: { type: 'json_object' },
163
+ });
164
+ const response = completion.choices[0]?.message?.content ?? '{}';
165
+
166
+ // Parse and validate response
167
+ const parsed = JSON.parse(response);
168
+ const validated = AIContentResponseSchema.safeParse(parsed);
169
+
170
+ if (!validated.success) {
171
+ return {};
172
+ }
173
+
174
+ const result: AIContentResult = { ...validated.data };
175
+
176
+ // Apply pricing hallucination guard
177
+ if (result.pricing && result.pricing.length > 0) {
178
+ result.pricing = sanitizePricing(result.pricing, hasEvidence);
179
+ }
180
+
181
+ return result;
182
+ } catch {
183
+ // Non-blocking: return empty on any failure
184
+ return {};
185
+ }
186
+ }
@@ -76,6 +76,16 @@ const PLACEHOLDER_PATTERNS: Array<{
76
76
  message: 'Default pricing amount ($29/mo)',
77
77
  severity: 'warning',
78
78
  },
79
+ {
80
+ pattern: /coming soon/i,
81
+ message: 'Contains "coming soon" placeholder text',
82
+ severity: 'error',
83
+ },
84
+ {
85
+ pattern: /placeholder/i,
86
+ message: 'Contains "placeholder" text',
87
+ severity: 'error',
88
+ },
79
89
  ];
80
90
 
81
91
  /**
@@ -156,7 +166,100 @@ function findLineNumber(content: string, pattern: RegExp): number | undefined {
156
166
  }
157
167
 
158
168
  /**
159
- * Scan generated website files for placeholder fingerprints
169
+ * Collect all page routes from the src/app directory structure.
170
+ * A route exists if there is a page.tsx (or page.jsx/page.ts/page.js) in a directory.
171
+ *
172
+ * @param appDir - The src/app directory path
173
+ * @returns Set of route paths (e.g., '/', '/pricing', '/blog')
174
+ */
175
+ async function collectPageRoutes(appDir: string): Promise<Set<string>> {
176
+ const routes = new Set<string>();
177
+
178
+ async function walk(dir: string, routePrefix: string): Promise<void> {
179
+ try {
180
+ const entries = await fs.readdir(dir, { withFileTypes: true });
181
+
182
+ // Check if this directory has a page file
183
+ const hasPage = entries.some(
184
+ (e) => e.isFile() && /^page\.(tsx|jsx|ts|js)$/.test(e.name)
185
+ );
186
+ if (hasPage) {
187
+ routes.add(routePrefix || '/');
188
+ }
189
+
190
+ // Recurse into subdirectories (skip special dirs)
191
+ for (const entry of entries) {
192
+ if (!entry.isDirectory()) continue;
193
+ if (entry.name.startsWith('_') || entry.name === 'api' || SKIP_DIRS.has(entry.name)) continue;
194
+ await walk(path.join(dir, entry.name), `${routePrefix}/${entry.name}`);
195
+ }
196
+ } catch {
197
+ // Directory not accessible
198
+ }
199
+ }
200
+
201
+ await walk(appDir, '');
202
+ return routes;
203
+ }
204
+
205
+ /**
206
+ * Scan for internal links that point to pages that do not exist.
207
+ *
208
+ * @param websiteDir - The website project directory
209
+ * @param files - Already-collected source file paths
210
+ * @returns Array of scan issues for broken internal links
211
+ */
212
+ async function scanInternalLinks(
213
+ websiteDir: string,
214
+ files: string[]
215
+ ): Promise<ScanIssue[]> {
216
+ const issues: ScanIssue[] = [];
217
+ const appDir = path.join(websiteDir, 'src', 'app');
218
+
219
+ // Collect existing page routes
220
+ const routes = await collectPageRoutes(appDir);
221
+
222
+ // Regex to find href="/..." values in source files
223
+ const hrefPattern = /href=["'](\/([\w-/]*)?)["']/g;
224
+
225
+ for (const filePath of files) {
226
+ try {
227
+ const content = await fs.readFile(filePath, 'utf-8');
228
+ const relativePath = path.relative(websiteDir, filePath);
229
+
230
+ let match;
231
+ while ((match = hrefPattern.exec(content)) !== null) {
232
+ const href = match[1];
233
+
234
+ // Skip anchor links (/#section), API routes, and external/protocol links
235
+ if (href.startsWith('/#') || href.startsWith('/api/') || href.startsWith('/api')) continue;
236
+
237
+ // Normalize: strip trailing slash for comparison
238
+ const normalizedHref = href === '/' ? '/' : href.replace(/\/$/, '');
239
+
240
+ if (!routes.has(normalizedHref)) {
241
+ const lineNum = content.slice(0, match.index).split('\n').length;
242
+ issues.push({
243
+ file: relativePath,
244
+ message: `Internal link "${href}" points to a page that does not exist`,
245
+ severity: 'error',
246
+ line: lineNum,
247
+ });
248
+ }
249
+ }
250
+
251
+ // Reset regex lastIndex for next file
252
+ hrefPattern.lastIndex = 0;
253
+ } catch {
254
+ // Skip unreadable files
255
+ }
256
+ }
257
+
258
+ return issues;
259
+ }
260
+
261
+ /**
262
+ * Scan generated website files for placeholder fingerprints and broken internal links
160
263
  *
161
264
  * @param websiteDir - The website project directory to scan
162
265
  * @returns Scan result with issues and quality score
@@ -200,6 +303,15 @@ export async function scanGeneratedContent(websiteDir: string): Promise<ScanResu
200
303
  }
201
304
  }
202
305
 
306
+ // Scan for broken internal links
307
+ try {
308
+ const linkIssues = await scanInternalLinks(websiteDir, files);
309
+ issues.push(...linkIssues);
310
+ score -= linkIssues.length * 15;
311
+ } catch {
312
+ // Non-blocking: link scan failures should not stop the overall scan
313
+ }
314
+
203
315
  return {
204
316
  issues,
205
317
  filesScanned: files.length,
@@ -17,10 +17,17 @@ import {
17
17
  } from './doc-parser.js';
18
18
  import { getScanDirectories } from './workspace-root.js';
19
19
  import type { WebsiteStrategyDocument, BrandAssetsContract } from '../types/website-strategy.js';
20
+ import { generateMissingWebsiteContent } from './website-content-ai.js';
20
21
 
21
22
  /** Per-file character cap to prevent a single large doc from consuming the budget */
22
23
  const PER_FILE_CAP = 8000;
23
24
 
25
+ /** Allowed extensions for user-provided doc paths */
26
+ const ALLOWED_DOC_EXTENSIONS = ['.md', '.mdx', '.txt'];
27
+
28
+ /** Maximum file size for user-provided docs (2MB) */
29
+ const MAX_DOC_SIZE = 2 * 1024 * 1024;
30
+
24
31
  /**
25
32
  * Structured content context for website generation
26
33
  */
@@ -48,6 +55,15 @@ export interface WebsiteContentContext {
48
55
  strategy?: WebsiteStrategyDocument;
49
56
  /** Resolved brand assets contract for deterministic logo/favicon placement */
50
57
  brandAssets?: BrandAssetsContract;
58
+ /** Diagnostics for pricing extraction pipeline */
59
+ pricingDiagnostics?: {
60
+ charsScanned: number;
61
+ foundPricingHeader: boolean;
62
+ extractionMethod: 'known_plan_names' | 'table_fallback' | 'ai' | 'none';
63
+ extractedTiers?: Array<{ name: string; price: string }>;
64
+ reasonIfEmpty?: string;
65
+ source: 'docs' | 'ai' | 'default' | 'none';
66
+ };
51
67
  }
52
68
 
53
69
  /**
@@ -279,20 +295,104 @@ export async function readProjectDocs(
279
295
  return sections.join('\n\n');
280
296
  }
281
297
 
298
+ /**
299
+ * Extract absolute file paths from user-provided text (idea/specification).
300
+ * Supports:
301
+ * - Unix paths: '/path/to/file.md'
302
+ * - Windows paths: "C:\Users\me\file.md"
303
+ * - Paths with spaces: '/Users/me/My Docs/pricing.md'
304
+ * - Extensions: .md, .mdx, .txt
305
+ *
306
+ * @param text - User-provided idea or specification text
307
+ * @returns Array of absolute file paths found in the text
308
+ */
309
+ export function extractDocPathsFromText(text: string): string[] {
310
+ const paths: string[] = [];
311
+
312
+ // 1. Quoted paths (most reliable — handles spaces, cross-platform)
313
+ const quotedPattern = /['"]([A-Za-z]:[\\\/].+?\.(?:md|mdx|txt)|\/[^'"]+\.(?:md|mdx|txt))['"]/gi;
314
+ let match;
315
+ while ((match = quotedPattern.exec(text)) !== null) {
316
+ paths.push(match[1]);
317
+ }
318
+
319
+ // 2. Unquoted Unix absolute paths (no spaces — spaces break word boundaries)
320
+ const unquotedUnix = /(?:^|\s)(\/\S+\.(?:md|mdx|txt))(?=[\s,;)]|$)/gm;
321
+ while ((match = unquotedUnix.exec(text)) !== null) {
322
+ if (!paths.includes(match[1])) paths.push(match[1]);
323
+ }
324
+
325
+ // 3. Unquoted Windows paths (drive letter)
326
+ const unquotedWin = /(?:^|\s)([A-Za-z]:[\\\/]\S+\.(?:md|mdx|txt))(?=[\s,;)]|$)/gm;
327
+ while ((match = unquotedWin.exec(text)) !== null) {
328
+ if (!paths.includes(match[1])) paths.push(match[1]);
329
+ }
330
+
331
+ return [...new Set(paths)];
332
+ }
333
+
334
+ /**
335
+ * Validate and filter doc paths for safe ingestion.
336
+ * Checks: isAbsolute, file exists, size < 2MB, extension allowed.
337
+ *
338
+ * @param paths - Array of absolute paths to validate
339
+ * @returns Array of valid, readable paths
340
+ */
341
+ export async function validateAndFilterDocPaths(paths: string[]): Promise<string[]> {
342
+ const valid: string[] = [];
343
+ for (const p of paths) {
344
+ const ext = path.extname(p).toLowerCase();
345
+ if (!ALLOWED_DOC_EXTENSIONS.includes(ext)) {
346
+ console.log(`[doc-ingest] Skipped: ${p} (unsupported format ${ext})`);
347
+ continue;
348
+ }
349
+ const isAbs = path.isAbsolute(p) || /^[A-Za-z]:[\\\/]/.test(p);
350
+ if (!isAbs) continue;
351
+ try {
352
+ const stat = await fs.stat(p);
353
+ if (stat.size > MAX_DOC_SIZE) {
354
+ console.log(`[doc-ingest] Skipped: ${p} (${(stat.size / 1024).toFixed(0)}KB exceeds 2MB limit)`);
355
+ continue;
356
+ }
357
+ console.log(`[doc-ingest] Read: ${p} (${(stat.size / 1024).toFixed(1)}KB)`);
358
+ valid.push(p);
359
+ } catch {
360
+ console.log(`[doc-ingest] Skipped: ${p} (file not found)`);
361
+ }
362
+ }
363
+ return valid;
364
+ }
365
+
282
366
  /**
283
367
  * Build a structured website content context from discovered docs
284
368
  *
285
369
  * @param cwd - Working directory to scan for docs
286
370
  * @param projectName - The project name (folder name fallback)
287
371
  * @param specification - Optional expanded specification text
372
+ * @param extraDocPaths - Optional extra doc paths extracted from user idea text
288
373
  * @returns Structured content context for website templates
289
374
  */
290
375
  export async function buildWebsiteContext(
291
376
  cwd: string,
292
377
  projectName: string,
293
- specification?: string
378
+ specification?: string,
379
+ extraDocPaths?: string[],
294
380
  ): Promise<WebsiteContentContext> {
295
381
  const docPaths = await discoverProjectDocs(cwd);
382
+
383
+ // Merge validated extra doc paths (from idea text)
384
+ if (extraDocPaths?.length) {
385
+ const validPaths = await validateAndFilterDocPaths(extraDocPaths);
386
+ const seen = new Set(docPaths.map((p) => path.resolve(p)));
387
+ for (const p of validPaths) {
388
+ const resolved = path.resolve(p);
389
+ if (!seen.has(resolved)) {
390
+ docPaths.push(resolved);
391
+ seen.add(resolved);
392
+ }
393
+ }
394
+ }
395
+
296
396
  const rawDocs = docPaths.length > 0 ? await readProjectDocs(docPaths) : '';
297
397
  const brandAssets = await findBrandAssets(cwd);
298
398
 
@@ -307,7 +407,20 @@ export async function buildWebsiteContext(
307
407
 
308
408
  context.tagline = extractTagline(cleanDocs, context.productName);
309
409
  context.description = extractDescription(cleanDocs, specification);
310
- context.pricing = extractPricing(cleanDocs);
410
+
411
+ // Extract pricing with provenance tracking
412
+ const pricingResult = extractPricing(cleanDocs);
413
+ context.pricing = pricingResult.tiers.length > 0 ? pricingResult.tiers : undefined;
414
+ context.pricingDiagnostics = {
415
+ charsScanned: cleanDocs.length,
416
+ foundPricingHeader: !!cleanDocs.match(/##\s+(?:[\d.]*\s*)?Pricing\b/i),
417
+ extractionMethod: pricingResult.evidence?.extractionMethod ?? 'none',
418
+ extractedTiers: pricingResult.tiers.map((t) => ({ name: t.name, price: t.price })),
419
+ reasonIfEmpty: pricingResult.tiers.length === 0
420
+ ? 'No pricing table rows matched in docs'
421
+ : undefined,
422
+ source: pricingResult.source,
423
+ };
311
424
 
312
425
  // Extract brand info
313
426
  if (brandAssets.logoPath) {
@@ -319,6 +432,36 @@ export async function buildWebsiteContext(
319
432
  context.brand = { ...context.brand, primaryColor };
320
433
  }
321
434
 
435
+ // v2.2.2: Per-field AI content generation fallback
436
+ // Reason: triggers when ANY field is missing (not all-or-nothing), but only fills missing fields
437
+ const pricingMissing = context.pricing == null || context.pricing.length === 0;
438
+ const hasMissingContent = !context.tagline || !context.description || context.features.length === 0 || pricingMissing;
439
+ if (hasMissingContent && (rawDocs.length > 50 || specification)) {
440
+ try {
441
+ const aiContent = await generateMissingWebsiteContent(
442
+ context.productName,
443
+ specification ?? '',
444
+ rawDocs,
445
+ );
446
+ // Only fill fields that are actually missing (don't overwrite extracted truth)
447
+ if (!context.tagline && aiContent.tagline) context.tagline = aiContent.tagline;
448
+ if (!context.description && aiContent.description) context.description = aiContent.description;
449
+ if (context.features.length === 0 && aiContent.features?.length) context.features = aiContent.features;
450
+ if (pricingMissing && aiContent.pricing?.length) {
451
+ context.pricing = aiContent.pricing;
452
+ context.pricingDiagnostics = {
453
+ ...context.pricingDiagnostics!,
454
+ extractionMethod: 'ai',
455
+ source: 'ai',
456
+ extractedTiers: aiContent.pricing.map((t) => ({ name: t.name, price: t.price })),
457
+ reasonIfEmpty: undefined,
458
+ };
459
+ }
460
+ } catch {
461
+ // Non-blocking: continue with doc-parser results
462
+ }
463
+ }
464
+
322
465
  return context;
323
466
  }
324
467
 
@@ -388,14 +531,6 @@ export function validateWebsiteContext(
388
531
  contentScore -= 20;
389
532
  }
390
533
 
391
- // Strategy validation
392
- if (!context.strategy) {
393
- issues.push(
394
- 'Website strategy missing. Strategy generation may have failed or been skipped.'
395
- );
396
- contentScore -= 15;
397
- }
398
-
399
534
  // Brand color validation: brand/color docs exist but no color extracted
400
535
  if (!context.brand?.primaryColor && context.rawDocs && /color|brand/i.test(context.rawDocs)) {
401
536
  issues.push(
@@ -421,8 +556,12 @@ export function validateWebsiteContext(
421
556
  const nameMatches = tierNames.filter((n) => DEFAULT_PRICING_NAMES.includes(n));
422
557
  const priceMatches = tierPrices.filter((p) => DEFAULT_PRICING_AMOUNTS.includes(p));
423
558
 
424
- // Reason: if most tier names AND prices match defaults, it's likely placeholder content
425
- if (nameMatches.length >= 2 && priceMatches.length >= 2) {
559
+ // Reason: only warn when pricing did NOT come from docs or AI (provenance-based check)
560
+ const pricingSource = context.pricingDiagnostics?.source;
561
+ if (
562
+ nameMatches.length >= 2 && priceMatches.length >= 2
563
+ && pricingSource !== 'docs' && pricingSource !== 'ai'
564
+ ) {
426
565
  warnings.push(
427
566
  'Pricing tiers appear to use default values (Starter/Pro/Enterprise at $0/$29/Custom). ' +
428
567
  'Add a pricing section to your docs for accurate tiers.'
@@ -23,6 +23,15 @@ export interface WebsiteDebugTrace {
23
23
  dataSource: 'strategy' | 'docs' | 'defaults' | 'skipped';
24
24
  itemCount: number;
25
25
  }>;
26
+ /** Pricing extraction diagnostics */
27
+ pricingDiagnostics?: {
28
+ charsScanned: number;
29
+ foundPricingHeader: boolean;
30
+ extractionMethod: 'known_plan_names' | 'table_fallback' | 'ai' | 'none';
31
+ extractedTiers?: Array<{ name: string; price: string }>;
32
+ reasonIfEmpty?: string;
33
+ source: 'docs' | 'ai' | 'default' | 'none';
34
+ };
26
35
  /** Validation result from quality gate */
27
36
  validationPassed: boolean;
28
37
  validationIssues: string[];
@@ -104,6 +113,23 @@ export function formatDebugTrace(trace: WebsiteDebugTrace): string {
104
113
  }
105
114
  lines.push('');
106
115
 
116
+ if (trace.pricingDiagnostics) {
117
+ lines.push('Pricing Diagnostics:');
118
+ lines.push(` Source: ${trace.pricingDiagnostics.source}`);
119
+ lines.push(` Method: ${trace.pricingDiagnostics.extractionMethod}`);
120
+ lines.push(` Chars Scanned: ${trace.pricingDiagnostics.charsScanned}`);
121
+ lines.push(` Found Header: ${trace.pricingDiagnostics.foundPricingHeader}`);
122
+ if (trace.pricingDiagnostics.extractedTiers?.length) {
123
+ for (const t of trace.pricingDiagnostics.extractedTiers) {
124
+ lines.push(` - ${t.name}: ${t.price}`);
125
+ }
126
+ }
127
+ if (trace.pricingDiagnostics.reasonIfEmpty) {
128
+ lines.push(` Reason empty: ${trace.pricingDiagnostics.reasonIfEmpty}`);
129
+ }
130
+ lines.push('');
131
+ }
132
+
107
133
  lines.push(`Validation: ${trace.validationPassed ? 'PASSED' : 'FAILED'}`);
108
134
  if (trace.validationIssues.length > 0) {
109
135
  for (const issue of trace.validationIssues) {
@@ -16,6 +16,9 @@ import {
16
16
  generateWebsiteTest,
17
17
  generateWebsiteDocsPage,
18
18
  generateWebsiteBlogPage,
19
+ generateWebsiteContactPage,
20
+ generateWebsitePrivacyPage,
21
+ generateWebsiteTermsPage,
19
22
  } from './templates/website.js';
20
23
  import {
21
24
  generateWebsitePackageJson,
@@ -134,6 +137,9 @@ export async function generateWebsiteProject(
134
137
  await ensureDir(path.join(projectDir, 'src', 'app', 'pricing'));
135
138
  await ensureDir(path.join(projectDir, 'src', 'app', 'docs'));
136
139
  await ensureDir(path.join(projectDir, 'src', 'app', 'blog'));
140
+ await ensureDir(path.join(projectDir, 'src', 'app', 'contact'));
141
+ await ensureDir(path.join(projectDir, 'src', 'app', 'privacy'));
142
+ await ensureDir(path.join(projectDir, 'src', 'app', 'terms'));
137
143
  await ensureDir(path.join(projectDir, 'src', 'app', 'api', 'lead'));
138
144
  await ensureDir(path.join(projectDir, 'src', 'components'));
139
145
  await ensureDir(path.join(projectDir, 'src', 'lib'));
@@ -187,6 +193,7 @@ export async function generateWebsiteProject(
187
193
  pricingTiers: contentContext.pricing?.length || 0,
188
194
  },
189
195
  sectionsRendered: [],
196
+ pricingDiagnostics: contentContext.pricingDiagnostics,
190
197
  validationPassed: true,
191
198
  validationIssues: [],
192
199
  };
@@ -206,7 +213,10 @@ export async function generateWebsiteProject(
206
213
  },
207
214
  {
208
215
  path: path.join(projectDir, 'tsconfig.json'),
209
- content: generateWebsiteTsconfig(),
216
+ content: generateWebsiteTsconfig({
217
+ workspaceMode,
218
+ projectName: workspaceMode ? projectName : undefined,
219
+ }),
210
220
  },
211
221
  {
212
222
  path: path.join(projectDir, 'tailwind.config.ts'),
@@ -248,11 +258,23 @@ export async function generateWebsiteProject(
248
258
  },
249
259
  {
250
260
  path: path.join(projectDir, 'src', 'app', 'docs', 'page.tsx'),
251
- content: generateWebsiteDocsPage(),
261
+ content: generateWebsiteDocsPage(projectName, contentContext),
252
262
  },
253
263
  {
254
264
  path: path.join(projectDir, 'src', 'app', 'blog', 'page.tsx'),
255
- content: generateWebsiteBlogPage(),
265
+ content: generateWebsiteBlogPage(projectName, contentContext),
266
+ },
267
+ {
268
+ path: path.join(projectDir, 'src', 'app', 'contact', 'page.tsx'),
269
+ content: generateWebsiteContactPage(projectName, contentContext),
270
+ },
271
+ {
272
+ path: path.join(projectDir, 'src', 'app', 'privacy', 'page.tsx'),
273
+ content: generateWebsitePrivacyPage(projectName, contentContext),
274
+ },
275
+ {
276
+ path: path.join(projectDir, 'src', 'app', 'terms', 'page.tsx'),
277
+ content: generateWebsiteTermsPage(projectName, contentContext),
256
278
  },
257
279
 
258
280
  // Shared components
@@ -462,6 +484,9 @@ export function getWebsiteProjectFiles(_projectName: string): string[] {
462
484
  'src/app/pricing/page.tsx',
463
485
  'src/app/docs/page.tsx',
464
486
  'src/app/blog/page.tsx',
487
+ 'src/app/contact/page.tsx',
488
+ 'src/app/privacy/page.tsx',
489
+ 'src/app/terms/page.tsx',
465
490
  'src/app/sitemap.ts',
466
491
  'src/app/robots.ts',
467
492
  // Tests