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.
- package/CHANGELOG.md +54 -0
- package/README.md +264 -63
- package/dist/adapters/gemini.d.ts +1 -0
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +9 -4
- package/dist/adapters/gemini.js.map +1 -1
- package/dist/adapters/grok.d.ts +1 -0
- package/dist/adapters/grok.d.ts.map +1 -1
- package/dist/adapters/grok.js +9 -4
- package/dist/adapters/grok.js.map +1 -1
- package/dist/adapters/openai.d.ts +1 -1
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +35 -9
- package/dist/adapters/openai.js.map +1 -1
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +54 -4
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/interactive.d.ts +29 -0
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +132 -7
- package/dist/cli/interactive.js.map +1 -1
- package/dist/generators/all.d.ts +8 -2
- package/dist/generators/all.d.ts.map +1 -1
- package/dist/generators/all.js +37 -316
- package/dist/generators/all.js.map +1 -1
- package/dist/generators/doc-parser.d.ts +64 -0
- package/dist/generators/doc-parser.d.ts.map +1 -0
- package/dist/generators/doc-parser.js +407 -0
- package/dist/generators/doc-parser.js.map +1 -0
- package/dist/generators/frontend-design-analyzer.d.ts +30 -0
- package/dist/generators/frontend-design-analyzer.d.ts.map +1 -0
- package/dist/generators/frontend-design-analyzer.js +208 -0
- package/dist/generators/frontend-design-analyzer.js.map +1 -0
- package/dist/generators/shared-packages.d.ts +45 -0
- package/dist/generators/shared-packages.d.ts.map +1 -0
- package/dist/generators/shared-packages.js +456 -0
- package/dist/generators/shared-packages.js.map +1 -0
- package/dist/generators/templates/index.d.ts +8 -0
- package/dist/generators/templates/index.d.ts.map +1 -1
- package/dist/generators/templates/index.js +8 -0
- package/dist/generators/templates/index.js.map +1 -1
- package/dist/generators/templates/website-components.d.ts +33 -0
- package/dist/generators/templates/website-components.d.ts.map +1 -0
- package/dist/generators/templates/website-components.js +303 -0
- package/dist/generators/templates/website-components.js.map +1 -0
- package/dist/generators/templates/website-config.d.ts +55 -0
- package/dist/generators/templates/website-config.d.ts.map +1 -0
- package/dist/generators/templates/website-config.js +425 -0
- package/dist/generators/templates/website-config.js.map +1 -0
- package/dist/generators/templates/website-conversion.d.ts +27 -0
- package/dist/generators/templates/website-conversion.d.ts.map +1 -0
- package/dist/generators/templates/website-conversion.js +326 -0
- package/dist/generators/templates/website-conversion.js.map +1 -0
- package/dist/generators/templates/website-landing.d.ts +24 -0
- package/dist/generators/templates/website-landing.d.ts.map +1 -0
- package/dist/generators/templates/website-landing.js +276 -0
- package/dist/generators/templates/website-landing.js.map +1 -0
- package/dist/generators/templates/website-layout.d.ts +42 -0
- package/dist/generators/templates/website-layout.d.ts.map +1 -0
- package/dist/generators/templates/website-layout.js +408 -0
- package/dist/generators/templates/website-layout.js.map +1 -0
- package/dist/generators/templates/website-pricing.d.ts +11 -0
- package/dist/generators/templates/website-pricing.d.ts.map +1 -0
- package/dist/generators/templates/website-pricing.js +313 -0
- package/dist/generators/templates/website-pricing.js.map +1 -0
- package/dist/generators/templates/website-sections.d.ts +102 -0
- package/dist/generators/templates/website-sections.d.ts.map +1 -0
- package/dist/generators/templates/website-sections.js +444 -0
- package/dist/generators/templates/website-sections.js.map +1 -0
- package/dist/generators/templates/website-seo.d.ts +76 -0
- package/dist/generators/templates/website-seo.d.ts.map +1 -0
- package/dist/generators/templates/website-seo.js +326 -0
- package/dist/generators/templates/website-seo.js.map +1 -0
- package/dist/generators/templates/website.d.ts +10 -83
- package/dist/generators/templates/website.d.ts.map +1 -1
- package/dist/generators/templates/website.js +12 -875
- package/dist/generators/templates/website.js.map +1 -1
- package/dist/generators/website-content-scanner.d.ts +37 -0
- package/dist/generators/website-content-scanner.d.ts.map +1 -0
- package/dist/generators/website-content-scanner.js +165 -0
- package/dist/generators/website-content-scanner.js.map +1 -0
- package/dist/generators/website-context.d.ts +119 -0
- package/dist/generators/website-context.d.ts.map +1 -0
- package/dist/generators/website-context.js +350 -0
- package/dist/generators/website-context.js.map +1 -0
- package/dist/generators/website-debug.d.ts +68 -0
- package/dist/generators/website-debug.d.ts.map +1 -0
- package/dist/generators/website-debug.js +93 -0
- package/dist/generators/website-debug.js.map +1 -0
- package/dist/generators/website.d.ts +5 -0
- package/dist/generators/website.d.ts.map +1 -1
- package/dist/generators/website.js +136 -11
- package/dist/generators/website.js.map +1 -1
- package/dist/generators/workspace-root.d.ts +27 -0
- package/dist/generators/workspace-root.d.ts.map +1 -0
- package/dist/generators/workspace-root.js +100 -0
- package/dist/generators/workspace-root.js.map +1 -0
- package/dist/state/index.d.ts +35 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +40 -0
- package/dist/state/index.js.map +1 -1
- package/dist/types/consensus.d.ts +3 -0
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +1 -0
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/website-strategy.d.ts +263 -0
- package/dist/types/website-strategy.d.ts.map +1 -0
- package/dist/types/website-strategy.js +105 -0
- package/dist/types/website-strategy.js.map +1 -0
- package/dist/types/workflow.d.ts +21 -0
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +8 -0
- package/dist/types/workflow.js.map +1 -1
- package/dist/upgrade/handlers.d.ts +15 -0
- package/dist/upgrade/handlers.d.ts.map +1 -1
- package/dist/upgrade/handlers.js +52 -0
- package/dist/upgrade/handlers.js.map +1 -1
- package/dist/workflow/auto-fix-bundler.d.ts +37 -0
- package/dist/workflow/auto-fix-bundler.d.ts.map +1 -0
- package/dist/workflow/auto-fix-bundler.js +320 -0
- package/dist/workflow/auto-fix-bundler.js.map +1 -0
- package/dist/workflow/auto-fix.d.ts.map +1 -1
- package/dist/workflow/auto-fix.js +10 -3
- package/dist/workflow/auto-fix.js.map +1 -1
- package/dist/workflow/consensus.d.ts.map +1 -1
- package/dist/workflow/consensus.js +2 -0
- package/dist/workflow/consensus.js.map +1 -1
- package/dist/workflow/execution-mode.d.ts.map +1 -1
- package/dist/workflow/execution-mode.js +18 -0
- package/dist/workflow/execution-mode.js.map +1 -1
- package/dist/workflow/index.d.ts +4 -0
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +37 -0
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/overview.d.ts +89 -0
- package/dist/workflow/overview.d.ts.map +1 -0
- package/dist/workflow/overview.js +358 -0
- package/dist/workflow/overview.js.map +1 -0
- package/dist/workflow/plan-mode.d.ts +6 -4
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js +148 -6
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/website-strategy.d.ts +79 -0
- package/dist/workflow/website-strategy.d.ts.map +1 -0
- package/dist/workflow/website-strategy.js +310 -0
- package/dist/workflow/website-strategy.js.map +1 -0
- package/dist/workflow/website-updater.d.ts +17 -0
- package/dist/workflow/website-updater.d.ts.map +1 -0
- package/dist/workflow/website-updater.js +116 -0
- package/dist/workflow/website-updater.js.map +1 -0
- package/dist/workflow/workflow-logger.d.ts +1 -1
- package/dist/workflow/workflow-logger.d.ts.map +1 -1
- package/dist/workflow/workflow-logger.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/gemini.ts +10 -4
- package/src/adapters/grok.ts +10 -4
- package/src/adapters/openai.ts +38 -6
- package/src/cli/commands/create.ts +58 -4
- package/src/cli/interactive.ts +143 -7
- package/src/generators/all.ts +49 -332
- package/src/generators/doc-parser.ts +449 -0
- package/src/generators/frontend-design-analyzer.ts +261 -0
- package/src/generators/shared-packages.ts +500 -0
- package/src/generators/templates/index.ts +8 -0
- package/src/generators/templates/website-components.ts +330 -0
- package/src/generators/templates/website-config.ts +444 -0
- package/src/generators/templates/website-conversion.ts +341 -0
- package/src/generators/templates/website-landing.ts +331 -0
- package/src/generators/templates/website-layout.ts +443 -0
- package/src/generators/templates/website-pricing.ts +330 -0
- package/src/generators/templates/website-sections.ts +541 -0
- package/src/generators/templates/website-seo.ts +370 -0
- package/src/generators/templates/website.ts +38 -905
- package/src/generators/website-content-scanner.ts +208 -0
- package/src/generators/website-context.ts +493 -0
- package/src/generators/website-debug.ts +130 -0
- package/src/generators/website.ts +178 -20
- package/src/generators/workspace-root.ts +113 -0
- package/src/state/index.ts +56 -0
- package/src/types/consensus.ts +3 -0
- package/src/types/website-strategy.ts +243 -0
- package/src/types/workflow.ts +21 -0
- package/src/upgrade/handlers.ts +65 -0
- package/src/workflow/auto-fix-bundler.ts +392 -0
- package/src/workflow/auto-fix.ts +11 -3
- package/src/workflow/consensus.ts +2 -0
- package/src/workflow/execution-mode.ts +21 -0
- package/src/workflow/index.ts +37 -0
- package/src/workflow/overview.ts +475 -0
- package/src/workflow/plan-mode.ts +193 -8
- package/src/workflow/website-strategy.ts +379 -0
- package/src/workflow/website-updater.ts +142 -0
- package/src/workflow/workflow-logger.ts +1 -0
- package/tests/adapters/persona-switching.test.ts +63 -0
- package/tests/cli/project-naming.test.ts +136 -0
- package/tests/generators/doc-parser.test.ts +121 -0
- package/tests/generators/frontend-design-analyzer.test.ts +90 -0
- package/tests/generators/quality-gate.test.ts +183 -0
- package/tests/generators/shared-packages.test.ts +83 -0
- package/tests/generators/website-components.test.ts +159 -0
- package/tests/generators/website-config.test.ts +84 -0
- package/tests/generators/website-content-scanner.test.ts +181 -0
- package/tests/generators/website-context.test.ts +331 -0
- package/tests/generators/website-debug.test.ts +77 -0
- package/tests/generators/website-landing.test.ts +188 -0
- package/tests/generators/website-pricing.test.ts +98 -0
- package/tests/generators/website-sections.test.ts +245 -0
- package/tests/generators/website-seo-quality.test.ts +246 -0
- package/tests/generators/workspace-root.test.ts +105 -0
- package/tests/upgrade/handlers.test.ts +162 -0
- package/tests/workflow/auto-fix-bundler.test.ts +242 -0
- package/tests/workflow/overview.test.ts +392 -0
- package/tests/workflow/plan-mode.test.ts +111 -1
- 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:
|
|
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
|
+
}
|