popeye-cli 1.4.7 → 1.6.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 (214) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +264 -63
  3. package/dist/adapters/gemini.d.ts +1 -0
  4. package/dist/adapters/gemini.d.ts.map +1 -1
  5. package/dist/adapters/gemini.js +9 -4
  6. package/dist/adapters/gemini.js.map +1 -1
  7. package/dist/adapters/grok.d.ts +1 -0
  8. package/dist/adapters/grok.d.ts.map +1 -1
  9. package/dist/adapters/grok.js +9 -4
  10. package/dist/adapters/grok.js.map +1 -1
  11. package/dist/adapters/openai.d.ts +1 -1
  12. package/dist/adapters/openai.d.ts.map +1 -1
  13. package/dist/adapters/openai.js +35 -9
  14. package/dist/adapters/openai.js.map +1 -1
  15. package/dist/cli/commands/create.d.ts.map +1 -1
  16. package/dist/cli/commands/create.js +54 -4
  17. package/dist/cli/commands/create.js.map +1 -1
  18. package/dist/cli/interactive.d.ts +29 -0
  19. package/dist/cli/interactive.d.ts.map +1 -1
  20. package/dist/cli/interactive.js +132 -7
  21. package/dist/cli/interactive.js.map +1 -1
  22. package/dist/generators/all.d.ts +8 -2
  23. package/dist/generators/all.d.ts.map +1 -1
  24. package/dist/generators/all.js +37 -316
  25. package/dist/generators/all.js.map +1 -1
  26. package/dist/generators/doc-parser.d.ts +64 -0
  27. package/dist/generators/doc-parser.d.ts.map +1 -0
  28. package/dist/generators/doc-parser.js +407 -0
  29. package/dist/generators/doc-parser.js.map +1 -0
  30. package/dist/generators/frontend-design-analyzer.d.ts +30 -0
  31. package/dist/generators/frontend-design-analyzer.d.ts.map +1 -0
  32. package/dist/generators/frontend-design-analyzer.js +208 -0
  33. package/dist/generators/frontend-design-analyzer.js.map +1 -0
  34. package/dist/generators/shared-packages.d.ts +45 -0
  35. package/dist/generators/shared-packages.d.ts.map +1 -0
  36. package/dist/generators/shared-packages.js +456 -0
  37. package/dist/generators/shared-packages.js.map +1 -0
  38. package/dist/generators/templates/index.d.ts +8 -0
  39. package/dist/generators/templates/index.d.ts.map +1 -1
  40. package/dist/generators/templates/index.js +8 -0
  41. package/dist/generators/templates/index.js.map +1 -1
  42. package/dist/generators/templates/website-components.d.ts +33 -0
  43. package/dist/generators/templates/website-components.d.ts.map +1 -0
  44. package/dist/generators/templates/website-components.js +303 -0
  45. package/dist/generators/templates/website-components.js.map +1 -0
  46. package/dist/generators/templates/website-config.d.ts +55 -0
  47. package/dist/generators/templates/website-config.d.ts.map +1 -0
  48. package/dist/generators/templates/website-config.js +425 -0
  49. package/dist/generators/templates/website-config.js.map +1 -0
  50. package/dist/generators/templates/website-conversion.d.ts +27 -0
  51. package/dist/generators/templates/website-conversion.d.ts.map +1 -0
  52. package/dist/generators/templates/website-conversion.js +326 -0
  53. package/dist/generators/templates/website-conversion.js.map +1 -0
  54. package/dist/generators/templates/website-landing.d.ts +24 -0
  55. package/dist/generators/templates/website-landing.d.ts.map +1 -0
  56. package/dist/generators/templates/website-landing.js +276 -0
  57. package/dist/generators/templates/website-landing.js.map +1 -0
  58. package/dist/generators/templates/website-layout.d.ts +42 -0
  59. package/dist/generators/templates/website-layout.d.ts.map +1 -0
  60. package/dist/generators/templates/website-layout.js +408 -0
  61. package/dist/generators/templates/website-layout.js.map +1 -0
  62. package/dist/generators/templates/website-pricing.d.ts +11 -0
  63. package/dist/generators/templates/website-pricing.d.ts.map +1 -0
  64. package/dist/generators/templates/website-pricing.js +313 -0
  65. package/dist/generators/templates/website-pricing.js.map +1 -0
  66. package/dist/generators/templates/website-sections.d.ts +102 -0
  67. package/dist/generators/templates/website-sections.d.ts.map +1 -0
  68. package/dist/generators/templates/website-sections.js +444 -0
  69. package/dist/generators/templates/website-sections.js.map +1 -0
  70. package/dist/generators/templates/website-seo.d.ts +76 -0
  71. package/dist/generators/templates/website-seo.d.ts.map +1 -0
  72. package/dist/generators/templates/website-seo.js +326 -0
  73. package/dist/generators/templates/website-seo.js.map +1 -0
  74. package/dist/generators/templates/website.d.ts +10 -83
  75. package/dist/generators/templates/website.d.ts.map +1 -1
  76. package/dist/generators/templates/website.js +12 -875
  77. package/dist/generators/templates/website.js.map +1 -1
  78. package/dist/generators/website-content-scanner.d.ts +37 -0
  79. package/dist/generators/website-content-scanner.d.ts.map +1 -0
  80. package/dist/generators/website-content-scanner.js +165 -0
  81. package/dist/generators/website-content-scanner.js.map +1 -0
  82. package/dist/generators/website-context.d.ts +119 -0
  83. package/dist/generators/website-context.d.ts.map +1 -0
  84. package/dist/generators/website-context.js +350 -0
  85. package/dist/generators/website-context.js.map +1 -0
  86. package/dist/generators/website-debug.d.ts +68 -0
  87. package/dist/generators/website-debug.d.ts.map +1 -0
  88. package/dist/generators/website-debug.js +93 -0
  89. package/dist/generators/website-debug.js.map +1 -0
  90. package/dist/generators/website.d.ts +5 -0
  91. package/dist/generators/website.d.ts.map +1 -1
  92. package/dist/generators/website.js +136 -11
  93. package/dist/generators/website.js.map +1 -1
  94. package/dist/generators/workspace-root.d.ts +27 -0
  95. package/dist/generators/workspace-root.d.ts.map +1 -0
  96. package/dist/generators/workspace-root.js +100 -0
  97. package/dist/generators/workspace-root.js.map +1 -0
  98. package/dist/state/index.d.ts +35 -0
  99. package/dist/state/index.d.ts.map +1 -1
  100. package/dist/state/index.js +40 -0
  101. package/dist/state/index.js.map +1 -1
  102. package/dist/types/consensus.d.ts +3 -0
  103. package/dist/types/consensus.d.ts.map +1 -1
  104. package/dist/types/consensus.js +1 -0
  105. package/dist/types/consensus.js.map +1 -1
  106. package/dist/types/website-strategy.d.ts +263 -0
  107. package/dist/types/website-strategy.d.ts.map +1 -0
  108. package/dist/types/website-strategy.js +105 -0
  109. package/dist/types/website-strategy.js.map +1 -0
  110. package/dist/types/workflow.d.ts +21 -0
  111. package/dist/types/workflow.d.ts.map +1 -1
  112. package/dist/types/workflow.js +8 -0
  113. package/dist/types/workflow.js.map +1 -1
  114. package/dist/upgrade/handlers.d.ts +15 -0
  115. package/dist/upgrade/handlers.d.ts.map +1 -1
  116. package/dist/upgrade/handlers.js +52 -0
  117. package/dist/upgrade/handlers.js.map +1 -1
  118. package/dist/workflow/auto-fix-bundler.d.ts +37 -0
  119. package/dist/workflow/auto-fix-bundler.d.ts.map +1 -0
  120. package/dist/workflow/auto-fix-bundler.js +320 -0
  121. package/dist/workflow/auto-fix-bundler.js.map +1 -0
  122. package/dist/workflow/auto-fix.d.ts.map +1 -1
  123. package/dist/workflow/auto-fix.js +10 -3
  124. package/dist/workflow/auto-fix.js.map +1 -1
  125. package/dist/workflow/consensus.d.ts.map +1 -1
  126. package/dist/workflow/consensus.js +2 -0
  127. package/dist/workflow/consensus.js.map +1 -1
  128. package/dist/workflow/execution-mode.d.ts.map +1 -1
  129. package/dist/workflow/execution-mode.js +18 -0
  130. package/dist/workflow/execution-mode.js.map +1 -1
  131. package/dist/workflow/index.d.ts +4 -0
  132. package/dist/workflow/index.d.ts.map +1 -1
  133. package/dist/workflow/index.js +37 -0
  134. package/dist/workflow/index.js.map +1 -1
  135. package/dist/workflow/overview.d.ts +89 -0
  136. package/dist/workflow/overview.d.ts.map +1 -0
  137. package/dist/workflow/overview.js +358 -0
  138. package/dist/workflow/overview.js.map +1 -0
  139. package/dist/workflow/plan-mode.d.ts +6 -4
  140. package/dist/workflow/plan-mode.d.ts.map +1 -1
  141. package/dist/workflow/plan-mode.js +148 -6
  142. package/dist/workflow/plan-mode.js.map +1 -1
  143. package/dist/workflow/website-strategy.d.ts +79 -0
  144. package/dist/workflow/website-strategy.d.ts.map +1 -0
  145. package/dist/workflow/website-strategy.js +310 -0
  146. package/dist/workflow/website-strategy.js.map +1 -0
  147. package/dist/workflow/website-updater.d.ts +17 -0
  148. package/dist/workflow/website-updater.d.ts.map +1 -0
  149. package/dist/workflow/website-updater.js +116 -0
  150. package/dist/workflow/website-updater.js.map +1 -0
  151. package/dist/workflow/workflow-logger.d.ts +1 -1
  152. package/dist/workflow/workflow-logger.d.ts.map +1 -1
  153. package/dist/workflow/workflow-logger.js.map +1 -1
  154. package/package.json +1 -1
  155. package/src/adapters/gemini.ts +10 -4
  156. package/src/adapters/grok.ts +10 -4
  157. package/src/adapters/openai.ts +38 -6
  158. package/src/cli/commands/create.ts +58 -4
  159. package/src/cli/interactive.ts +143 -7
  160. package/src/generators/all.ts +49 -332
  161. package/src/generators/doc-parser.ts +449 -0
  162. package/src/generators/frontend-design-analyzer.ts +261 -0
  163. package/src/generators/shared-packages.ts +500 -0
  164. package/src/generators/templates/index.ts +8 -0
  165. package/src/generators/templates/website-components.ts +330 -0
  166. package/src/generators/templates/website-config.ts +444 -0
  167. package/src/generators/templates/website-conversion.ts +341 -0
  168. package/src/generators/templates/website-landing.ts +331 -0
  169. package/src/generators/templates/website-layout.ts +443 -0
  170. package/src/generators/templates/website-pricing.ts +330 -0
  171. package/src/generators/templates/website-sections.ts +541 -0
  172. package/src/generators/templates/website-seo.ts +370 -0
  173. package/src/generators/templates/website.ts +38 -905
  174. package/src/generators/website-content-scanner.ts +208 -0
  175. package/src/generators/website-context.ts +493 -0
  176. package/src/generators/website-debug.ts +130 -0
  177. package/src/generators/website.ts +178 -20
  178. package/src/generators/workspace-root.ts +113 -0
  179. package/src/state/index.ts +56 -0
  180. package/src/types/consensus.ts +3 -0
  181. package/src/types/website-strategy.ts +243 -0
  182. package/src/types/workflow.ts +21 -0
  183. package/src/upgrade/handlers.ts +65 -0
  184. package/src/workflow/auto-fix-bundler.ts +392 -0
  185. package/src/workflow/auto-fix.ts +11 -3
  186. package/src/workflow/consensus.ts +2 -0
  187. package/src/workflow/execution-mode.ts +21 -0
  188. package/src/workflow/index.ts +37 -0
  189. package/src/workflow/overview.ts +475 -0
  190. package/src/workflow/plan-mode.ts +193 -8
  191. package/src/workflow/website-strategy.ts +379 -0
  192. package/src/workflow/website-updater.ts +142 -0
  193. package/src/workflow/workflow-logger.ts +1 -0
  194. package/tests/adapters/persona-switching.test.ts +63 -0
  195. package/tests/cli/project-naming.test.ts +136 -0
  196. package/tests/generators/doc-parser.test.ts +121 -0
  197. package/tests/generators/frontend-design-analyzer.test.ts +90 -0
  198. package/tests/generators/quality-gate.test.ts +183 -0
  199. package/tests/generators/shared-packages.test.ts +83 -0
  200. package/tests/generators/website-components.test.ts +159 -0
  201. package/tests/generators/website-config.test.ts +84 -0
  202. package/tests/generators/website-content-scanner.test.ts +181 -0
  203. package/tests/generators/website-context.test.ts +331 -0
  204. package/tests/generators/website-debug.test.ts +77 -0
  205. package/tests/generators/website-landing.test.ts +188 -0
  206. package/tests/generators/website-pricing.test.ts +98 -0
  207. package/tests/generators/website-sections.test.ts +245 -0
  208. package/tests/generators/website-seo-quality.test.ts +246 -0
  209. package/tests/generators/workspace-root.test.ts +105 -0
  210. package/tests/upgrade/handlers.test.ts +162 -0
  211. package/tests/workflow/auto-fix-bundler.test.ts +242 -0
  212. package/tests/workflow/overview.test.ts +392 -0
  213. package/tests/workflow/plan-mode.test.ts +111 -1
  214. package/tests/workflow/website-strategy.test.ts +246 -0
