hone-ai 0.5.0 → 0.10.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.
@@ -1,10 +1,10 @@
1
- import { loadConfig, resolveModelForPhase } from './config';
2
- import { readFile, writeFile, readdir } from 'fs/promises';
3
- import { join } from 'path';
4
- import { existsSync } from 'fs';
5
- import * as readline from 'readline';
6
- import { exitWithError, ErrorMessages } from './errors';
7
- import { AgentClient } from './agent-client';
1
+ import { loadConfig, resolveModelForPhase } from './config'
2
+ import { readFile, writeFile, readdir } from 'fs/promises'
3
+ import { join } from 'path'
4
+ import { existsSync } from 'fs'
5
+ import * as readline from 'readline'
6
+ import { exitWithError, ErrorMessages } from './errors'
7
+ import { AgentClient } from './agent-client'
8
8
 
9
9
  export function slugify(text: string): string {
10
10
  return text
@@ -14,58 +14,58 @@ export function slugify(text: string): string {
14
14
  .replace(/\s+/g, '-')
15
15
  .replace(/-+/g, '-')
16
16
  .replace(/^-+|-+$/g, '')
17
- .slice(0, 50);
17
+ .slice(0, 50)
18
18
  }
19
19
 
20
20
  async function analyzeCodebase(): Promise<string> {
21
- const analysis: string[] = [];
22
- const projectRoot = process.cwd();
23
-
21
+ const analysis: string[] = []
22
+ const projectRoot = process.cwd()
23
+
24
24
  // Package.json analysis
25
25
  if (existsSync(join(projectRoot, 'package.json'))) {
26
- const pkg = JSON.parse(await readFile(join(projectRoot, 'package.json'), 'utf-8'));
27
- analysis.push(`Project: ${pkg.name || 'unnamed'}`);
28
- if (pkg.description) analysis.push(`Description: ${pkg.description}`);
29
-
26
+ const pkg = JSON.parse(await readFile(join(projectRoot, 'package.json'), 'utf-8'))
27
+ analysis.push(`Project: ${pkg.name || 'unnamed'}`)
28
+ if (pkg.description) analysis.push(`Description: ${pkg.description}`)
29
+
30
30
  if (pkg.dependencies) {
31
- const deps = Object.keys(pkg.dependencies);
32
- if (deps.includes('react')) analysis.push('Framework: React');
33
- if (deps.includes('next')) analysis.push('Framework: Next.js');
34
- if (deps.includes('vue')) analysis.push('Framework: Vue');
35
- if (deps.includes('typescript')) analysis.push('Language: TypeScript');
36
- if (deps.includes('commander')) analysis.push('CLI: commander.js');
37
- if (deps.includes('express')) analysis.push('Backend: Express');
38
- if (deps.includes('fastify')) analysis.push('Backend: Fastify');
39
-
31
+ const deps = Object.keys(pkg.dependencies)
32
+ if (deps.includes('react')) analysis.push('Framework: React')
33
+ if (deps.includes('next')) analysis.push('Framework: Next.js')
34
+ if (deps.includes('vue')) analysis.push('Framework: Vue')
35
+ if (deps.includes('typescript')) analysis.push('Language: TypeScript')
36
+ if (deps.includes('commander')) analysis.push('CLI: commander.js')
37
+ if (deps.includes('express')) analysis.push('Backend: Express')
38
+ if (deps.includes('fastify')) analysis.push('Backend: Fastify')
39
+
40
40
  // Testing frameworks
41
- if (deps.includes('jest') || pkg.devDependencies?.jest) analysis.push('Testing: Jest');
42
- if (deps.includes('vitest') || pkg.devDependencies?.vitest) analysis.push('Testing: Vitest');
43
- if (deps.includes('mocha') || pkg.devDependencies?.mocha) analysis.push('Testing: Mocha');
41
+ if (deps.includes('jest') || pkg.devDependencies?.jest) analysis.push('Testing: Jest')
42
+ if (deps.includes('vitest') || pkg.devDependencies?.vitest) analysis.push('Testing: Vitest')
43
+ if (deps.includes('mocha') || pkg.devDependencies?.mocha) analysis.push('Testing: Mocha')
44
44
  }
45
-
45
+
46
46
  if (pkg.scripts) {
47
- const scripts = Object.keys(pkg.scripts);
48
- if (scripts.includes('build')) analysis.push('Build: Configured');
49
- if (scripts.includes('test')) analysis.push('Test script: Available');
50
- if (scripts.includes('lint')) analysis.push('Linting: Available');
51
- if (scripts.includes('dev') || scripts.includes('start')) analysis.push('Dev server: Available');
47
+ const scripts = Object.keys(pkg.scripts)
48
+ if (scripts.includes('build')) analysis.push('Build: Configured')
49
+ if (scripts.includes('test')) analysis.push('Test script: Available')
50
+ if (scripts.includes('lint')) analysis.push('Linting: Available')
51
+ if (scripts.includes('dev') || scripts.includes('start'))
52
+ analysis.push('Dev server: Available')
52
53
  }
53
54
  }
54
-
55
+
55
56
  // Directory structure analysis
56
- const srcExists = existsSync(join(projectRoot, 'src'));
57
- const componentsExists = existsSync(join(projectRoot, 'src/components')) ||
58
- existsSync(join(projectRoot, 'components'));
59
- const libExists = existsSync(join(projectRoot, 'lib')) ||
60
- existsSync(join(projectRoot, 'src/lib'));
61
- const utilsExists = existsSync(join(projectRoot, 'utils')) ||
62
- existsSync(join(projectRoot, 'src/utils'));
63
-
64
- if (srcExists) analysis.push('Structure: src/ directory');
65
- if (componentsExists) analysis.push('Has: Components directory');
66
- if (libExists) analysis.push('Has: Lib directory');
67
- if (utilsExists) analysis.push('Has: Utils directory');
68
-
57
+ const srcExists = existsSync(join(projectRoot, 'src'))
58
+ const componentsExists =
59
+ existsSync(join(projectRoot, 'src/components')) || existsSync(join(projectRoot, 'components'))
60
+ const libExists = existsSync(join(projectRoot, 'lib')) || existsSync(join(projectRoot, 'src/lib'))
61
+ const utilsExists =
62
+ existsSync(join(projectRoot, 'utils')) || existsSync(join(projectRoot, 'src/utils'))
63
+
64
+ if (srcExists) analysis.push('Structure: src/ directory')
65
+ if (componentsExists) analysis.push('Has: Components directory')
66
+ if (libExists) analysis.push('Has: Lib directory')
67
+ if (utilsExists) analysis.push('Has: Utils directory')
68
+
69
69
  // Configuration files
70
70
  const configs = [
71
71
  { file: 'tsconfig.json', desc: 'TypeScript config' },
@@ -76,78 +76,78 @@ async function analyzeCodebase(): Promise<string> {
76
76
  { file: 'next.config.js', desc: 'Next.js config' },
77
77
  { file: 'vite.config.ts', desc: 'Vite config' },
78
78
  { file: 'docker-compose.yml', desc: 'Docker Compose' },
79
- { file: 'Dockerfile', desc: 'Docker' }
80
- ];
81
-
79
+ { file: 'Dockerfile', desc: 'Docker' },
80
+ ]
81
+
82
82
  for (const config of configs) {
83
83
  if (existsSync(join(projectRoot, config.file))) {
84
- analysis.push(`Config: ${config.desc}`);
84
+ analysis.push(`Config: ${config.desc}`)
85
85
  }
86
86
  }
87
-
87
+
88
88
  // Check for specific project patterns
89
89
  if (existsSync(join(projectRoot, '.plans'))) {
90
- analysis.push('Project type: Uses hone for task management');
90
+ analysis.push('Project type: Uses hone for task management')
91
91
  }
92
-
92
+
93
93
  if (existsSync(join(projectRoot, 'AGENTS.md'))) {
94
- analysis.push('Documentation: Has AGENTS.md (development guidelines)');
94
+ analysis.push('Documentation: Has AGENTS.md (development guidelines)')
95
95
  }
96
-
96
+
97
97
  // README analysis
98
98
  if (existsSync(join(projectRoot, 'README.md'))) {
99
99
  try {
100
- const readme = await readFile(join(projectRoot, 'README.md'), 'utf-8');
101
- const hasInstallSection = readme.toLowerCase().includes('install');
102
- const hasUsageSection = readme.toLowerCase().includes('usage');
103
- if (hasInstallSection) analysis.push('README: Has installation instructions');
104
- if (hasUsageSection) analysis.push('README: Has usage instructions');
100
+ const readme = await readFile(join(projectRoot, 'README.md'), 'utf-8')
101
+ const hasInstallSection = readme.toLowerCase().includes('install')
102
+ const hasUsageSection = readme.toLowerCase().includes('usage')
103
+ if (hasInstallSection) analysis.push('README: Has installation instructions')
104
+ if (hasUsageSection) analysis.push('README: Has usage instructions')
105
105
  } catch {
106
106
  // Ignore errors reading README
107
107
  }
108
108
  }
109
-
109
+
110
110
  // Check for common patterns in source files
111
111
  if (srcExists) {
112
112
  try {
113
- const srcFiles = await readdir(join(projectRoot, 'src'));
113
+ const srcFiles = await readdir(join(projectRoot, 'src'))
114
114
  if (srcFiles.includes('index.ts') || srcFiles.includes('index.js')) {
115
- analysis.push('Entry point: src/index');
115
+ analysis.push('Entry point: src/index')
116
116
  }
117
117
  if (srcFiles.includes('cli.ts') || srcFiles.includes('cli.js')) {
118
- analysis.push('Has: CLI module');
118
+ analysis.push('Has: CLI module')
119
119
  }
120
120
  if (srcFiles.includes('config.ts') || srcFiles.includes('config.js')) {
121
- analysis.push('Has: Configuration module');
121
+ analysis.push('Has: Configuration module')
122
122
  }
123
123
  if (srcFiles.some(f => f.includes('test'))) {
124
- analysis.push('Testing: Test files in src');
124
+ analysis.push('Testing: Test files in src')
125
125
  }
126
126
  } catch {
127
127
  // Ignore errors reading src directory
128
128
  }
129
129
  }
130
-
131
- return analysis.length > 0 ? analysis.join(', ') : 'No specific patterns detected';
130
+
131
+ return analysis.length > 0 ? analysis.join(', ') : 'No specific patterns detected'
132
132
  }
133
133
 
134
134
  interface QAResponse {
135
- question: string | null;
136
- shouldContinue: boolean;
135
+ question: string | null
136
+ shouldContinue: boolean
137
137
  }
138
138
 
139
139
  async function askQuestion(prompt: string): Promise<string> {
140
140
  const rl = readline.createInterface({
141
141
  input: process.stdin,
142
- output: process.stdout
143
- });
144
-
145
- return new Promise((resolve) => {
146
- rl.question(prompt, (answer) => {
147
- rl.close();
148
- resolve(answer.trim());
149
- });
150
- });
142
+ output: process.stdout,
143
+ })
144
+
145
+ return new Promise(resolve => {
146
+ rl.question(prompt, answer => {
147
+ rl.close()
148
+ resolve(answer.trim())
149
+ })
150
+ })
151
151
  }
