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
package/src/adapters/openai.ts
CHANGED
|
@@ -43,7 +43,7 @@ export async function requestConsensus(
|
|
|
43
43
|
const client = await createClient();
|
|
44
44
|
|
|
45
45
|
// Build the consensus review prompt (matches spec section 11.1)
|
|
46
|
-
const prompt = buildConsensusPrompt(plan, context);
|
|
46
|
+
const prompt = buildConsensusPrompt(plan, context, config.reviewerPersona);
|
|
47
47
|
|
|
48
48
|
try {
|
|
49
49
|
const completion = await client.chat.completions.create({
|
|
@@ -68,9 +68,14 @@ export async function requestConsensus(
|
|
|
68
68
|
/**
|
|
69
69
|
* Build the consensus review prompt
|
|
70
70
|
* Follows spec section 11.1 format
|
|
71
|
+
*
|
|
72
|
+
* @param plan - The plan to review
|
|
73
|
+
* @param context - Project context
|
|
74
|
+
* @param persona - Optional custom reviewer persona (defaults to senior software architect)
|
|
71
75
|
*/
|
|
72
|
-
function buildConsensusPrompt(plan: string, context: string): string {
|
|
73
|
-
|
|
76
|
+
function buildConsensusPrompt(plan: string, context: string, persona?: string): string {
|
|
77
|
+
const reviewerRole = persona || 'a senior software architect';
|
|
78
|
+
return `You are ${reviewerRole} reviewing a development plan.
|
|
74
79
|
Analyze the following plan for completeness, correctness, and feasibility.
|
|
75
80
|
|
|
76
81
|
PROJECT CONTEXT:
|
|
@@ -256,17 +261,28 @@ export async function listAvailableModels(): Promise<string[]> {
|
|
|
256
261
|
*/
|
|
257
262
|
export async function expandIdea(
|
|
258
263
|
idea: string,
|
|
259
|
-
language: OutputLanguage
|
|
264
|
+
language: OutputLanguage,
|
|
265
|
+
userDocs?: string
|
|
260
266
|
): Promise<string> {
|
|
261
267
|
const client = await createClient();
|
|
262
268
|
|
|
269
|
+
const isWebsiteProject = language === 'website' || language === 'all';
|
|
270
|
+
|
|
263
271
|
const languageDesc = language === 'fullstack'
|
|
264
272
|
? 'React (TypeScript) frontend with FastAPI (Python) backend'
|
|
273
|
+
: language === 'website'
|
|
274
|
+
? 'Next.js (TypeScript) marketing website'
|
|
275
|
+
: language === 'all'
|
|
276
|
+
? 'React frontend + FastAPI backend + Next.js marketing website'
|
|
265
277
|
: language === 'python'
|
|
266
278
|
? 'Python'
|
|
267
279
|
: 'TypeScript';
|
|
268
280
|
|
|
269
|
-
const
|
|
281
|
+
const personaRole = isWebsiteProject
|
|
282
|
+
? 'a Senior Product Marketing Strategist and Fullstack Web Architect'
|
|
283
|
+
: 'a senior software architect';
|
|
284
|
+
|
|
285
|
+
let prompt = `You are ${personaRole}. A user wants to build a project with the following idea:
|
|
270
286
|
|
|
271
287
|
"${idea}"
|
|
272
288
|
|
|
@@ -285,10 +301,26 @@ Expand this into a complete software specification including:
|
|
|
285
301
|
5. **API Specification** (if applicable): Key endpoints and their purposes
|
|
286
302
|
6. **Data Models**: Key entities and their relationships
|
|
287
303
|
7. **Non-Functional Requirements**: Performance, security, scalability considerations
|
|
288
|
-
8. **Deployment**: Docker configuration and deployment approach
|
|
304
|
+
8. **Deployment**: Docker configuration and deployment approach`;
|
|
305
|
+
|
|
306
|
+
// Add website-specific sections for website projects
|
|
307
|
+
if (isWebsiteProject) {
|
|
308
|
+
prompt += `
|
|
309
|
+
9. **Ideal Customer Profile (ICP)**: Primary persona, pain points, goals, objections
|
|
310
|
+
10. **SEO Strategy**: Primary keywords, long-tail keywords, content themes
|
|
311
|
+
11. **Conversion Funnel**: Primary CTA, secondary CTA, trust signals, lead capture mechanism
|
|
312
|
+
12. **Site Map**: All pages with their purpose and conversion goal
|
|
313
|
+
13. **Competitive Positioning**: Category, key differentiators vs alternatives`;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
prompt += `
|
|
289
317
|
|
|
290
318
|
Be specific and actionable. The specification should be detailed enough that a developer could implement it without further clarification.`;
|
|
291
319
|
|
|
320
|
+
if (userDocs) {
|
|
321
|
+
prompt += `\n\nThe user has provided the following project documentation that describes their product:\n\n${userDocs.slice(0, 4000)}\n\nUse this documentation to create an accurate, detailed specification that reflects the actual product described. Do not invent features or pricing not mentioned in the docs.`;
|
|
322
|
+
}
|
|
323
|
+
|
|
292
324
|
const completion = await client.chat.completions.create({
|
|
293
325
|
model: 'gpt-4o',
|
|
294
326
|
messages: [{ role: 'user', content: prompt }],
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { Command } from 'commander';
|
|
7
7
|
import path from 'node:path';
|
|
8
|
+
import { promises as fs } from 'node:fs';
|
|
8
9
|
import { ProjectSpecSchema, type OutputLanguage, type OpenAIModel } from '../../types/project.js';
|
|
9
10
|
import { requireAuth } from '../../auth/index.js';
|
|
10
11
|
import { runWorkflow } from '../../workflow/index.js';
|
|
@@ -99,8 +100,8 @@ export function createCreateCommand(): Command {
|
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
// Generate project name from idea if not provided
|
|
102
|
-
const projectName = options.name || generateProjectName(idea);
|
|
103
103
|
const outputDir = path.resolve(options.output);
|
|
104
|
+
const projectName = options.name || await generateProjectName(idea, outputDir);
|
|
104
105
|
const projectDir = path.join(outputDir, projectName);
|
|
105
106
|
const threshold = parseInt(options.threshold, 10);
|
|
106
107
|
const maxIterations = parseInt(options.maxIterations, 10);
|
|
@@ -255,10 +256,63 @@ export function createCreateCommand(): Command {
|
|
|
255
256
|
}
|
|
256
257
|
|
|
257
258
|
/**
|
|
258
|
-
*
|
|
259
|
+
* Directories that are too generic to use as project names
|
|
259
260
|
*/
|
|
260
|
-
|
|
261
|
-
|
|
261
|
+
const GENERIC_DIR_NAMES = new Set([
|
|
262
|
+
'home', 'desktop', 'documents', 'downloads', 'projects', 'project',
|
|
263
|
+
'repos', 'code', 'dev', 'workspace', 'workspaces', 'src', 'tmp',
|
|
264
|
+
'temp', 'users', 'user', 'root', 'var', 'opt',
|
|
265
|
+
]);
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Generate a project name, preferring CWD-derived names over prompt text.
|
|
269
|
+
*
|
|
270
|
+
* Priority: docs in CWD -> CWD basename -> idea text extraction
|
|
271
|
+
*
|
|
272
|
+
* @param idea - The user's project idea text
|
|
273
|
+
* @param cwd - Optional directory for context-aware naming
|
|
274
|
+
* @returns A kebab-case project name
|
|
275
|
+
*/
|
|
276
|
+
async function generateProjectName(idea: string, cwd?: string): Promise<string> {
|
|
277
|
+
const toKebab = (name: string): string =>
|
|
278
|
+
name
|
|
279
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
280
|
+
.toLowerCase()
|
|
281
|
+
.replace(/[^a-z0-9-]/g, '-')
|
|
282
|
+
.replace(/-+/g, '-')
|
|
283
|
+
.replace(/^-|-$/g, '');
|
|
284
|
+
|
|
285
|
+
if (cwd) {
|
|
286
|
+
// Check for doc-derived name
|
|
287
|
+
try {
|
|
288
|
+
const entries = await fs.readdir(cwd, { withFileTypes: true });
|
|
289
|
+
const mdFiles = entries
|
|
290
|
+
.filter(e => e.isFile() && e.name.endsWith('.md') && !e.name.toLowerCase().startsWith('readme'))
|
|
291
|
+
.map(e => path.join(cwd, e.name));
|
|
292
|
+
|
|
293
|
+
for (const mdFile of mdFiles) {
|
|
294
|
+
try {
|
|
295
|
+
const content = await fs.readFile(mdFile, 'utf-8');
|
|
296
|
+
const headingMatch = content.match(/^#\s+([A-Z][a-zA-Z0-9]+)/m);
|
|
297
|
+
if (headingMatch && headingMatch[1] && headingMatch[1].length >= 3 && headingMatch[1].length <= 30) {
|
|
298
|
+
return toKebab(headingMatch[1]);
|
|
299
|
+
}
|
|
300
|
+
} catch {
|
|
301
|
+
// Skip unreadable files
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
} catch {
|
|
305
|
+
// Directory not readable
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Check CWD basename
|
|
309
|
+
const dirName = path.basename(cwd);
|
|
310
|
+
if (dirName.length >= 3 && !GENERIC_DIR_NAMES.has(dirName.toLowerCase())) {
|
|
311
|
+
return toKebab(dirName);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Fallback: extract from idea text
|
|
262
316
|
return idea
|
|
263
317
|
.toLowerCase()
|
|
264
318
|
.replace(/[^a-z0-9\s]/g, '')
|
package/src/cli/interactive.ts
CHANGED
|
@@ -843,6 +843,7 @@ function showHelp(): void {
|
|
|
843
843
|
['/upgrade [target]', 'Upgrade project type (e.g., fullstack -> all)'],
|
|
844
844
|
['/new <idea>', 'Force start a new project (skips existing check)'],
|
|
845
845
|
['/resume', 'Resume interrupted project'],
|
|
846
|
+
['/overview [fix]', 'Project review with analysis; fix to auto-discover docs'],
|
|
846
847
|
['/clear', 'Clear screen'],
|
|
847
848
|
['/exit', 'Exit Popeye'],
|
|
848
849
|
];
|
|
@@ -997,6 +998,10 @@ async function handleInput(input: string, state: SessionState): Promise<boolean>
|
|
|
997
998
|
await handleUpgrade(state, args);
|
|
998
999
|
break;
|
|
999
1000
|
|
|
1001
|
+
case '/overview':
|
|
1002
|
+
await handleOverview(state, args);
|
|
1003
|
+
break;
|
|
1004
|
+
|
|
1000
1005
|
case '/new':
|
|
1001
1006
|
// Force start a new project even if existing projects found
|
|
1002
1007
|
if (args.length === 0) {
|
|
@@ -1050,6 +1055,48 @@ async function handleStatus(state: SessionState): Promise<void> {
|
|
|
1050
1055
|
console.log(summary);
|
|
1051
1056
|
}
|
|
1052
1057
|
|
|
1058
|
+
/**
|
|
1059
|
+
* Handle /overview command - full project plan and milestone review
|
|
1060
|
+
*/
|
|
1061
|
+
async function handleOverview(state: SessionState, args: string[] = []): Promise<void> {
|
|
1062
|
+
if (!state.projectDir) {
|
|
1063
|
+
printInfo('No active project. Start or resume a project first.');
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
const subcommand = args[0]?.toLowerCase();
|
|
1068
|
+
|
|
1069
|
+
try {
|
|
1070
|
+
if (subcommand === 'fix') {
|
|
1071
|
+
// Run fix mode: re-discover docs, find brand assets, update website content
|
|
1072
|
+
const { fixOverviewIssues, generateOverview, formatOverview } = await import('../workflow/overview.js');
|
|
1073
|
+
|
|
1074
|
+
printInfo('Running overview fix...');
|
|
1075
|
+
const fixResult = await fixOverviewIssues(state.projectDir, (msg) => {
|
|
1076
|
+
printInfo(msg);
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
// Show fix results
|
|
1080
|
+
console.log('');
|
|
1081
|
+
for (const msg of fixResult.messages) {
|
|
1082
|
+
printInfo(msg);
|
|
1083
|
+
}
|
|
1084
|
+
console.log('');
|
|
1085
|
+
|
|
1086
|
+
// Show updated overview after fix
|
|
1087
|
+
const overview = await generateOverview(state.projectDir);
|
|
1088
|
+
console.log(formatOverview(overview));
|
|
1089
|
+
} else {
|
|
1090
|
+
// Display-only mode: show overview with analysis
|
|
1091
|
+
const { generateOverview, formatOverview } = await import('../workflow/overview.js');
|
|
1092
|
+
const overview = await generateOverview(state.projectDir);
|
|
1093
|
+
console.log(formatOverview(overview));
|
|
1094
|
+
}
|
|
1095
|
+
} catch (error) {
|
|
1096
|
+
printInfo(`Could not generate overview: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1053
1100
|
/**
|
|
1054
1101
|
* Handle /config command
|
|
1055
1102
|
*/
|
|
@@ -2215,10 +2262,99 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
|
|
|
2215
2262
|
}
|
|
2216
2263
|
|
|
2217
2264
|
/**
|
|
2218
|
-
*
|
|
2219
|
-
*
|
|
2265
|
+
* Directories that are too generic to use as project names.
|
|
2266
|
+
* If the CWD basename matches one of these, we skip CWD-based naming.
|
|
2267
|
+
*/
|
|
2268
|
+
const GENERIC_DIR_NAMES = new Set([
|
|
2269
|
+
'home', 'desktop', 'documents', 'downloads', 'projects', 'project',
|
|
2270
|
+
'repos', 'code', 'dev', 'workspace', 'workspaces', 'src', 'tmp',
|
|
2271
|
+
'temp', 'users', 'user', 'root', 'var', 'opt',
|
|
2272
|
+
]);
|
|
2273
|
+
|
|
2274
|
+
/**
|
|
2275
|
+
* Try to extract a product name from .md files in a directory.
|
|
2276
|
+
* Looks for top-level headings (# ProductName) in markdown files.
|
|
2277
|
+
*
|
|
2278
|
+
* @param dir - Directory to scan for .md files
|
|
2279
|
+
* @returns Product name if found, null otherwise
|
|
2280
|
+
*/
|
|
2281
|
+
export async function extractNameFromDocs(dir: string): Promise<string | null> {
|
|
2282
|
+
try {
|
|
2283
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
2284
|
+
const mdFiles = entries
|
|
2285
|
+
.filter(e => e.isFile() && e.name.endsWith('.md') && !e.name.toLowerCase().startsWith('readme'))
|
|
2286
|
+
.map(e => path.join(dir, e.name));
|
|
2287
|
+
|
|
2288
|
+
for (const mdFile of mdFiles) {
|
|
2289
|
+
try {
|
|
2290
|
+
const content = await fs.readFile(mdFile, 'utf-8');
|
|
2291
|
+
// Look for a top-level heading like "# Gateco" or "# Gateco - Subtitle"
|
|
2292
|
+
const headingMatch = content.match(/^#\s+([A-Z][a-zA-Z0-9]+)/m);
|
|
2293
|
+
if (headingMatch && headingMatch[1]) {
|
|
2294
|
+
const name = headingMatch[1];
|
|
2295
|
+
// Validate: must be a reasonable product name (3-30 chars, not a generic word)
|
|
2296
|
+
if (name.length >= 3 && name.length <= 30 && !GENERIC_DIR_NAMES.has(name.toLowerCase())) {
|
|
2297
|
+
return name;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
} catch {
|
|
2301
|
+
// Skip unreadable files
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
} catch {
|
|
2305
|
+
// Directory not readable
|
|
2306
|
+
}
|
|
2307
|
+
return null;
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
/**
|
|
2311
|
+
* Generate a meaningful project name from an idea, with CWD-aware logic.
|
|
2312
|
+
*
|
|
2313
|
+
* Priority chain:
|
|
2314
|
+
* 1. If CWD contains .md docs with a "# ProductName" heading, use that
|
|
2315
|
+
* 2. If CWD basename is a meaningful name (not generic), use it
|
|
2316
|
+
* 3. Fall back to extracting a name from the idea text
|
|
2317
|
+
*
|
|
2318
|
+
* @param idea - The user's project idea text
|
|
2319
|
+
* @param cwd - Optional current working directory for context-aware naming
|
|
2320
|
+
* @returns A kebab-case project name
|
|
2321
|
+
*/
|
|
2322
|
+
export async function generateProjectName(idea: string, cwd?: string): Promise<string> {
|
|
2323
|
+
// Normalize to kebab-case helper
|
|
2324
|
+
const toKebab = (name: string): string =>
|
|
2325
|
+
name
|
|
2326
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
2327
|
+
.toLowerCase()
|
|
2328
|
+
.replace(/[^a-z0-9-]/g, '-')
|
|
2329
|
+
.replace(/-+/g, '-')
|
|
2330
|
+
.replace(/^-|-$/g, '');
|
|
2331
|
+
|
|
2332
|
+
if (cwd) {
|
|
2333
|
+
// Priority 1: Check for doc-derived name in CWD
|
|
2334
|
+
const docName = await extractNameFromDocs(cwd);
|
|
2335
|
+
if (docName) {
|
|
2336
|
+
return toKebab(docName);
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
// Priority 2: Use CWD basename if it's meaningful
|
|
2340
|
+
const dirName = path.basename(cwd);
|
|
2341
|
+
if (dirName.length >= 3 && !GENERIC_DIR_NAMES.has(dirName.toLowerCase())) {
|
|
2342
|
+
return toKebab(dirName);
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
// Priority 3: Extract from idea text (original logic)
|
|
2347
|
+
return generateProjectNameFromIdea(idea);
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
/**
|
|
2351
|
+
* Extract a project name from the idea text alone.
|
|
2352
|
+
* This is the original generateProjectName logic, used as a fallback.
|
|
2353
|
+
*
|
|
2354
|
+
* @param idea - The user's project idea text
|
|
2355
|
+
* @returns A kebab-case project name
|
|
2220
2356
|
*/
|
|
2221
|
-
function
|
|
2357
|
+
export function generateProjectNameFromIdea(idea: string): string {
|
|
2222
2358
|
// 1. First, try to find explicit project name patterns
|
|
2223
2359
|
const explicitPatterns = [
|
|
2224
2360
|
/(?:called|named|for|planning|project)\s+["']?([A-Z][a-zA-Z0-9]+)["']?/i,
|
|
@@ -2418,8 +2554,8 @@ async function handleIdea(idea: string, state: SessionState): Promise<void> {
|
|
|
2418
2554
|
}
|
|
2419
2555
|
}
|
|
2420
2556
|
|
|
2421
|
-
// Generate a meaningful project name
|
|
2422
|
-
const projectName = generateProjectName(idea);
|
|
2557
|
+
// Generate a meaningful project name (CWD-aware: checks docs and dir name first)
|
|
2558
|
+
const projectName = await generateProjectName(idea, cwd);
|
|
2423
2559
|
const projectDir = path.join(cwd, projectName);
|
|
2424
2560
|
|
|
2425
2561
|
console.log();
|
|
@@ -2534,8 +2670,8 @@ async function handleNewProject(idea: string, state: SessionState): Promise<void
|
|
|
2534
2670
|
|
|
2535
2671
|
const cwd = state.projectDir || process.cwd();
|
|
2536
2672
|
|
|
2537
|
-
// Generate a meaningful project name
|
|
2538
|
-
const projectName = generateProjectName(idea);
|
|
2673
|
+
// Generate a meaningful project name (CWD-aware: checks docs and dir name first)
|
|
2674
|
+
const projectName = await generateProjectName(idea, cwd);
|
|
2539
2675
|
const projectDir = path.join(cwd, projectName);
|
|
2540
2676
|
|
|
2541
2677
|
console.log();
|