forgedev 1.0.0 → 1.0.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.
Files changed (36) hide show
  1. package/CLAUDE.md +3 -3
  2. package/README.md +246 -246
  3. package/bin/devforge.js +4 -4
  4. package/package.json +33 -33
  5. package/src/claude-configurator.js +260 -260
  6. package/src/cli.js +119 -119
  7. package/src/composer.js +214 -214
  8. package/src/doctor-checks.js +743 -743
  9. package/src/doctor-prompts.js +295 -295
  10. package/src/doctor.js +281 -281
  11. package/src/guided.js +315 -315
  12. package/src/index.js +148 -148
  13. package/src/init-mode.js +138 -134
  14. package/src/prompts.js +155 -155
  15. package/src/scanner.js +368 -368
  16. package/templates/claude-code/agents/code-quality-reviewer.md +41 -41
  17. package/templates/claude-code/agents/production-readiness.md +55 -55
  18. package/templates/claude-code/agents/security-reviewer.md +41 -41
  19. package/templates/claude-code/agents/spec-validator.md +34 -34
  20. package/templates/claude-code/agents/uat-validator.md +37 -37
  21. package/templates/claude-code/claude-md/base.md +33 -33
  22. package/templates/claude-code/commands/done.md +19 -19
  23. package/templates/claude-code/commands/generate-prd.md +45 -45
  24. package/templates/claude-code/commands/generate-uat.md +35 -35
  25. package/templates/claude-code/commands/next.md +20 -20
  26. package/templates/claude-code/commands/optimize-claude-md.md +31 -31
  27. package/templates/claude-code/commands/status.md +24 -24
  28. package/templates/claude-code/commands/workflows.md +26 -0
  29. package/templates/claude-code/hooks/polyglot.json +36 -36
  30. package/templates/claude-code/hooks/python.json +36 -36
  31. package/templates/claude-code/hooks/scripts/autofix-polyglot.sh +16 -16
  32. package/templates/claude-code/hooks/scripts/autofix-python.sh +14 -14
  33. package/templates/claude-code/hooks/scripts/autofix-typescript.sh +14 -14
  34. package/templates/claude-code/hooks/scripts/guard-protected-files.sh +21 -21
  35. package/templates/claude-code/hooks/typescript.json +36 -36
  36. package/templates/claude-code/commands/help.md +0 -26
