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.
- package/CLAUDE.md +3 -3
- package/README.md +246 -246
- package/bin/devforge.js +4 -4
- package/package.json +33 -33
- package/src/claude-configurator.js +260 -260
- package/src/cli.js +119 -119
- package/src/composer.js +214 -214
- package/src/doctor-checks.js +743 -743
- package/src/doctor-prompts.js +295 -295
- package/src/doctor.js +281 -281
- package/src/guided.js +315 -315
- package/src/index.js +148 -148
- package/src/init-mode.js +138 -134
- package/src/prompts.js +155 -155
- package/src/scanner.js +368 -368
- package/templates/claude-code/agents/code-quality-reviewer.md +41 -41
- package/templates/claude-code/agents/production-readiness.md +55 -55
- package/templates/claude-code/agents/security-reviewer.md +41 -41
- package/templates/claude-code/agents/spec-validator.md +34 -34
- package/templates/claude-code/agents/uat-validator.md +37 -37
- package/templates/claude-code/claude-md/base.md +33 -33
- package/templates/claude-code/commands/done.md +19 -19
- package/templates/claude-code/commands/generate-prd.md +45 -45
- package/templates/claude-code/commands/generate-uat.md +35 -35
- package/templates/claude-code/commands/next.md +20 -20
- package/templates/claude-code/commands/optimize-claude-md.md +31 -31
- package/templates/claude-code/commands/status.md +24 -24
- package/templates/claude-code/commands/workflows.md +26 -0
- package/templates/claude-code/hooks/polyglot.json +36 -36
- package/templates/claude-code/hooks/python.json +36 -36
- package/templates/claude-code/hooks/scripts/autofix-polyglot.sh +16 -16
- package/templates/claude-code/hooks/scripts/autofix-python.sh +14 -14
- package/templates/claude-code/hooks/scripts/autofix-typescript.sh +14 -14
- package/templates/claude-code/hooks/scripts/guard-protected-files.sh +21 -21
- package/templates/claude-code/hooks/typescript.json +36 -36
- package/templates/claude-code/commands/help.md +0 -26
package/src/doctor-prompts.js
CHANGED
|
@@ -1,295 +1,295 @@
|
|
|
1
|
-
const PROMPT_TEMPLATES = {
|
|
2
|
-
CLAUDE_MD_TOO_LONG: (issue) => {
|
|
3
|
-
const lines = issue.title.match(/(\d+) lines/)?.[1] || '?';
|
|
4
|
-
return `Read CLAUDE.md. It's ${lines} lines — too long for Claude Code to follow reliably (target: <150).
|
|
5
|
-
|
|
6
|
-
Propose a split:
|
|
7
|
-
- What stays in CLAUDE.md (universal rules, commands, pitfalls)
|
|
8
|
-
- What moves to .claude/skills/ (schemas, specs, detailed lists)
|
|
9
|
-
- What moves to backend/CLAUDE.md or src/CLAUDE.md (directory-scoped rules)
|
|
10
|
-
|
|
11
|
-
Show me the proposal as a table. Do NOT modify until I approve.`;
|
|
12
|
-
},
|
|
13
|
-
|
|
14
|
-
UNAUTH_ENDPOINT: (issue) => {
|
|
15
|
-
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
16
|
-
return `These endpoints are missing authentication:
|
|
17
|
-
${fileList}
|
|
18
|
-
|
|
19
|
-
For each: add the appropriate auth check.
|
|
20
|
-
- FastAPI: add Depends(get_current_user) and appropriate RBAC
|
|
21
|
-
- Next.js: add getServerSession or auth() check
|
|
22
|
-
Follow the pattern used in endpoints that already have auth.
|
|
23
|
-
Write a test for each endpoint confirming 401 without auth.`;
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
FLAKY_TESTS: (issue) => {
|
|
27
|
-
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
28
|
-
return `These tests use waitForTimeout() or hardcoded delays — they will fail randomly:
|
|
29
|
-
${fileList}
|
|
30
|
-
|
|
31
|
-
For each:
|
|
32
|
-
1. Replace waitForTimeout with waitForSelector, waitForResponse, or other event-based waits
|
|
33
|
-
2. Replace cy.wait(N) with cy.intercept + cy.wait('@alias')
|
|
34
|
-
3. Remove arbitrary setTimeout delays in tests
|
|
35
|
-
4. Run each test 3x to verify it's stable`;
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
CROSS_DOMAIN_IMPORT: (issue) => {
|
|
39
|
-
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
40
|
-
return `These files import across the frontend/backend boundary:
|
|
41
|
-
${fileList}
|
|
42
|
-
|
|
43
|
-
Fix by:
|
|
44
|
-
1. Move shared types to a shared/ directory
|
|
45
|
-
2. Use API calls instead of direct imports
|
|
46
|
-
3. Duplicate small utilities rather than cross-importing
|
|
47
|
-
Never import server code into client bundles.`;
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
BARE_EXCEPT: (issue) => {
|
|
51
|
-
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
52
|
-
return `These locations use bare except: blocks (catch-all that swallows real errors):
|
|
53
|
-
${fileList}
|
|
54
|
-
|
|
55
|
-
For each: replace with a specific exception type.
|
|
56
|
-
- If catching expected errors: except ValueError, except KeyError, etc.
|
|
57
|
-
- If catching any exception for logging: except Exception as e: with logging
|
|
58
|
-
- Never use bare except: — it hides bugs`;
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
MISSING_HEALTH: (issue) => {
|
|
62
|
-
return `Add a health check endpoint to the application.
|
|
63
|
-
|
|
64
|
-
For FastAPI: Add a /health endpoint that returns {"status": "ok"} and optionally checks database connectivity.
|
|
65
|
-
For Next.js: Add src/app/api/health/route.ts that returns {"status": "ok"}.
|
|
66
|
-
For both: Include database connectivity check if a database is configured.
|
|
67
|
-
|
|
68
|
-
This endpoint is used by load balancers and monitoring to verify the app is running.`;
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
MISSING_SHUTDOWN: (issue) => {
|
|
72
|
-
return `Add graceful shutdown handling to the application.
|
|
73
|
-
|
|
74
|
-
For FastAPI: Use the lifespan context manager to handle startup/shutdown events.
|
|
75
|
-
For Next.js: The framework handles this, but ensure any custom servers handle SIGTERM.
|
|
76
|
-
For Docker: Ensure the process responds to SIGTERM within the stop_grace_period.
|
|
77
|
-
|
|
78
|
-
On shutdown: close database connections, finish in-flight requests, flush logs.`;
|
|
79
|
-
},
|
|
80
|
-
|
|
81
|
-
MISSING_UAT: () => {
|
|
82
|
-
return `Generate UAT (User Acceptance Test) scenarios for this project.
|
|
83
|
-
|
|
84
|
-
Read the codebase to understand all user-facing features, then create:
|
|
85
|
-
1. docs/uat/UAT_TEMPLATE.md with test scenarios for each feature
|
|
86
|
-
2. docs/uat/UAT_CHECKLIST.csv with a checklist format
|
|
87
|
-
|
|
88
|
-
Each scenario should have:
|
|
89
|
-
- Description (what's being tested)
|
|
90
|
-
- Preconditions
|
|
91
|
-
- Steps (numbered)
|
|
92
|
-
- Expected result
|
|
93
|
-
- Priority (P0 = critical path, P1 = important, P2 = nice to have)`;
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
SCATTERED_API_CALLS: (issue) => {
|
|
97
|
-
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
98
|
-
return `These components call fetch() or axios directly instead of through a centralized API client:
|
|
99
|
-
${fileList}
|
|
100
|
-
|
|
101
|
-
Create a centralized API client (e.g., src/lib/api.ts or src/services/api.ts) with typed methods for each endpoint. Then refactor each component to use the centralized client.
|
|
102
|
-
|
|
103
|
-
Benefits: single place to add auth headers, error handling, base URL config, and response typing.`;
|
|
104
|
-
},
|
|
105
|
-
|
|
106
|
-
LARGE_FILES: (issue) => {
|
|
107
|
-
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
108
|
-
return `These files are very large and should be split:
|
|
109
|
-
${fileList}
|
|
110
|
-
|
|
111
|
-
For each:
|
|
112
|
-
1. Identify logical sections that could be separate files
|
|
113
|
-
2. Extract into focused modules (one responsibility per file)
|
|
114
|
-
3. Update imports
|
|
115
|
-
4. Run tests after each extraction
|
|
116
|
-
|
|
117
|
-
Don't refactor all at once — one file per session.`;
|
|
118
|
-
},
|
|
119
|
-
|
|
120
|
-
MISSING_SCOPED_CLAUDE_MD: (issue) => {
|
|
121
|
-
const dir = issue.title.includes('frontend') ? 'frontend' : 'backend';
|
|
122
|
-
return `Create ${dir}/CLAUDE.md with ${dir}-specific rules.
|
|
123
|
-
|
|
124
|
-
Keep it under 30 lines. Include:
|
|
125
|
-
- Framework-specific patterns and conventions
|
|
126
|
-
- Naming conventions for this layer
|
|
127
|
-
- Common pitfalls specific to this part of the codebase
|
|
128
|
-
|
|
129
|
-
This scopes rules so Claude Code only loads them when working in this directory.`;
|
|
130
|
-
},
|
|
131
|
-
|
|
132
|
-
UNUSED_DEPENDENCIES: (issue) => {
|
|
133
|
-
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
134
|
-
return `These dependencies may be unused:
|
|
135
|
-
${fileList}
|
|
136
|
-
|
|
137
|
-
For each: search the codebase to confirm it's truly unused (some deps are used implicitly by frameworks or plugins). If confirmed unused, remove with npm uninstall.`;
|
|
138
|
-
},
|
|
139
|
-
|
|
140
|
-
HARDCODED_VALUES: (issue) => {
|
|
141
|
-
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
142
|
-
return `These locations have hardcoded values that should be environment variables:
|
|
143
|
-
${fileList}
|
|
144
|
-
|
|
145
|
-
For each: move to .env and reference via process.env.VARIABLE_NAME (or os.environ in Python). Update .env.example with the variable name and a placeholder value.`;
|
|
146
|
-
},
|
|
147
|
-
|
|
148
|
-
UNUSED_EXPORTS: (issue) => {
|
|
149
|
-
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
150
|
-
return `These exported symbols may not be imported anywhere:
|
|
151
|
-
${fileList}
|
|
152
|
-
|
|
153
|
-
For each: verify it's truly unused, then either remove the export keyword or delete the function/constant entirely. Run tests after each change.`;
|
|
154
|
-
},
|
|
155
|
-
|
|
156
|
-
DUPLICATE_CODE: (issue) => {
|
|
157
|
-
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
158
|
-
return `These files contain duplicated code blocks:
|
|
159
|
-
${fileList}
|
|
160
|
-
|
|
161
|
-
For each group of duplicates: extract the shared logic into a utility function in the appropriate lib/ or utils/ directory. Update all call sites. Run tests after each extraction.`;
|
|
162
|
-
},
|
|
163
|
-
|
|
164
|
-
DEAD_FEATURES: (issue) => {
|
|
165
|
-
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
166
|
-
return `These files appear to be dead or stub features:
|
|
167
|
-
${fileList}
|
|
168
|
-
|
|
169
|
-
For each: determine if the feature is planned (keep stub but add TODO with ticket reference) or abandoned (delete the file). Check git blame for context.`;
|
|
170
|
-
},
|
|
171
|
-
|
|
172
|
-
INLINE_AI_PROMPTS: (issue) => {
|
|
173
|
-
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
174
|
-
return `These files have inline AI/LLM prompts mixed with application logic:
|
|
175
|
-
${fileList}
|
|
176
|
-
|
|
177
|
-
Extract all prompts into a dedicated prompts file (e.g., src/lib/prompts.ts or prompts/). This makes prompts easier to version, A/B test, and iterate on without changing application code.`;
|
|
178
|
-
},
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
export function generatePrompt(issue) {
|
|
182
|
-
const template = PROMPT_TEMPLATES[issue.promptId];
|
|
183
|
-
if (!template) {
|
|
184
|
-
return `Fix: ${issue.title}\n\nImpact: ${issue.impact}\nFiles: ${(issue.files || []).join(', ')}`;
|
|
185
|
-
}
|
|
186
|
-
return template(issue);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export function generateAllPrompts(issues) {
|
|
190
|
-
const sections = [];
|
|
191
|
-
let sessionNum = 1;
|
|
192
|
-
|
|
193
|
-
// Group by severity
|
|
194
|
-
const critical = issues.filter(i => i.severity === 'critical');
|
|
195
|
-
const warnings = issues.filter(i => i.severity === 'warning');
|
|
196
|
-
const info = issues.filter(i => i.severity === 'info');
|
|
197
|
-
|
|
198
|
-
for (const issue of [...critical, ...warnings, ...info]) {
|
|
199
|
-
const prompt = generatePrompt(issue);
|
|
200
|
-
const effortLabel = issue.effort === 'quick' ? '~10 min' :
|
|
201
|
-
issue.effort === 'medium' ? '~30 min' : '~1 hour';
|
|
202
|
-
|
|
203
|
-
sections.push(`## Session ${sessionNum}: ${issue.title}
|
|
204
|
-
**Severity:** ${issue.severity} | **Effort:** ${effortLabel}
|
|
205
|
-
**Impact:** ${issue.impact}
|
|
206
|
-
${issue.files && issue.files.length > 0 ? `**Files:** ${issue.files.join(', ')}` : ''}
|
|
207
|
-
|
|
208
|
-
\`\`\`
|
|
209
|
-
${prompt}
|
|
210
|
-
\`\`\`
|
|
211
|
-
`);
|
|
212
|
-
sessionNum++;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return `# Fix Prompts — Run These in Claude Code (In Order)
|
|
216
|
-
|
|
217
|
-
${sections.join('\n')}
|
|
218
|
-
Run each as a separate Claude Code session. /clear between sessions.
|
|
219
|
-
`;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export function generateReport(issues, projectName) {
|
|
223
|
-
const critical = issues.filter(i => i.severity === 'critical');
|
|
224
|
-
const warnings = issues.filter(i => i.severity === 'warning');
|
|
225
|
-
const info = issues.filter(i => i.severity === 'info');
|
|
226
|
-
const healthy = []; // Could be populated by passing in good checks
|
|
227
|
-
|
|
228
|
-
let report = `# DevForge Doctor Report — ${projectName}
|
|
229
|
-
Generated: ${new Date().toISOString().split('T')[0]}
|
|
230
|
-
|
|
231
|
-
## Summary
|
|
232
|
-
- Critical: ${critical.length} issues
|
|
233
|
-
- Warning: ${warnings.length} issues
|
|
234
|
-
- Info: ${info.length} suggestions
|
|
235
|
-
|
|
236
|
-
`;
|
|
237
|
-
|
|
238
|
-
if (critical.length > 0) {
|
|
239
|
-
report += `## Critical Issues\n\n`;
|
|
240
|
-
for (let i = 0; i < critical.length; i++) {
|
|
241
|
-
const issue = critical[i];
|
|
242
|
-
const prompt = generatePrompt(issue);
|
|
243
|
-
report += `### ${i + 1}. ${issue.title}
|
|
244
|
-
**Impact:** ${issue.impact}
|
|
245
|
-
**Effort:** ${issue.effort}
|
|
246
|
-
${issue.files && issue.files.length > 0 ? `**Files:** ${issue.files.join(', ')}` : ''}
|
|
247
|
-
|
|
248
|
-
**Fix prompt:**
|
|
249
|
-
\`\`\`
|
|
250
|
-
${prompt}
|
|
251
|
-
\`\`\`
|
|
252
|
-
|
|
253
|
-
`;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (warnings.length > 0) {
|
|
258
|
-
report += `## Warnings\n\n`;
|
|
259
|
-
for (let i = 0; i < warnings.length; i++) {
|
|
260
|
-
const issue = warnings[i];
|
|
261
|
-
const prompt = generatePrompt(issue);
|
|
262
|
-
report += `### ${i + 1}. ${issue.title}
|
|
263
|
-
**Impact:** ${issue.impact}
|
|
264
|
-
**Effort:** ${issue.effort}
|
|
265
|
-
${issue.files && issue.files.length > 0 ? `**Files:** ${issue.files.join(', ')}` : ''}
|
|
266
|
-
|
|
267
|
-
**Fix prompt:**
|
|
268
|
-
\`\`\`
|
|
269
|
-
${prompt}
|
|
270
|
-
\`\`\`
|
|
271
|
-
|
|
272
|
-
`;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (info.length > 0) {
|
|
277
|
-
report += `## Suggestions\n\n`;
|
|
278
|
-
for (let i = 0; i < info.length; i++) {
|
|
279
|
-
const issue = info[i];
|
|
280
|
-
const prompt = generatePrompt(issue);
|
|
281
|
-
report += `### ${i + 1}. ${issue.title}
|
|
282
|
-
**Impact:** ${issue.impact}
|
|
283
|
-
**Effort:** ${issue.effort}
|
|
284
|
-
|
|
285
|
-
**Fix prompt:**
|
|
286
|
-
\`\`\`
|
|
287
|
-
${prompt}
|
|
288
|
-
\`\`\`
|
|
289
|
-
|
|
290
|
-
`;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return report;
|
|
295
|
-
}
|
|
1
|
+
const PROMPT_TEMPLATES = {
|
|
2
|
+
CLAUDE_MD_TOO_LONG: (issue) => {
|
|
3
|
+
const lines = issue.title.match(/(\d+) lines/)?.[1] || '?';
|
|
4
|
+
return `Read CLAUDE.md. It's ${lines} lines — too long for Claude Code to follow reliably (target: <150).
|
|
5
|
+
|
|
6
|
+
Propose a split:
|
|
7
|
+
- What stays in CLAUDE.md (universal rules, commands, pitfalls)
|
|
8
|
+
- What moves to .claude/skills/ (schemas, specs, detailed lists)
|
|
9
|
+
- What moves to backend/CLAUDE.md or src/CLAUDE.md (directory-scoped rules)
|
|
10
|
+
|
|
11
|
+
Show me the proposal as a table. Do NOT modify until I approve.`;
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
UNAUTH_ENDPOINT: (issue) => {
|
|
15
|
+
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
16
|
+
return `These endpoints are missing authentication:
|
|
17
|
+
${fileList}
|
|
18
|
+
|
|
19
|
+
For each: add the appropriate auth check.
|
|
20
|
+
- FastAPI: add Depends(get_current_user) and appropriate RBAC
|
|
21
|
+
- Next.js: add getServerSession or auth() check
|
|
22
|
+
Follow the pattern used in endpoints that already have auth.
|
|
23
|
+
Write a test for each endpoint confirming 401 without auth.`;
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
FLAKY_TESTS: (issue) => {
|
|
27
|
+
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
28
|
+
return `These tests use waitForTimeout() or hardcoded delays — they will fail randomly:
|
|
29
|
+
${fileList}
|
|
30
|
+
|
|
31
|
+
For each:
|
|
32
|
+
1. Replace waitForTimeout with waitForSelector, waitForResponse, or other event-based waits
|
|
33
|
+
2. Replace cy.wait(N) with cy.intercept + cy.wait('@alias')
|
|
34
|
+
3. Remove arbitrary setTimeout delays in tests
|
|
35
|
+
4. Run each test 3x to verify it's stable`;
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
CROSS_DOMAIN_IMPORT: (issue) => {
|
|
39
|
+
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
40
|
+
return `These files import across the frontend/backend boundary:
|
|
41
|
+
${fileList}
|
|
42
|
+
|
|
43
|
+
Fix by:
|
|
44
|
+
1. Move shared types to a shared/ directory
|
|
45
|
+
2. Use API calls instead of direct imports
|
|
46
|
+
3. Duplicate small utilities rather than cross-importing
|
|
47
|
+
Never import server code into client bundles.`;
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
BARE_EXCEPT: (issue) => {
|
|
51
|
+
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
52
|
+
return `These locations use bare except: blocks (catch-all that swallows real errors):
|
|
53
|
+
${fileList}
|
|
54
|
+
|
|
55
|
+
For each: replace with a specific exception type.
|
|
56
|
+
- If catching expected errors: except ValueError, except KeyError, etc.
|
|
57
|
+
- If catching any exception for logging: except Exception as e: with logging
|
|
58
|
+
- Never use bare except: — it hides bugs`;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
MISSING_HEALTH: (issue) => {
|
|
62
|
+
return `Add a health check endpoint to the application.
|
|
63
|
+
|
|
64
|
+
For FastAPI: Add a /health endpoint that returns {"status": "ok"} and optionally checks database connectivity.
|
|
65
|
+
For Next.js: Add src/app/api/health/route.ts that returns {"status": "ok"}.
|
|
66
|
+
For both: Include database connectivity check if a database is configured.
|
|
67
|
+
|
|
68
|
+
This endpoint is used by load balancers and monitoring to verify the app is running.`;
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
MISSING_SHUTDOWN: (issue) => {
|
|
72
|
+
return `Add graceful shutdown handling to the application.
|
|
73
|
+
|
|
74
|
+
For FastAPI: Use the lifespan context manager to handle startup/shutdown events.
|
|
75
|
+
For Next.js: The framework handles this, but ensure any custom servers handle SIGTERM.
|
|
76
|
+
For Docker: Ensure the process responds to SIGTERM within the stop_grace_period.
|
|
77
|
+
|
|
78
|
+
On shutdown: close database connections, finish in-flight requests, flush logs.`;
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
MISSING_UAT: () => {
|
|
82
|
+
return `Generate UAT (User Acceptance Test) scenarios for this project.
|
|
83
|
+
|
|
84
|
+
Read the codebase to understand all user-facing features, then create:
|
|
85
|
+
1. docs/uat/UAT_TEMPLATE.md with test scenarios for each feature
|
|
86
|
+
2. docs/uat/UAT_CHECKLIST.csv with a checklist format
|
|
87
|
+
|
|
88
|
+
Each scenario should have:
|
|
89
|
+
- Description (what's being tested)
|
|
90
|
+
- Preconditions
|
|
91
|
+
- Steps (numbered)
|
|
92
|
+
- Expected result
|
|
93
|
+
- Priority (P0 = critical path, P1 = important, P2 = nice to have)`;
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
SCATTERED_API_CALLS: (issue) => {
|
|
97
|
+
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
98
|
+
return `These components call fetch() or axios directly instead of through a centralized API client:
|
|
99
|
+
${fileList}
|
|
100
|
+
|
|
101
|
+
Create a centralized API client (e.g., src/lib/api.ts or src/services/api.ts) with typed methods for each endpoint. Then refactor each component to use the centralized client.
|
|
102
|
+
|
|
103
|
+
Benefits: single place to add auth headers, error handling, base URL config, and response typing.`;
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
LARGE_FILES: (issue) => {
|
|
107
|
+
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
108
|
+
return `These files are very large and should be split:
|
|
109
|
+
${fileList}
|
|
110
|
+
|
|
111
|
+
For each:
|
|
112
|
+
1. Identify logical sections that could be separate files
|
|
113
|
+
2. Extract into focused modules (one responsibility per file)
|
|
114
|
+
3. Update imports
|
|
115
|
+
4. Run tests after each extraction
|
|
116
|
+
|
|
117
|
+
Don't refactor all at once — one file per session.`;
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
MISSING_SCOPED_CLAUDE_MD: (issue) => {
|
|
121
|
+
const dir = issue.title.includes('frontend') ? 'frontend' : 'backend';
|
|
122
|
+
return `Create ${dir}/CLAUDE.md with ${dir}-specific rules.
|
|
123
|
+
|
|
124
|
+
Keep it under 30 lines. Include:
|
|
125
|
+
- Framework-specific patterns and conventions
|
|
126
|
+
- Naming conventions for this layer
|
|
127
|
+
- Common pitfalls specific to this part of the codebase
|
|
128
|
+
|
|
129
|
+
This scopes rules so Claude Code only loads them when working in this directory.`;
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
UNUSED_DEPENDENCIES: (issue) => {
|
|
133
|
+
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
134
|
+
return `These dependencies may be unused:
|
|
135
|
+
${fileList}
|
|
136
|
+
|
|
137
|
+
For each: search the codebase to confirm it's truly unused (some deps are used implicitly by frameworks or plugins). If confirmed unused, remove with npm uninstall.`;
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
HARDCODED_VALUES: (issue) => {
|
|
141
|
+
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
142
|
+
return `These locations have hardcoded values that should be environment variables:
|
|
143
|
+
${fileList}
|
|
144
|
+
|
|
145
|
+
For each: move to .env and reference via process.env.VARIABLE_NAME (or os.environ in Python). Update .env.example with the variable name and a placeholder value.`;
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
UNUSED_EXPORTS: (issue) => {
|
|
149
|
+
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
150
|
+
return `These exported symbols may not be imported anywhere:
|
|
151
|
+
${fileList}
|
|
152
|
+
|
|
153
|
+
For each: verify it's truly unused, then either remove the export keyword or delete the function/constant entirely. Run tests after each change.`;
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
DUPLICATE_CODE: (issue) => {
|
|
157
|
+
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
158
|
+
return `These files contain duplicated code blocks:
|
|
159
|
+
${fileList}
|
|
160
|
+
|
|
161
|
+
For each group of duplicates: extract the shared logic into a utility function in the appropriate lib/ or utils/ directory. Update all call sites. Run tests after each extraction.`;
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
DEAD_FEATURES: (issue) => {
|
|
165
|
+
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
166
|
+
return `These files appear to be dead or stub features:
|
|
167
|
+
${fileList}
|
|
168
|
+
|
|
169
|
+
For each: determine if the feature is planned (keep stub but add TODO with ticket reference) or abandoned (delete the file). Check git blame for context.`;
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
INLINE_AI_PROMPTS: (issue) => {
|
|
173
|
+
const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
|
|
174
|
+
return `These files have inline AI/LLM prompts mixed with application logic:
|
|
175
|
+
${fileList}
|
|
176
|
+
|
|
177
|
+
Extract all prompts into a dedicated prompts file (e.g., src/lib/prompts.ts or prompts/). This makes prompts easier to version, A/B test, and iterate on without changing application code.`;
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export function generatePrompt(issue) {
|
|
182
|
+
const template = PROMPT_TEMPLATES[issue.promptId];
|
|
183
|
+
if (!template) {
|
|
184
|
+
return `Fix: ${issue.title}\n\nImpact: ${issue.impact}\nFiles: ${(issue.files || []).join(', ')}`;
|
|
185
|
+
}
|
|
186
|
+
return template(issue);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function generateAllPrompts(issues) {
|
|
190
|
+
const sections = [];
|
|
191
|
+
let sessionNum = 1;
|
|
192
|
+
|
|
193
|
+
// Group by severity
|
|
194
|
+
const critical = issues.filter(i => i.severity === 'critical');
|
|
195
|
+
const warnings = issues.filter(i => i.severity === 'warning');
|
|
196
|
+
const info = issues.filter(i => i.severity === 'info');
|
|
197
|
+
|
|
198
|
+
for (const issue of [...critical, ...warnings, ...info]) {
|
|
199
|
+
const prompt = generatePrompt(issue);
|
|
200
|
+
const effortLabel = issue.effort === 'quick' ? '~10 min' :
|
|
201
|
+
issue.effort === 'medium' ? '~30 min' : '~1 hour';
|
|
202
|
+
|
|
203
|
+
sections.push(`## Session ${sessionNum}: ${issue.title}
|
|
204
|
+
**Severity:** ${issue.severity} | **Effort:** ${effortLabel}
|
|
205
|
+
**Impact:** ${issue.impact}
|
|
206
|
+
${issue.files && issue.files.length > 0 ? `**Files:** ${issue.files.join(', ')}` : ''}
|
|
207
|
+
|
|
208
|
+
\`\`\`
|
|
209
|
+
${prompt}
|
|
210
|
+
\`\`\`
|
|
211
|
+
`);
|
|
212
|
+
sessionNum++;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return `# Fix Prompts — Run These in Claude Code (In Order)
|
|
216
|
+
|
|
217
|
+
${sections.join('\n')}
|
|
218
|
+
Run each as a separate Claude Code session. /clear between sessions.
|
|
219
|
+
`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function generateReport(issues, projectName) {
|
|
223
|
+
const critical = issues.filter(i => i.severity === 'critical');
|
|
224
|
+
const warnings = issues.filter(i => i.severity === 'warning');
|
|
225
|
+
const info = issues.filter(i => i.severity === 'info');
|
|
226
|
+
const healthy = []; // Could be populated by passing in good checks
|
|
227
|
+
|
|
228
|
+
let report = `# DevForge Doctor Report — ${projectName}
|
|
229
|
+
Generated: ${new Date().toISOString().split('T')[0]}
|
|
230
|
+
|
|
231
|
+
## Summary
|
|
232
|
+
- Critical: ${critical.length} issues
|
|
233
|
+
- Warning: ${warnings.length} issues
|
|
234
|
+
- Info: ${info.length} suggestions
|
|
235
|
+
|
|
236
|
+
`;
|
|
237
|
+
|
|
238
|
+
if (critical.length > 0) {
|
|
239
|
+
report += `## Critical Issues\n\n`;
|
|
240
|
+
for (let i = 0; i < critical.length; i++) {
|
|
241
|
+
const issue = critical[i];
|
|
242
|
+
const prompt = generatePrompt(issue);
|
|
243
|
+
report += `### ${i + 1}. ${issue.title}
|
|
244
|
+
**Impact:** ${issue.impact}
|
|
245
|
+
**Effort:** ${issue.effort}
|
|
246
|
+
${issue.files && issue.files.length > 0 ? `**Files:** ${issue.files.join(', ')}` : ''}
|
|
247
|
+
|
|
248
|
+
**Fix prompt:**
|
|
249
|
+
\`\`\`
|
|
250
|
+
${prompt}
|
|
251
|
+
\`\`\`
|
|
252
|
+
|
|
253
|
+
`;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (warnings.length > 0) {
|
|
258
|
+
report += `## Warnings\n\n`;
|
|
259
|
+
for (let i = 0; i < warnings.length; i++) {
|
|
260
|
+
const issue = warnings[i];
|
|
261
|
+
const prompt = generatePrompt(issue);
|
|
262
|
+
report += `### ${i + 1}. ${issue.title}
|
|
263
|
+
**Impact:** ${issue.impact}
|
|
264
|
+
**Effort:** ${issue.effort}
|
|
265
|
+
${issue.files && issue.files.length > 0 ? `**Files:** ${issue.files.join(', ')}` : ''}
|
|
266
|
+
|
|
267
|
+
**Fix prompt:**
|
|
268
|
+
\`\`\`
|
|
269
|
+
${prompt}
|
|
270
|
+
\`\`\`
|
|
271
|
+
|
|
272
|
+
`;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (info.length > 0) {
|
|
277
|
+
report += `## Suggestions\n\n`;
|
|
278
|
+
for (let i = 0; i < info.length; i++) {
|
|
279
|
+
const issue = info[i];
|
|
280
|
+
const prompt = generatePrompt(issue);
|
|
281
|
+
report += `### ${i + 1}. ${issue.title}
|
|
282
|
+
**Impact:** ${issue.impact}
|
|
283
|
+
**Effort:** ${issue.effort}
|
|
284
|
+
|
|
285
|
+
**Fix prompt:**
|
|
286
|
+
\`\`\`
|
|
287
|
+
${prompt}
|
|
288
|
+
\`\`\`
|
|
289
|
+
|
|
290
|
+
`;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return report;
|
|
295
|
+
}
|