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
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project overview generator
|
|
3
|
+
* Provides a comprehensive view of project state, plan, and progress
|
|
4
|
+
* with analysis of issues and ability to fix them
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { loadProject, getProgress, storeUserDocs, storeBrandContext } from '../state/index.js';
|
|
9
|
+
import type { ProjectState } from '../types/workflow.js';
|
|
10
|
+
import {
|
|
11
|
+
discoverProjectDocs,
|
|
12
|
+
readProjectDocs,
|
|
13
|
+
findBrandAssets,
|
|
14
|
+
} from '../generators/website-context.js';
|
|
15
|
+
import { updateWebsiteContent } from './website-updater.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Detected issue in project analysis
|
|
19
|
+
*/
|
|
20
|
+
export interface OverviewIssue {
|
|
21
|
+
severity: 'warning' | 'error';
|
|
22
|
+
category: string;
|
|
23
|
+
message: string;
|
|
24
|
+
fix?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Structured project overview
|
|
29
|
+
*/
|
|
30
|
+
export interface ProjectOverview {
|
|
31
|
+
name: string;
|
|
32
|
+
idea: string;
|
|
33
|
+
language: string;
|
|
34
|
+
phase: string;
|
|
35
|
+
status: string;
|
|
36
|
+
specification: {
|
|
37
|
+
summary: string;
|
|
38
|
+
keyFeatures: string[];
|
|
39
|
+
};
|
|
40
|
+
plan: {
|
|
41
|
+
totalMilestones: number;
|
|
42
|
+
totalTasks: number;
|
|
43
|
+
milestones: Array<{
|
|
44
|
+
name: string;
|
|
45
|
+
status: string;
|
|
46
|
+
taskCount: number;
|
|
47
|
+
completedTasks: number;
|
|
48
|
+
tasks: Array<{ name: string; status: string }>;
|
|
49
|
+
}>;
|
|
50
|
+
};
|
|
51
|
+
progress: {
|
|
52
|
+
completedMilestones: number;
|
|
53
|
+
completedTasks: number;
|
|
54
|
+
percentComplete: number;
|
|
55
|
+
};
|
|
56
|
+
userDocs?: string[];
|
|
57
|
+
brandContext?: { logoPath?: string; primaryColor?: string };
|
|
58
|
+
/** Detected issues and recommendations */
|
|
59
|
+
issues: OverviewIssue[];
|
|
60
|
+
/** Available docs in CWD that are not yet discovered */
|
|
61
|
+
availableDocs: string[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Result of an overview fix operation
|
|
66
|
+
*/
|
|
67
|
+
export interface OverviewFixResult {
|
|
68
|
+
docsDiscovered: number;
|
|
69
|
+
docsStored: boolean;
|
|
70
|
+
brandFound: boolean;
|
|
71
|
+
websiteUpdated: boolean;
|
|
72
|
+
messages: string[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Generate a complete project overview from state
|
|
77
|
+
*
|
|
78
|
+
* @param projectDir - The project directory
|
|
79
|
+
* @returns Structured project overview with analysis
|
|
80
|
+
*/
|
|
81
|
+
export async function generateOverview(
|
|
82
|
+
projectDir: string
|
|
83
|
+
): Promise<ProjectOverview> {
|
|
84
|
+
const state = await loadProject(projectDir);
|
|
85
|
+
const progress = await getProgress(projectDir);
|
|
86
|
+
|
|
87
|
+
// Extract specification summary
|
|
88
|
+
const specSummary = extractSpecSummary(state);
|
|
89
|
+
const keyFeatures = extractKeyFeatures(state);
|
|
90
|
+
|
|
91
|
+
// Build milestone details
|
|
92
|
+
const milestones = state.milestones.map((m) => ({
|
|
93
|
+
name: m.name,
|
|
94
|
+
status: m.status,
|
|
95
|
+
taskCount: m.tasks.length,
|
|
96
|
+
completedTasks: m.tasks.filter((t) => t.status === 'complete').length,
|
|
97
|
+
tasks: m.tasks.map((t) => ({
|
|
98
|
+
name: t.name,
|
|
99
|
+
status: t.status,
|
|
100
|
+
})),
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
// Extract user doc file names if available
|
|
104
|
+
const userDocs = state.userDocs
|
|
105
|
+
? extractDocNames(state.userDocs)
|
|
106
|
+
: undefined;
|
|
107
|
+
|
|
108
|
+
// Check for available docs in CWD that haven't been discovered
|
|
109
|
+
const parentDir = path.dirname(projectDir);
|
|
110
|
+
const availableDocPaths = await discoverProjectDocs(parentDir);
|
|
111
|
+
const availableDocs = availableDocPaths.map((p) => path.basename(p));
|
|
112
|
+
|
|
113
|
+
// Run analysis to detect issues
|
|
114
|
+
const issues = analyzeProject(state, availableDocs, progress);
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
name: state.name,
|
|
118
|
+
idea: state.idea,
|
|
119
|
+
language: state.language,
|
|
120
|
+
phase: state.phase,
|
|
121
|
+
status: state.status,
|
|
122
|
+
specification: {
|
|
123
|
+
summary: specSummary,
|
|
124
|
+
keyFeatures,
|
|
125
|
+
},
|
|
126
|
+
plan: {
|
|
127
|
+
totalMilestones: state.milestones.length,
|
|
128
|
+
totalTasks: state.milestones.reduce((sum, m) => sum + m.tasks.length, 0),
|
|
129
|
+
milestones,
|
|
130
|
+
},
|
|
131
|
+
progress: {
|
|
132
|
+
completedMilestones: progress.completedMilestones,
|
|
133
|
+
completedTasks: progress.completedTasks,
|
|
134
|
+
percentComplete: progress.percentComplete,
|
|
135
|
+
},
|
|
136
|
+
userDocs,
|
|
137
|
+
brandContext: state.brandContext,
|
|
138
|
+
issues,
|
|
139
|
+
availableDocs,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Fix detected issues by re-discovering docs and updating website content
|
|
145
|
+
*
|
|
146
|
+
* @param projectDir - The project directory
|
|
147
|
+
* @param onProgress - Optional progress callback
|
|
148
|
+
* @returns Fix result with summary of actions taken
|
|
149
|
+
*/
|
|
150
|
+
export async function fixOverviewIssues(
|
|
151
|
+
projectDir: string,
|
|
152
|
+
onProgress?: (message: string) => void
|
|
153
|
+
): Promise<OverviewFixResult> {
|
|
154
|
+
const result: OverviewFixResult = {
|
|
155
|
+
docsDiscovered: 0,
|
|
156
|
+
docsStored: false,
|
|
157
|
+
brandFound: false,
|
|
158
|
+
websiteUpdated: false,
|
|
159
|
+
messages: [],
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
let state = await loadProject(projectDir);
|
|
163
|
+
const parentDir = path.dirname(projectDir);
|
|
164
|
+
|
|
165
|
+
// Step 1: Re-discover project documentation
|
|
166
|
+
onProgress?.('Scanning for project documentation...');
|
|
167
|
+
const docPaths = await discoverProjectDocs(parentDir);
|
|
168
|
+
|
|
169
|
+
if (docPaths.length > 0) {
|
|
170
|
+
const userDocs = await readProjectDocs(docPaths);
|
|
171
|
+
state = await storeUserDocs(projectDir, userDocs);
|
|
172
|
+
result.docsDiscovered = docPaths.length;
|
|
173
|
+
result.docsStored = true;
|
|
174
|
+
const docNames = docPaths.map((p) => path.basename(p)).join(', ');
|
|
175
|
+
result.messages.push(`Discovered ${docPaths.length} doc(s): ${docNames}`);
|
|
176
|
+
onProgress?.(`Found ${docPaths.length} doc(s): ${docNames}`);
|
|
177
|
+
} else {
|
|
178
|
+
result.messages.push('No project documentation found in parent directory');
|
|
179
|
+
onProgress?.('No project documentation found in parent directory');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Step 2: Find brand assets
|
|
183
|
+
onProgress?.('Scanning for brand assets...');
|
|
184
|
+
const brandAssets = await findBrandAssets(parentDir);
|
|
185
|
+
|
|
186
|
+
if (brandAssets.logoPath) {
|
|
187
|
+
state = await storeBrandContext(projectDir, {
|
|
188
|
+
...state.brandContext,
|
|
189
|
+
logoPath: brandAssets.logoPath,
|
|
190
|
+
});
|
|
191
|
+
result.brandFound = true;
|
|
192
|
+
result.messages.push(`Found logo: ${path.basename(brandAssets.logoPath)}`);
|
|
193
|
+
onProgress?.(`Found logo: ${path.basename(brandAssets.logoPath)}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Extract primary/accent color from docs if not already set
|
|
197
|
+
// Use smart extraction: look for accent/primary CTA color tokens first,
|
|
198
|
+
// then fall back to brightness-filtered colors (skip very dark/light)
|
|
199
|
+
if (!state.brandContext?.primaryColor && state.userDocs) {
|
|
200
|
+
const accentMatch = state.userDocs.match(/accent[_-]?primary[^#]{0,40}(#[0-9a-fA-F]{6})/i)
|
|
201
|
+
|| state.userDocs.match(/(?:primary\s+(?:brand\s+)?(?:accent|color|CTA))[^#]{0,40}(#[0-9a-fA-F]{6})/i);
|
|
202
|
+
const color = accentMatch ? accentMatch[1] : findBrightColor(state.userDocs);
|
|
203
|
+
if (color) {
|
|
204
|
+
state = await storeBrandContext(projectDir, {
|
|
205
|
+
...state.brandContext,
|
|
206
|
+
primaryColor: color,
|
|
207
|
+
});
|
|
208
|
+
result.messages.push(`Extracted brand color: ${color}`);
|
|
209
|
+
onProgress?.(`Extracted brand color: ${color}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Step 3: Update website content if applicable
|
|
214
|
+
const language = state.language;
|
|
215
|
+
if (language === 'website' || language === 'all' || language === 'fullstack') {
|
|
216
|
+
onProgress?.('Updating website content with discovered context...');
|
|
217
|
+
try {
|
|
218
|
+
await updateWebsiteContent(projectDir, state, language, onProgress);
|
|
219
|
+
result.websiteUpdated = true;
|
|
220
|
+
result.messages.push('Website content files updated with project context');
|
|
221
|
+
} catch (err) {
|
|
222
|
+
result.messages.push(`Website update failed: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Summary
|
|
227
|
+
if (result.docsDiscovered === 0 && !result.brandFound) {
|
|
228
|
+
result.messages.push(
|
|
229
|
+
'Tip: Place project docs (spec, pricing, color-scheme, etc.) as .md files in the parent directory, then run /overview fix again'
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Analyze project state and detect issues */
|
|
237
|
+
function analyzeProject(
|
|
238
|
+
state: ProjectState,
|
|
239
|
+
availableDocs: string[],
|
|
240
|
+
progress: { totalTasks: number; completedTasks: number; percentComplete: number }
|
|
241
|
+
): OverviewIssue[] {
|
|
242
|
+
const issues: OverviewIssue[] = [];
|
|
243
|
+
const push = (severity: 'warning' | 'error', category: string, message: string, fix?: string) =>
|
|
244
|
+
issues.push({ severity, category, message, fix });
|
|
245
|
+
|
|
246
|
+
if (!state.userDocs && availableDocs.length > 0) {
|
|
247
|
+
push('warning', 'docs',
|
|
248
|
+
`Found ${availableDocs.length} doc(s) in CWD (${availableDocs.join(', ')}) but project has no user docs stored`,
|
|
249
|
+
'Run /overview fix to discover and apply project documentation');
|
|
250
|
+
}
|
|
251
|
+
if (!state.userDocs && availableDocs.length === 0) {
|
|
252
|
+
push('warning', 'docs',
|
|
253
|
+
'No project documentation found. Website will use generic placeholder content.',
|
|
254
|
+
'Add .md files (spec, pricing, brand, etc.) to the project parent directory, then run /overview fix');
|
|
255
|
+
}
|
|
256
|
+
if (progress.totalTasks > 0 && progress.totalTasks <= 3) {
|
|
257
|
+
push('warning', 'plan',
|
|
258
|
+
`Project has only ${progress.totalTasks} task(s) - this may indicate an incomplete or oversimplified plan`,
|
|
259
|
+
'Consider resetting to plan phase with richer specification');
|
|
260
|
+
}
|
|
261
|
+
if (!state.brandContext?.primaryColor && !state.brandContext?.logoPath) {
|
|
262
|
+
push('warning', 'brand',
|
|
263
|
+
'No brand context (colors, logo) detected. Website uses default styling.',
|
|
264
|
+
'Add a color-scheme.md or logo file to the project parent directory, then run /overview fix');
|
|
265
|
+
}
|
|
266
|
+
if (state.specification) {
|
|
267
|
+
const specStart = state.specification.trim().slice(0, 200).toLowerCase();
|
|
268
|
+
const genericPats = ["here's a comprehensive", "here is a comprehensive",
|
|
269
|
+
"here's a detailed", "here is a detailed", 'based on your idea', 'based on the idea'];
|
|
270
|
+
if (genericPats.some((p) => specStart.includes(p))) {
|
|
271
|
+
push('warning', 'spec',
|
|
272
|
+
'Specification appears to be generic AI-generated content without project-specific documentation input',
|
|
273
|
+
'Add project docs to CWD and run /overview fix to re-enrich. For a full re-spec, reset to plan phase.');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const hasWebsite = state.language === 'website' || state.language === 'all' || state.language === 'fullstack';
|
|
277
|
+
if (hasWebsite && !state.userDocs && state.phase === 'complete') {
|
|
278
|
+
push('error', 'website',
|
|
279
|
+
'Project includes a website but was completed without any user documentation. Website likely has placeholder content.',
|
|
280
|
+
'Run /overview fix to discover docs and update website template files');
|
|
281
|
+
}
|
|
282
|
+
// Show strategy error if present
|
|
283
|
+
if (state.strategyError) {
|
|
284
|
+
push('error', 'strategy',
|
|
285
|
+
`Website strategy generation failed: ${state.strategyError}`,
|
|
286
|
+
'Ensure OpenAI API key is set and product docs are available, then re-run planning');
|
|
287
|
+
}
|
|
288
|
+
return issues;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Format a project overview for terminal display
|
|
293
|
+
*
|
|
294
|
+
* @param overview - The project overview to format
|
|
295
|
+
* @returns Formatted string for terminal output
|
|
296
|
+
*/
|
|
297
|
+
export function formatOverview(overview: ProjectOverview): string {
|
|
298
|
+
const l: string[] = [''];
|
|
299
|
+
l.push(` PROJECT OVERVIEW: ${overview.name}`);
|
|
300
|
+
l.push(` ${'='.repeat(40 + overview.name.length)}`, '');
|
|
301
|
+
l.push(` Language: ${overview.language}`);
|
|
302
|
+
l.push(` Phase: ${overview.phase}`);
|
|
303
|
+
l.push(` Status: ${overview.status}`, '');
|
|
304
|
+
|
|
305
|
+
if (overview.specification.summary) {
|
|
306
|
+
l.push(' SPECIFICATION', ` ${overview.specification.summary}`);
|
|
307
|
+
if (overview.specification.keyFeatures.length > 0) {
|
|
308
|
+
l.push('', ' Key Features:');
|
|
309
|
+
for (const f of overview.specification.keyFeatures.slice(0, 8)) l.push(` - ${f}`);
|
|
310
|
+
}
|
|
311
|
+
l.push('');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const barWidth = 30;
|
|
315
|
+
const filled = Math.round((overview.progress.percentComplete / 100) * barWidth);
|
|
316
|
+
const bar = `[${'='.repeat(filled)}${filled < barWidth ? '>' : ''}${' '.repeat(Math.max(0, barWidth - filled - 1))}]`;
|
|
317
|
+
l.push(` Progress: ${bar} ${overview.progress.percentComplete}% (${overview.progress.completedTasks}/${overview.plan.totalTasks} tasks)`, '');
|
|
318
|
+
|
|
319
|
+
if (overview.plan.milestones.length > 0) {
|
|
320
|
+
l.push(' MILESTONES', ` ${'-'.repeat(50)}`);
|
|
321
|
+
for (const m of overview.plan.milestones) {
|
|
322
|
+
l.push(` ${getStatusIcon(m.status)} ${m.name} (${m.completedTasks}/${m.taskCount} tasks)`);
|
|
323
|
+
for (const t of m.tasks) l.push(` ${getStatusIcon(t.status)} ${t.name}`);
|
|
324
|
+
l.push('');
|
|
325
|
+
}
|
|
326
|
+
} else {
|
|
327
|
+
l.push(' No milestones defined yet.', '');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (overview.userDocs && overview.userDocs.length > 0) {
|
|
331
|
+
l.push(' DISCOVERED DOCS');
|
|
332
|
+
for (const doc of overview.userDocs) l.push(` - ${doc}`);
|
|
333
|
+
l.push('');
|
|
334
|
+
}
|
|
335
|
+
if (overview.brandContext) {
|
|
336
|
+
l.push(' BRAND CONTEXT');
|
|
337
|
+
if (overview.brandContext.primaryColor) l.push(` Primary Color: ${overview.brandContext.primaryColor}`);
|
|
338
|
+
if (overview.brandContext.logoPath) l.push(` Logo: ${path.basename(overview.brandContext.logoPath)}`);
|
|
339
|
+
l.push('');
|
|
340
|
+
}
|
|
341
|
+
if (overview.issues.length > 0) {
|
|
342
|
+
l.push(' ANALYSIS', ` ${'-'.repeat(50)}`);
|
|
343
|
+
for (const issue of overview.issues) {
|
|
344
|
+
const icon = issue.severity === 'error' ? '[!!]' : '[!]';
|
|
345
|
+
l.push(` ${icon} ${issue.category.toUpperCase()}: ${issue.message}`);
|
|
346
|
+
if (issue.fix) l.push(` -> ${issue.fix}`);
|
|
347
|
+
}
|
|
348
|
+
l.push('');
|
|
349
|
+
}
|
|
350
|
+
if (overview.availableDocs.length > 0 && !overview.userDocs) {
|
|
351
|
+
l.push(' AVAILABLE DOCS (not yet imported)');
|
|
352
|
+
for (const doc of overview.availableDocs) l.push(` - ${doc}`);
|
|
353
|
+
l.push('');
|
|
354
|
+
}
|
|
355
|
+
if (overview.issues.length > 0) {
|
|
356
|
+
l.push(' Run /overview fix to auto-discover docs, detect brand assets, and update website content.', '');
|
|
357
|
+
}
|
|
358
|
+
return l.join('\n');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Extract a summary from the specification
|
|
363
|
+
*/
|
|
364
|
+
function extractSpecSummary(state: ProjectState): string {
|
|
365
|
+
if (!state.specification) return '';
|
|
366
|
+
|
|
367
|
+
const lines = state.specification.split('\n');
|
|
368
|
+
// Find first non-empty, non-heading line that isn't a generic AI preamble
|
|
369
|
+
const genericPrefixes = [
|
|
370
|
+
"here's a comprehensive",
|
|
371
|
+
"here is a comprehensive",
|
|
372
|
+
"here's a detailed",
|
|
373
|
+
'based on your idea',
|
|
374
|
+
];
|
|
375
|
+
|
|
376
|
+
for (const line of lines) {
|
|
377
|
+
const trimmed = line.trim();
|
|
378
|
+
if (trimmed && !trimmed.startsWith('#') && trimmed.length > 20) {
|
|
379
|
+
const lower = trimmed.toLowerCase();
|
|
380
|
+
if (!genericPrefixes.some((p) => lower.startsWith(p))) {
|
|
381
|
+
return trimmed.slice(0, 500);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return state.specification.slice(0, 500);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Extract key features from specification
|
|
391
|
+
*/
|
|
392
|
+
function extractKeyFeatures(state: ProjectState): string[] {
|
|
393
|
+
if (!state.specification) return [];
|
|
394
|
+
|
|
395
|
+
const features: string[] = [];
|
|
396
|
+
const lines = state.specification.split('\n');
|
|
397
|
+
let inFeatures = false;
|
|
398
|
+
|
|
399
|
+
for (const line of lines) {
|
|
400
|
+
const trimmed = line.trim();
|
|
401
|
+
|
|
402
|
+
// Detect feature section headers
|
|
403
|
+
if (/^#{1,3}\s*(core\s+)?features/i.test(trimmed)) {
|
|
404
|
+
inFeatures = true;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Stop at next heading
|
|
409
|
+
if (inFeatures && /^#{1,3}\s/.test(trimmed) && !/feature/i.test(trimmed)) {
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Collect bullet points in feature section
|
|
414
|
+
if (inFeatures && /^[-*+]\s+/.test(trimmed)) {
|
|
415
|
+
const feature = trimmed.replace(/^[-*+]\s+/, '').replace(/^\*\*(.+?)\*\*:?\s*/, '$1: ');
|
|
416
|
+
if (feature.length > 5) {
|
|
417
|
+
features.push(feature.slice(0, 100));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return features.slice(0, 10);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Extract doc file names from raw docs string
|
|
427
|
+
*/
|
|
428
|
+
function extractDocNames(rawDocs: string): string[] {
|
|
429
|
+
const names: string[] = [];
|
|
430
|
+
const headerPattern = /^--- (.+) ---$/gm;
|
|
431
|
+
let match;
|
|
432
|
+
|
|
433
|
+
while ((match = headerPattern.exec(rawDocs)) !== null) {
|
|
434
|
+
names.push(match[1]);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return names;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Get a status icon for terminal display
|
|
442
|
+
*/
|
|
443
|
+
function getStatusIcon(status: string): string {
|
|
444
|
+
switch (status) {
|
|
445
|
+
case 'complete':
|
|
446
|
+
return '[x]';
|
|
447
|
+
case 'in-progress':
|
|
448
|
+
return '[~]';
|
|
449
|
+
case 'failed':
|
|
450
|
+
return '[!]';
|
|
451
|
+
case 'paused':
|
|
452
|
+
return '[-]';
|
|
453
|
+
default:
|
|
454
|
+
return '[ ]';
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Find first hex color that isn't very dark or very light
|
|
460
|
+
* Skips background/neutral colors to find a reasonable brand accent
|
|
461
|
+
*/
|
|
462
|
+
function findBrightColor(text: string): string | undefined {
|
|
463
|
+
const allColors = [...text.matchAll(/#([0-9a-fA-F]{6})/g)];
|
|
464
|
+
for (const match of allColors) {
|
|
465
|
+
const hex = match[1];
|
|
466
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
467
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
468
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
469
|
+
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
|
|
470
|
+
if (brightness > 60 && brightness < 210) {
|
|
471
|
+
return '#' + hex;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return undefined;
|
|
475
|
+
}
|