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.
Files changed (68) hide show
  1. package/cheatsheet.md +65 -0
  2. package/dist/cli/commands/debug-context.d.ts +64 -0
  3. package/dist/cli/commands/debug-context.d.ts.map +1 -0
  4. package/dist/cli/commands/debug-context.js +221 -0
  5. package/dist/cli/commands/debug-context.js.map +1 -0
  6. package/dist/cli/commands/debug-prompts.d.ts +25 -0
  7. package/dist/cli/commands/debug-prompts.d.ts.map +1 -0
  8. package/dist/cli/commands/debug-prompts.js +80 -0
  9. package/dist/cli/commands/debug-prompts.js.map +1 -0
  10. package/dist/cli/commands/debug.d.ts +68 -0
  11. package/dist/cli/commands/debug.d.ts.map +1 -0
  12. package/dist/cli/commands/debug.js +543 -0
  13. package/dist/cli/commands/debug.js.map +1 -0
  14. package/dist/cli/commands/index.d.ts +1 -0
  15. package/dist/cli/commands/index.d.ts.map +1 -1
  16. package/dist/cli/commands/index.js +1 -0
  17. package/dist/cli/commands/index.js.map +1 -1
  18. package/dist/cli/index.d.ts.map +1 -1
  19. package/dist/cli/index.js +2 -1
  20. package/dist/cli/index.js.map +1 -1
  21. package/dist/cli/interactive.d.ts.map +1 -1
  22. package/dist/cli/interactive.js +25 -0
  23. package/dist/cli/interactive.js.map +1 -1
  24. package/dist/generators/all.d.ts.map +1 -1
  25. package/dist/generators/all.js +2 -0
  26. package/dist/generators/all.js.map +1 -1
  27. package/dist/generators/templates/database-docker.d.ts.map +1 -1
  28. package/dist/generators/templates/database-docker.js +10 -0
  29. package/dist/generators/templates/database-docker.js.map +1 -1
  30. package/dist/generators/templates/fullstack.d.ts +4 -1
  31. package/dist/generators/templates/fullstack.d.ts.map +1 -1
  32. package/dist/generators/templates/fullstack.js +6 -2
  33. package/dist/generators/templates/fullstack.js.map +1 -1
  34. package/package.json +1 -1
  35. package/skills/ARBITRATOR.md +137 -0
  36. package/skills/ARCHITECT.md +167 -0
  37. package/skills/AUDITOR.md +128 -0
  38. package/skills/AUDIT_REPORT_SCHEMA.md +20 -0
  39. package/skills/BACKEND_PROGRAMMER.md +95 -0
  40. package/skills/CONSENSUS_PACKET_SCHEMA.md +166 -0
  41. package/skills/DB_EXPERT.md +106 -0
  42. package/skills/DEBUGGER.md +286 -0
  43. package/skills/DISPATCHER.md +157 -0
  44. package/skills/FRONTEND_PROGRAMMER.md +84 -0
  45. package/skills/JOURNALIST.md +247 -0
  46. package/skills/MARKETING_EXPERT.md +23 -0
  47. package/skills/PHASE_GATE_ENGINE_SPEC.md +78 -0
  48. package/skills/PLAN_PACKET_SCHEMA.md +222 -0
  49. package/skills/POPEYE_CONSTITUTION.md +177 -0
  50. package/skills/POPEYE_FULL_AUTONOMY_PIPELINE.md +484 -0
  51. package/skills/PRODUCTION_READINESS_SCHEMA.md +19 -0
  52. package/skills/QA_TESTER.md +40 -0
  53. package/skills/RCA_PACKET_SCHEMA.md +22 -0
  54. package/skills/RELEASE_MANAGER.md +60 -0
  55. package/skills/REVIEWER.md +133 -0
  56. package/skills/SOCIAL_EXPERT.md +22 -0
  57. package/skills/UI_UX_SPECIALIST.md +22 -0
  58. package/skills/WEBSITE_PROGRAMMER.md +37 -0
  59. package/src/cli/commands/debug-context.ts +265 -0
  60. package/src/cli/commands/debug-prompts.ts +91 -0
  61. package/src/cli/commands/debug.ts +662 -0
  62. package/src/cli/commands/index.ts +1 -0
  63. package/src/cli/index.ts +2 -0
  64. package/src/cli/interactive.ts +27 -0
  65. package/src/generators/all.ts +2 -0
  66. package/src/generators/templates/database-docker.ts +10 -0
  67. package/src/generators/templates/fullstack.ts +6 -2
  68. package/tests/cli/commands/debug.test.ts +376 -0
@@ -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
  */
@@ -198,6 +198,8 @@ export function generateAllDockerCompose(projectName: string): string {
198
198
  dockerfile: Dockerfile
199
199
  ports:
200
200
  - "8000:8000"
201
+ env_file:
202
+ - ./apps/backend/.env
201
203
  environment:
202
204
  - DEBUG=false
203
205
  - FRONTEND_URL=http://frontend:80
@@ -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: '#3B82F6',
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
+ });