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.
Files changed (214) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +264 -63
  3. package/dist/adapters/gemini.d.ts +1 -0
  4. package/dist/adapters/gemini.d.ts.map +1 -1
  5. package/dist/adapters/gemini.js +9 -4
  6. package/dist/adapters/gemini.js.map +1 -1
  7. package/dist/adapters/grok.d.ts +1 -0
  8. package/dist/adapters/grok.d.ts.map +1 -1
  9. package/dist/adapters/grok.js +9 -4
  10. package/dist/adapters/grok.js.map +1 -1
  11. package/dist/adapters/openai.d.ts +1 -1
  12. package/dist/adapters/openai.d.ts.map +1 -1
  13. package/dist/adapters/openai.js +35 -9
  14. package/dist/adapters/openai.js.map +1 -1
  15. package/dist/cli/commands/create.d.ts.map +1 -1
  16. package/dist/cli/commands/create.js +54 -4
  17. package/dist/cli/commands/create.js.map +1 -1
  18. package/dist/cli/interactive.d.ts +29 -0
  19. package/dist/cli/interactive.d.ts.map +1 -1
  20. package/dist/cli/interactive.js +132 -7
  21. package/dist/cli/interactive.js.map +1 -1
  22. package/dist/generators/all.d.ts +8 -2
  23. package/dist/generators/all.d.ts.map +1 -1
  24. package/dist/generators/all.js +37 -316
  25. package/dist/generators/all.js.map +1 -1
  26. package/dist/generators/doc-parser.d.ts +64 -0
  27. package/dist/generators/doc-parser.d.ts.map +1 -0
  28. package/dist/generators/doc-parser.js +407 -0
  29. package/dist/generators/doc-parser.js.map +1 -0
  30. package/dist/generators/frontend-design-analyzer.d.ts +30 -0
  31. package/dist/generators/frontend-design-analyzer.d.ts.map +1 -0
  32. package/dist/generators/frontend-design-analyzer.js +208 -0
  33. package/dist/generators/frontend-design-analyzer.js.map +1 -0
  34. package/dist/generators/shared-packages.d.ts +45 -0
  35. package/dist/generators/shared-packages.d.ts.map +1 -0
  36. package/dist/generators/shared-packages.js +456 -0
  37. package/dist/generators/shared-packages.js.map +1 -0
  38. package/dist/generators/templates/index.d.ts +8 -0
  39. package/dist/generators/templates/index.d.ts.map +1 -1
  40. package/dist/generators/templates/index.js +8 -0
  41. package/dist/generators/templates/index.js.map +1 -1
  42. package/dist/generators/templates/website-components.d.ts +33 -0
  43. package/dist/generators/templates/website-components.d.ts.map +1 -0
  44. package/dist/generators/templates/website-components.js +303 -0
  45. package/dist/generators/templates/website-components.js.map +1 -0
  46. package/dist/generators/templates/website-config.d.ts +55 -0
  47. package/dist/generators/templates/website-config.d.ts.map +1 -0
  48. package/dist/generators/templates/website-config.js +425 -0
  49. package/dist/generators/templates/website-config.js.map +1 -0
  50. package/dist/generators/templates/website-conversion.d.ts +27 -0
  51. package/dist/generators/templates/website-conversion.d.ts.map +1 -0
  52. package/dist/generators/templates/website-conversion.js +326 -0
  53. package/dist/generators/templates/website-conversion.js.map +1 -0
  54. package/dist/generators/templates/website-landing.d.ts +24 -0
  55. package/dist/generators/templates/website-landing.d.ts.map +1 -0
  56. package/dist/generators/templates/website-landing.js +276 -0
  57. package/dist/generators/templates/website-landing.js.map +1 -0
  58. package/dist/generators/templates/website-layout.d.ts +42 -0
  59. package/dist/generators/templates/website-layout.d.ts.map +1 -0
  60. package/dist/generators/templates/website-layout.js +408 -0
  61. package/dist/generators/templates/website-layout.js.map +1 -0
  62. package/dist/generators/templates/website-pricing.d.ts +11 -0
  63. package/dist/generators/templates/website-pricing.d.ts.map +1 -0
  64. package/dist/generators/templates/website-pricing.js +313 -0
  65. package/dist/generators/templates/website-pricing.js.map +1 -0
  66. package/dist/generators/templates/website-sections.d.ts +102 -0
  67. package/dist/generators/templates/website-sections.d.ts.map +1 -0
  68. package/dist/generators/templates/website-sections.js +444 -0
  69. package/dist/generators/templates/website-sections.js.map +1 -0
  70. package/dist/generators/templates/website-seo.d.ts +76 -0
  71. package/dist/generators/templates/website-seo.d.ts.map +1 -0
  72. package/dist/generators/templates/website-seo.js +326 -0
  73. package/dist/generators/templates/website-seo.js.map +1 -0
  74. package/dist/generators/templates/website.d.ts +10 -83
  75. package/dist/generators/templates/website.d.ts.map +1 -1
  76. package/dist/generators/templates/website.js +12 -875
  77. package/dist/generators/templates/website.js.map +1 -1
  78. package/dist/generators/website-content-scanner.d.ts +37 -0
  79. package/dist/generators/website-content-scanner.d.ts.map +1 -0
  80. package/dist/generators/website-content-scanner.js +165 -0
  81. package/dist/generators/website-content-scanner.js.map +1 -0
  82. package/dist/generators/website-context.d.ts +119 -0
  83. package/dist/generators/website-context.d.ts.map +1 -0
  84. package/dist/generators/website-context.js +350 -0
  85. package/dist/generators/website-context.js.map +1 -0
  86. package/dist/generators/website-debug.d.ts +68 -0
  87. package/dist/generators/website-debug.d.ts.map +1 -0
  88. package/dist/generators/website-debug.js +93 -0
  89. package/dist/generators/website-debug.js.map +1 -0
  90. package/dist/generators/website.d.ts +5 -0
  91. package/dist/generators/website.d.ts.map +1 -1
  92. package/dist/generators/website.js +136 -11
  93. package/dist/generators/website.js.map +1 -1
  94. package/dist/generators/workspace-root.d.ts +27 -0
  95. package/dist/generators/workspace-root.d.ts.map +1 -0
  96. package/dist/generators/workspace-root.js +100 -0
  97. package/dist/generators/workspace-root.js.map +1 -0
  98. package/dist/state/index.d.ts +35 -0
  99. package/dist/state/index.d.ts.map +1 -1
  100. package/dist/state/index.js +40 -0
  101. package/dist/state/index.js.map +1 -1
  102. package/dist/types/consensus.d.ts +3 -0
  103. package/dist/types/consensus.d.ts.map +1 -1
  104. package/dist/types/consensus.js +1 -0
  105. package/dist/types/consensus.js.map +1 -1
  106. package/dist/types/website-strategy.d.ts +263 -0
  107. package/dist/types/website-strategy.d.ts.map +1 -0
  108. package/dist/types/website-strategy.js +105 -0
  109. package/dist/types/website-strategy.js.map +1 -0
  110. package/dist/types/workflow.d.ts +21 -0
  111. package/dist/types/workflow.d.ts.map +1 -1
  112. package/dist/types/workflow.js +8 -0
  113. package/dist/types/workflow.js.map +1 -1
  114. package/dist/upgrade/handlers.d.ts +15 -0
  115. package/dist/upgrade/handlers.d.ts.map +1 -1
  116. package/dist/upgrade/handlers.js +52 -0
  117. package/dist/upgrade/handlers.js.map +1 -1
  118. package/dist/workflow/auto-fix-bundler.d.ts +37 -0
  119. package/dist/workflow/auto-fix-bundler.d.ts.map +1 -0
  120. package/dist/workflow/auto-fix-bundler.js +320 -0
  121. package/dist/workflow/auto-fix-bundler.js.map +1 -0
  122. package/dist/workflow/auto-fix.d.ts.map +1 -1
  123. package/dist/workflow/auto-fix.js +10 -3
  124. package/dist/workflow/auto-fix.js.map +1 -1
  125. package/dist/workflow/consensus.d.ts.map +1 -1
  126. package/dist/workflow/consensus.js +2 -0
  127. package/dist/workflow/consensus.js.map +1 -1
  128. package/dist/workflow/execution-mode.d.ts.map +1 -1
  129. package/dist/workflow/execution-mode.js +18 -0
  130. package/dist/workflow/execution-mode.js.map +1 -1
  131. package/dist/workflow/index.d.ts +4 -0
  132. package/dist/workflow/index.d.ts.map +1 -1
  133. package/dist/workflow/index.js +37 -0
  134. package/dist/workflow/index.js.map +1 -1
  135. package/dist/workflow/overview.d.ts +89 -0
  136. package/dist/workflow/overview.d.ts.map +1 -0
  137. package/dist/workflow/overview.js +358 -0
  138. package/dist/workflow/overview.js.map +1 -0
  139. package/dist/workflow/plan-mode.d.ts +6 -4
  140. package/dist/workflow/plan-mode.d.ts.map +1 -1
  141. package/dist/workflow/plan-mode.js +148 -6
  142. package/dist/workflow/plan-mode.js.map +1 -1
  143. package/dist/workflow/website-strategy.d.ts +79 -0
  144. package/dist/workflow/website-strategy.d.ts.map +1 -0
  145. package/dist/workflow/website-strategy.js +310 -0
  146. package/dist/workflow/website-strategy.js.map +1 -0
  147. package/dist/workflow/website-updater.d.ts +17 -0
  148. package/dist/workflow/website-updater.d.ts.map +1 -0
  149. package/dist/workflow/website-updater.js +116 -0
  150. package/dist/workflow/website-updater.js.map +1 -0
  151. package/dist/workflow/workflow-logger.d.ts +1 -1
  152. package/dist/workflow/workflow-logger.d.ts.map +1 -1
  153. package/dist/workflow/workflow-logger.js.map +1 -1
  154. package/package.json +1 -1
  155. package/src/adapters/gemini.ts +10 -4
  156. package/src/adapters/grok.ts +10 -4
  157. package/src/adapters/openai.ts +38 -6
  158. package/src/cli/commands/create.ts +58 -4
  159. package/src/cli/interactive.ts +143 -7
  160. package/src/generators/all.ts +49 -332
  161. package/src/generators/doc-parser.ts +449 -0
  162. package/src/generators/frontend-design-analyzer.ts +261 -0
  163. package/src/generators/shared-packages.ts +500 -0
  164. package/src/generators/templates/index.ts +8 -0
  165. package/src/generators/templates/website-components.ts +330 -0
  166. package/src/generators/templates/website-config.ts +444 -0
  167. package/src/generators/templates/website-conversion.ts +341 -0
  168. package/src/generators/templates/website-landing.ts +331 -0
  169. package/src/generators/templates/website-layout.ts +443 -0
  170. package/src/generators/templates/website-pricing.ts +330 -0
  171. package/src/generators/templates/website-sections.ts +541 -0
  172. package/src/generators/templates/website-seo.ts +370 -0
  173. package/src/generators/templates/website.ts +38 -905
  174. package/src/generators/website-content-scanner.ts +208 -0
  175. package/src/generators/website-context.ts +493 -0
  176. package/src/generators/website-debug.ts +130 -0
  177. package/src/generators/website.ts +178 -20
  178. package/src/generators/workspace-root.ts +113 -0
  179. package/src/state/index.ts +56 -0
  180. package/src/types/consensus.ts +3 -0
  181. package/src/types/website-strategy.ts +243 -0
  182. package/src/types/workflow.ts +21 -0
  183. package/src/upgrade/handlers.ts +65 -0
  184. package/src/workflow/auto-fix-bundler.ts +392 -0
  185. package/src/workflow/auto-fix.ts +11 -3
  186. package/src/workflow/consensus.ts +2 -0
  187. package/src/workflow/execution-mode.ts +21 -0
  188. package/src/workflow/index.ts +37 -0
  189. package/src/workflow/overview.ts +475 -0
  190. package/src/workflow/plan-mode.ts +193 -8
  191. package/src/workflow/website-strategy.ts +379 -0
  192. package/src/workflow/website-updater.ts +142 -0
  193. package/src/workflow/workflow-logger.ts +1 -0
  194. package/tests/adapters/persona-switching.test.ts +63 -0
  195. package/tests/cli/project-naming.test.ts +136 -0
  196. package/tests/generators/doc-parser.test.ts +121 -0
  197. package/tests/generators/frontend-design-analyzer.test.ts +90 -0
  198. package/tests/generators/quality-gate.test.ts +183 -0
  199. package/tests/generators/shared-packages.test.ts +83 -0
  200. package/tests/generators/website-components.test.ts +159 -0
  201. package/tests/generators/website-config.test.ts +84 -0
  202. package/tests/generators/website-content-scanner.test.ts +181 -0
  203. package/tests/generators/website-context.test.ts +331 -0
  204. package/tests/generators/website-debug.test.ts +77 -0
  205. package/tests/generators/website-landing.test.ts +188 -0
  206. package/tests/generators/website-pricing.test.ts +98 -0
  207. package/tests/generators/website-sections.test.ts +245 -0
  208. package/tests/generators/website-seo-quality.test.ts +246 -0
  209. package/tests/generators/workspace-root.test.ts +105 -0
  210. package/tests/upgrade/handlers.test.ts +162 -0
  211. package/tests/workflow/auto-fix-bundler.test.ts +242 -0
  212. package/tests/workflow/overview.test.ts +392 -0
  213. package/tests/workflow/plan-mode.test.ts +111 -1
  214. package/tests/workflow/website-strategy.test.ts +246 -0
@@ -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
- return `You are a senior software architect reviewing a development plan.
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 prompt = `You are a senior software architect. A user wants to build a project with the following idea:
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
- * Generate a project name from an idea
259
+ * Directories that are too generic to use as project names
259
260
  */
260
- function generateProjectName(idea: string): string {
261
- // Take first few words, lowercase, replace spaces with hyphens
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, '')
@@ -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
- * Generate a meaningful project name from an idea
2219
- * Extracts key nouns and creates a kebab-case name
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 generateProjectName(idea: string): string {
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();