popeye-cli 1.4.1 → 1.4.2
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/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +6 -0
- package/dist/adapters/claude.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +82 -5
- package/dist/cli/interactive.js.map +1 -1
- package/dist/types/workflow.d.ts +12 -0
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +4 -0
- package/dist/types/workflow.js.map +1 -1
- package/dist/workflow/auto-fix.d.ts +17 -1
- package/dist/workflow/auto-fix.d.ts.map +1 -1
- package/dist/workflow/auto-fix.js +97 -16
- package/dist/workflow/auto-fix.js.map +1 -1
- package/dist/workflow/execution-mode.d.ts +24 -0
- package/dist/workflow/execution-mode.d.ts.map +1 -1
- package/dist/workflow/execution-mode.js +292 -19
- package/dist/workflow/execution-mode.js.map +1 -1
- package/dist/workflow/index.d.ts +1 -0
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +1 -0
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/milestone-workflow.d.ts.map +1 -1
- package/dist/workflow/milestone-workflow.js +63 -3
- package/dist/workflow/milestone-workflow.js.map +1 -1
- package/dist/workflow/project-structure.d.ts +29 -0
- package/dist/workflow/project-structure.d.ts.map +1 -0
- package/dist/workflow/project-structure.js +193 -0
- package/dist/workflow/project-structure.js.map +1 -0
- package/dist/workflow/project-verification.d.ts +24 -1
- package/dist/workflow/project-verification.d.ts.map +1 -1
- package/dist/workflow/project-verification.js +105 -22
- package/dist/workflow/project-verification.js.map +1 -1
- package/dist/workflow/remediation.d.ts +124 -0
- package/dist/workflow/remediation.d.ts.map +1 -0
- package/dist/workflow/remediation.js +510 -0
- package/dist/workflow/remediation.js.map +1 -0
- package/dist/workflow/task-workflow.d.ts.map +1 -1
- package/dist/workflow/task-workflow.js +202 -4
- package/dist/workflow/task-workflow.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/claude.ts +6 -0
- package/src/cli/interactive.ts +85 -5
- package/src/types/workflow.ts +9 -0
- package/src/workflow/auto-fix.ts +123 -18
- package/src/workflow/execution-mode.ts +364 -20
- package/src/workflow/index.ts +1 -0
- package/src/workflow/milestone-workflow.ts +80 -3
- package/src/workflow/project-structure.ts +233 -0
- package/src/workflow/project-verification.ts +128 -21
- package/src/workflow/remediation.ts +711 -0
- package/src/workflow/task-workflow.ts +237 -4
- package/tests/workflow/auto-fix-enhanced.test.ts +351 -0
- package/tests/workflow/project-structure.test.ts +131 -0
- package/tests/workflow/project-verification.test.ts +130 -0
- package/tests/workflow/remediation.test.ts +238 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project structure scanner for build verification context enrichment
|
|
3
|
+
* Scans project directory and returns a concise summary for embedding in AI prompts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { promises as fs } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { isWorkspace, languageToApps, type OutputLanguage } from '../types/project.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Summary of a project's directory structure
|
|
12
|
+
*/
|
|
13
|
+
export interface ProjectStructureSummary {
|
|
14
|
+
tree: string;
|
|
15
|
+
fileCounts: Record<string, number>;
|
|
16
|
+
appFileCounts?: Record<string, Record<string, number>>;
|
|
17
|
+
workspaceApps: Array<{ name: string; path: string; exists: boolean }>;
|
|
18
|
+
totalSourceFiles: number;
|
|
19
|
+
tsconfigInfo?: string;
|
|
20
|
+
formatted: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Directories to skip during scanning */
|
|
24
|
+
const SKIP_DIRS = new Set([
|
|
25
|
+
'node_modules', 'dist', 'build', '.git', '__pycache__', '.venv', 'venv',
|
|
26
|
+
'.next', '.turbo', '.cache', 'coverage', 'out', '.vercel',
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
/** Source file extensions to count */
|
|
30
|
+
const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.py']);
|
|
31
|
+
|
|
32
|
+
/** Maximum tree entries before truncation */
|
|
33
|
+
const MAX_TREE_ENTRIES = 30;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Recursively scan a directory up to a given depth, building a tree and counting files
|
|
37
|
+
*
|
|
38
|
+
* @param dir - Directory to scan
|
|
39
|
+
* @param depth - Current recursion depth
|
|
40
|
+
* @param maxDepth - Maximum depth to recurse
|
|
41
|
+
* @returns Tree lines and file counts by extension
|
|
42
|
+
*/
|
|
43
|
+
async function scanDirectory(
|
|
44
|
+
dir: string,
|
|
45
|
+
depth: number,
|
|
46
|
+
maxDepth: number
|
|
47
|
+
): Promise<{ lines: string[]; counts: Record<string, number> }> {
|
|
48
|
+
const lines: string[] = [];
|
|
49
|
+
const counts: Record<string, number> = {};
|
|
50
|
+
|
|
51
|
+
if (depth > maxDepth) return { lines, counts };
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
55
|
+
const sorted = entries.sort((a, b) => {
|
|
56
|
+
// Directories first, then files
|
|
57
|
+
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
58
|
+
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
59
|
+
return a.name.localeCompare(b.name);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
for (const entry of sorted) {
|
|
63
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
64
|
+
|
|
65
|
+
const indent = ' '.repeat(depth);
|
|
66
|
+
if (entry.isDirectory()) {
|
|
67
|
+
lines.push(`${indent}${entry.name}/`);
|
|
68
|
+
const sub = await scanDirectory(path.join(dir, entry.name), depth + 1, maxDepth);
|
|
69
|
+
lines.push(...sub.lines);
|
|
70
|
+
for (const [ext, count] of Object.entries(sub.counts)) {
|
|
71
|
+
counts[ext] = (counts[ext] || 0) + count;
|
|
72
|
+
}
|
|
73
|
+
} else if (entry.isFile()) {
|
|
74
|
+
const ext = path.extname(entry.name);
|
|
75
|
+
if (SOURCE_EXTENSIONS.has(ext)) {
|
|
76
|
+
counts[ext] = (counts[ext] || 0) + 1;
|
|
77
|
+
}
|
|
78
|
+
lines.push(`${indent}${entry.name}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
// Directory access error - ignore
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { lines, counts };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Count source files within a specific directory (shallow recursion, max 5 levels)
|
|
90
|
+
*
|
|
91
|
+
* @param dir - Directory to count files in
|
|
92
|
+
* @returns File counts by extension
|
|
93
|
+
*/
|
|
94
|
+
async function countSourceFiles(dir: string): Promise<Record<string, number>> {
|
|
95
|
+
const result = await scanDirectory(dir, 0, 5);
|
|
96
|
+
return result.counts;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Read and summarize tsconfig.json if present
|
|
101
|
+
*
|
|
102
|
+
* @param projectDir - Project root directory
|
|
103
|
+
* @returns Brief tsconfig summary string or undefined
|
|
104
|
+
*/
|
|
105
|
+
async function summarizeTsconfig(projectDir: string): Promise<string | undefined> {
|
|
106
|
+
try {
|
|
107
|
+
const tsconfigPath = path.join(projectDir, 'tsconfig.json');
|
|
108
|
+
const content = await fs.readFile(tsconfigPath, 'utf-8');
|
|
109
|
+
const tsconfig = JSON.parse(content);
|
|
110
|
+
|
|
111
|
+
const parts: string[] = ['tsconfig: exists'];
|
|
112
|
+
|
|
113
|
+
if (tsconfig.references) {
|
|
114
|
+
parts.push(`references=${tsconfig.references.length}`);
|
|
115
|
+
}
|
|
116
|
+
if (tsconfig.include) {
|
|
117
|
+
const includeStr = JSON.stringify(tsconfig.include);
|
|
118
|
+
parts.push(`include=${includeStr.length > 60 ? includeStr.slice(0, 57) + '...' : includeStr}`);
|
|
119
|
+
}
|
|
120
|
+
if (tsconfig.exclude) {
|
|
121
|
+
parts.push(`exclude=${tsconfig.exclude.length} entries`);
|
|
122
|
+
}
|
|
123
|
+
if (tsconfig.compilerOptions?.baseUrl) {
|
|
124
|
+
parts.push(`baseUrl="${tsconfig.compilerOptions.baseUrl}"`);
|
|
125
|
+
}
|
|
126
|
+
if (tsconfig.compilerOptions?.paths) {
|
|
127
|
+
parts.push('paths=true');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return parts.join(', ');
|
|
131
|
+
} catch {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Scan a project directory and return a concise summary for embedding in AI prompts
|
|
138
|
+
*
|
|
139
|
+
* @param projectDir - Root directory of the project
|
|
140
|
+
* @param language - Project language type
|
|
141
|
+
* @returns Project structure summary with tree, counts, and formatted string
|
|
142
|
+
*/
|
|
143
|
+
export async function getProjectStructureSummary(
|
|
144
|
+
projectDir: string,
|
|
145
|
+
language: string
|
|
146
|
+
): Promise<ProjectStructureSummary> {
|
|
147
|
+
// Scan directory tree (max 3 levels deep)
|
|
148
|
+
const { lines: treeLines, counts: fileCounts } = await scanDirectory(projectDir, 0, 3);
|
|
149
|
+
|
|
150
|
+
// Truncate tree if too large
|
|
151
|
+
let tree: string;
|
|
152
|
+
if (treeLines.length > MAX_TREE_ENTRIES) {
|
|
153
|
+
const extra = treeLines.length - MAX_TREE_ENTRIES;
|
|
154
|
+
tree = treeLines.slice(0, MAX_TREE_ENTRIES).join('\n') + `\n... (+${extra} more)`;
|
|
155
|
+
} else {
|
|
156
|
+
tree = treeLines.join('\n');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const totalSourceFiles = Object.values(fileCounts).reduce((sum, c) => sum + c, 0);
|
|
160
|
+
|
|
161
|
+
// Workspace app detection
|
|
162
|
+
const workspaceApps: ProjectStructureSummary['workspaceApps'] = [];
|
|
163
|
+
let appFileCounts: Record<string, Record<string, number>> | undefined;
|
|
164
|
+
|
|
165
|
+
if (isWorkspace(language as OutputLanguage)) {
|
|
166
|
+
const apps = languageToApps(language as OutputLanguage);
|
|
167
|
+
appFileCounts = {};
|
|
168
|
+
|
|
169
|
+
for (const appType of apps) {
|
|
170
|
+
const appPath = path.join('apps', appType);
|
|
171
|
+
const absolutePath = path.join(projectDir, appPath);
|
|
172
|
+
let exists = false;
|
|
173
|
+
try {
|
|
174
|
+
await fs.access(absolutePath);
|
|
175
|
+
exists = true;
|
|
176
|
+
} catch {
|
|
177
|
+
// App directory doesn't exist
|
|
178
|
+
}
|
|
179
|
+
workspaceApps.push({ name: appType, path: appPath, exists });
|
|
180
|
+
|
|
181
|
+
if (exists) {
|
|
182
|
+
appFileCounts[appType] = await countSourceFiles(absolutePath);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// tsconfig awareness
|
|
188
|
+
const tsconfigInfo = await summarizeTsconfig(projectDir);
|
|
189
|
+
|
|
190
|
+
// Assemble formatted summary
|
|
191
|
+
const formattedParts: string[] = [];
|
|
192
|
+
formattedParts.push(`Source files: ${totalSourceFiles} total`);
|
|
193
|
+
|
|
194
|
+
const countEntries = Object.entries(fileCounts).filter(([, c]) => c > 0);
|
|
195
|
+
if (countEntries.length > 0) {
|
|
196
|
+
formattedParts.push(`Extensions: ${countEntries.map(([ext, c]) => `${ext}=${c}`).join(', ')}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (workspaceApps.length > 0) {
|
|
200
|
+
const appSummary = workspaceApps
|
|
201
|
+
.map(a => `${a.name}: ${a.exists ? 'exists' : 'MISSING'}`)
|
|
202
|
+
.join(', ');
|
|
203
|
+
formattedParts.push(`Workspace apps: ${appSummary}`);
|
|
204
|
+
|
|
205
|
+
if (appFileCounts) {
|
|
206
|
+
for (const [appName, counts] of Object.entries(appFileCounts)) {
|
|
207
|
+
const appCountStr = Object.entries(counts)
|
|
208
|
+
.filter(([, c]) => c > 0)
|
|
209
|
+
.map(([ext, c]) => `${ext}=${c}`)
|
|
210
|
+
.join(', ');
|
|
211
|
+
if (appCountStr) {
|
|
212
|
+
formattedParts.push(` ${appName}: ${appCountStr}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (tsconfigInfo) {
|
|
219
|
+
formattedParts.push(tsconfigInfo);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const formatted = formattedParts.join('\n');
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
tree,
|
|
226
|
+
fileCounts,
|
|
227
|
+
appFileCounts,
|
|
228
|
+
workspaceApps,
|
|
229
|
+
totalSourceFiles,
|
|
230
|
+
tsconfigInfo,
|
|
231
|
+
formatted,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
@@ -36,6 +36,14 @@ export interface VerificationReport {
|
|
|
36
36
|
criticalIssues: string[];
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Resolved project paths for frontend and backend directories
|
|
41
|
+
*/
|
|
42
|
+
export interface ProjectPaths {
|
|
43
|
+
frontendDir: string | null;
|
|
44
|
+
backendDir: string | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
/**
|
|
40
48
|
* Check if a file exists
|
|
41
49
|
*/
|
|
@@ -125,15 +133,66 @@ function findTodoPlaceholders(content: string): string[] {
|
|
|
125
133
|
return todos;
|
|
126
134
|
}
|
|
127
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Resolve the correct frontend/backend paths based on language and what exists on disk.
|
|
138
|
+
*
|
|
139
|
+
* Workspace projects (fullstack, all) use apps/ or packages/ subdirectories.
|
|
140
|
+
* Single-language projects use the project root as their frontend or backend dir.
|
|
141
|
+
*
|
|
142
|
+
* @param projectDir - The project root directory
|
|
143
|
+
* @param language - The project language/type
|
|
144
|
+
* @returns Resolved frontend and backend directory paths (null if not applicable)
|
|
145
|
+
*/
|
|
146
|
+
export async function resolveProjectPaths(projectDir: string, language: string): Promise<ProjectPaths> {
|
|
147
|
+
// Workspace projects (fullstack, all): check apps/ first, then packages/
|
|
148
|
+
if (language === 'fullstack' || language === 'all') {
|
|
149
|
+
const appsF = path.join(projectDir, 'apps', 'frontend');
|
|
150
|
+
const pkgsF = path.join(projectDir, 'packages', 'frontend');
|
|
151
|
+
const appsB = path.join(projectDir, 'apps', 'backend');
|
|
152
|
+
const pkgsB = path.join(projectDir, 'packages', 'backend');
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
frontendDir: await fileExists(appsF) ? appsF : await fileExists(pkgsF) ? pkgsF : null,
|
|
156
|
+
backendDir: await fileExists(appsB) ? appsB : await fileExists(pkgsB) ? pkgsB : null,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Website: root IS the frontend
|
|
161
|
+
if (language === 'website') {
|
|
162
|
+
return { frontendDir: projectDir, backendDir: null };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// TypeScript/JavaScript: root IS the frontend
|
|
166
|
+
if (language === 'typescript' || language === 'javascript') {
|
|
167
|
+
return { frontendDir: projectDir, backendDir: null };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Python: root IS the backend
|
|
171
|
+
if (language === 'python') {
|
|
172
|
+
return { frontendDir: null, backendDir: projectDir };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return { frontendDir: null, backendDir: null };
|
|
176
|
+
}
|
|
177
|
+
|
|
128
178
|
/**
|
|
129
179
|
* Verify CSS/Styling setup
|
|
130
180
|
*/
|
|
131
|
-
async function verifyStylingSetup(
|
|
181
|
+
async function verifyStylingSetup(paths: ProjectPaths): Promise<VerificationResult[]> {
|
|
132
182
|
const results: VerificationResult[] = [];
|
|
133
|
-
const frontendDir =
|
|
183
|
+
const frontendDir = paths.frontendDir;
|
|
184
|
+
|
|
185
|
+
if (!frontendDir) {
|
|
186
|
+
return results;
|
|
187
|
+
}
|
|
134
188
|
|
|
135
189
|
// Check if frontend uses Tailwind classes
|
|
136
|
-
const
|
|
190
|
+
const srcDir = path.join(frontendDir, 'src');
|
|
191
|
+
if (!await fileExists(srcDir)) {
|
|
192
|
+
return results;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const tsxFiles = await findFiles(srcDir, /\.tsx$/);
|
|
137
196
|
let usesTailwind = false;
|
|
138
197
|
|
|
139
198
|
for (const file of tsxFiles.slice(0, 20)) { // Check first 20 files
|
|
@@ -216,9 +275,13 @@ async function verifyStylingSetup(projectDir: string): Promise<VerificationResul
|
|
|
216
275
|
/**
|
|
217
276
|
* Verify authentication setup
|
|
218
277
|
*/
|
|
219
|
-
async function verifyAuthSetup(
|
|
278
|
+
async function verifyAuthSetup(paths: ProjectPaths): Promise<VerificationResult[]> {
|
|
220
279
|
const results: VerificationResult[] = [];
|
|
221
|
-
const frontendDir =
|
|
280
|
+
const frontendDir = paths.frontendDir;
|
|
281
|
+
|
|
282
|
+
if (!frontendDir) {
|
|
283
|
+
return results;
|
|
284
|
+
}
|
|
222
285
|
|
|
223
286
|
// Check if project uses Auth0
|
|
224
287
|
const pkgJson = await readFile(path.join(frontendDir, 'package.json'));
|
|
@@ -265,9 +328,13 @@ async function verifyAuthSetup(projectDir: string): Promise<VerificationResult[]
|
|
|
265
328
|
/**
|
|
266
329
|
* Verify routes are complete (no TODO placeholders)
|
|
267
330
|
*/
|
|
268
|
-
async function verifyRouteCompleteness(
|
|
331
|
+
async function verifyRouteCompleteness(paths: ProjectPaths): Promise<VerificationResult[]> {
|
|
269
332
|
const results: VerificationResult[] = [];
|
|
270
|
-
const frontendDir =
|
|
333
|
+
const frontendDir = paths.frontendDir;
|
|
334
|
+
|
|
335
|
+
if (!frontendDir) {
|
|
336
|
+
return results;
|
|
337
|
+
}
|
|
271
338
|
|
|
272
339
|
// Check routes file
|
|
273
340
|
const routesFile = await readFile(path.join(frontendDir, 'src', 'routes', 'index.tsx'));
|
|
@@ -288,7 +355,12 @@ async function verifyRouteCompleteness(projectDir: string): Promise<Verification
|
|
|
288
355
|
}
|
|
289
356
|
|
|
290
357
|
// Check all page components
|
|
291
|
-
const
|
|
358
|
+
const pagesDir = path.join(frontendDir, 'src', 'pages');
|
|
359
|
+
if (!await fileExists(pagesDir)) {
|
|
360
|
+
return results;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const pageFiles = await findFiles(pagesDir, /\.tsx$/);
|
|
292
364
|
const incompletePages: string[] = [];
|
|
293
365
|
|
|
294
366
|
for (const file of pageFiles) {
|
|
@@ -325,13 +397,15 @@ async function verifyRouteCompleteness(projectDir: string): Promise<Verification
|
|
|
325
397
|
/**
|
|
326
398
|
* Verify database setup
|
|
327
399
|
*/
|
|
328
|
-
async function verifyDatabaseSetup(projectDir: string): Promise<VerificationResult[]> {
|
|
400
|
+
async function verifyDatabaseSetup(projectDir: string, paths: ProjectPaths): Promise<VerificationResult[]> {
|
|
329
401
|
const results: VerificationResult[] = [];
|
|
330
|
-
const backendDir =
|
|
402
|
+
const backendDir = paths.backendDir;
|
|
331
403
|
|
|
332
|
-
// Check .env.example has database config
|
|
333
|
-
|
|
334
|
-
|
|
404
|
+
// Check .env.example has database config (check root and backend dir)
|
|
405
|
+
let envExample = await readFile(path.join(projectDir, '.env.example'));
|
|
406
|
+
if (!envExample && backendDir) {
|
|
407
|
+
envExample = await readFile(path.join(backendDir, '.env.example'));
|
|
408
|
+
}
|
|
335
409
|
|
|
336
410
|
const hasDbConfig = envExample?.includes('DATABASE_URL') ||
|
|
337
411
|
envExample?.includes('DB_HOST');
|
|
@@ -368,11 +442,35 @@ async function verifyDatabaseSetup(projectDir: string): Promise<VerificationResu
|
|
|
368
442
|
/**
|
|
369
443
|
* Verify the app actually starts
|
|
370
444
|
*/
|
|
371
|
-
async function verifyAppStarts(
|
|
445
|
+
async function verifyAppStarts(paths: ProjectPaths): Promise<VerificationResult[]> {
|
|
372
446
|
const results: VerificationResult[] = [];
|
|
373
|
-
const frontendDir =
|
|
447
|
+
const frontendDir = paths.frontendDir;
|
|
448
|
+
|
|
449
|
+
if (!frontendDir) {
|
|
450
|
+
return results;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Verify directory exists before attempting build
|
|
454
|
+
if (!await fileExists(frontendDir)) {
|
|
455
|
+
return results;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Check package.json exists and has a build script
|
|
459
|
+
const pkgJsonContent = await readFile(path.join(frontendDir, 'package.json'));
|
|
460
|
+
if (!pkgJsonContent) {
|
|
461
|
+
return results;
|
|
462
|
+
}
|
|
374
463
|
|
|
375
|
-
|
|
464
|
+
try {
|
|
465
|
+
const pkgJson = JSON.parse(pkgJsonContent);
|
|
466
|
+
if (!pkgJson.scripts?.build) {
|
|
467
|
+
return results;
|
|
468
|
+
}
|
|
469
|
+
} catch {
|
|
470
|
+
return results;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Try to build frontend
|
|
376
474
|
try {
|
|
377
475
|
await execAsync('npm run build', {
|
|
378
476
|
cwd: frontendDir,
|
|
@@ -404,27 +502,36 @@ async function verifyAppStarts(projectDir: string): Promise<VerificationResult[]
|
|
|
404
502
|
|
|
405
503
|
/**
|
|
406
504
|
* Run comprehensive project verification
|
|
505
|
+
*
|
|
506
|
+
* @param projectDir - The project root directory
|
|
507
|
+
* @param language - The project language/type (e.g. 'fullstack', 'typescript', 'python')
|
|
508
|
+
* @param onProgress - Optional progress callback
|
|
509
|
+
* @returns Verification report
|
|
407
510
|
*/
|
|
408
511
|
export async function runComprehensiveVerification(
|
|
409
512
|
projectDir: string,
|
|
513
|
+
language: string,
|
|
410
514
|
onProgress?: (message: string) => void
|
|
411
515
|
): Promise<VerificationReport> {
|
|
412
516
|
const allResults: VerificationResult[] = [];
|
|
413
517
|
|
|
518
|
+
// Resolve correct paths based on language and disk layout
|
|
519
|
+
const paths = await resolveProjectPaths(projectDir, language);
|
|
520
|
+
|
|
414
521
|
onProgress?.('Checking styling setup...');
|
|
415
|
-
allResults.push(...await verifyStylingSetup(
|
|
522
|
+
allResults.push(...await verifyStylingSetup(paths));
|
|
416
523
|
|
|
417
524
|
onProgress?.('Checking authentication setup...');
|
|
418
|
-
allResults.push(...await verifyAuthSetup(
|
|
525
|
+
allResults.push(...await verifyAuthSetup(paths));
|
|
419
526
|
|
|
420
527
|
onProgress?.('Checking route completeness...');
|
|
421
|
-
allResults.push(...await verifyRouteCompleteness(
|
|
528
|
+
allResults.push(...await verifyRouteCompleteness(paths));
|
|
422
529
|
|
|
423
530
|
onProgress?.('Checking database setup...');
|
|
424
|
-
allResults.push(...await verifyDatabaseSetup(projectDir));
|
|
531
|
+
allResults.push(...await verifyDatabaseSetup(projectDir, paths));
|
|
425
532
|
|
|
426
533
|
onProgress?.('Verifying app builds...');
|
|
427
|
-
allResults.push(...await verifyAppStarts(
|
|
534
|
+
allResults.push(...await verifyAppStarts(paths));
|
|
428
535
|
|
|
429
536
|
// Calculate summary
|
|
430
537
|
const passedChecks = allResults.filter(r => r.passed).length;
|