@@ -17,11 +17,28 @@ import {
17
17
  setPhase,
18
18
  storePlan,
19
19
  storeSpecification,
20
+ storeUserDocs,
21
+ storeBrandContext,
22
+ storeSourceDocPaths,
23
+ storeWebsiteStrategyPath,
20
24
  addMilestones,
21
25
  } from '../state/index.js';
26
+ import {
27
+ discoverProjectDocs,
28
+ readProjectDocs,
29
+ findBrandAssets,
30
+ resolveBrandAssets,
31
+ } from '../generators/website-context.js';
22
32
  import { iterateUntilConsensus, type ConsensusProcessResult } from './consensus.js';
23
33
  import { getWorkflowLogger } from './workflow-logger.js';
24
34
  import { designUI, saveUISpecification } from './ui-designer.js';
35
+ import {
36
+ generateWebsiteStrategy,
37
+ formatStrategyForPlanContext,
38
+ storeWebsiteStrategy,
39
+ loadWebsiteStrategy,
40
+ } from './website-strategy.js';
41
+ import type { WebsiteStrategyDocument } from '../types/website-strategy.js';
25
42
 
26
43
  /**
27
44
  * Options for plan mode
@@ -49,16 +66,18 @@ export interface PlanModeResult {
49
66
  * @param idea - The brief project idea
50
67
  * @param language - Target programming language
51
68
  * @param onProgress - Progress callback
69
+ * @param userDocs - Optional user documentation for context
52
70
  * @returns Expanded specification
53
71
  */