152
152
 
153
153
  async function generateClarifyingQuestion(
@@ -156,37 +156,48 @@ async function generateClarifyingQuestion(
156
156
  previousQA: Array<{ question: string; answer: string }>,
157
157
  roundNumber: number
158
158
  ): Promise<QAResponse> {
159
- const config = await loadConfig();
160
- const model = resolveModelForPhase(config, 'prd');
161
-
162
- const client = new AgentClient({
159
+ const config = await loadConfig()
160
+ const model = resolveModelForPhase(config, 'prd')
161
+
162
+ const client = new AgentClient({
163
163
  agent: config.defaultAgent,
164
- model
165
- });
166
-
164
+ model,
165
+ })
166
+
167
167
  // Show progress indicator for question generation
168
- process.stdout.write(`Generating question ${roundNumber}... `);
169
-
170
- const qaHistory = previousQA.map(qa => `Q: ${qa.question}\nA: ${qa.answer}`).join('\n\n');
171
-
168
+ process.stdout.write(`Generating question ${roundNumber}... `)
169
+
170
+ const qaHistory = previousQA.map(qa => `Q: ${qa.question}\nA: ${qa.answer}`).join('\n\n')
171
+
172
172
  // Read AGENTS.md for context about the project
173
- let agentsContent = '';
174
- const agentsPath = join(process.cwd(), 'AGENTS.md');
173
+ let agentsContent = ''
174
+ const agentsPath = join(process.cwd(), 'AGENTS.md')
175
175
  if (existsSync(agentsPath)) {
176
176
  try {
177
- agentsContent = await readFile(agentsPath, 'utf-8');
177
+ agentsContent = await readFile(agentsPath, 'utf-8')
178
178
  } catch {
179
179
  // Ignore errors reading AGENTS.md
180
180
  }
181
181
  }
182
-
182
+
183
183
  const systemPrompt = `You are helping generate a Product Requirements Document (PRD) for a software feature.
184
184
  The user has provided a feature description, and you need to ask clarifying questions to make the PRD comprehensive.
185
185
 
186
- IMPORTANT: Before asking questions, carefully analyze the provided codebase context and project documentation.
187
- Many common questions about testing frameworks, build systems, dependencies, project structure, and technical
186
+ IMPORTANT: Before asking questions, carefully analyze the provided codebase context and project documentation.
187
+ Many common questions about testing frameworks, build systems, dependencies, project structure, and technical
188
188
  patterns can be answered from the codebase analysis. Only ask questions that cannot be determined from the code.
189
189
 
190
+ FILE AND URL PROCESSING:
191
+ - If the feature description contains file paths (e.g., "./file.txt", "/path/to/file", "src/component.js"), automatically read and incorporate their content using the Read tool
192
+ - If the feature description contains URLs (e.g., "https://example.com", "http://site.com/page"), automatically fetch and incorporate their content using the WebFetch tool
193
+ - Process these references transparently - no need to ask the user about them unless you cannot access them
194
+ - REFERENCE ACCESS FAILURES: When file reads or URL fetches fail:
195
+ * Continue with question generation using available context
196
+ * Ask clarifying questions about the inaccessible reference's intended purpose or relevance
197
+ * Examples: "I couldn't access [file/URL]. What was its relevance to this feature?" or "Since [reference] is inaccessible, could you clarify what information it contained that's relevant to the requirements?"
198
+ * Prioritize these clarification questions early in the Q&A session when references are critical to understanding scope
199
+ - Use the content from files and URLs to better inform your clarifying questions when accessible
200
+
190
201
  Rules:
191
202
  - Ask ONE specific, focused question at a time
192
203
  - Questions should help clarify requirements, scope, UX, technical approach, or edge cases
@@ -197,38 +208,45 @@ Rules:
197
208
 
198
209
  Codebase context: ${codebaseAnalysis}
199
210
 
200
- ${agentsContent ? `Project documentation (AGENTS.md):
211
+ ${
212
+ agentsContent
213
+ ? `Project documentation (AGENTS.md):
201
214
  ${agentsContent}
202
215
 
203
- ` : ''}Feature description: ${featureDescription}
216
+ `
217
+ : ''
218
+ }Feature description: ${featureDescription}
204
219
 
205
- ${qaHistory ? `Previous Q&A:\n${qaHistory}` : 'This is the first question.'}`;
220
+ ${qaHistory ? `Previous Q&A:\n${qaHistory}` : 'This is the first question.'}`
206
221
 
207
222
  try {
208
223
  const response = await client.messages.create({
209
224
  max_tokens: 500,
210
- messages: [{
211
- role: 'user',
212
- content: 'What is your next clarifying question, or respond with "DONE" if you have enough information?'
213
- }],
214
- system: systemPrompt
215
- });
216
-
217
- const content = response.content[0];
218
- const text = content && content.type === 'text' ? content.text.trim() : '';
219
-
225
+ messages: [
226
+ {
227
+ role: 'user',
228
+ content:
229
+ 'What is your next clarifying question, or respond with "DONE" if you have enough information?',
230
+ },
231
+ ],
232
+ system: systemPrompt,
233
+ })
234
+
235
+ const content = response.content[0]
236
+ const text = content && content.type === 'text' ? content.text.trim() : ''
237
+
220
238
  // Clear progress indicator
221
- process.stdout.write('✓\n');
222
-
239
+ process.stdout.write('✓\n')
240
+
223
241
  if (text.toUpperCase().includes('DONE') || text === '') {
224
- return { question: null, shouldContinue: false };
242
+ return { question: null, shouldContinue: false }
225
243
  }
226
-
227
- return { question: text, shouldContinue: true };
244
+
245
+ return { question: text, shouldContinue: true }
228
246
  } catch (error) {
229
247
  // Clear progress indicator with error
230
- process.stdout.write('✗\n');
231
- throw error;
248
+ process.stdout.write('✗\n')
249
+ throw error
232
250
  }
233
251
  }
234
252
 
@@ -237,32 +255,39 @@ async function generatePRDContent(
237
255
  codebaseAnalysis: string,
238
256
  qa: Array<{ question: string; answer: string }>
239
257
  ): Promise<{ content: string; featureName: string }> {
240
- const config = await loadConfig();
241
- const model = resolveModelForPhase(config, 'prd');
242
-
258
+ const config = await loadConfig()
259
+ const model = resolveModelForPhase(config, 'prd')
260
+
243
261
  const client = new AgentClient({
244
262
  agent: config.defaultAgent,
245
- model
246
- });
247
-
263
+ model,
264
+ })
265
+
248
266
  // Show progress indicator for PRD generation
249
- process.stdout.write('Writing PRD document... ');
250
-
251
- const qaHistory = qa.map(q => `Q: ${q.question}\nA: ${q.answer}`).join('\n\n');
252
-
267
+ process.stdout.write('Writing PRD document... ')
268
+
269
+ const qaHistory = qa.map(q => `Q: ${q.question}\nA: ${q.answer}`).join('\n\n')
270
+
253
271
  // Read AGENTS.md for context about the project
254
- let agentsContent = '';
255
- const agentsPath = join(process.cwd(), 'AGENTS.md');
272
+ let agentsContent = ''
273
+ const agentsPath = join(process.cwd(), 'AGENTS.md')
256
274
  if (existsSync(agentsPath)) {
257
275
  try {
258
- agentsContent = await readFile(agentsPath, 'utf-8');
276
+ agentsContent = await readFile(agentsPath, 'utf-8')
259
277
  } catch {
260
278
  // Ignore errors reading AGENTS.md
261
279
  }
262
280
  }
263
-
281
+
264
282
  const systemPrompt = `You are a technical product manager writing a Product Requirements Document (PRD).
265
283
 
284
+ FILE AND URL PROCESSING:
285
+ - If the feature description contains file paths (e.g., "./file.txt", "/path/to/file", "src/component.js"), automatically read and incorporate their content using the Read tool
286
+ - If the feature description contains URLs (e.g., "https://example.com", "http://site.com/page"), automatically fetch and incorporate their content using the WebFetch tool
287
+ - Process these references transparently to enrich the PRD content with actual file contents and web page information
288
+ - If references fail to load, note them in the "Open Questions" section and continue PRD generation
289
+ - Use the content from files and URLs to inform technical decisions, requirements, and acceptance criteria
290
+
266
291
  Generate a comprehensive PRD with the following structure:
267
292
 
268
293
  # PRD: <Feature Name>
@@ -303,53 +328,56 @@ Context:
303
328
  - Feature: ${featureDescription}
304
329
  ${agentsContent ? `\n- Project Documentation (AGENTS.md):\n${agentsContent}` : ''}${qaHistory ? `\n- Q&A Session:\n${qaHistory}` : ''}
305
330
 
306
- Write a complete, detailed PRD following the structure above. Use the codebase analysis and project
307
- documentation to inform technical decisions and ensure the PRD aligns with the existing project patterns.`;
331
+ Write a complete, detailed PRD following the structure above. Use the codebase analysis and project
332
+ documentation to inform technical decisions and ensure the PRD aligns with the existing project patterns.`
308
333
 
309
334
  try {
310
335
  const response = await client.messages.create({
311
336
  max_tokens: 4000,
312
- messages: [{
313
- role: 'user',
314
- content: 'Generate the PRD now.'
315
- }],
316
- system: systemPrompt
317
- });
318
-
319
- const content = response.content[0];
320
- const prdContent = content && content.type === 'text' ? content.text : '';
321
-
337
+ messages: [
338
+ {
339
+ role: 'user',
340
+ content: 'Generate the PRD now.',
341
+ },
342
+ ],
343
+ system: systemPrompt,
344
+ })
345
+
346
+ const content = response.content[0]
347
+ const prdContent = content && content.type === 'text' ? content.text : ''
348
+
322
349
  if (!prdContent.trim()) {
323
- throw new Error('Agent returned empty PRD content');
350
+ throw new Error('Agent returned empty PRD content')
324
351
  }
325
-
352
+
326
353
  // Extract feature name from the first heading
327
- const match = prdContent.match(/# PRD: (.+)/);
328
- const featureName = match && match[1] ? match[1].trim() : featureDescription;
329
-
354
+ const match = prdContent.match(/# PRD: (.+)/)
355
+ const featureName = match && match[1] ? match[1].trim() : featureDescription
356
+
330
357
  // Clear progress indicator
331
- process.stdout.write('✓\n');
332
-
333
- return { content: prdContent, featureName };
358
+ process.stdout.write('✓\n')
359
+
360
+ return { content: prdContent, featureName }
334
361
  } catch (error) {
335
362
  // Clear progress indicator with error
336
- process.stdout.write('✗\n');
337
- const { message, details } = ErrorMessages.NETWORK_ERROR_FINAL(error);
338
- exitWithError(message, details);
339
- throw error; // Never reached but satisfies TypeScript
363
+ process.stdout.write('✗\n')
364
+
365
+ // Re-throw the error to be handled by the caller
366
+ // This allows the generatePRD function to implement fallback behavior
367
+ throw error
340
368
  }
341
369
  }
342
370
 
343
371
  export async function generatePRD(featureDescription: string): Promise<string> {
344
- console.log('\nAnalyzing codebase...');
345
- const codebaseAnalysis = await analyzeCodebase();
346
- console.log(`Found: ${codebaseAnalysis}\n`);
347
-
348
- const qa: Array<{ question: string; answer: string }> = [];
349
- const maxRounds = 5;
350
-
351
- console.log('I have a few questions to refine this PRD:\n');
352
-
372
+ console.log('\nAnalyzing codebase...')
373
+ const codebaseAnalysis = await analyzeCodebase()
374
+ console.log(`Found: ${codebaseAnalysis}\n`)
375
+
376
+ const qa: Array<{ question: string; answer: string }> = []
377
+ const maxRounds = 5
378
+
379
+ console.log('I have a few questions to refine this PRD:\n')
380
+
353
381
  for (let round = 1; round <= maxRounds; round++) {
354
382
  try {
355
383
  const { question, shouldContinue } = await generateClarifyingQuestion(
@@ -357,48 +385,106 @@ export async function generatePRD(featureDescription: string): Promise<string> {
357
385
  codebaseAnalysis,
358
386
  qa,
359
387
  round
360
- );
361
-
388
+ )
389
+
362
390
  if (!shouldContinue || !question) {
363
- break;
391
+ break
364
392
  }
365
-
366
- console.log(`${round}. ${question}`);
367
- const answer = await askQuestion('> ');
368
-
393
+
394
+ console.log(`${round}. ${question}`)
395
+ const answer = await askQuestion('> ')
396
+
369
397
  if (answer.toLowerCase() === 'done') {
370
- break;
398
+ break
371
399
  }
372
-
373
- qa.push({ question, answer });
374
- console.log('');
400
+
401
+ qa.push({ question, answer })
402
+ console.log('')
375
403
  } catch (error) {
376
- console.error(`\nError generating clarifying question for round ${round}: ${error instanceof Error ? error.message : String(error)}`);
377
- console.log('Continuing with available information...\n');
378
- break;
404
+ console.error(
405
+ `\nError generating clarifying question for round ${round}: ${error instanceof Error ? error.message : String(error)}`
406
+ )
407
+ console.log('Continuing with available information...\n')
408
+ break
379
409
  }
380
410
  }
381
-
382
- console.log('\nGenerating PRD...');
411
+
412
+ console.log('\nGenerating PRD...')
383
413
  try {
384
414
  const { content, featureName } = await generatePRDContent(
385
415
  featureDescription,
386
416
  codebaseAnalysis,
387
417
  qa
388
- );
389
-
390
- const slug = slugify(featureName);
391
- const filename = `prd-${slug}.md`;
392
- const filepath = join(process.cwd(), '.plans', filename);
393
-
394
- await writeFile(filepath, content, 'utf-8');
395
-
396
- console.log(`✓ Saved to .plans/${filename}\n`);
397
-
398
- return filename;
418
+ )
419
+
420
+ const slug = slugify(featureName)
421
+ const filename = `prd-${slug}.md`
422
+ const filepath = join(process.cwd(), '.plans', filename)
423
+
424
+ await writeFile(filepath, content, 'utf-8')
425
+
426
+ console.log(`✓ Saved to .plans/${filename}\n`)
427
+ console.log(
428
+ `Review and edit if needed the PRD and then execute "hone prd-to-tasks .plans/${filename}"\n`
429
+ )
430
+
431
+ return filename
399
432
  } catch (error) {
400
- const { message, details } = ErrorMessages.NETWORK_ERROR_FINAL(error);
401
- exitWithError(message, details);
402
- throw error; // Never reached but satisfies TypeScript
433
+ console.error(
434
+ `\nError generating PRD: ${error instanceof Error ? error.message : String(error)}`
435
+ )
436
+ console.log('Generating fallback PRD with available information...\n')
437
+
438
+ // Generate a fallback PRD with basic structure and available context
439
+ const fallbackFeatureName = featureDescription.split('\n')[0] || 'New Feature'
440
+ const slug = slugify(fallbackFeatureName)
441
+ const filename = `prd-${slug}.md`
442
+ const filepath = join(process.cwd(), '.plans', filename)
443
+
444
+ const qaHistory = qa.map(q => `Q: ${q.question}\nA: ${q.answer}`).join('\n\n')
445
+
446
+ const fallbackContent = `# PRD: ${fallbackFeatureName}
447
+
448
+ ## Overview
449
+ ${featureDescription}
450
+
451
+ ## Goals
452
+ To be determined based on further analysis.
453
+
454
+ ## Non-Goals
455
+ To be determined based on further analysis.
456
+
457
+ ## Requirements
458
+
459
+ ### Functional Requirements
460
+ - REQ-F-001: To be defined based on detailed analysis
461
+
462
+ ### Non-Functional Requirements
463
+ - REQ-NF-001: To be defined based on detailed analysis
464
+
465
+ ## Technical Considerations
466
+ Based on codebase analysis: ${codebaseAnalysis}
467
+
468
+ ## Acceptance Criteria
469
+ - [ ] Requirements to be defined after successful agent processing
470
+
471
+ ## Out of Scope
472
+ To be determined.
473
+
474
+ ## Open Questions
475
+ - This PRD was generated in fallback mode due to agent processing issues
476
+ - Full requirements analysis could not be completed
477
+ - Consider regenerating this PRD when system connectivity is restored
478
+ ${qaHistory ? `\n\nPrevious Q&A Session:\n${qaHistory}` : ''}`
479
+
480
+ await writeFile(filepath, fallbackContent, 'utf-8')
481
+
482
+ console.log(`✓ Generated fallback PRD: .plans/${filename}`)
483
+ console.log(
484
+ 'Note: This PRD contains basic structure. Consider regenerating when system is available.\n'
485
+ )
486
+ console.log(`Review and edit the PRD and then execute "hone prd-to-tasks .plans/${filename}"\n`)
487
+
488
+ return filename
403
489
  }
404
490
  }