popeye-cli 1.9.5 → 1.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.
- package/cheatsheet.md +65 -0
- package/dist/cli/commands/debug-context.d.ts +64 -0
- package/dist/cli/commands/debug-context.d.ts.map +1 -0
- package/dist/cli/commands/debug-context.js +221 -0
- package/dist/cli/commands/debug-context.js.map +1 -0
- package/dist/cli/commands/debug-prompts.d.ts +25 -0
- package/dist/cli/commands/debug-prompts.d.ts.map +1 -0
- package/dist/cli/commands/debug-prompts.js +80 -0
- package/dist/cli/commands/debug-prompts.js.map +1 -0
- package/dist/cli/commands/debug.d.ts +68 -0
- package/dist/cli/commands/debug.d.ts.map +1 -0
- package/dist/cli/commands/debug.js +543 -0
- package/dist/cli/commands/debug.js.map +1 -0
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +25 -0
- package/dist/cli/interactive.js.map +1 -1
- package/dist/generators/all.d.ts.map +1 -1
- package/dist/generators/all.js +2 -0
- package/dist/generators/all.js.map +1 -1
- package/dist/generators/templates/database-docker.d.ts.map +1 -1
- package/dist/generators/templates/database-docker.js +10 -0
- package/dist/generators/templates/database-docker.js.map +1 -1
- package/dist/generators/templates/fullstack.d.ts +4 -1
- package/dist/generators/templates/fullstack.d.ts.map +1 -1
- package/dist/generators/templates/fullstack.js +6 -2
- package/dist/generators/templates/fullstack.js.map +1 -1
- package/package.json +1 -1
- package/skills/ARBITRATOR.md +137 -0
- package/skills/ARCHITECT.md +167 -0
- package/skills/AUDITOR.md +128 -0
- package/skills/AUDIT_REPORT_SCHEMA.md +20 -0
- package/skills/BACKEND_PROGRAMMER.md +95 -0
- package/skills/CONSENSUS_PACKET_SCHEMA.md +166 -0
- package/skills/DB_EXPERT.md +106 -0
- package/skills/DEBUGGER.md +286 -0
- package/skills/DISPATCHER.md +157 -0
- package/skills/FRONTEND_PROGRAMMER.md +84 -0
- package/skills/JOURNALIST.md +247 -0
- package/skills/MARKETING_EXPERT.md +23 -0
- package/skills/PHASE_GATE_ENGINE_SPEC.md +78 -0
- package/skills/PLAN_PACKET_SCHEMA.md +222 -0
- package/skills/POPEYE_CONSTITUTION.md +177 -0
- package/skills/POPEYE_FULL_AUTONOMY_PIPELINE.md +484 -0
- package/skills/PRODUCTION_READINESS_SCHEMA.md +19 -0
- package/skills/QA_TESTER.md +40 -0
- package/skills/RCA_PACKET_SCHEMA.md +22 -0
- package/skills/RELEASE_MANAGER.md +60 -0
- package/skills/REVIEWER.md +133 -0
- package/skills/SOCIAL_EXPERT.md +22 -0
- package/skills/UI_UX_SPECIALIST.md +22 -0
- package/skills/WEBSITE_PROGRAMMER.md +37 -0
- package/src/cli/commands/debug-context.ts +265 -0
- package/src/cli/commands/debug-prompts.ts +91 -0
- package/src/cli/commands/debug.ts +662 -0
- package/src/cli/commands/index.ts +1 -0
- package/src/cli/index.ts +2 -0
- package/src/cli/interactive.ts +27 -0
- package/src/generators/all.ts +2 -0
- package/src/generators/templates/database-docker.ts +10 -0
- package/src/generators/templates/fullstack.ts +6 -2
- package/tests/cli/commands/debug.test.ts +376 -0
package/src/cli/interactive.ts
CHANGED
|
@@ -847,6 +847,7 @@ function showHelp(): void {
|
|
|
847
847
|
['/db [action]', 'Database management (status/configure/apply)'],
|
|
848
848
|
['/doctor', 'Run database and project readiness checks'],
|
|
849
849
|
['/review', 'Run post-build audit/review with findings and recovery'],
|
|
850
|
+
['/debug', 'Start interactive debugging session (use /back to return)'],
|
|
850
851
|
['/clear', 'Clear screen'],
|
|
851
852
|
['/exit', 'Exit Popeye'],
|
|
852
853
|
];
|
|
@@ -1028,6 +1029,11 @@ async function handleInput(input: string, state: SessionState): Promise<boolean>
|
|
|
1028
1029
|
await handleReviewSlashCommand(state, args);
|
|
1029
1030
|
break;
|
|
1030
1031
|
|
|
1032
|
+
case '/debug':
|
|
1033
|
+
case '/dbg':
|
|
1034
|
+
await handleDebugSlashCommand(state);
|
|
1035
|
+
break;
|
|
1036
|
+
|
|
1031
1037
|
default:
|
|
1032
1038
|
printError(`Unknown command: ${cmd}`);
|
|
1033
1039
|
printInfo('Type /help for available commands');
|
|
@@ -1208,6 +1214,27 @@ async function handleReviewSlashCommand(state: SessionState, args: string[] = []
|
|
|
1208
1214
|
}
|
|
1209
1215
|
}
|
|
1210
1216
|
|
|
1217
|
+
/**
|
|
1218
|
+
* Handle /debug slash command - start interactive debugging session
|
|
1219
|
+
*/
|
|
1220
|
+
async function handleDebugSlashCommand(state: SessionState): Promise<void> {
|
|
1221
|
+
if (!state.projectDir) {
|
|
1222
|
+
printError('No active project. Create or resume a project first.');
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
try {
|
|
1227
|
+
const { runDebugSession } = await import('./commands/debug.js');
|
|
1228
|
+
await runDebugSession({
|
|
1229
|
+
projectDir: state.projectDir,
|
|
1230
|
+
language: state.language,
|
|
1231
|
+
});
|
|
1232
|
+
printInfo('Returned to main Popeye session.');
|
|
1233
|
+
} catch (err) {
|
|
1234
|
+
printError(err instanceof Error ? err.message : 'Debug session failed');
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1211
1238
|
/**
|
|
1212
1239
|
* Handle /overview command - full project plan and milestone review
|
|
1213
1240
|
*/
|
package/src/generators/all.ts
CHANGED
|
@@ -150,6 +150,8 @@ export function generateAllDockerComposeWithDb(projectName: string): string {
|
|
|
150
150
|
dockerfile: Dockerfile
|
|
151
151
|
ports:
|
|
152
152
|
- "8000:8000"
|
|
153
|
+
env_file:
|
|
154
|
+
- ./apps/backend/.env
|
|
153
155
|
environment:
|
|
154
156
|
- DEBUG=false
|
|
155
157
|
- FRONTEND_URL=http://frontend:80
|
|
@@ -211,5 +213,13 @@ DB_VECTOR_REQUIRED=true
|
|
|
211
213
|
|
|
212
214
|
# Admin Wizard
|
|
213
215
|
ADMIN_SETUP_TOKEN=change-me-to-a-random-string
|
|
216
|
+
|
|
217
|
+
# JWT Configuration
|
|
218
|
+
SECRET_KEY=change-me-in-production
|
|
219
|
+
|
|
220
|
+
# Google OAuth2 (optional - uncomment to enable)
|
|
221
|
+
# GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
|
|
222
|
+
# GOOGLE_CLIENT_SECRET=your-client-secret
|
|
223
|
+
# GOOGLE_REDIRECT_URI=http://localhost:8000/api/v1/auth/google/callback
|
|
214
224
|
`;
|
|
215
225
|
}
|
|
@@ -495,15 +495,19 @@ DATABASE_URL=sqlite:///./data/app.db
|
|
|
495
495
|
|
|
496
496
|
/**
|
|
497
497
|
* Generate UI spec placeholder for fullstack project
|
|
498
|
+
*
|
|
499
|
+
* @param projectName - The project name
|
|
500
|
+
* @param brandColor - Optional brand primary color hex (e.g. '#2563EB')
|
|
498
501
|
*/
|
|
499
|
-
export function generateUiSpec(projectName: string): string {
|
|
502
|
+
export function generateUiSpec(projectName: string, brandColor?: string): string {
|
|
503
|
+
const primary = brandColor || '#3B82F6';
|
|
500
504
|
return JSON.stringify(
|
|
501
505
|
{
|
|
502
506
|
name: projectName,
|
|
503
507
|
version: '1.0',
|
|
504
508
|
theme: {
|
|
505
509
|
colors: {
|
|
506
|
-
primary
|
|
510
|
+
primary,
|
|
507
511
|
secondary: '#6B7280',
|
|
508
512
|
accent: '#10B981',
|
|
509
513
|
},
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the debug command
|
|
3
|
+
* Tests extractPathsFromError, detectTechFromError, selectRelevantFiles,
|
|
4
|
+
* buildDebugPrompt, and gatherDebugContext.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
8
|
+
import {
|
|
9
|
+
extractPathsFromError,
|
|
10
|
+
detectTechFromError,
|
|
11
|
+
selectRelevantFiles,
|
|
12
|
+
isConfigFile,
|
|
13
|
+
extractImagePaths,
|
|
14
|
+
} from '../../../src/cli/commands/debug-context.js';
|
|
15
|
+
import type { FileIndexEntry } from '../../../src/cli/commands/debug-context.js';
|
|
16
|
+
import {
|
|
17
|
+
getDebugSystemPrompt,
|
|
18
|
+
formatConversationHistory,
|
|
19
|
+
} from '../../../src/cli/commands/debug-prompts.js';
|
|
20
|
+
import type { DebugMessage } from '../../../src/cli/commands/debug-prompts.js';
|
|
21
|
+
import { buildDebugPrompt } from '../../../src/cli/commands/debug.js';
|
|
22
|
+
import type { DebugContext } from '../../../src/cli/commands/debug.js';
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// extractPathsFromError
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
describe('extractPathsFromError', () => {
|
|
28
|
+
it('should extract paths from Python tracebacks', () => {
|
|
29
|
+
const error = `Traceback (most recent call last):
|
|
30
|
+
File "/app/src/module/service.py", line 42, in process
|
|
31
|
+
result = await db.execute(query)
|
|
32
|
+
File "/app/src/database/connection.py", line 15, in execute
|
|
33
|
+
raise ConnectionError("Connection refused")`;
|
|
34
|
+
|
|
35
|
+
const paths = extractPathsFromError(error);
|
|
36
|
+
expect(paths).toContain('src/module/service.py');
|
|
37
|
+
expect(paths).toContain('src/database/connection.py');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should extract paths from TypeScript errors', () => {
|
|
41
|
+
const error = `src/components/App.tsx(15,3): error TS2339: Property 'foo' does not exist.
|
|
42
|
+
src/utils/helpers.ts:42:10 - error TS7006: Parameter implicitly has an 'any' type.`;
|
|
43
|
+
|
|
44
|
+
const paths = extractPathsFromError(error);
|
|
45
|
+
expect(paths).toContain('src/components/App.tsx');
|
|
46
|
+
expect(paths).toContain('src/utils/helpers.ts');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should return empty array when no paths found', () => {
|
|
50
|
+
const error = 'Error: Something went wrong with no file references';
|
|
51
|
+
const paths = extractPathsFromError(error);
|
|
52
|
+
expect(paths).toEqual([]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should deduplicate paths', () => {
|
|
56
|
+
const error = `Error in src/main.ts:10:5
|
|
57
|
+
Another error in src/main.ts:20:3`;
|
|
58
|
+
|
|
59
|
+
const paths = extractPathsFromError(error);
|
|
60
|
+
const mainCount = paths.filter((p) => p === 'src/main.ts').length;
|
|
61
|
+
expect(mainCount).toBe(1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should extract module-not-found paths', () => {
|
|
65
|
+
const error = `Cannot find module './services/auth' from 'src/routes/login.ts'`;
|
|
66
|
+
const paths = extractPathsFromError(error);
|
|
67
|
+
expect(paths).toContain('services/auth');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should extract Python module-not-found paths', () => {
|
|
71
|
+
const error = `ModuleNotFoundError: No module named 'src.services.auth'`;
|
|
72
|
+
const paths = extractPathsFromError(error);
|
|
73
|
+
expect(paths).toContain('src/services/auth');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should strip /app/ prefix from Docker container paths', () => {
|
|
77
|
+
const error = `File "/app/src/main.py", line 1`;
|
|
78
|
+
const paths = extractPathsFromError(error);
|
|
79
|
+
expect(paths).toContain('src/main.py');
|
|
80
|
+
expect(paths).not.toContain('/app/src/main.py');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// detectTechFromError
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
describe('detectTechFromError', () => {
|
|
88
|
+
it('should detect alembic-related tags', () => {
|
|
89
|
+
const error = `alembic.util.exc.CommandError: Can't locate revision identified by '12345'`;
|
|
90
|
+
const { tags } = detectTechFromError(error);
|
|
91
|
+
expect(tags).toContain('alembic');
|
|
92
|
+
expect(tags).toContain('database');
|
|
93
|
+
expect(tags).toContain('migration');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should detect vite/bundler tags', () => {
|
|
97
|
+
const error = `[vite] Internal server error: Module not found: @/components/Button`;
|
|
98
|
+
const { tags } = detectTechFromError(error);
|
|
99
|
+
expect(tags).toContain('vite');
|
|
100
|
+
expect(tags).toContain('bundler');
|
|
101
|
+
expect(tags).toContain('frontend');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should return empty tags for generic errors', () => {
|
|
105
|
+
const error = `TypeError: Cannot read properties of undefined`;
|
|
106
|
+
const { tags } = detectTechFromError(error);
|
|
107
|
+
expect(tags).toEqual([]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should detect multiple technologies', () => {
|
|
111
|
+
const error = `docker-compose error: postgres service unhealthy, fastapi startup failed`;
|
|
112
|
+
const { tags } = detectTechFromError(error);
|
|
113
|
+
expect(tags).toContain('docker');
|
|
114
|
+
expect(tags).toContain('compose');
|
|
115
|
+
expect(tags).toContain('postgres');
|
|
116
|
+
expect(tags).toContain('fastapi');
|
|
117
|
+
expect(tags).toContain('python');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should detect redis tags', () => {
|
|
121
|
+
const error = `redis.exceptions.ConnectionError: Error 111 connecting to redis:6379`;
|
|
122
|
+
const { tags } = detectTechFromError(error);
|
|
123
|
+
expect(tags).toContain('redis');
|
|
124
|
+
expect(tags).toContain('cache');
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// selectRelevantFiles
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
describe('selectRelevantFiles', () => {
|
|
132
|
+
const baseIndex: FileIndexEntry[] = [
|
|
133
|
+
{ relativePath: 'src/main.py', size: 500, mtime: 1000, isConfig: false },
|
|
134
|
+
{ relativePath: 'src/routes/auth.py', size: 300, mtime: 1000, isConfig: false },
|
|
135
|
+
{ relativePath: 'src/routes/users.py', size: 400, mtime: 1000, isConfig: false },
|
|
136
|
+
{ relativePath: 'src/database/models.py', size: 600, mtime: 1000, isConfig: false },
|
|
137
|
+
{ relativePath: 'alembic/versions/001_init.py', size: 200, mtime: 1000, isConfig: false },
|
|
138
|
+
{ relativePath: 'docker-compose.yml', size: 150, mtime: 1000, isConfig: true },
|
|
139
|
+
{ relativePath: 'package.json', size: 100, mtime: 1000, isConfig: true },
|
|
140
|
+
{ relativePath: 'requirements.txt', size: 80, mtime: 1000, isConfig: true },
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
it('should select files mentioned in the error', () => {
|
|
144
|
+
const errorPaths = ['src/routes/auth.py'];
|
|
145
|
+
const result = selectRelevantFiles(baseIndex, errorPaths, []);
|
|
146
|
+
expect(result).toContain('src/routes/auth.py');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should include sibling files from the same directory', () => {
|
|
150
|
+
const errorPaths = ['src/routes/auth.py'];
|
|
151
|
+
const result = selectRelevantFiles(baseIndex, errorPaths, []);
|
|
152
|
+
expect(result).toContain('src/routes/auth.py');
|
|
153
|
+
expect(result).toContain('src/routes/users.py');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should include migration files when database tags detected', () => {
|
|
157
|
+
const result = selectRelevantFiles(baseIndex, [], ['database', 'migration']);
|
|
158
|
+
expect(result).toContain('alembic/versions/001_init.py');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should include docker files when docker tags detected', () => {
|
|
162
|
+
const result = selectRelevantFiles(baseIndex, [], ['docker', 'compose']);
|
|
163
|
+
expect(result).toContain('docker-compose.yml');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should fall back to config files when few matches', () => {
|
|
167
|
+
const result = selectRelevantFiles(baseIndex, ['nonexistent.py'], []);
|
|
168
|
+
const hasConfig = result.some((p) =>
|
|
169
|
+
p === 'package.json' || p === 'docker-compose.yml' || p === 'requirements.txt'
|
|
170
|
+
);
|
|
171
|
+
expect(hasConfig).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should return empty array for empty file index', () => {
|
|
175
|
+
const result = selectRelevantFiles([], ['src/main.py'], ['python']);
|
|
176
|
+
expect(result).toEqual([]);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should limit results to MAX_FILES', () => {
|
|
180
|
+
// Create large index
|
|
181
|
+
const largeIndex: FileIndexEntry[] = Array.from({ length: 100 }, (_, i) => ({
|
|
182
|
+
relativePath: `src/file${i}.py`,
|
|
183
|
+
size: 100,
|
|
184
|
+
mtime: 1000,
|
|
185
|
+
isConfig: false,
|
|
186
|
+
}));
|
|
187
|
+
// All files match by being in src/
|
|
188
|
+
const errorPaths = Array.from({ length: 50 }, (_, i) => `src/file${i}.py`);
|
|
189
|
+
const result = selectRelevantFiles(largeIndex, errorPaths, []);
|
|
190
|
+
expect(result.length).toBeLessThanOrEqual(15);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
// isConfigFile
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
describe('isConfigFile', () => {
|
|
198
|
+
it('should identify package.json as config', () => {
|
|
199
|
+
expect(isConfigFile('package.json')).toBe(true);
|
|
200
|
+
expect(isConfigFile('apps/backend/package.json')).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should identify docker-compose.yml as config', () => {
|
|
204
|
+
expect(isConfigFile('docker-compose.yml')).toBe(true);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should not identify source files as config', () => {
|
|
208
|
+
expect(isConfigFile('src/main.ts')).toBe(false);
|
|
209
|
+
expect(isConfigFile('src/utils/helpers.py')).toBe(false);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
// extractImagePaths
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
describe('extractImagePaths', () => {
|
|
217
|
+
it('should extract quoted screenshot paths', () => {
|
|
218
|
+
const input = `'/var/folders/abc/Screenshot 2026-02-19.png' the logo is missing`;
|
|
219
|
+
const paths = extractImagePaths(input);
|
|
220
|
+
expect(paths).toContain('/var/folders/abc/Screenshot 2026-02-19.png');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should extract double-quoted image paths', () => {
|
|
224
|
+
const input = `Look at "/tmp/error.jpg" for the bug`;
|
|
225
|
+
const paths = extractImagePaths(input);
|
|
226
|
+
expect(paths).toContain('/tmp/error.jpg');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should extract unquoted absolute paths', () => {
|
|
230
|
+
const input = `See /Users/dev/screenshots/bug.png for details`;
|
|
231
|
+
const paths = extractImagePaths(input);
|
|
232
|
+
expect(paths).toContain('/Users/dev/screenshots/bug.png');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should return empty array when no images found', () => {
|
|
236
|
+
const input = `TypeError: Cannot read properties of undefined`;
|
|
237
|
+
const paths = extractImagePaths(input);
|
|
238
|
+
expect(paths).toEqual([]);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should handle multiple image paths', () => {
|
|
242
|
+
const input = `'/tmp/before.png' vs '/tmp/after.jpg'`;
|
|
243
|
+
const paths = extractImagePaths(input);
|
|
244
|
+
expect(paths.length).toBe(2);
|
|
245
|
+
expect(paths).toContain('/tmp/before.png');
|
|
246
|
+
expect(paths).toContain('/tmp/after.jpg');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should support webp and gif extensions', () => {
|
|
250
|
+
const input = `'/tmp/screen.webp' and '/tmp/anim.gif'`;
|
|
251
|
+
const paths = extractImagePaths(input);
|
|
252
|
+
expect(paths).toContain('/tmp/screen.webp');
|
|
253
|
+
expect(paths).toContain('/tmp/anim.gif');
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// buildDebugPrompt
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
describe('buildDebugPrompt', () => {
|
|
261
|
+
const baseContext: DebugContext = {
|
|
262
|
+
projectDir: '/test/project',
|
|
263
|
+
structureSummary: 'src/\n main.ts\n utils/',
|
|
264
|
+
purpose: 'A test REST API',
|
|
265
|
+
claudeMd: '# Project Rules\nUse TypeScript',
|
|
266
|
+
readme: '# Test Project',
|
|
267
|
+
anchorFiles: { 'package.json': '{"name": "test"}' },
|
|
268
|
+
fileIndex: [],
|
|
269
|
+
language: 'backend',
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
it('should include project context, relevant files, history, and current message', () => {
|
|
273
|
+
const history: DebugMessage[] = [
|
|
274
|
+
{ role: 'user', content: 'prev error' },
|
|
275
|
+
{ role: 'assistant', content: 'prev fix' },
|
|
276
|
+
];
|
|
277
|
+
const relevantContents = { 'src/main.ts': 'console.log("hello")' };
|
|
278
|
+
|
|
279
|
+
const prompt = buildDebugPrompt(baseContext, history, 'new error here', relevantContents);
|
|
280
|
+
|
|
281
|
+
expect(prompt).toContain('A test REST API');
|
|
282
|
+
expect(prompt).toContain('backend');
|
|
283
|
+
expect(prompt).toContain('package.json');
|
|
284
|
+
expect(prompt).toContain('console.log("hello")');
|
|
285
|
+
expect(prompt).toContain('prev error');
|
|
286
|
+
expect(prompt).toContain('prev fix');
|
|
287
|
+
expect(prompt).toContain('new error here');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should omit history section when history is empty', () => {
|
|
291
|
+
const prompt = buildDebugPrompt(baseContext, [], 'some error', {});
|
|
292
|
+
expect(prompt).not.toContain('Previous Conversation');
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should include CLAUDE.md when present', () => {
|
|
296
|
+
const prompt = buildDebugPrompt(baseContext, [], 'error', {});
|
|
297
|
+
expect(prompt).toContain('Use TypeScript');
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('should handle missing CLAUDE.md gracefully', () => {
|
|
301
|
+
const ctx = { ...baseContext, claudeMd: undefined };
|
|
302
|
+
const prompt = buildDebugPrompt(ctx, [], 'error', {});
|
|
303
|
+
expect(prompt).toContain('error');
|
|
304
|
+
expect(prompt).not.toContain('CLAUDE.md');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('should handle empty relevant files', () => {
|
|
308
|
+
const prompt = buildDebugPrompt(baseContext, [], 'error', {});
|
|
309
|
+
expect(prompt).not.toContain('Relevant Source Files');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should include screenshot instructions when image paths provided', () => {
|
|
313
|
+
const prompt = buildDebugPrompt(baseContext, [], 'logo is missing', {}, ['/tmp/screenshot.png']);
|
|
314
|
+
expect(prompt).toContain('Screenshots Attached');
|
|
315
|
+
expect(prompt).toContain('/tmp/screenshot.png');
|
|
316
|
+
expect(prompt).toContain('Read tool');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('should omit screenshot section when no images', () => {
|
|
320
|
+
const prompt = buildDebugPrompt(baseContext, [], 'error', {}, []);
|
|
321
|
+
expect(prompt).not.toContain('Screenshots Attached');
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
// getDebugSystemPrompt
|
|
327
|
+
// ---------------------------------------------------------------------------
|
|
328
|
+
describe('getDebugSystemPrompt', () => {
|
|
329
|
+
it('should return a non-empty system prompt', () => {
|
|
330
|
+
const prompt = getDebugSystemPrompt();
|
|
331
|
+
expect(prompt.length).toBeGreaterThan(100);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('should include required response sections', () => {
|
|
335
|
+
const prompt = getDebugSystemPrompt();
|
|
336
|
+
expect(prompt).toContain('Diagnosis');
|
|
337
|
+
expect(prompt).toContain('Evidence');
|
|
338
|
+
expect(prompt).toContain('Proposed Fix');
|
|
339
|
+
expect(prompt).toContain('Commands to Verify');
|
|
340
|
+
expect(prompt).toContain('Ready to Apply');
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('should include screenshot/image instructions', () => {
|
|
344
|
+
const prompt = getDebugSystemPrompt();
|
|
345
|
+
expect(prompt).toContain('Screenshots');
|
|
346
|
+
expect(prompt).toContain('Read');
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// ---------------------------------------------------------------------------
|
|
351
|
+
// formatConversationHistory
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
describe('formatConversationHistory', () => {
|
|
354
|
+
it('should format messages with role labels', () => {
|
|
355
|
+
const history: DebugMessage[] = [
|
|
356
|
+
{ role: 'user', content: 'my error' },
|
|
357
|
+
{ role: 'assistant', content: 'here is the fix' },
|
|
358
|
+
];
|
|
359
|
+
const formatted = formatConversationHistory(history);
|
|
360
|
+
expect(formatted).toContain('**User:**');
|
|
361
|
+
expect(formatted).toContain('**Assistant:**');
|
|
362
|
+
expect(formatted).toContain('my error');
|
|
363
|
+
expect(formatted).toContain('here is the fix');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('should return empty string for empty history', () => {
|
|
367
|
+
const formatted = formatConversationHistory([]);
|
|
368
|
+
expect(formatted).toBe('');
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('should include Previous Conversation header', () => {
|
|
372
|
+
const history: DebugMessage[] = [{ role: 'user', content: 'test' }];
|
|
373
|
+
const formatted = formatConversationHistory(history);
|
|
374
|
+
expect(formatted).toContain('## Previous Conversation');
|
|
375
|
+
});
|
|
376
|
+
});
|