package/src/doctor.js CHANGED
@@ -1,281 +1,281 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import chalk from 'chalk';
4
- import { log, ensureDir, writeFile } from './utils.js';
5
- import { scanProject } from './scanner.js';
6
- import { runAllChecks } from './doctor-checks.js';
7
- import { generatePrompt, generateAllPrompts, generateReport } from './doctor-prompts.js';
8
- import { askDoctorAction } from './prompts.js';
9
-
10
- export async function runDoctor(projectDir) {
11
- console.log('');
12
- console.log(chalk.bold.cyan(' 🔨 DevForge Doctor') + chalk.dim(' — Project Health Check'));
13
- console.log('');
14
- console.log(' Scanning...');
15
- console.log('');
16
-
17
- const scan = scanProject(projectDir);
18
-
19
- if (scan.stackId === 'unknown') {
20
- log.warn('Could not detect a supported stack in this directory.');
21
- log.dim(' Supported: Next.js, FastAPI, or polyglot (Next.js + FastAPI)');
22
- log.dim(' Make sure you\'re in the project root directory.');
23
- process.exit(1);
24
- }
25
-
26
- // Print vitals
27
- printVitals(scan);
28
-
29
- // Run all checks
30
- const issues = runAllChecks(projectDir, scan);
31
-
32
- if (issues.length === 0) {
33
- console.log(chalk.green(' ✓ Your project looks healthy! No issues found.'));
34
- console.log('');
35
- return;
36
- }
37
-
38
- // Print issues summary
39
- printIssues(issues);
40
-
41
- // Ask what to do
42
- const action = await askDoctorAction();
43
-
44
- switch (action) {
45
- case 'guided':
46
- await guidedFix(issues);
47
- break;
48
- case 'report':
49
- await saveReport(issues, scan, projectDir);
50
- break;
51
- case 'autofix':
52
- await autoFixSafe(issues, projectDir);
53
- break;
54
- case 'prompts':
55
- await exportPrompts(issues, projectDir);
56
- break;
57
- }
58
- }
59
-
60
- function printVitals(scan) {
61
- console.log(chalk.bold(' 📊 Project Vitals:'));
62
- console.log('');
63
-
64
- const parts = [];
65
- if (scan.frontend.detected) {
66
- parts.push(` ${chalk.bold('Frontend:')} ${scan.frontend.framework} (${scan.frontend.language})`);
67
- }
68
- if (scan.backend.detected) {
69
- parts.push(` ${chalk.bold('Backend:')} ${scan.backend.framework} (${scan.backend.language})`);
70
- }
71
- if (scan.database.detected) {
72
- parts.push(` ${chalk.bold('Database:')} ${scan.database.type} (${scan.database.orm})`);
73
- }
74
-
75
- const testParts = [scan.testing.unit, scan.testing.e2e, scan.testing.backend].filter(Boolean);
76
- if (testParts.length) {
77
- parts.push(` ${chalk.bold('Testing:')} ${testParts.join(' + ')}`);
78
- }
79
-
80
- if (scan.ai) {
81
- parts.push(` ${chalk.bold('AI:')} integration detected`);
82
- }
83
-
84
- console.log(parts.join('\n'));
85
- console.log('');
86
- }
87
-
88
- function printIssues(issues) {
89
- const critical = issues.filter(i => i.severity === 'critical');
90
- const warnings = issues.filter(i => i.severity === 'warning');
91
- const info = issues.filter(i => i.severity === 'info');
92
-
93
- console.log(chalk.bold(' Health Issues Found:'));
94
- console.log('');
95
-
96
- if (critical.length > 0) {
97
- console.log(chalk.red.bold(' 🔴 CRITICAL'));
98
- for (let i = 0; i < critical.length; i++) {
99
- const issue = critical[i];
100
- console.log(chalk.red(` ${i + 1}. ${issue.title}`));
101
- console.log(chalk.dim(` → ${issue.impact}`));
102
- if (issue.files && issue.files.length > 0 && issue.files.length <= 3) {
103
- console.log(chalk.dim(` Files: ${issue.files.join(', ')}`));
104
- }
105
- }
106
- console.log('');
107
- }
108
-
109
- if (warnings.length > 0) {
110
- console.log(chalk.yellow.bold(' 🟡 WARNING'));
111
- for (let i = 0; i < warnings.length; i++) {
112
- const issue = warnings[i];
113
- console.log(chalk.yellow(` ${critical.length + i + 1}. ${issue.title}`));
114
- console.log(chalk.dim(` → ${issue.impact}`));
115
- }
116
- console.log('');
117
- }
118
-
119
- if (info.length > 0) {
120
- console.log(chalk.blue.bold(' ℹ️ SUGGESTIONS'));
121
- for (let i = 0; i < info.length; i++) {
122
- const issue = info[i];
123
- console.log(chalk.blue(` ${critical.length + warnings.length + i + 1}. ${issue.title}`));
124
- console.log(chalk.dim(` → ${issue.impact}`));
125
- }
126
- console.log('');
127
- }
128
-
129
- // Print good checks
130
- const goodChecks = [];
131
- if (!critical.some(i => i.promptId === 'UNAUTH_ENDPOINT') && !warnings.some(i => i.promptId === 'UNAUTH_ENDPOINT')) {
132
- goodChecks.push('All endpoints have authentication');
133
- }
134
- if (!critical.some(i => i.promptId === 'FLAKY_TESTS')) {
135
- goodChecks.push('No flaky test patterns detected');
136
- }
137
- if (!warnings.some(i => i.promptId === 'BARE_EXCEPT')) {
138
- goodChecks.push('No bare except blocks');
139
- }
140
-
141
- if (goodChecks.length > 0) {
142
- console.log(chalk.green.bold(' 🟢 GOOD'));
143
- for (const check of goodChecks) {
144
- console.log(chalk.green(` ✓ ${check}`));
145
- }
146
- console.log('');
147
- }
148
- }
149
-
150
- async function guidedFix(issues) {
151
- const { input } = await import('@inquirer/prompts');
152
- const critical = issues.filter(i => i.severity === 'critical');
153
- const warnings = issues.filter(i => i.severity === 'warning');
154
- const allIssues = [...critical, ...warnings];
155
-
156
- if (allIssues.length === 0) {
157
- log.info('No critical or warning issues to fix.');
158
- return;
159
- }
160
-
161
- for (let i = 0; i < allIssues.length; i++) {
162
- const issue = allIssues[i];
163
- const prompt = generatePrompt(issue);
164
-
165
- console.log('');
166
- console.log(chalk.bold(` Issue ${i + 1} of ${allIssues.length}: ${issue.title}`));
167
- console.log('');
168
- console.log(chalk.dim(` ${issue.impact}`));
169
- console.log('');
170
- console.log(' To fix this, open Claude Code and paste:');
171
- console.log(chalk.cyan(' ┌' + '─'.repeat(68) + '┐'));
172
- const promptLines = prompt.split('\n');
173
- for (const line of promptLines) {
174
- const padded = line.padEnd(68);
175
- console.log(chalk.cyan(' │ ') + padded + chalk.cyan(' │'));
176
- }
177
- console.log(chalk.cyan(' └' + '─'.repeat(68) + '┘'));
178
-
179
- if (i < allIssues.length - 1) {
180
- await input({ message: 'Press enter for next issue, or type q to quit:' }).then(answer => {
181
- if (answer.toLowerCase() === 'q') {
182
- process.exit(0);
183
- }
184
- });
185
- }
186
- }
187
-
188
- console.log('');
189
- log.success('All issues shown. Fix them in Claude Code one at a time.');
190
- console.log('');
191
- }
192
-
193
- async function saveReport(issues, scan, projectDir) {
194
- const report = generateReport(issues, scan.projectName);
195
- const reportPath = path.join(projectDir, 'docs', 'doctor-report.md');
196
- writeFile(reportPath, report);
197
- console.log('');
198
- log.success(`Report saved to ${path.relative(projectDir, reportPath)}`);
199
- console.log('');
200
- }
201
-
202
- async function autoFixSafe(issues, projectDir) {
203
- console.log('');
204
- console.log(' Auto-fixing safe issues...');
205
- console.log('');
206
-
207
- let fixed = 0;
208
-
209
- // Create missing directories
210
- const dirsToCreate = ['docs/uat', 'docs/plans'];
211
- for (const dir of dirsToCreate) {
212
- const fullPath = path.join(projectDir, dir);
213
- if (!fs.existsSync(fullPath)) {
214
- ensureDir(fullPath);
215
- console.log(chalk.green(` ✓ Created ${dir}/ directory`));
216
- fixed++;
217
- }
218
- }
219
-
220
- // Fix hook script permissions (Unix only)
221
- if (process.platform !== 'win32') {
222
- const hooksDir = path.join(projectDir, '.claude', 'hooks');
223
- if (fs.existsSync(hooksDir)) {
224
- const hookFiles = fs.readdirSync(hooksDir).filter(f => f.endsWith('.sh'));
225
- for (const hookFile of hookFiles) {
226
- const hookPath = path.join(hooksDir, hookFile);
227
- try {
228
- fs.chmodSync(hookPath, '755');
229
- console.log(chalk.green(` ✓ Fixed ${path.join('.claude/hooks', hookFile)} permissions (chmod +x)`));
230
- fixed++;
231
- } catch {
232
- // Skip
233
- }
234
- }
235
- }
236
- }
237
-
238
- // Add missing .gitignore entries
239
- const gitignorePath = path.join(projectDir, '.gitignore');
240
- if (fs.existsSync(gitignorePath)) {
241
- const content = fs.readFileSync(gitignorePath, 'utf-8');
242
- const entriesToAdd = [];
243
-
244
- if (!content.includes('.claude/todos')) entriesToAdd.push('.claude/todos');
245
- if (!content.includes('.claude/plans')) entriesToAdd.push('.claude/plans');
246
-
247
- if (entriesToAdd.length > 0) {
248
- const addition = '\n# Claude Code temp files\n' + entriesToAdd.join('\n') + '\n';
249
- fs.appendFileSync(gitignorePath, addition);
250
- console.log(chalk.green(` ✓ Added ${entriesToAdd.length} entries to .gitignore`));
251
- fixed++;
252
- }
253
- }
254
-
255
- const skipped = issues.filter(i => !i.autoFixable).length;
256
-
257
- if (fixed === 0) {
258
- console.log(chalk.dim(' Nothing to auto-fix.'));
259
- }
260
-
261
- if (skipped > 0) {
262
- console.log(chalk.dim(` ⊘ Skipped ${skipped} issues that need Claude Code to fix`));
263
- }
264
-
265
- console.log('');
266
- if (skipped > 0) {
267
- console.log(` For the remaining issues, run: ${chalk.cyan('npx devforge doctor')} → option 1 or 2`);
268
- console.log('');
269
- }
270
- }
271
-
272
- async function exportPrompts(issues, projectDir) {
273
- const content = generateAllPrompts(issues);
274
- const promptsPath = path.join(projectDir, 'docs', 'doctor-prompts.md');
275
- writeFile(promptsPath, content);
276
- console.log('');
277
- log.success(`Fix prompts saved to ${path.relative(projectDir, promptsPath)}`);
278
- console.log(chalk.dim(' Open Claude Code and work through them one at a time.'));
279
- console.log(chalk.dim(' /clear between sessions.'));
280
- console.log('');
281
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import chalk from 'chalk';
4
+ import { log, ensureDir, writeFile } from './utils.js';
5
+ import { scanProject } from './scanner.js';
6
+ import { runAllChecks } from './doctor-checks.js';
7
+ import { generatePrompt, generateAllPrompts, generateReport } from './doctor-prompts.js';
8
+ import { askDoctorAction } from './prompts.js';
9
+
10
+ export async function runDoctor(projectDir) {
11
+ console.log('');
12
+ console.log(chalk.bold.cyan(' 🔨 DevForge Doctor') + chalk.dim(' — Project Health Check'));
13
+ console.log('');
14
+ console.log(' Scanning...');
15
+ console.log('');
16
+
17
+ const scan = scanProject(projectDir);
18
+
19
+ if (scan.stackId === 'unknown') {
20
+ log.warn('Could not detect a supported stack in this directory.');
21
+ log.dim(' Supported: Next.js, FastAPI, or polyglot (Next.js + FastAPI)');
22
+ log.dim(' Make sure you\'re in the project root directory.');
23
+ process.exit(1);
24
+ }
25
+
26
+ // Print vitals
27
+ printVitals(scan);
28
+
29
+ // Run all checks
30
+ const issues = runAllChecks(projectDir, scan);
31
+
32
+ if (issues.length === 0) {
33
+ console.log(chalk.green(' ✓ Your project looks healthy! No issues found.'));
34
+ console.log('');
35
+ return;
36
+ }
37
+
38
+ // Print issues summary
39
+ printIssues(issues);
40
+
41
+ // Ask what to do
42
+ const action = await askDoctorAction();
43
+
44
+ switch (action) {
45
+ case 'guided':
46
+ await guidedFix(issues);
47
+ break;
48
+ case 'report':
49
+ await saveReport(issues, scan, projectDir);
50
+ break;
51
+ case 'autofix':
52
+ await autoFixSafe(issues, projectDir);
53
+ break;
54
+ case 'prompts':
55
+ await exportPrompts(issues, projectDir);
56
+ break;
57
+ }
58
+ }
59
+
60
+ function printVitals(scan) {
61
+ console.log(chalk.bold(' 📊 Project Vitals:'));
62
+ console.log('');
63
+
64
+ const parts = [];
65
+ if (scan.frontend.detected) {
66
+ parts.push(` ${chalk.bold('Frontend:')} ${scan.frontend.framework} (${scan.frontend.language})`);
67
+ }
68
+ if (scan.backend.detected) {
69
+ parts.push(` ${chalk.bold('Backend:')} ${scan.backend.framework} (${scan.backend.language})`);
70
+ }
71
+ if (scan.database.detected) {
72
+ parts.push(` ${chalk.bold('Database:')} ${scan.database.type} (${scan.database.orm})`);
73
+ }
74
+
75
+ const testParts = [scan.testing.unit, scan.testing.e2e, scan.testing.backend].filter(Boolean);
76
+ if (testParts.length) {
77
+ parts.push(` ${chalk.bold('Testing:')} ${testParts.join(' + ')}`);
78
+ }
79
+
80
+ if (scan.ai) {
81
+ parts.push(` ${chalk.bold('AI:')} integration detected`);
82
+ }
83
+
84
+ console.log(parts.join('\n'));
85
+ console.log('');
86
+ }
87
+
88
+ function printIssues(issues) {
89
+ const critical = issues.filter(i => i.severity === 'critical');
90
+ const warnings = issues.filter(i => i.severity === 'warning');
91
+ const info = issues.filter(i => i.severity === 'info');
92
+
93
+ console.log(chalk.bold(' Health Issues Found:'));
94
+ console.log('');
95
+
96
+ if (critical.length > 0) {
97
+ console.log(chalk.red.bold(' 🔴 CRITICAL'));
98
+ for (let i = 0; i < critical.length; i++) {
99
+ const issue = critical[i];
100
+ console.log(chalk.red(` ${i + 1}. ${issue.title}`));
101
+ console.log(chalk.dim(` → ${issue.impact}`));
102
+ if (issue.files && issue.files.length > 0 && issue.files.length <= 3) {
103
+ console.log(chalk.dim(` Files: ${issue.files.join(', ')}`));
104
+ }
105
+ }
106
+ console.log('');
107
+ }
108
+
109
+ if (warnings.length > 0) {
110
+ console.log(chalk.yellow.bold(' 🟡 WARNING'));
111
+ for (let i = 0; i < warnings.length; i++) {
112
+ const issue = warnings[i];
113
+ console.log(chalk.yellow(` ${critical.length + i + 1}. ${issue.title}`));
114
+ console.log(chalk.dim(` → ${issue.impact}`));
115
+ }
116
+ console.log('');
117
+ }
118
+
119
+ if (info.length > 0) {
120
+ console.log(chalk.blue.bold(' ℹ️ SUGGESTIONS'));
121
+ for (let i = 0; i < info.length; i++) {
122
+ const issue = info[i];
123
+ console.log(chalk.blue(` ${critical.length + warnings.length + i + 1}. ${issue.title}`));
124
+ console.log(chalk.dim(` → ${issue.impact}`));
125
+ }
126
+ console.log('');
127
+ }
128
+
129
+ // Print good checks
130
+ const goodChecks = [];
131
+ if (!critical.some(i => i.promptId === 'UNAUTH_ENDPOINT') && !warnings.some(i => i.promptId === 'UNAUTH_ENDPOINT')) {
132
+ goodChecks.push('All endpoints have authentication');
133
+ }
134
+ if (!critical.some(i => i.promptId === 'FLAKY_TESTS')) {
135
+ goodChecks.push('No flaky test patterns detected');
136
+ }
137
+ if (!warnings.some(i => i.promptId === 'BARE_EXCEPT')) {
138
+ goodChecks.push('No bare except blocks');
139
+ }
140
+
141
+ if (goodChecks.length > 0) {
142
+ console.log(chalk.green.bold(' 🟢 GOOD'));
143
+ for (const check of goodChecks) {
144
+ console.log(chalk.green(` ✓ ${check}`));
145
+ }
146
+ console.log('');
147
+ }
148
+ }
149
+
150
+ async function guidedFix(issues) {
151
+ const { input } = await import('@inquirer/prompts');
152
+ const critical = issues.filter(i => i.severity === 'critical');
153
+ const warnings = issues.filter(i => i.severity === 'warning');
154
+ const allIssues = [...critical, ...warnings];
155
+
156
+ if (allIssues.length === 0) {
157
+ log.info('No critical or warning issues to fix.');
158
+ return;
159
+ }
160
+
161
+ for (let i = 0; i < allIssues.length; i++) {
162
+ const issue = allIssues[i];
163
+ const prompt = generatePrompt(issue);
164
+
165
+ console.log('');
166
+ console.log(chalk.bold(` Issue ${i + 1} of ${allIssues.length}: ${issue.title}`));
167
+ console.log('');
168
+ console.log(chalk.dim(` ${issue.impact}`));
169
+ console.log('');
170
+ console.log(' To fix this, open Claude Code and paste:');
171
+ console.log(chalk.cyan(' ┌' + '─'.repeat(68) + '┐'));
172
+ const promptLines = prompt.split('\n');
173
+ for (const line of promptLines) {
174
+ const padded = line.padEnd(68);
175
+ console.log(chalk.cyan(' │ ') + padded + chalk.cyan(' │'));
176
+ }
177
+ console.log(chalk.cyan(' └' + '─'.repeat(68) + '┘'));
178
+
179
+ if (i < allIssues.length - 1) {
180
+ await input({ message: 'Press enter for next issue, or type q to quit:' }).then(answer => {
181
+ if (answer.toLowerCase() === 'q') {
182
+ process.exit(0);
183
+ }
184
+ });
185
+ }
186
+ }
187
+
188
+ console.log('');
189
+ log.success('All issues shown. Fix them in Claude Code one at a time.');
190
+ console.log('');
191
+ }
192
+
193
+ async function saveReport(issues, scan, projectDir) {
194
+ const report = generateReport(issues, scan.projectName);
195
+ const reportPath = path.join(projectDir, 'docs', 'doctor-report.md');
196
+ writeFile(reportPath, report);
197
+ console.log('');
198
+ log.success(`Report saved to ${path.relative(projectDir, reportPath)}`);
199
+ console.log('');
200
+ }
201
+
202
+ async function autoFixSafe(issues, projectDir) {
203
+ console.log('');
204
+ console.log(' Auto-fixing safe issues...');
205
+ console.log('');
206
+
207
+ let fixed = 0;
208
+
209
+ // Create missing directories
210
+ const dirsToCreate = ['docs/uat', 'docs/plans'];
211
+ for (const dir of dirsToCreate) {
212
+ const fullPath = path.join(projectDir, dir);
213
+ if (!fs.existsSync(fullPath)) {
214
+ ensureDir(fullPath);
215
+ console.log(chalk.green(` ✓ Created ${dir}/ directory`));
216
+ fixed++;
217
+ }
218
+ }
219
+
220
+ // Fix hook script permissions (Unix only)
221
+ if (process.platform !== 'win32') {
222
+ const hooksDir = path.join(projectDir, '.claude', 'hooks');
223
+ if (fs.existsSync(hooksDir)) {
224
+ const hookFiles = fs.readdirSync(hooksDir).filter(f => f.endsWith('.sh'));
225
+ for (const hookFile of hookFiles) {
226
+ const hookPath = path.join(hooksDir, hookFile);
227
+ try {
228
+ fs.chmodSync(hookPath, '755');
229
+ console.log(chalk.green(` ✓ Fixed ${path.join('.claude/hooks', hookFile)} permissions (chmod +x)`));
230
+ fixed++;
231
+ } catch {
232
+ // Skip
233
+ }
234
+ }
235
+ }
236
+ }
237
+
238
+ // Add missing .gitignore entries
239
+ const gitignorePath = path.join(projectDir, '.gitignore');
240
+ if (fs.existsSync(gitignorePath)) {
241
+ const content = fs.readFileSync(gitignorePath, 'utf-8');
242
+ const entriesToAdd = [];
243
+
244
+ if (!content.includes('.claude/todos')) entriesToAdd.push('.claude/todos');
245
+ if (!content.includes('.claude/plans')) entriesToAdd.push('.claude/plans');
246
+
247
+ if (entriesToAdd.length > 0) {
248
+ const addition = '\n# Claude Code temp files\n' + entriesToAdd.join('\n') + '\n';
249
+ fs.appendFileSync(gitignorePath, addition);
250
+ console.log(chalk.green(` ✓ Added ${entriesToAdd.length} entries to .gitignore`));
251
+ fixed++;
252
+ }
253
+ }
254
+
255
+ const skipped = issues.filter(i => !i.autoFixable).length;
256
+
257
+ if (fixed === 0) {
258
+ console.log(chalk.dim(' Nothing to auto-fix.'));
259
+ }
260
+
261
+ if (skipped > 0) {
262
+ console.log(chalk.dim(` ⊘ Skipped ${skipped} issues that need Claude Code to fix`));
263
+ }
264
+
265
+ console.log('');
266
+ if (skipped > 0) {
267
+ console.log(` For the remaining issues, run: ${chalk.cyan('npx devforge doctor')} → option 1 or 2`);
268
+ console.log('');
269
+ }
270
+ }
271
+
272
+ async function exportPrompts(issues, projectDir) {
273
+ const content = generateAllPrompts(issues);
274
+ const promptsPath = path.join(projectDir, 'docs', 'doctor-prompts.md');
275
+ writeFile(promptsPath, content);
276
+ console.log('');
277
+ log.success(`Fix prompts saved to ${path.relative(projectDir, promptsPath)}`);
278
+ console.log(chalk.dim(' Open Claude Code and work through them one at a time.'));
279
+ console.log(chalk.dim(' /clear between sessions.'));
280
+ console.log('');
281
+ }