54
72
  export async function expandIdea(
55
73
  idea: string,
56
74
  language: OutputLanguage,
57
- onProgress?: (message: string) => void
75
+ onProgress?: (message: string) => void,
76
+ userDocs?: string
58
77
  ): Promise<string> {
59
78
  onProgress?.('Expanding idea into specification...');
60
79
 
61
- const specification = await openaiExpandIdea(idea, language);
80
+ const specification = await openaiExpandIdea(idea, language, userDocs);
62
81
 
63
82
  onProgress?.('Specification created');
64
83
  return specification;
@@ -247,7 +266,7 @@ function isActionableTask(name: string): boolean {
247
266
  /**
248
267
  * Task app tag for fullstack projects
249
268
  */
250
- export type TaskAppTag = 'FE' | 'BE' | 'INT';
269
+ export type TaskAppTag = 'FE' | 'BE' | 'INT' | 'WEB';
251
270
 
252
271
  /**
253
272
  * Task with app targeting information for fullstack projects
@@ -256,7 +275,7 @@ export interface ParsedFullstackTask {
256
275
  name: string;
257
276
  description: string;
258
277
  appTag?: TaskAppTag;
259
- appTarget?: 'frontend' | 'backend' | 'unified';
278
+ appTarget?: 'frontend' | 'backend' | 'unified' | 'website';
260
279
  files?: string[];
261
280
  dependencies?: string[];
262
281
  acceptanceCriteria?: string[];
@@ -271,7 +290,7 @@ export interface ParsedFullstackTask {
271
290
  * @returns The parsed app tag or undefined
272
291
  */
273
292
  export function parseTaskTag(taskName: string): TaskAppTag | undefined {
274
- const tagMatch = taskName.match(/\[(FE|BE|INT)\]/i);
293
+ const tagMatch = taskName.match(/\[(FE|BE|INT|WEB)\]/i);
275
294
  if (tagMatch) {
276
295
  return tagMatch[1].toUpperCase() as TaskAppTag;
277
296
  }
@@ -284,10 +303,11 @@ export function parseTaskTag(taskName: string): TaskAppTag | undefined {
284
303
  * @param tag - The task tag
285
304
  * @returns The app target
286
305
  */
287
- export function tagToAppTarget(tag: TaskAppTag): 'frontend' | 'backend' | 'unified' {
306
+ export function tagToAppTarget(tag: TaskAppTag): 'frontend' | 'backend' | 'unified' | 'website' {
288
307
  switch (tag) {
289
308
  case 'FE': return 'frontend';
290
309
  case 'BE': return 'backend';
310
+ case 'WEB': return 'website';
291
311
  case 'INT': return 'unified';
292
312
  }
293
313
  }
@@ -359,6 +379,7 @@ export function validateFullstackPlan(plan: string): {
359
379
  feTasks: number;
360
380
  beTasks: number;
361
381
  intTasks: number;
382
+ webTasks: number;
362
383
  untaggedTasks: number;
363
384
  };
364
385
  } {
@@ -367,6 +388,7 @@ export function validateFullstackPlan(plan: string): {
367
388
  let feTasks = 0;
368
389
  let beTasks = 0;
369
390
  let intTasks = 0;
391
+ let webTasks = 0;
370
392
  let untaggedTasks = 0;
371
393
 
372
394
  // Find all task headers
@@ -383,6 +405,7 @@ export function validateFullstackPlan(plan: string): {
383
405
  case 'FE': feTasks++; break;
384
406
  case 'BE': beTasks++; break;
385
407
  case 'INT': intTasks++; break;
408
+ case 'WEB': webTasks++; break;
386
409
  }
387
410
  } else {
388
411
  untaggedTasks++;
@@ -417,6 +440,7 @@ export function validateFullstackPlan(plan: string): {
417
440
  feTasks,
418
441
  beTasks,
419
442
  intTasks,
443
+ webTasks,
420
444
  untaggedTasks,
421
445
  },
422
446
  };
@@ -581,6 +605,7 @@ export function parsePlanMilestones(plan: string): Omit<Milestone, 'id'>[] {
581
605
  while ((taskMatch = explicitTaskPattern.exec(plan)) !== null) {
582
606
  const name = taskMatch[1].trim()
583
607
  .replace(/^\*\*(.+)\*\*$/, '$1') // Remove bold
608
+ .replace(/^\[(?:FE|BE|INT|WEB|API|DB)\]\s*:?\s*/i, '') // Strip app tags before verb check
584
609
  .replace(/^:/, '') // Remove leading colon
585
610
  .trim();
586
611
 
@@ -890,6 +915,33 @@ export async function runPlanMode(
890
915
  });
891
916
  }
892
917
 
918
+ // Discover user documentation from the CWD (parent of project dir)
919
+ let userDocs = state.userDocs || '';
920
+ if (!userDocs) {
921
+ const parentDir = path.dirname(projectDir);
922
+ const docPaths = await discoverProjectDocs(parentDir);
923
+ if (docPaths.length > 0) {
924
+ userDocs = await readProjectDocs(docPaths);
925
+ onProgress?.('doc-discovery', `Found ${docPaths.length} project doc(s)`);
926
+ state = await storeUserDocs(projectDir, userDocs);
927
+ // Store source doc paths for later re-reading
928
+ state = await storeSourceDocPaths(projectDir, docPaths);
929
+ await logger.info('init', 'docs_found', `Found ${docPaths.length} project docs`, {
930
+ docCount: docPaths.length,
931
+ docPaths: docPaths.map((p) => path.basename(p)),
932
+ });
933
+
934
+ // Generate PROJECT_BRIEF.md in .popeye/ for persistent reference
935
+ await generateProjectBrief(projectDir, spec.name || state.name, userDocs, docPaths);
936
+ onProgress?.('doc-discovery', 'Generated PROJECT_BRIEF.md');
937
+ }
938
+ const brandAssets = await findBrandAssets(parentDir);
939
+ if (brandAssets.logoPath) {
940
+ state = await storeBrandContext(projectDir, { logoPath: brandAssets.logoPath });
941
+ onProgress?.('doc-discovery', `Found brand logo: ${path.basename(brandAssets.logoPath)}`);
942
+ }
943
+ }
944
+
893
945
  // Expand idea if we don't have a specification
894
946
  if (!state.specification) {
895
947
  onProgress?.('expand-idea', 'Expanding idea into specification...');
@@ -898,7 +950,8 @@ export async function runPlanMode(
898
950
  const specification = await expandIdea(
899
951
  spec.idea,
900
952
  spec.language,
901
- (msg) => onProgress?.('expand-idea', msg)
953
+ (msg) => onProgress?.('expand-idea', msg),
954
+ userDocs || undefined
902
955
  );
903
956
 
904
957
  state = await storeSpecification(projectDir, specification);
@@ -928,6 +981,62 @@ export async function runPlanMode(
928
981
  });
929
982
  }
930
983
 
984
+ // Generate website strategy for website projects (after spec, before plan)
985
+ const isWebsiteProject = spec.language === 'website' || spec.language === 'all';
986
+ let websiteStrategy: WebsiteStrategyDocument | undefined;
987
+
988
+ if (isWebsiteProject && state.specification) {
989
+ try {
990
+ onProgress?.('get-context', 'Generating website marketing strategy...');
991
+ await logger.stageStart('website-strategy', 'Generating website marketing strategy');
992
+
993
+ const parentDir = path.dirname(projectDir);
994
+ const brandAssets = await resolveBrandAssets(parentDir, state.brandContext);
995
+
996
+ const strategyInput = {
997
+ productContext: (userDocs || '') + '\n\n' + (state.specification || ''),
998
+ projectName: spec.name || state.name,
999
+ brandAssets,
1000
+ };
1001
+
1002
+ // Check for cached strategy
1003
+ const cached = await loadWebsiteStrategy(projectDir);
1004
+ if (cached) {
1005
+ websiteStrategy = cached.strategy;
1006
+ onProgress?.('get-context', `Loaded cached strategy: ${cached.strategy.siteArchitecture.pages.length} pages`);
1007
+ } else {
1008
+ const result = await generateWebsiteStrategy(
1009
+ strategyInput,
1010
+ (msg) => onProgress?.('get-context', msg)
1011
+ );
1012
+ websiteStrategy = result.strategy;
1013
+ await storeWebsiteStrategy(projectDir, result.strategy, result.metadata);
1014
+ state = await storeWebsiteStrategyPath(projectDir, '.popeye/website-strategy.json');
1015
+
1016
+ onProgress?.(
1017
+ 'get-context',
1018
+ `Strategy: ${result.strategy.siteArchitecture.pages.length} pages, ` +
1019
+ `${result.strategy.seoStrategy.primaryKeywords.length} keywords`
1020
+ );
1021
+ }
1022
+
1023
+ await logger.stageComplete('website-strategy', 'Website strategy generated', {
1024
+ pageCount: websiteStrategy.siteArchitecture.pages.length,
1025
+ keywordCount: websiteStrategy.seoStrategy.primaryKeywords.length,
1026
+ });
1027
+ } catch (strategyError) {
1028
+ // Non-blocking: strategy generation failure should not stop the workflow
1029
+ const errorMsg = strategyError instanceof Error ? strategyError.message : 'Unknown error';
1030
+ onProgress?.(
1031
+ 'get-context',
1032
+ `WARNING: Website strategy failed: ${errorMsg}`
1033
+ );
1034
+ await logger.error('website-strategy', 'strategy_failed', 'Website strategy generation failed', {
1035
+ error: errorMsg,
1036
+ });
1037
+ }
1038
+ }
1039
+
931
1040
  // Get project context
932
1041
  onProgress?.('get-context', 'Gathering project context...');
933
1042
  let context = await getProjectContext(
@@ -941,6 +1050,12 @@ export async function runPlanMode(
941
1050
  context = `${context}\n\nADDITIONAL GUIDANCE FROM USER:\n${additionalContext}`;
942
1051
  }
943
1052
 
1053
+ // Inject website strategy as a separate context block (not appended to specification)
1054
+ if (websiteStrategy) {
1055
+ const strategyContext = formatStrategyForPlanContext(websiteStrategy);
1056
+ context = `${context}\n\n## WEBSITE STRATEGY (authoritative reference)\n${strategyContext}`;
1057
+ }
1058
+
944
1059
  // Create initial plan if we don't have one
945
1060
  if (!state.plan) {
946
1061
  onProgress?.('create-plan', 'Creating development plan...');
@@ -986,12 +1101,22 @@ export async function runPlanMode(
986
1101
  onProgress?.('consensus', 'Starting consensus review...');
987
1102
  await logger.stageStart('consensus', 'Starting consensus review process');
988
1103
 
1104
+ // Set marketing persona for website project reviews
1105
+ const resolvedConsensusConfig = { ...consensusConfig };
1106
+ if (isWebsiteProject && !resolvedConsensusConfig.reviewerPersona) {
1107
+ resolvedConsensusConfig.reviewerPersona =
1108
+ 'a Senior Product Marketing Strategist, SEO expert, and Fullstack Web Architect. ' +
1109
+ 'Evaluate for: conversion optimization, messaging clarity, SEO best practices ' +
1110
+ '(JSON-LD, heading hierarchy, meta tags), Lighthouse 95+ performance, WCAG 2.1 AA accessibility. ' +
1111
+ 'Reject generic template structures. Every section must connect to real product capabilities.';
1112
+ }
1113
+
989
1114
  const consensusResult = await iterateUntilConsensus(
990
1115
  state.plan!,
991
1116
  context,
992
1117
  {
993
1118
  projectDir,
994
- config: consensusConfig,
1119
+ config: resolvedConsensusConfig,
995
1120
  isFullstack: isWorkspace(spec.language),
996
1121
  language: spec.language,
997
1122
  onIteration: (iteration, result) => {
@@ -1262,3 +1387,63 @@ export async function resumePlanMode(
1262
1387
  }
1263
1388
  );
1264
1389
  }
1390
+
1391
+ /**
1392
+ * Generate a PROJECT_BRIEF.md file in .popeye/ that summarizes
1393
+ * key project details extracted from user documentation.
1394
+ *
1395
+ * This brief is loaded at every workflow stage for consistent context.
1396
+ *
1397
+ * @param projectDir - The project root directory
1398
+ * @param projectName - The project/product name
1399
+ * @param userDocs - Combined user documentation content
1400
+ * @param docPaths - Absolute paths to source doc files
1401
+ */
1402
+ async function generateProjectBrief(
1403
+ projectDir: string,
1404
+ projectName: string,
1405
+ userDocs: string,
1406
+ docPaths: string[]
1407
+ ): Promise<void> {
1408
+ const briefDir = path.join(projectDir, '.popeye');
1409
+ const briefPath = path.join(briefDir, 'PROJECT_BRIEF.md');
1410
+
1411
+ // Extract key details from docs
1412
+ const productName = extractBriefField(userDocs, /^#\s+(.+)/m) || projectName;
1413
+ const tagline = extractBriefField(userDocs, /(?:tagline|subtitle|description)[:\s]+["']?(.+?)["']?\s*$/im) || '';
1414
+ const primaryColor = extractBriefField(userDocs, /#([0-9a-fA-F]{6})\b/) || '';
1415
+
1416
+ const briefContent = [
1417
+ `# Project Brief: ${productName}`,
1418
+ '',
1419
+ `**Product Name:** ${productName}`,
1420
+ tagline ? `**Tagline:** ${tagline}` : '',
1421
+ primaryColor ? `**Primary Color:** #${primaryColor}` : '',
1422
+ '',
1423
+ '## Source Documents',
1424
+ ...docPaths.map(p => `- ${path.basename(p)}`),
1425
+ '',
1426
+ '---',
1427
+ '*Auto-generated by Popeye. Re-read source docs for full context.*',
1428
+ '',
1429
+ ].filter(line => line !== undefined).join('\n');
1430
+
1431
+ try {
1432
+ await fs.mkdir(briefDir, { recursive: true });
1433
+ await fs.writeFile(briefPath, briefContent, 'utf-8');
1434
+ } catch {
1435
+ // Non-critical: brief generation failure shouldn't stop workflow
1436
+ }
1437
+ }
1438
+
1439
+ /**
1440
+ * Extract a field value from documentation using a regex pattern.
1441
+ *
1442
+ * @param content - Documentation content to search
1443
+ * @param pattern - Regex with a capture group for the value
1444
+ * @returns The captured value, or null if not found
1445
+ */
1446
+ function extractBriefField(content: string, pattern: RegExp): string | null {
1447
+ const match = content.match(pattern);
1448
+ return match?.[1]?.trim() || null;
1449
+ }