popeye-cli 1.4.7 → 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 (134) 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/consensus.d.ts.map +1 -1
  74. package/dist/workflow/consensus.js +2 -0
  75. package/dist/workflow/consensus.js.map +1 -1
  76. package/dist/workflow/execution-mode.d.ts.map +1 -1
  77. package/dist/workflow/execution-mode.js +18 -0
  78. package/dist/workflow/execution-mode.js.map +1 -1
  79. package/dist/workflow/index.d.ts +3 -0
  80. package/dist/workflow/index.d.ts.map +1 -1
  81. package/dist/workflow/index.js +25 -0
  82. package/dist/workflow/index.js.map +1 -1
  83. package/dist/workflow/overview.d.ts +89 -0
  84. package/dist/workflow/overview.d.ts.map +1 -0
  85. package/dist/workflow/overview.js +354 -0
  86. package/dist/workflow/overview.js.map +1 -0
  87. package/dist/workflow/plan-mode.d.ts +2 -1
  88. package/dist/workflow/plan-mode.d.ts.map +1 -1
  89. package/dist/workflow/plan-mode.js +83 -5
  90. package/dist/workflow/plan-mode.js.map +1 -1
  91. package/dist/workflow/website-strategy.d.ts +70 -0
  92. package/dist/workflow/website-strategy.d.ts.map +1 -0
  93. package/dist/workflow/website-strategy.js +238 -0
  94. package/dist/workflow/website-strategy.js.map +1 -0
  95. package/dist/workflow/website-updater.d.ts +17 -0
  96. package/dist/workflow/website-updater.d.ts.map +1 -0
  97. package/dist/workflow/website-updater.js +105 -0
  98. package/dist/workflow/website-updater.js.map +1 -0
  99. package/dist/workflow/workflow-logger.d.ts +1 -1
  100. package/dist/workflow/workflow-logger.d.ts.map +1 -1
  101. package/dist/workflow/workflow-logger.js.map +1 -1
  102. package/package.json +1 -1
  103. package/src/adapters/gemini.ts +10 -4
  104. package/src/adapters/grok.ts +10 -4
  105. package/src/adapters/openai.ts +38 -6
  106. package/src/cli/interactive.ts +47 -0
  107. package/src/generators/all.ts +6 -1
  108. package/src/generators/doc-parser.ts +372 -0
  109. package/src/generators/templates/index.ts +4 -0
  110. package/src/generators/templates/website-components.ts +305 -0
  111. package/src/generators/templates/website-config.ts +291 -0
  112. package/src/generators/templates/website-conversion.ts +341 -0
  113. package/src/generators/templates/website-seo.ts +370 -0
  114. package/src/generators/templates/website.ts +451 -505
  115. package/src/generators/website-context.ts +265 -0
  116. package/src/generators/website.ts +109 -19
  117. package/src/state/index.ts +42 -0
  118. package/src/types/consensus.ts +3 -0
  119. package/src/types/website-strategy.ts +243 -0
  120. package/src/types/workflow.ts +15 -0
  121. package/src/workflow/consensus.ts +2 -0
  122. package/src/workflow/execution-mode.ts +21 -0
  123. package/src/workflow/index.ts +25 -0
  124. package/src/workflow/overview.ts +469 -0
  125. package/src/workflow/plan-mode.ts +115 -4
  126. package/src/workflow/website-strategy.ts +305 -0
  127. package/src/workflow/website-updater.ts +131 -0
  128. package/src/workflow/workflow-logger.ts +1 -0
  129. package/tests/adapters/persona-switching.test.ts +63 -0
  130. package/tests/generators/website-components.test.ts +159 -0
  131. package/tests/generators/website-context.test.ts +222 -0
  132. package/tests/generators/website-seo-quality.test.ts +246 -0
  133. package/tests/workflow/overview.test.ts +392 -0
  134. 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
  /**
@@ -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