popeye-cli 1.2.1 → 1.4.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/.env.example +4 -1
- package/CONTRIBUTING.md +10 -0
- package/README.md +224 -17
- package/dist/adapters/claude.d.ts +3 -2
- package/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +214 -0
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/gemini.d.ts +2 -2
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/grok.d.ts +2 -1
- package/dist/adapters/grok.d.ts.map +1 -1
- package/dist/adapters/grok.js.map +1 -1
- package/dist/adapters/index.d.ts +8 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +12 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/openai.d.ts +2 -2
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js.map +1 -1
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +25 -5
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +5 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +354 -28
- package/dist/cli/interactive.js.map +1 -1
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/schema.d.ts +4 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +2 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/generators/all.d.ts +70 -0
- package/dist/generators/all.d.ts.map +1 -0
- package/dist/generators/all.js +826 -0
- package/dist/generators/all.js.map +1 -0
- package/dist/generators/fullstack.d.ts +9 -0
- package/dist/generators/fullstack.d.ts.map +1 -1
- package/dist/generators/fullstack.js.map +1 -1
- package/dist/generators/index.d.ts +3 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +33 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/templates/index.d.ts +2 -0
- package/dist/generators/templates/index.d.ts.map +1 -1
- package/dist/generators/templates/index.js +2 -0
- package/dist/generators/templates/index.js.map +1 -1
- package/dist/generators/templates/website.d.ts +85 -0
- package/dist/generators/templates/website.d.ts.map +1 -0
- package/dist/generators/templates/website.js +877 -0
- package/dist/generators/templates/website.js.map +1 -0
- package/dist/generators/website.d.ts +56 -0
- package/dist/generators/website.d.ts.map +1 -0
- package/dist/generators/website.js +269 -0
- package/dist/generators/website.js.map +1 -0
- package/dist/types/consensus.d.ts +18 -23
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +8 -3
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -2
- package/dist/types/index.js.map +1 -1
- package/dist/types/project.d.ts +130 -17
- package/dist/types/project.d.ts.map +1 -1
- package/dist/types/project.js +55 -8
- package/dist/types/project.js.map +1 -1
- package/dist/types/workflow.d.ts +2 -0
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +2 -1
- package/dist/types/workflow.js.map +1 -1
- package/dist/upgrade/context.d.ts +37 -0
- package/dist/upgrade/context.d.ts.map +1 -0
- package/dist/upgrade/context.js +284 -0
- package/dist/upgrade/context.js.map +1 -0
- package/dist/upgrade/handlers.d.ts +103 -0
- package/dist/upgrade/handlers.d.ts.map +1 -0
- package/dist/upgrade/handlers.js +384 -0
- package/dist/upgrade/handlers.js.map +1 -0
- package/dist/upgrade/index.d.ts +26 -0
- package/dist/upgrade/index.d.ts.map +1 -0
- package/dist/upgrade/index.js +194 -0
- package/dist/upgrade/index.js.map +1 -0
- package/dist/upgrade/transitions.d.ts +34 -0
- package/dist/upgrade/transitions.d.ts.map +1 -0
- package/dist/upgrade/transitions.js +56 -0
- package/dist/upgrade/transitions.js.map +1 -0
- package/dist/workflow/consensus.d.ts +2 -1
- package/dist/workflow/consensus.d.ts.map +1 -1
- package/dist/workflow/consensus.js.map +1 -1
- package/dist/workflow/index.d.ts +6 -0
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +8 -0
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/plan-mode.d.ts +3 -3
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js +41 -5
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/plan-parser.d.ts +97 -0
- package/dist/workflow/plan-parser.d.ts.map +1 -0
- package/dist/workflow/plan-parser.js +235 -0
- package/dist/workflow/plan-parser.js.map +1 -0
- package/dist/workflow/plan-storage.d.ts +40 -12
- package/dist/workflow/plan-storage.d.ts.map +1 -1
- package/dist/workflow/plan-storage.js +47 -20
- package/dist/workflow/plan-storage.js.map +1 -1
- package/dist/workflow/seo-tests.d.ts +43 -0
- package/dist/workflow/seo-tests.d.ts.map +1 -0
- package/dist/workflow/seo-tests.js +192 -0
- package/dist/workflow/seo-tests.js.map +1 -0
- package/dist/workflow/separation-guard.d.ts +35 -0
- package/dist/workflow/separation-guard.d.ts.map +1 -0
- package/dist/workflow/separation-guard.js +154 -0
- package/dist/workflow/separation-guard.js.map +1 -0
- package/dist/workflow/task-workflow.d.ts.map +1 -1
- package/dist/workflow/task-workflow.js +3 -2
- package/dist/workflow/task-workflow.js.map +1 -1
- package/dist/workflow/test-runner.d.ts.map +1 -1
- package/dist/workflow/test-runner.js +128 -0
- package/dist/workflow/test-runner.js.map +1 -1
- package/dist/workflow/workspace-manager.d.ts +31 -20
- package/dist/workflow/workspace-manager.d.ts.map +1 -1
- package/dist/workflow/workspace-manager.js +38 -9
- package/dist/workflow/workspace-manager.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/claude.ts +221 -4
- package/src/adapters/gemini.ts +2 -2
- package/src/adapters/grok.ts +2 -1
- package/src/adapters/index.ts +15 -0
- package/src/adapters/openai.ts +2 -2
- package/src/cli/commands/create.ts +25 -5
- package/src/cli/index.ts +5 -2
- package/src/cli/interactive.ts +400 -29
- package/src/config/schema.ts +2 -1
- package/src/generators/all.ts +897 -0
- package/src/generators/fullstack.ts +10 -0
- package/src/generators/index.ts +54 -0
- package/src/generators/templates/index.ts +2 -0
- package/src/generators/templates/website.ts +906 -0
- package/src/generators/website.ts +350 -0
- package/src/types/consensus.ts +20 -8
- package/src/types/index.ts +35 -0
- package/src/types/project.ts +157 -11
- package/src/types/workflow.ts +2 -1
- package/src/upgrade/context.ts +332 -0
- package/src/upgrade/handlers.ts +477 -0
- package/src/upgrade/index.ts +244 -0
- package/src/upgrade/transitions.ts +80 -0
- package/src/workflow/consensus.ts +3 -2
- package/src/workflow/index.ts +8 -0
- package/src/workflow/plan-mode.ts +44 -10
- package/src/workflow/plan-parser.ts +317 -0
- package/src/workflow/plan-storage.ts +69 -30
- package/src/workflow/seo-tests.ts +246 -0
- package/src/workflow/separation-guard.ts +200 -0
- package/src/workflow/task-workflow.ts +3 -2
- package/src/workflow/test-runner.ts +149 -0
- package/src/workflow/workspace-manager.ts +68 -31
- package/tests/cli/model-command.test.ts +93 -0
- package/tests/types/project.test.ts +90 -15
- package/tests/types/workflow-schema.test.ts +59 -0
- package/tests/upgrade/context.test.ts +211 -0
- package/tests/upgrade/transitions.test.ts +85 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Website project generator
|
|
3
|
+
* Creates SEO-ready Next.js marketing website
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { promises as fs } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import type { ProjectSpec } from '../types/project.js';
|
|
9
|
+
import {
|
|
10
|
+
generateWebsitePackageJson,
|
|
11
|
+
generateNextConfig,
|
|
12
|
+
generateWebsiteTsconfig,
|
|
13
|
+
generateWebsiteTailwindConfig,
|
|
14
|
+
generateWebsitePostcssConfig,
|
|
15
|
+
generateWebsiteLayout,
|
|
16
|
+
generateWebsiteGlobalsCss,
|
|
17
|
+
generateWebsiteLandingPage,
|
|
18
|
+
generateWebsitePricingPage,
|
|
19
|
+
generateWebsiteSitemap,
|
|
20
|
+
generateWebsiteRobots,
|
|
21
|
+
generateWebsiteDockerfile,
|
|
22
|
+
generateWebsiteReadme,
|
|
23
|
+
generateWebsiteSpec,
|
|
24
|
+
generateWebsiteVitestConfig,
|
|
25
|
+
generateWebsiteVitestSetup,
|
|
26
|
+
generateWebsiteTest,
|
|
27
|
+
generateWebsiteDocsPage,
|
|
28
|
+
generateWebsiteBlogPage,
|
|
29
|
+
generateWebsiteNextEnv,
|
|
30
|
+
} from './templates/website.js';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Project generation result
|
|
34
|
+
*/
|
|
35
|
+
export interface GenerationResult {
|
|
36
|
+
success: boolean;
|
|
37
|
+
projectDir: string;
|
|
38
|
+
filesCreated: string[];
|
|
39
|
+
error?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Website generator options for workspace/monorepo support
|
|
44
|
+
*/
|
|
45
|
+
export interface WebsiteGeneratorOptions {
|
|
46
|
+
/** Base directory for project (defaults to outputDir/projectName) */
|
|
47
|
+
baseDir?: string;
|
|
48
|
+
/** Override auto-derived package name */
|
|
49
|
+
packageName?: string;
|
|
50
|
+
/** Adjust paths for monorepo structure */
|
|
51
|
+
workspaceMode?: boolean;
|
|
52
|
+
/** Skip Docker files (fullstack uses root docker-compose) */
|
|
53
|
+
skipDocker?: boolean;
|
|
54
|
+
/** Skip README (fullstack has root README) */
|
|
55
|
+
skipReadme?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create a directory if it doesn't exist
|
|
60
|
+
*/
|
|
61
|
+
async function ensureDir(dirPath: string): Promise<void> {
|
|
62
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Write a file with content
|
|
67
|
+
*/
|
|
68
|
+
async function writeFile(filePath: string, content: string): Promise<void> {
|
|
69
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Generate a complete Next.js website project
|
|
74
|
+
*
|
|
75
|
+
* @param spec - Project specification
|
|
76
|
+
* @param outputDir - Output directory
|
|
77
|
+
* @param options - Generator options for workspace/monorepo support
|
|
78
|
+
* @returns Generation result
|
|
79
|
+
*/
|
|
80
|
+
export async function generateWebsiteProject(
|
|
81
|
+
spec: ProjectSpec,
|
|
82
|
+
outputDir: string,
|
|
83
|
+
options: WebsiteGeneratorOptions = {}
|
|
84
|
+
): Promise<GenerationResult> {
|
|
85
|
+
const {
|
|
86
|
+
baseDir,
|
|
87
|
+
// packageName reserved for future use
|
|
88
|
+
workspaceMode = false,
|
|
89
|
+
skipDocker = false,
|
|
90
|
+
skipReadme = false,
|
|
91
|
+
} = options;
|
|
92
|
+
|
|
93
|
+
const projectName = spec.name || 'my-project';
|
|
94
|
+
|
|
95
|
+
// In workspace mode with baseDir, use it directly; otherwise create subdirectory
|
|
96
|
+
const projectDir = baseDir || path.join(outputDir, projectName);
|
|
97
|
+
const filesCreated: string[] = [];
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
// Create project directory structure
|
|
101
|
+
await ensureDir(projectDir);
|
|
102
|
+
await ensureDir(path.join(projectDir, 'src', 'app'));
|
|
103
|
+
await ensureDir(path.join(projectDir, 'src', 'app', 'pricing'));
|
|
104
|
+
await ensureDir(path.join(projectDir, 'src', 'app', 'docs'));
|
|
105
|
+
await ensureDir(path.join(projectDir, 'src', 'app', 'blog'));
|
|
106
|
+
await ensureDir(path.join(projectDir, 'src', 'components'));
|
|
107
|
+
await ensureDir(path.join(projectDir, 'src', 'lib'));
|
|
108
|
+
await ensureDir(path.join(projectDir, 'content', 'blog'));
|
|
109
|
+
await ensureDir(path.join(projectDir, 'content', 'docs'));
|
|
110
|
+
await ensureDir(path.join(projectDir, 'public'));
|
|
111
|
+
await ensureDir(path.join(projectDir, 'tests'));
|
|
112
|
+
|
|
113
|
+
// Only create .popeye dir in standalone mode
|
|
114
|
+
if (!workspaceMode) {
|
|
115
|
+
await ensureDir(path.join(projectDir, '.popeye'));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Generate and write files
|
|
119
|
+
const files: Array<{ path: string; content: string }> = [
|
|
120
|
+
// Config files
|
|
121
|
+
{
|
|
122
|
+
path: path.join(projectDir, 'package.json'),
|
|
123
|
+
content: generateWebsitePackageJson(projectName),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
path: path.join(projectDir, 'next.config.mjs'),
|
|
127
|
+
content: generateNextConfig(),
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
path: path.join(projectDir, 'tsconfig.json'),
|
|
131
|
+
content: generateWebsiteTsconfig(),
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
path: path.join(projectDir, 'tailwind.config.ts'),
|
|
135
|
+
content: generateWebsiteTailwindConfig(),
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
path: path.join(projectDir, 'postcss.config.js'),
|
|
139
|
+
content: generateWebsitePostcssConfig(),
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
path: path.join(projectDir, 'vitest.config.ts'),
|
|
143
|
+
content: generateWebsiteVitestConfig(),
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
path: path.join(projectDir, 'next-env.d.ts'),
|
|
147
|
+
content: generateWebsiteNextEnv(),
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
// App Router files
|
|
151
|
+
{
|
|
152
|
+
path: path.join(projectDir, 'src', 'app', 'layout.tsx'),
|
|
153
|
+
content: generateWebsiteLayout(projectName),
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
path: path.join(projectDir, 'src', 'app', 'globals.css'),
|
|
157
|
+
content: generateWebsiteGlobalsCss(),
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
path: path.join(projectDir, 'src', 'app', 'page.tsx'),
|
|
161
|
+
content: generateWebsiteLandingPage(projectName),
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
path: path.join(projectDir, 'src', 'app', 'pricing', 'page.tsx'),
|
|
165
|
+
content: generateWebsitePricingPage(projectName),
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
path: path.join(projectDir, 'src', 'app', 'docs', 'page.tsx'),
|
|
169
|
+
content: generateWebsiteDocsPage(),
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
path: path.join(projectDir, 'src', 'app', 'blog', 'page.tsx'),
|
|
173
|
+
content: generateWebsiteBlogPage(),
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
// SEO files
|
|
177
|
+
{
|
|
178
|
+
path: path.join(projectDir, 'src', 'app', 'sitemap.ts'),
|
|
179
|
+
content: generateWebsiteSitemap(projectName),
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
path: path.join(projectDir, 'src', 'app', 'robots.ts'),
|
|
183
|
+
content: generateWebsiteRobots(projectName),
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
// Test files
|
|
187
|
+
{
|
|
188
|
+
path: path.join(projectDir, 'tests', 'setup.ts'),
|
|
189
|
+
content: generateWebsiteVitestSetup(),
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
path: path.join(projectDir, 'tests', 'page.test.tsx'),
|
|
193
|
+
content: generateWebsiteTest(projectName),
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
// Placeholder files
|
|
197
|
+
{
|
|
198
|
+
path: path.join(projectDir, 'public', '.gitkeep'),
|
|
199
|
+
content: '',
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
path: path.join(projectDir, 'content', 'blog', '.gitkeep'),
|
|
203
|
+
content: '',
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
path: path.join(projectDir, 'content', 'docs', '.gitkeep'),
|
|
207
|
+
content: '',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
path: path.join(projectDir, 'src', 'components', '.gitkeep'),
|
|
211
|
+
content: '',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
path: path.join(projectDir, 'src', 'lib', '.gitkeep'),
|
|
215
|
+
content: '',
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
// Environment
|
|
219
|
+
{
|
|
220
|
+
path: path.join(projectDir, '.env.example'),
|
|
221
|
+
content: 'NEXT_PUBLIC_SITE_URL=http://localhost:3001\nNEXT_PUBLIC_APP_URL=http://localhost:3000\n',
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
path: path.join(projectDir, '.gitignore'),
|
|
225
|
+
content:
|
|
226
|
+
'node_modules/\n.next/\nout/\n.env\n.env.local\n.env.*.local\ncoverage/\n*.log\n.DS_Store\n',
|
|
227
|
+
},
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
// Add website spec in standalone mode
|
|
231
|
+
if (!workspaceMode) {
|
|
232
|
+
files.push({
|
|
233
|
+
path: path.join(projectDir, '.popeye', 'website-spec.json'),
|
|
234
|
+
content: generateWebsiteSpec(projectName),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Add README if not skipped
|
|
239
|
+
if (!skipReadme) {
|
|
240
|
+
files.push({
|
|
241
|
+
path: path.join(projectDir, 'README.md'),
|
|
242
|
+
content: generateWebsiteReadme(projectName),
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Add Docker files if not skipped
|
|
247
|
+
if (!skipDocker) {
|
|
248
|
+
files.push({
|
|
249
|
+
path: path.join(projectDir, 'Dockerfile'),
|
|
250
|
+
content: generateWebsiteDockerfile(),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Write all files
|
|
255
|
+
for (const file of files) {
|
|
256
|
+
await writeFile(file.path, file.content);
|
|
257
|
+
filesCreated.push(file.path);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
success: true,
|
|
262
|
+
projectDir,
|
|
263
|
+
filesCreated,
|
|
264
|
+
};
|
|
265
|
+
} catch (error) {
|
|
266
|
+
return {
|
|
267
|
+
success: false,
|
|
268
|
+
projectDir,
|
|
269
|
+
filesCreated,
|
|
270
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Get the list of files that would be generated for a website project
|
|
277
|
+
*
|
|
278
|
+
* @param _projectName - Project name
|
|
279
|
+
* @returns List of relative file paths
|
|
280
|
+
*/
|
|
281
|
+
export function getWebsiteProjectFiles(_projectName: string): string[] {
|
|
282
|
+
return [
|
|
283
|
+
// Config
|
|
284
|
+
'package.json',
|
|
285
|
+
'next.config.mjs',
|
|
286
|
+
'tsconfig.json',
|
|
287
|
+
'tailwind.config.ts',
|
|
288
|
+
'postcss.config.js',
|
|
289
|
+
'vitest.config.ts',
|
|
290
|
+
'next-env.d.ts',
|
|
291
|
+
'.gitignore',
|
|
292
|
+
'.env.example',
|
|
293
|
+
'README.md',
|
|
294
|
+
'Dockerfile',
|
|
295
|
+
// App Router
|
|
296
|
+
'src/app/layout.tsx',
|
|
297
|
+
'src/app/globals.css',
|
|
298
|
+
'src/app/page.tsx',
|
|
299
|
+
'src/app/pricing/page.tsx',
|
|
300
|
+
'src/app/docs/page.tsx',
|
|
301
|
+
'src/app/blog/page.tsx',
|
|
302
|
+
'src/app/sitemap.ts',
|
|
303
|
+
'src/app/robots.ts',
|
|
304
|
+
// Tests
|
|
305
|
+
'tests/setup.ts',
|
|
306
|
+
'tests/page.test.tsx',
|
|
307
|
+
// Content
|
|
308
|
+
'content/blog/.gitkeep',
|
|
309
|
+
'content/docs/.gitkeep',
|
|
310
|
+
// Spec
|
|
311
|
+
'.popeye/website-spec.json',
|
|
312
|
+
];
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Validate a website project structure
|
|
317
|
+
*
|
|
318
|
+
* @param projectDir - Project directory
|
|
319
|
+
* @returns Validation result
|
|
320
|
+
*/
|
|
321
|
+
export async function validateWebsiteProject(projectDir: string): Promise<{
|
|
322
|
+
valid: boolean;
|
|
323
|
+
missingFiles: string[];
|
|
324
|
+
}> {
|
|
325
|
+
const missingFiles: string[] = [];
|
|
326
|
+
|
|
327
|
+
const requiredFiles = [
|
|
328
|
+
'package.json',
|
|
329
|
+
'next.config.mjs',
|
|
330
|
+
'tsconfig.json',
|
|
331
|
+
'src/app/layout.tsx',
|
|
332
|
+
'src/app/page.tsx',
|
|
333
|
+
'src/app/sitemap.ts',
|
|
334
|
+
'src/app/robots.ts',
|
|
335
|
+
];
|
|
336
|
+
|
|
337
|
+
for (const file of requiredFiles) {
|
|
338
|
+
const filePath = path.join(projectDir, file);
|
|
339
|
+
try {
|
|
340
|
+
await fs.access(filePath);
|
|
341
|
+
} catch {
|
|
342
|
+
missingFiles.push(file);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
valid: missingFiles.length === 0,
|
|
348
|
+
missingFiles,
|
|
349
|
+
};
|
|
350
|
+
}
|
package/src/types/consensus.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import type { OpenAIModel } from './project.js';
|
|
8
|
+
import { OpenAIModelSchema } from './project.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Supported AI providers for reviews and arbitration
|
|
@@ -12,9 +13,9 @@ import type { OpenAIModel } from './project.js';
|
|
|
12
13
|
export type AIProvider = 'openai' | 'gemini' | 'grok';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
|
-
*
|
|
16
|
+
* Gemini model type - flexible string to support new models
|
|
16
17
|
*/
|
|
17
|
-
export type GeminiModel =
|
|
18
|
+
export type GeminiModel = string;
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Grok model type (flexible string - API evolves fast)
|
|
@@ -115,9 +116,14 @@ export const DEFAULT_CONSENSUS_CONFIG: Omit<ConsensusConfig, 'openaiKey' | 'gemi
|
|
|
115
116
|
export const AIProviderSchema = z.enum(['openai', 'gemini', 'grok']);
|
|
116
117
|
|
|
117
118
|
/**
|
|
118
|
-
*
|
|
119
|
+
* Known Gemini models (used for suggestions and display, not strict validation)
|
|
119
120
|
*/
|
|
120
|
-
export const
|
|
121
|
+
export const KNOWN_GEMINI_MODELS = ['gemini-2.0-flash', 'gemini-1.5-pro', 'gemini-1.5-flash'] as const;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Zod schema for Gemini model - accepts any non-empty string to support new models
|
|
125
|
+
*/
|
|
126
|
+
export const GeminiModelSchema = z.string().min(1, 'Model name must not be empty');
|
|
121
127
|
|
|
122
128
|
/**
|
|
123
129
|
* Zod schema for Grok model (flexible string)
|
|
@@ -133,7 +139,7 @@ export const ConsensusConfigSchema = z.object({
|
|
|
133
139
|
openaiKey: z.string().optional(),
|
|
134
140
|
geminiKey: z.string().optional(),
|
|
135
141
|
grokKey: z.string().optional(),
|
|
136
|
-
openaiModel:
|
|
142
|
+
openaiModel: OpenAIModelSchema,
|
|
137
143
|
geminiModel: GeminiModelSchema.default('gemini-2.0-flash'),
|
|
138
144
|
grokModel: GrokModelSchema.default(DEFAULT_GROK_MODEL),
|
|
139
145
|
reviewer: AIProviderSchema.default('openai'),
|
|
@@ -195,9 +201,9 @@ export interface EscalationDetails {
|
|
|
195
201
|
}
|
|
196
202
|
|
|
197
203
|
/**
|
|
198
|
-
* App target for fullstack reviews
|
|
204
|
+
* App target for fullstack/all reviews
|
|
199
205
|
*/
|
|
200
|
-
export type ReviewAppTarget = 'frontend' | 'backend' | 'unified';
|
|
206
|
+
export type ReviewAppTarget = 'frontend' | 'backend' | 'website' | 'unified';
|
|
201
207
|
|
|
202
208
|
/**
|
|
203
209
|
* Tagged concern/recommendation with app context
|
|
@@ -208,11 +214,12 @@ export interface TaggedItem {
|
|
|
208
214
|
}
|
|
209
215
|
|
|
210
216
|
/**
|
|
211
|
-
* Per-app consensus scores for fullstack projects
|
|
217
|
+
* Per-app consensus scores for fullstack/all projects
|
|
212
218
|
*/
|
|
213
219
|
export interface AppConsensusScores {
|
|
214
220
|
frontend?: number;
|
|
215
221
|
backend?: number;
|
|
222
|
+
website?: number;
|
|
216
223
|
unified: number; // Combined/overall score
|
|
217
224
|
}
|
|
218
225
|
|
|
@@ -253,6 +260,7 @@ export interface FullstackConsensusIteration extends ConsensusIteration {
|
|
|
253
260
|
appApproved?: {
|
|
254
261
|
frontend?: boolean;
|
|
255
262
|
backend?: boolean;
|
|
263
|
+
website?: boolean;
|
|
256
264
|
unified: boolean;
|
|
257
265
|
};
|
|
258
266
|
}
|
|
@@ -295,6 +303,10 @@ export interface ConsensusTrackingRecord {
|
|
|
295
303
|
backendApproved?: boolean;
|
|
296
304
|
backendIterations?: number;
|
|
297
305
|
|
|
306
|
+
websiteScore?: number;
|
|
307
|
+
websiteApproved?: boolean;
|
|
308
|
+
websiteIterations?: number;
|
|
309
|
+
|
|
298
310
|
/** All corrections/revisions made */
|
|
299
311
|
corrections: CorrectionRecord[];
|
|
300
312
|
|
package/src/types/index.ts
CHANGED
|
@@ -7,13 +7,31 @@
|
|
|
7
7
|
export {
|
|
8
8
|
OutputLanguageSchema,
|
|
9
9
|
OpenAIModelSchema,
|
|
10
|
+
KNOWN_OPENAI_MODELS,
|
|
10
11
|
ProjectSpecSchema,
|
|
11
12
|
OPENAI_MODELS,
|
|
13
|
+
languageToApps,
|
|
14
|
+
hasApp,
|
|
12
15
|
type OutputLanguage,
|
|
13
16
|
type OpenAIModel,
|
|
14
17
|
type ProjectSpec,
|
|
15
18
|
type GeneratedProject,
|
|
16
19
|
type GenerationOptions,
|
|
20
|
+
type AppType,
|
|
21
|
+
type WorkspaceApp,
|
|
22
|
+
type WorkspaceAppCommands,
|
|
23
|
+
type WorkspaceAppDocker,
|
|
24
|
+
type WorkspaceShared,
|
|
25
|
+
type WorkspaceCommands,
|
|
26
|
+
type WorkspaceDocker,
|
|
27
|
+
type WorkspaceConfig,
|
|
28
|
+
type WebsiteSpec,
|
|
29
|
+
type WebsiteBrandColors,
|
|
30
|
+
type WebsiteTypography,
|
|
31
|
+
type WebsiteSeo,
|
|
32
|
+
type WebsitePage,
|
|
33
|
+
type WebsiteCta,
|
|
34
|
+
type WebsiteFeatures,
|
|
17
35
|
} from './project.js';
|
|
18
36
|
|
|
19
37
|
// Workflow types
|
|
@@ -37,12 +55,29 @@ export {
|
|
|
37
55
|
export {
|
|
38
56
|
ConsensusConfigSchema,
|
|
39
57
|
DEFAULT_CONSENSUS_CONFIG,
|
|
58
|
+
DEFAULT_GROK_MODEL,
|
|
59
|
+
AIProviderSchema,
|
|
60
|
+
KNOWN_GEMINI_MODELS,
|
|
61
|
+
GeminiModelSchema,
|
|
62
|
+
GrokModelSchema,
|
|
63
|
+
type AIProvider,
|
|
64
|
+
type GeminiModel,
|
|
65
|
+
type GrokModel,
|
|
40
66
|
type ConsensusResult,
|
|
41
67
|
type ConsensusIteration,
|
|
42
68
|
type ConsensusConfig,
|
|
43
69
|
type PlanDocument,
|
|
44
70
|
type ConsensusRequest,
|
|
45
71
|
type EscalationDetails,
|
|
72
|
+
type ArbitrationResult,
|
|
73
|
+
type ReviewAppTarget,
|
|
74
|
+
type TaggedItem,
|
|
75
|
+
type AppConsensusScores,
|
|
76
|
+
type AppFeedback,
|
|
77
|
+
type FullstackConsensusResult,
|
|
78
|
+
type FullstackConsensusIteration,
|
|
79
|
+
type CorrectionRecord,
|
|
80
|
+
type ConsensusTrackingRecord,
|
|
46
81
|
} from './consensus.js';
|
|
47
82
|
|
|
48
83
|
// CLI types
|
package/src/types/project.ts
CHANGED
|
@@ -8,9 +8,66 @@ import { z } from 'zod';
|
|
|
8
8
|
/**
|
|
9
9
|
* Supported output languages for generated projects
|
|
10
10
|
*/
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Supported output languages for generated projects
|
|
13
|
+
* - python: Backend only (FastAPI)
|
|
14
|
+
* - typescript: Frontend only (React/Vite)
|
|
15
|
+
* - fullstack: FE + BE
|
|
16
|
+
* - website: Website only (Next.js SSG/SSR)
|
|
17
|
+
* - all: FE + BE + Website (everything)
|
|
18
|
+
*/
|
|
19
|
+
export const OutputLanguageSchema = z.enum([
|
|
20
|
+
'python',
|
|
21
|
+
'typescript',
|
|
22
|
+
'fullstack',
|
|
23
|
+
'website',
|
|
24
|
+
'all',
|
|
25
|
+
]);
|
|
12
26
|
export type OutputLanguage = z.infer<typeof OutputLanguageSchema>;
|
|
13
27
|
|
|
28
|
+
/**
|
|
29
|
+
* App types that can be generated
|
|
30
|
+
*/
|
|
31
|
+
export type AppType = 'frontend' | 'backend' | 'website';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Maps a language to the apps it will generate
|
|
35
|
+
*
|
|
36
|
+
* @param language - The output language
|
|
37
|
+
* @returns Array of app types to generate
|
|
38
|
+
*/
|
|
39
|
+
export function languageToApps(language: OutputLanguage): AppType[] {
|
|
40
|
+
const mapping: Record<OutputLanguage, AppType[]> = {
|
|
41
|
+
python: ['backend'],
|
|
42
|
+
typescript: ['frontend'],
|
|
43
|
+
fullstack: ['frontend', 'backend'],
|
|
44
|
+
website: ['website'],
|
|
45
|
+
all: ['frontend', 'backend', 'website'],
|
|
46
|
+
};
|
|
47
|
+
return mapping[language];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Checks if a language generates a specific app type
|
|
52
|
+
*
|
|
53
|
+
* @param language - The output language
|
|
54
|
+
* @param app - The app type to check
|
|
55
|
+
* @returns True if the language generates the app
|
|
56
|
+
*/
|
|
57
|
+
export function hasApp(language: OutputLanguage, app: AppType): boolean {
|
|
58
|
+
return languageToApps(language).includes(app);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Checks if a language is a workspace type (multi-app monorepo)
|
|
63
|
+
*
|
|
64
|
+
* @param language - The output language
|
|
65
|
+
* @returns True if the language uses workspace/monorepo structure
|
|
66
|
+
*/
|
|
67
|
+
export function isWorkspace(language: OutputLanguage): boolean {
|
|
68
|
+
return language === 'fullstack' || language === 'all';
|
|
69
|
+
}
|
|
70
|
+
|
|
14
71
|
/**
|
|
15
72
|
* Commands configuration for a workspace app
|
|
16
73
|
*/
|
|
@@ -56,6 +113,10 @@ export interface WorkspaceShared {
|
|
|
56
113
|
contracts?: string;
|
|
57
114
|
/** Generator command for FE client from OpenAPI */
|
|
58
115
|
contractsGenerator?: string;
|
|
116
|
+
/** Shared UI components package path */
|
|
117
|
+
ui?: string;
|
|
118
|
+
/** Design tokens package path */
|
|
119
|
+
designTokens?: string;
|
|
59
120
|
}
|
|
60
121
|
|
|
61
122
|
/**
|
|
@@ -78,13 +139,14 @@ export interface WorkspaceDocker {
|
|
|
78
139
|
}
|
|
79
140
|
|
|
80
141
|
/**
|
|
81
|
-
* Workspace configuration for fullstack projects
|
|
142
|
+
* Workspace configuration for fullstack/website/all projects
|
|
82
143
|
*/
|
|
83
144
|
export interface WorkspaceConfig {
|
|
84
145
|
version: '1.0';
|
|
85
146
|
apps: {
|
|
86
147
|
frontend?: WorkspaceApp;
|
|
87
148
|
backend?: WorkspaceApp;
|
|
149
|
+
website?: WorkspaceApp;
|
|
88
150
|
};
|
|
89
151
|
shared?: WorkspaceShared;
|
|
90
152
|
/** Repo-level commands that orchestrate across apps */
|
|
@@ -93,16 +155,100 @@ export interface WorkspaceConfig {
|
|
|
93
155
|
}
|
|
94
156
|
|
|
95
157
|
/**
|
|
96
|
-
*
|
|
158
|
+
* Brand colors for website design
|
|
97
159
|
*/
|
|
98
|
-
export
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
160
|
+
export interface WebsiteBrandColors {
|
|
161
|
+
primary: string;
|
|
162
|
+
secondary: string;
|
|
163
|
+
accent: string;
|
|
164
|
+
background: string;
|
|
165
|
+
foreground: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Typography configuration for website
|
|
170
|
+
*/
|
|
171
|
+
export interface WebsiteTypography {
|
|
172
|
+
headingFont: string;
|
|
173
|
+
bodyFont: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* SEO configuration for website
|
|
178
|
+
*/
|
|
179
|
+
export interface WebsiteSeo {
|
|
180
|
+
title: string;
|
|
181
|
+
description: string;
|
|
182
|
+
keywords: string[];
|
|
183
|
+
ogImage?: string;
|
|
184
|
+
twitterHandle?: string;
|
|
185
|
+
locale: string;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Page configuration for website
|
|
190
|
+
*/
|
|
191
|
+
export interface WebsitePage {
|
|
192
|
+
name: string;
|
|
193
|
+
path: string;
|
|
194
|
+
type: 'landing' | 'pricing' | 'docs' | 'blog' | 'changelog' | 'legal';
|
|
195
|
+
seo?: {
|
|
196
|
+
title?: string;
|
|
197
|
+
description?: string;
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Call-to-action configuration
|
|
203
|
+
*/
|
|
204
|
+
export interface WebsiteCta {
|
|
205
|
+
primary: {
|
|
206
|
+
text: string;
|
|
207
|
+
href: string;
|
|
208
|
+
};
|
|
209
|
+
secondary?: {
|
|
210
|
+
text: string;
|
|
211
|
+
href: string;
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Feature flags for website
|
|
217
|
+
*/
|
|
218
|
+
export interface WebsiteFeatures {
|
|
219
|
+
analytics?: boolean;
|
|
220
|
+
newsletter?: boolean;
|
|
221
|
+
mdxBlog?: boolean;
|
|
222
|
+
docsSearch?: boolean;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Website specification for Next.js marketing sites
|
|
227
|
+
*/
|
|
228
|
+
export interface WebsiteSpec {
|
|
229
|
+
version: '1.0';
|
|
230
|
+
brand: {
|
|
231
|
+
name: string;
|
|
232
|
+
tagline?: string;
|
|
233
|
+
colors: WebsiteBrandColors;
|
|
234
|
+
typography: WebsiteTypography;
|
|
235
|
+
};
|
|
236
|
+
seo: WebsiteSeo;
|
|
237
|
+
pages: WebsitePage[];
|
|
238
|
+
cta: WebsiteCta;
|
|
239
|
+
features?: WebsiteFeatures;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Known OpenAI models (used for suggestions and display, not strict validation)
|
|
244
|
+
*/
|
|
245
|
+
export const KNOWN_OPENAI_MODELS = ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1-preview', 'o1-mini'] as const;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* OpenAI model schema - accepts any non-empty string to support new models
|
|
249
|
+
*/
|
|
250
|
+
export const OpenAIModelSchema = z.string().min(1, 'Model name must not be empty');
|
|
251
|
+
export type OpenAIModel = string;
|
|
106
252
|
|
|
107
253
|
/**
|
|
108
254
|
* Project specification provided by user
|