popeye-cli 1.4.6 → 1.5.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 (140) hide show
  1. package/README.md +222 -63
  2. package/dist/adapters/gemini.d.ts +1 -0
  3. package/dist/adapters/gemini.d.ts.map +1 -1
  4. package/dist/adapters/gemini.js +9 -4
  5. package/dist/adapters/gemini.js.map +1 -1
  6. package/dist/adapters/grok.d.ts +1 -0
  7. package/dist/adapters/grok.d.ts.map +1 -1
  8. package/dist/adapters/grok.js +9 -4
  9. package/dist/adapters/grok.js.map +1 -1
  10. package/dist/adapters/openai.d.ts +1 -1
  11. package/dist/adapters/openai.d.ts.map +1 -1
  12. package/dist/adapters/openai.js +35 -9
  13. package/dist/adapters/openai.js.map +1 -1
  14. package/dist/cli/interactive.d.ts.map +1 -1
  15. package/dist/cli/interactive.js +42 -0
  16. package/dist/cli/interactive.js.map +1 -1
  17. package/dist/generators/all.d.ts +4 -1
  18. package/dist/generators/all.d.ts.map +1 -1
  19. package/dist/generators/all.js +2 -1
  20. package/dist/generators/all.js.map +1 -1
  21. package/dist/generators/doc-parser.d.ts +49 -0
  22. package/dist/generators/doc-parser.d.ts.map +1 -0
  23. package/dist/generators/doc-parser.js +336 -0
  24. package/dist/generators/doc-parser.js.map +1 -0
  25. package/dist/generators/templates/index.d.ts +4 -0
  26. package/dist/generators/templates/index.d.ts.map +1 -1
  27. package/dist/generators/templates/index.js +4 -0
  28. package/dist/generators/templates/index.js.map +1 -1
  29. package/dist/generators/templates/website-components.d.ts +33 -0
  30. package/dist/generators/templates/website-components.d.ts.map +1 -0
  31. package/dist/generators/templates/website-components.js +278 -0
  32. package/dist/generators/templates/website-components.js.map +1 -0
  33. package/dist/generators/templates/website-config.d.ts +41 -0
  34. package/dist/generators/templates/website-config.d.ts.map +1 -0
  35. package/dist/generators/templates/website-config.js +283 -0
  36. package/dist/generators/templates/website-config.js.map +1 -0
  37. package/dist/generators/templates/website-conversion.d.ts +27 -0
  38. package/dist/generators/templates/website-conversion.d.ts.map +1 -0
  39. package/dist/generators/templates/website-conversion.js +326 -0
  40. package/dist/generators/templates/website-conversion.js.map +1 -0
  41. package/dist/generators/templates/website-seo.d.ts +76 -0
  42. package/dist/generators/templates/website-seo.d.ts.map +1 -0
  43. package/dist/generators/templates/website-seo.js +326 -0
  44. package/dist/generators/templates/website-seo.js.map +1 -0
  45. package/dist/generators/templates/website.d.ts +14 -47
  46. package/dist/generators/templates/website.d.ts.map +1 -1
  47. package/dist/generators/templates/website.js +412 -499
  48. package/dist/generators/templates/website.js.map +1 -1
  49. package/dist/generators/website-context.d.ts +83 -0
  50. package/dist/generators/website-context.d.ts.map +1 -0
  51. package/dist/generators/website-context.js +190 -0
  52. package/dist/generators/website-context.js.map +1 -0
  53. package/dist/generators/website.d.ts +3 -0
  54. package/dist/generators/website.d.ts.map +1 -1
  55. package/dist/generators/website.js +73 -10
  56. package/dist/generators/website.js.map +1 -1
  57. package/dist/state/index.d.ts +27 -0
  58. package/dist/state/index.d.ts.map +1 -1
  59. package/dist/state/index.js +30 -0
  60. package/dist/state/index.js.map +1 -1
  61. package/dist/types/consensus.d.ts +3 -0
  62. package/dist/types/consensus.d.ts.map +1 -1
  63. package/dist/types/consensus.js +1 -0
  64. package/dist/types/consensus.js.map +1 -1
  65. package/dist/types/website-strategy.d.ts +263 -0
  66. package/dist/types/website-strategy.d.ts.map +1 -0
  67. package/dist/types/website-strategy.js +105 -0
  68. package/dist/types/website-strategy.js.map +1 -0
  69. package/dist/types/workflow.d.ts +15 -0
  70. package/dist/types/workflow.d.ts.map +1 -1
  71. package/dist/types/workflow.js +6 -0
  72. package/dist/types/workflow.js.map +1 -1
  73. package/dist/workflow/auto-fix.d.ts +7 -1
  74. package/dist/workflow/auto-fix.d.ts.map +1 -1
  75. package/dist/workflow/auto-fix.js +55 -3
  76. package/dist/workflow/auto-fix.js.map +1 -1
  77. package/dist/workflow/consensus.d.ts.map +1 -1
  78. package/dist/workflow/consensus.js +2 -0
  79. package/dist/workflow/consensus.js.map +1 -1
  80. package/dist/workflow/execution-mode.d.ts.map +1 -1
  81. package/dist/workflow/execution-mode.js +18 -0
  82. package/dist/workflow/execution-mode.js.map +1 -1
  83. package/dist/workflow/index.d.ts +3 -0
  84. package/dist/workflow/index.d.ts.map +1 -1
  85. package/dist/workflow/index.js +25 -0
  86. package/dist/workflow/index.js.map +1 -1
  87. package/dist/workflow/overview.d.ts +89 -0
  88. package/dist/workflow/overview.d.ts.map +1 -0
  89. package/dist/workflow/overview.js +354 -0
  90. package/dist/workflow/overview.js.map +1 -0
  91. package/dist/workflow/plan-mode.d.ts +2 -1
  92. package/dist/workflow/plan-mode.d.ts.map +1 -1
  93. package/dist/workflow/plan-mode.js +83 -5
  94. package/dist/workflow/plan-mode.js.map +1 -1
  95. package/dist/workflow/website-strategy.d.ts +70 -0
  96. package/dist/workflow/website-strategy.d.ts.map +1 -0
  97. package/dist/workflow/website-strategy.js +238 -0
  98. package/dist/workflow/website-strategy.js.map +1 -0
  99. package/dist/workflow/website-updater.d.ts +17 -0
  100. package/dist/workflow/website-updater.d.ts.map +1 -0
  101. package/dist/workflow/website-updater.js +105 -0
  102. package/dist/workflow/website-updater.js.map +1 -0
  103. package/dist/workflow/workflow-logger.d.ts +1 -1
  104. package/dist/workflow/workflow-logger.d.ts.map +1 -1
  105. package/dist/workflow/workflow-logger.js.map +1 -1
  106. package/package.json +1 -1
  107. package/src/adapters/gemini.ts +10 -4
  108. package/src/adapters/grok.ts +10 -4
  109. package/src/adapters/openai.ts +38 -6
  110. package/src/cli/interactive.ts +47 -0
  111. package/src/generators/all.ts +6 -1
  112. package/src/generators/doc-parser.ts +372 -0
  113. package/src/generators/templates/index.ts +4 -0
  114. package/src/generators/templates/website-components.ts +305 -0
  115. package/src/generators/templates/website-config.ts +291 -0
  116. package/src/generators/templates/website-conversion.ts +341 -0
  117. package/src/generators/templates/website-seo.ts +370 -0
  118. package/src/generators/templates/website.ts +451 -505
  119. package/src/generators/website-context.ts +265 -0
  120. package/src/generators/website.ts +109 -19
  121. package/src/state/index.ts +42 -0
  122. package/src/types/consensus.ts +3 -0
  123. package/src/types/website-strategy.ts +243 -0
  124. package/src/types/workflow.ts +15 -0
  125. package/src/workflow/auto-fix.ts +57 -3
  126. package/src/workflow/consensus.ts +2 -0
  127. package/src/workflow/execution-mode.ts +21 -0
  128. package/src/workflow/index.ts +25 -0
  129. package/src/workflow/overview.ts +469 -0
  130. package/src/workflow/plan-mode.ts +115 -4
  131. package/src/workflow/website-strategy.ts +305 -0
  132. package/src/workflow/website-updater.ts +131 -0
  133. package/src/workflow/workflow-logger.ts +1 -0
  134. package/tests/adapters/persona-switching.test.ts +63 -0
  135. package/tests/generators/website-components.test.ts +159 -0
  136. package/tests/generators/website-context.test.ts +222 -0
  137. package/tests/generators/website-seo-quality.test.ts +246 -0
  138. package/tests/workflow/auto-fix-enhanced.test.ts +61 -1
  139. package/tests/workflow/overview.test.ts +392 -0
  140. package/tests/workflow/website-strategy.test.ts +191 -0
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Website strategy type definitions
3
+ * Defines the strategic marketing document, site architecture,
4
+ * design tokens, and brand assets contract for website generation
5
+ */
6
+
7
+ import { z } from 'zod';
8
+
9
+ /**
10
+ * Navigation item for site header/footer
11
+ */
12
+ export interface NavItem {
13
+ label: string;
14
+ href: string;
15
+ children?: NavItem[];
16
+ }
17
+
18
+ /**
19
+ * Footer section with links
20
+ */
21
+ export interface FooterSection {
22
+ title: string;
23
+ links: Array<{ label: string; href: string }>;
24
+ }
25
+
26
+ /**
27
+ * Page definition in site architecture
28
+ */
29
+ export interface SiteArchitecturePage {
30
+ path: string;
31
+ title: string;
32
+ purpose: string;
33
+ pageType:
34
+ | 'landing'
35
+ | 'pricing'
36
+ | 'solution'
37
+ | 'use-cases'
38
+ | 'about'
39
+ | 'contact'
40
+ | 'blog'
41
+ | 'docs'
42
+ | 'legal';
43
+ sections: string[];
44
+ seoKeywords: string[];
45
+ conversionGoal: string;
46
+ }
47
+
48
+ /**
49
+ * Complete website strategy document
50
+ * Generated by AI from product context, user docs, and market inputs
51
+ */
52
+ export interface WebsiteStrategyDocument {
53
+ icp: {
54
+ primaryPersona: string;
55
+ painPoints: string[];
56
+ goals: string[];
57
+ objections: string[];
58
+ };
59
+ positioning: {
60
+ category: string;
61
+ differentiators: string[];
62
+ valueProposition: string;
63
+ proofPoints: string[];
64
+ };
65
+ messaging: {
66
+ headline: string;
67
+ subheadline: string;
68
+ elevatorPitch: string;
69
+ longDescription: string;
70
+ };
71
+ seoStrategy: {
72
+ primaryKeywords: string[];
73
+ secondaryKeywords: string[];
74
+ longTailKeywords: string[];
75
+ titleTemplates: Record<string, string>;
76
+ metaDescriptions: Record<string, string>;
77
+ };
78
+ siteArchitecture: {
79
+ pages: SiteArchitecturePage[];
80
+ navigation: NavItem[];
81
+ footerSections: FooterSection[];
82
+ };
83
+ conversionStrategy: {
84
+ primaryCta: { text: string; href: string };
85
+ secondaryCta: { text: string; href: string };
86
+ trustSignals: string[];
87
+ socialProof: string[];
88
+ leadCapture: 'none' | 'webhook' | 'resend' | 'postmark';
89
+ };
90
+ competitiveContext: {
91
+ category: string;
92
+ competitors: string[];
93
+ differentiators: string[];
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Strategy metadata for caching and staleness detection
99
+ */
100
+ export interface StrategyMetadata {
101
+ /** SHA-256 of (docs + spec + tokens) for staleness detection */
102
+ inputHash: string;
103
+ /** ISO timestamp of generation */
104
+ generatedAt: string;
105
+ /** Schema version */
106
+ version: number;
107
+ }
108
+
109
+ /**
110
+ * Design tokens extracted from project config or brand docs
111
+ */
112
+ export interface DesignTokens {
113
+ colors: Record<string, string>;
114
+ fonts: { heading: string; body: string; mono?: string };
115
+ borderRadius: string;
116
+ source: 'tailwind-config' | 'css-variables' | 'brand-docs' | 'defaults';
117
+ }
118
+
119
+ /**
120
+ * Brand assets contract for deterministic logo/favicon placement
121
+ */
122
+ export interface BrandAssetsContract {
123
+ /** Discovered source path for logo */
124
+ logoPath?: string;
125
+ /** Canonical output path: public/brand/logo.{ext} */
126
+ logoOutputPath: string;
127
+ /** Discovered source path for favicon */
128
+ faviconPath?: string;
129
+ /** Primary brand color */
130
+ primaryColor?: string;
131
+ /** Full color scheme */
132
+ colorScheme?: Record<string, string>;
133
+ }
134
+
135
+ // --- Zod Schemas ---
136
+
137
+ export const NavItemSchema: z.ZodType<NavItem> = z.lazy(() =>
138
+ z.object({
139
+ label: z.string().min(1),
140
+ href: z.string().min(1),
141
+ children: z.array(NavItemSchema).optional(),
142
+ })
143
+ );
144
+
145
+ export const FooterSectionSchema = z.object({
146
+ title: z.string().min(1),
147
+ links: z.array(
148
+ z.object({
149
+ label: z.string().min(1),
150
+ href: z.string().min(1),
151
+ })
152
+ ),
153
+ });
154
+
155
+ export const SiteArchitecturePageSchema = z.object({
156
+ path: z.string().min(1),
157
+ title: z.string().min(1),
158
+ purpose: z.string(),
159
+ pageType: z.enum([
160
+ 'landing',
161
+ 'pricing',
162
+ 'solution',
163
+ 'use-cases',
164
+ 'about',
165
+ 'contact',
166
+ 'blog',
167
+ 'docs',
168
+ 'legal',
169
+ ]),
170
+ sections: z.array(z.string()),
171
+ seoKeywords: z.array(z.string()),
172
+ conversionGoal: z.string(),
173
+ });
174
+
175
+ export const WebsiteStrategySchema = z.object({
176
+ icp: z.object({
177
+ primaryPersona: z.string().min(1),
178
+ painPoints: z.array(z.string()),
179
+ goals: z.array(z.string()),
180
+ objections: z.array(z.string()),
181
+ }),
182
+ positioning: z.object({
183
+ category: z.string().min(1),
184
+ differentiators: z.array(z.string()),
185
+ valueProposition: z.string().min(1),
186
+ proofPoints: z.array(z.string()),
187
+ }),
188
+ messaging: z.object({
189
+ headline: z.string().min(1),
190
+ subheadline: z.string().min(1),
191
+ elevatorPitch: z.string().min(1),
192
+ longDescription: z.string().min(1),
193
+ }),
194
+ seoStrategy: z.object({
195
+ primaryKeywords: z.array(z.string()),
196
+ secondaryKeywords: z.array(z.string()),
197
+ longTailKeywords: z.array(z.string()),
198
+ titleTemplates: z.record(z.string(), z.string()),
199
+ metaDescriptions: z.record(z.string(), z.string()),
200
+ }),
201
+ siteArchitecture: z.object({
202
+ pages: z.array(SiteArchitecturePageSchema),
203
+ navigation: z.array(NavItemSchema),
204
+ footerSections: z.array(FooterSectionSchema),
205
+ }),
206
+ conversionStrategy: z.object({
207
+ primaryCta: z.object({ text: z.string().min(1), href: z.string().min(1) }),
208
+ secondaryCta: z.object({ text: z.string().min(1), href: z.string().min(1) }),
209
+ trustSignals: z.array(z.string()),
210
+ socialProof: z.array(z.string()),
211
+ leadCapture: z.enum(['none', 'webhook', 'resend', 'postmark']),
212
+ }),
213
+ competitiveContext: z.object({
214
+ category: z.string(),
215
+ competitors: z.array(z.string()),
216
+ differentiators: z.array(z.string()),
217
+ }),
218
+ });
219
+
220
+ export const StrategyMetadataSchema = z.object({
221
+ inputHash: z.string().min(1),
222
+ generatedAt: z.string().min(1),
223
+ version: z.number(),
224
+ });
225
+
226
+ export const DesignTokensSchema = z.object({
227
+ colors: z.record(z.string(), z.string()),
228
+ fonts: z.object({
229
+ heading: z.string(),
230
+ body: z.string(),
231
+ mono: z.string().optional(),
232
+ }),
233
+ borderRadius: z.string(),
234
+ source: z.enum(['tailwind-config', 'css-variables', 'brand-docs', 'defaults']),
235
+ });
236
+
237
+ export const BrandAssetsContractSchema = z.object({
238
+ logoPath: z.string().optional(),
239
+ logoOutputPath: z.string().min(1),
240
+ faviconPath: z.string().optional(),
241
+ primaryColor: z.string().optional(),
242
+ colorScheme: z.record(z.string(), z.string()).optional(),
243
+ });
@@ -192,6 +192,15 @@ export interface ProjectState {
192
192
  error?: string;
193
193
  createdAt: string;
194
194
  updatedAt: string;
195
+ /** Raw user documentation discovered from CWD */
196
+ userDocs?: string;
197
+ /** Brand context discovered from CWD */
198
+ brandContext?: {
199
+ logoPath?: string;
200
+ primaryColor?: string;
201
+ };
202
+ /** Path to website strategy JSON file (relative to .popeye/) */
203
+ websiteStrategy?: string;
195
204
  }
196
205
 
197
206
  /**
@@ -229,6 +238,12 @@ export const ProjectStateSchema = z.object({
229
238
  error: z.string().optional(),
230
239
  createdAt: z.string(),
231
240
  updatedAt: z.string(),
241
+ userDocs: z.string().optional(),
242
+ brandContext: z.object({
243
+ logoPath: z.string().optional(),
244
+ primaryColor: z.string().optional(),
245
+ }).optional(),
246
+ websiteStrategy: z.string().optional(),
232
247
  });
233
248
 
234
249
  /**
@@ -6,6 +6,59 @@
6
6
  import { promises as fs } from 'node:fs';
7
7
  import path from 'node:path';
8
8
  import { executePrompt } from '../adapters/claude.js';
9
+ import { isWorkspace, type OutputLanguage } from '../types/project.js';
10
+
11
+ /** Standard workspace subdirectories to search when a file isn't at the root */
12
+ const WORKSPACE_SUBDIRS = ['apps/frontend', 'apps/backend', 'apps/website', 'packages/frontend', 'packages/backend'];
13
+
14
+ /**
15
+ * Resolve a (possibly relative) error file path to an absolute path that exists on disk.
16
+ * For workspace projects, if the file doesn't exist at the project root, searches
17
+ * workspace subdirectories (apps/frontend, apps/backend, etc.).
18
+ */
19
+ export async function resolveErrorFilePath(
20
+ filePath: string,
21
+ projectDir: string,
22
+ language: string,
23
+ ): Promise<string> {
24
+ // If already absolute and exists, use it directly
25
+ if (path.isAbsolute(filePath)) {
26
+ try {
27
+ await fs.access(filePath);
28
+ return filePath;
29
+ } catch {
30
+ // Absolute path doesn't exist — for workspace projects, try searching subdirs
31
+ if (!isWorkspace(language as OutputLanguage)) return filePath;
32
+ const basename = path.basename(filePath);
33
+ for (const subdir of WORKSPACE_SUBDIRS) {
34
+ const candidate = path.join(projectDir, subdir, basename);
35
+ try {
36
+ await fs.access(candidate);
37
+ return candidate;
38
+ } catch { /* continue */ }
39
+ }
40
+ return filePath;
41
+ }
42
+ }
43
+
44
+ // Relative path: try at project root first
45
+ const rootPath = path.join(projectDir, filePath);
46
+ try {
47
+ await fs.access(rootPath);
48
+ return rootPath;
49
+ } catch {
50
+ // Not at root — for workspace projects, search subdirs
51
+ if (!isWorkspace(language as OutputLanguage)) return rootPath;
52
+ for (const subdir of WORKSPACE_SUBDIRS) {
53
+ const candidate = path.join(projectDir, subdir, filePath);
54
+ try {
55
+ await fs.access(candidate);
56
+ return candidate;
57
+ } catch { /* continue */ }
58
+ }
59
+ return rootPath;
60
+ }
61
+ }
9
62
 
10
63
  /**
11
64
  * Build error details
@@ -180,7 +233,8 @@ export async function autoFixTypeScriptErrors(
180
233
  projectDir: string,
181
234
  buildOutput: string,
182
235
  maxAttempts: number = 3,
183
- onProgress?: (message: string) => void
236
+ onProgress?: (message: string) => void,
237
+ language: string = 'typescript',
184
238
  ): Promise<AutoFixResult> {
185
239
  const fixes: Array<{ file: string; description: string }> = [];
186
240
  let attempts = 0;
@@ -237,7 +291,7 @@ export async function autoFixTypeScriptErrors(
237
291
 
238
292
  // Fix each file
239
293
  for (const [filePath, fileErrors] of errorsByFile) {
240
- const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(projectDir, filePath);
294
+ const absolutePath = await resolveErrorFilePath(filePath, projectDir, language);
241
295
 
242
296
  try {
243
297
  // Read current file content
@@ -401,7 +455,7 @@ export async function buildWithAutoFix(
401
455
 
402
456
  // Try auto-fix for TypeScript-based projects (includes fullstack, website, all)
403
457
  if (isTypeScriptBased) {
404
- const fixResult = await autoFixTypeScriptErrors(projectDir, output, maxAttempts, onProgress);
458
+ const fixResult = await autoFixTypeScriptErrors(projectDir, output, maxAttempts, onProgress, language);
405
459
 
406
460
  // Log structural issue if detected
407
461
  if (fixResult.isStructuralIssue) {
@@ -79,6 +79,7 @@ async function requestReviewerConsensus(
79
79
  model: config.geminiModel,
80
80
  temperature: config.temperature,
81
81
  maxTokens: config.maxTokens,
82
+ reviewerPersona: config.reviewerPersona,
82
83
  });
83
84
  }
84
85
  if (reviewer === 'grok') {
@@ -86,6 +87,7 @@ async function requestReviewerConsensus(
86
87
  model: config.grokModel,
87
88
  temperature: config.temperature,
88
89
  maxTokens: config.maxTokens,
90
+ reviewerPersona: config.reviewerPersona,
89
91
  });
90
92
  }
91
93
  return requestOpenAIConsensus(plan, context, config);
@@ -782,6 +782,14 @@ function buildTaskContext(
782
782
  ): string {
783
783
  const lines: string[] = [];
784
784
 
785
+ // No-hardcode enforcement rule
786
+ lines.push('## CRITICAL RULES');
787
+ lines.push('- NEVER use hardcoded placeholder content, mock data, or invented copy.');
788
+ lines.push('- ALL text, features, pricing, colors, and data MUST come from the project specification and user documentation below.');
789
+ lines.push('- If information is not available in the spec, leave a TODO comment rather than inventing content.');
790
+ lines.push('- Do NOT hallucinate product names, features, pricing tiers, testimonials, or blog content.');
791
+ lines.push('');
792
+
785
793
  lines.push(`## Project: ${state.name}`);
786
794
  lines.push(`Language: ${state.language}`);
787
795
  lines.push('');
@@ -798,6 +806,19 @@ function buildTaskContext(
798
806
  lines.push('');
799
807
  }
800
808
 
809
+ // Include user documentation if available
810
+ if (state.userDocs) {
811
+ lines.push('## Project Documentation');
812
+ lines.push(state.userDocs.slice(0, 3000));
813
+ lines.push('');
814
+ }
815
+
816
+ // Include brand context if available
817
+ if (state.brandContext?.primaryColor) {
818
+ lines.push(`## Brand: Primary color ${state.brandContext.primaryColor}`);
819
+ lines.push('');
820
+ }
821
+
801
822
  // Include UI design context if available
802
823
  if (uiDesignContext) {
803
824
  lines.push(uiDesignContext);
@@ -50,6 +50,9 @@ export * from './task-workflow.js';
50
50
  export * from './milestone-workflow.js';
51
51
  export * from './plan-storage.js';
52
52
  export * from './workspace-manager.js';
53
+ export * from './website-updater.js';
54
+ export * from './website-strategy.js';
55
+ export * from './overview.js';
53
56
 
54
57
  /**
55
58
  * Workflow options
@@ -116,6 +119,17 @@ export async function runWorkflow(
116
119
  };
117
120
  }
118
121
 
122
+ // Post-plan: Update website content with project context
123
+ if (spec.language === 'website' || spec.language === 'all' || spec.language === 'fullstack') {
124
+ try {
125
+ onProgress?.('website-update', 'Updating website with project context...');
126
+ const { updateWebsiteContent } = await import('./website-updater.js');
127
+ await updateWebsiteContent(projectDir, planResult.state, spec.language, (msg) => onProgress?.('website-update', msg));
128
+ } catch {
129
+ // Non-blocking: website content update failure should not stop workflow
130
+ }
131
+ }
132
+
119
133
  // Phase 2: Execution Mode
120
134
  onProgress?.('workflow', 'Starting Execution Mode...');
121
135
 
@@ -224,6 +238,17 @@ export async function resumeWorkflow(
224
238
  };
225
239
  }
226
240
 
241
+ // Post-plan: Update website content with project context
242
+ if (state.language === 'website' || state.language === 'all' || state.language === 'fullstack') {
243
+ try {
244
+ onProgress?.('website-update', 'Updating website with project context...');
245
+ const { updateWebsiteContent } = await import('./website-updater.js');
246
+ await updateWebsiteContent(projectDir, planResult.state, state.language, (msg) => onProgress?.('website-update', msg));
247
+ } catch {
248
+ // Non-blocking
249
+ }
250
+ }
251
+
227
252
  // Continue to execution
228
253
  onProgress?.('workflow', 'Starting Execution Mode...');
229
254