/ __quarantined_tests__ / agents / context-builder-supplement.test.js
context-builder-supplement.test.js
  1  /**
  2   * Context Builder Supplement Tests
  3   *
  4   * Covers additional untested paths in src/agents/utils/context-builder.js:
  5   * - formatSuccessfulTask: null result (returns null), result with only approach
  6   * - formatFailedTask: null error_message, context with error_type, file (not file_path)
  7   * - formatRelatedTask: success outcome with 'completed' status, failed outcome icon
  8   * - extractFilePathFromContext: all candidate fields, error_message regex, stack_trace regex
  9   * - normalizeErrorMessage: null/undefined, long error, file paths, home paths
 10   * - getRelatedTasks: only errorType (no filePath), only filePath (no errorType)
 11   * - Cache: getCached TTL expiry path
 12   * - buildAgentContext: history disabled, no current task, various combinations
 13   */
 14  
 15  import { test, describe } from 'node:test';
 16  import assert from 'node:assert/strict';
 17  import Database from 'better-sqlite3';
 18  import fs from 'fs/promises';
 19  import path from 'path';
 20  import { fileURLToPath } from 'url';
 21  import { buildAgentContext, clearCache, resetDb } from '../../src/agents/utils/context-builder.js';
 22  import {
 23    createAgentTask,
 24    completeTask,
 25    failTask,
 26    resetDb as resetTaskDb,
 27  } from '../../src/agents/utils/task-manager.js';
 28  
 29  const __filename = fileURLToPath(import.meta.url);
 30  const __dirname = path.dirname(__filename);
 31  
 32  // ── DB Helpers ─────────────────────────────────────────────────────────────
 33  
 34  const dbPath = path.join(__dirname, '..', 'test-ctx-builder-supp.db');
 35  
 36  async function initDb() {
 37    try {
 38      await fs.unlink(dbPath);
 39    } catch (_) {
 40      /* ignore */
 41    }
 42  
 43    const db = new Database(dbPath);
 44    db.pragma('foreign_keys = ON');
 45  
 46    const migrationsDir = path.join(__dirname, '..', '..', 'db', 'migrations');
 47    const migrations = [
 48      '047-create-agent-system.sql',
 49      '052-create-agent-llm-usage.sql',
 50      '053-create-agent-outcomes.sql',
 51    ];
 52  
 53    for (const f of migrations) {
 54      try {
 55        const sql = await fs.readFile(path.join(migrationsDir, f), 'utf8');
 56        db.exec(sql);
 57      } catch (_) {
 58        /* ignore missing files */
 59      }
 60    }
 61  
 62    db.close();
 63  }
 64  
 65  async function cleanup() {
 66    try {
 67      await fs.unlink(dbPath);
 68    } catch (_) {
 69      /* ignore */
 70    }
 71  }
 72  
 73  function setTestDb() {
 74    process.env.DATABASE_PATH = dbPath;
 75    process.env.AGENT_REALTIME_NOTIFICATIONS = 'false';
 76  }
 77  
 78  function clearTestEnv() {
 79    delete process.env.DATABASE_PATH;
 80    delete process.env.AGENT_REALTIME_NOTIFICATIONS;
 81    delete process.env.AGENT_ENABLE_TASK_HISTORY;
 82  }
 83  
 84  function teardown() {
 85    clearTestEnv();
 86    resetDb();
 87    resetTaskDb();
 88    clearCache();
 89  }
 90  
 91  // ── buildAgentContext: history disabled ─────────────────────────────────────
 92  
 93  describe('buildAgentContext - AGENT_ENABLE_TASK_HISTORY=false', () => {
 94    test('returns null historyContext and 0 historyTokens when disabled', async () => {
 95      await initDb();
 96      setTestDb();
 97      process.env.AGENT_ENABLE_TASK_HISTORY = 'false';
 98  
 99      try {
100        const ctx = await buildAgentContext('developer', ['base.md']);
101        assert.equal(ctx.historyContext, null);
102        assert.equal(ctx.historyTokens, 0);
103        assert.equal(ctx.fullContext, ctx.baseContext);
104        assert.ok(typeof ctx.totalTokens === 'number');
105        assert.ok(ctx.totalTokens > 0);
106      } finally {
107        teardown();
108        await cleanup();
109      }
110    });
111  });
112  
113  // ── buildAgentContext: no history data ────────────────────────────────────
114  
115  describe('buildAgentContext - empty database', () => {
116    test('shows "No historical task data" message when no tasks exist', async () => {
117      await initDb();
118      setTestDb();
119  
120      try {
121        const ctx = await buildAgentContext('developer', ['base.md']);
122        assert.ok(ctx.historyContext.includes('No historical task data'));
123        assert.equal(ctx.metadata.historyStats.recentSuccesses, 0);
124        assert.equal(ctx.metadata.historyStats.recentFailures, 0);
125        assert.equal(ctx.metadata.historyStats.relatedTasks, 0);
126      } finally {
127        teardown();
128        await cleanup();
129      }
130    });
131  
132    test('totalTokens >= historyTokens', async () => {
133      await initDb();
134      setTestDb();
135  
136      try {
137        const ctx = await buildAgentContext('developer', ['base.md']);
138        assert.ok(ctx.totalTokens >= ctx.historyTokens);
139      } finally {
140        teardown();
141        await cleanup();
142      }
143    });
144  });
145  
146  // ── buildAgentContext: success path details ────────────────────────────────
147  
148  describe('buildAgentContext - successful tasks formatting', () => {
149    test('success with files_changed shows file in context', async () => {
150      await initDb();
151      setTestDb();
152  
153      try {
154        const taskId = await createAgentTask({
155          task_type: 'fix_bug',
156          assigned_to: 'developer',
157          context: { file_path: 'src/scoring.js' },
158        });
159        // completeTask stores result_json with files_changed and approach
160        completeTask(taskId, {
161          files_changed: ['src/scoring.js', 'tests/scoring.test.js'],
162          approach: 'Added null guard',
163        });
164  
165        clearCache();
166        const ctx = await buildAgentContext('developer', ['base.md']);
167        // Should show the file from files_changed
168        assert.ok(
169          ctx.historyContext.includes('src/scoring.js'),
170          'Should include file from files_changed'
171        );
172        assert.equal(ctx.metadata.historyStats.recentSuccesses, 1);
173      } finally {
174        teardown();
175        await cleanup();
176      }
177    });
178  
179    test('success with file_path (not files_changed) shows file in context', async () => {
180      await initDb();
181      setTestDb();
182  
183      try {
184        const taskId = await createAgentTask({
185          task_type: 'write_tests',
186          assigned_to: 'developer',
187          context: {},
188        });
189        completeTask(taskId, { file_path: 'tests/enrich.test.js', action_taken: 'Wrote unit tests' });
190  
191        const db = new Database(dbPath);
192        db.prepare(
193          'INSERT INTO agent_outcomes (task_id, agent_name, task_type, outcome, result_json) VALUES (?, ?, ?, ?, ?)'
194        ).run(
195          taskId,
196          'developer',
197          'write_tests',
198          'success',
199          JSON.stringify({ file_path: 'tests/enrich.test.js', action_taken: 'Wrote unit tests' })
200        );
201        db.close();
202  
203        clearCache();
204        const ctx = await buildAgentContext('developer', ['base.md']);
205        assert.ok(ctx.historyContext.includes('tests/enrich.test.js'));
206        assert.ok(ctx.historyContext.includes('Wrote unit tests'));
207      } finally {
208        teardown();
209        await cleanup();
210      }
211    });
212  
213    test('success task with duration shows duration in seconds', async () => {
214      await initDb();
215      setTestDb();
216  
217      try {
218        const taskId = await createAgentTask({
219          task_type: 'fix_bug',
220          assigned_to: 'developer',
221          context: {},
222        });
223        completeTask(taskId, { approach: 'Quick fix' });
224  
225        // Insert an outcome with duration_ms so the JOIN returns it
226        const db = new Database(dbPath);
227        db.prepare(
228          'INSERT INTO agent_outcomes (task_id, agent_name, task_type, outcome, duration_ms, result_json) VALUES (?, ?, ?, ?, ?, ?)'
229        ).run(
230          taskId,
231          'developer',
232          'fix_bug',
233          'success',
234          5000,
235          JSON.stringify({ approach: 'Quick fix' })
236        );
237        db.close();
238  
239        clearCache();
240        const ctx = await buildAgentContext('developer', ['base.md']);
241        // The duration comes from the JOIN with agent_outcomes (duration_ms=5000 -> 5s)
242        // The outcome_result has approach 'Quick fix'
243        // task.result_json has approach 'Quick fix' (from completeTask)
244        // Either way context should build successfully with 1 success
245        assert.equal(ctx.metadata.historyStats.recentSuccesses, 1);
246        assert.ok(ctx.historyContext.includes('fix_bug'), 'Should include task type');
247      } finally {
248        teardown();
249        await cleanup();
250      }
251    });
252  
253    test('success task with null result returns null from formatSuccessfulTask (filtered out)', async () => {
254      await initDb();
255      setTestDb();
256  
257      try {
258        // Insert task directly with null result_json
259        const db = new Database(dbPath);
260        db.prepare(
261          `INSERT INTO agent_tasks (task_type, assigned_to, status, result_json, completed_at)
262           VALUES (?, ?, ?, ?, datetime('now'))`
263        ).run('fix_bug', 'developer', 'completed', null);
264        db.close();
265  
266        clearCache();
267        const ctx = await buildAgentContext('developer', ['base.md']);
268        // Task is counted as success but formatSuccessfulTask returns null (no result) -> filtered
269        assert.ok(ctx.fullContext, 'Context should still be built');
270      } finally {
271        teardown();
272        await cleanup();
273      }
274    });
275  });
276  
277  // ── buildAgentContext: failure path details ────────────────────────────────
278  
279  describe('buildAgentContext - failed tasks formatting', () => {
280    test('failed task with error_message shows in context', async () => {
281      await initDb();
282      setTestDb();
283  
284      try {
285        const taskId = await createAgentTask({
286          task_type: 'fix_bug',
287          assigned_to: 'developer',
288          context: {},
289        });
290        failTask(taskId, 'TypeError: Cannot read property of undefined');
291  
292        clearCache();
293        const ctx = await buildAgentContext('developer', ['base.md']);
294        assert.ok(ctx.historyContext.includes('Past Failures'));
295        assert.equal(ctx.metadata.historyStats.recentFailures, 1);
296      } finally {
297        teardown();
298        await cleanup();
299      }
300    });
301  
302    test('failed task with null error_message is filtered out of display', async () => {
303      await initDb();
304      setTestDb();
305  
306      try {
307        const db = new Database(dbPath);
308        db.prepare(
309          `INSERT INTO agent_tasks (task_type, assigned_to, status, error_message, completed_at)
310           VALUES (?, ?, ?, ?, datetime('now'))`
311        ).run('fix_bug', 'developer', 'failed', null);
312        db.close();
313  
314        clearCache();
315        const ctx = await buildAgentContext('developer', ['base.md']);
316        // Failure exists in DB but formatFailedTask returns null -> not displayed
317        assert.ok(ctx.fullContext, 'Context should still build');
318        assert.ok(typeof ctx.metadata.historyStats.recentFailures === 'number');
319      } finally {
320        teardown();
321        await cleanup();
322      }
323    });
324  
325    test('failed task with context.error_type shows error type', async () => {
326      await initDb();
327      setTestDb();
328  
329      try {
330        const taskId = await createAgentTask({
331          task_type: 'fix_bug',
332          assigned_to: 'developer',
333          context: { error_type: 'auth_failure', file_path: 'src/auth.js' },
334        });
335        failTask(taskId, 'Authentication token expired');
336  
337        clearCache();
338        const ctx = await buildAgentContext('developer', ['base.md']);
339        assert.ok(ctx.historyContext.includes('auth_failure'));
340      } finally {
341        teardown();
342        await cleanup();
343      }
344    });
345  
346    test('failed task with context.file (not file_path) shows file', async () => {
347      await initDb();
348      setTestDb();
349  
350      try {
351        const db = new Database(dbPath);
352        db.prepare(
353          `INSERT INTO agent_tasks (task_type, assigned_to, status, error_message, context_json, completed_at)
354           VALUES (?, ?, ?, ?, ?, datetime('now'))`
355        ).run(
356          'fix_bug',
357          'developer',
358          'failed',
359          'Something broke',
360          JSON.stringify({ file: 'src/sms.js', error_type: 'send_failed' })
361        );
362        db.close();
363  
364        clearCache();
365        const ctx = await buildAgentContext('developer', ['base.md']);
366        assert.ok(ctx.historyContext.includes('src/sms.js'));
367      } finally {
368        teardown();
369        await cleanup();
370      }
371    });
372  
373    test('error message with file path gets normalized', async () => {
374      await initDb();
375      setTestDb();
376  
377      try {
378        const taskId = await createAgentTask({
379          task_type: 'fix_bug',
380          assigned_to: 'developer',
381          context: {},
382        });
383        failTask(taskId, 'Error in /home/user/code/src/scrape.js:42:3 - undefined is not a function');
384  
385        clearCache();
386        const ctx = await buildAgentContext('developer', ['base.md']);
387        // normalizeErrorMessage should replace /home paths and file paths
388        assert.ok(
389          ctx.historyContext.includes('[path]') || ctx.historyContext.includes('[file:line]')
390        );
391      } finally {
392        teardown();
393        await cleanup();
394      }
395    });
396  });
397  
398  // ── buildAgentContext: related tasks ───────────────────────────────────────
399  
400  describe('buildAgentContext - related tasks', () => {
401    test('no related tasks when currentTask has null context_json', async () => {
402      await initDb();
403      setTestDb();
404  
405      try {
406        const ctx = await buildAgentContext('developer', ['base.md'], { context_json: null });
407        assert.equal(ctx.metadata.historyStats.relatedTasks, 0);
408      } finally {
409        teardown();
410        await cleanup();
411      }
412    });
413  
414    test('no related tasks when context has no file_path or error_type', async () => {
415      await initDb();
416      setTestDb();
417  
418      try {
419        const ctx = await buildAgentContext('developer', ['base.md'], {
420          context_json: { description: 'just some context without path or error_type' },
421        });
422        assert.equal(ctx.metadata.historyStats.relatedTasks, 0);
423      } finally {
424        teardown();
425        await cleanup();
426      }
427    });
428  
429    test('finds related tasks by error_type only (no file_path)', async () => {
430      await initDb();
431      setTestDb();
432  
433      try {
434        const taskId = await createAgentTask({
435          task_type: 'fix_bug',
436          assigned_to: 'developer',
437          context: { error_type: 'rate_limit' },
438        });
439        completeTask(taskId, { approach: 'Added retry logic' });
440  
441        clearCache();
442  
443        const ctx = await buildAgentContext('developer', ['base.md'], {
444          context_json: { error_type: 'rate_limit' },
445        });
446        assert.ok(ctx.metadata.historyStats.relatedTasks >= 1);
447      } finally {
448        teardown();
449        await cleanup();
450      }
451    });
452  
453    test('finds related tasks by file_path only (no error_type)', async () => {
454      await initDb();
455      setTestDb();
456  
457      try {
458        const taskId = await createAgentTask({
459          task_type: 'fix_bug',
460          assigned_to: 'developer',
461          context: { file_path: 'src/capture.js' },
462        });
463        completeTask(taskId, { approach: 'Fixed capture timeout' });
464  
465        clearCache();
466  
467        const ctx = await buildAgentContext('developer', ['base.md'], {
468          context_json: { file_path: 'src/capture.js' },
469        });
470        assert.ok(ctx.metadata.historyStats.relatedTasks >= 1);
471      } finally {
472        teardown();
473        await cleanup();
474      }
475    });
476  
477    test('related task with completed status shows checkmark icon', async () => {
478      await initDb();
479      setTestDb();
480  
481      try {
482        const taskId = await createAgentTask({
483          task_type: 'fix_bug',
484          assigned_to: 'developer',
485          context: { file_path: 'src/score.js' },
486        });
487        completeTask(taskId, { approach: 'Fixed score bug' });
488  
489        clearCache();
490  
491        const ctx = await buildAgentContext('developer', ['base.md'], {
492          context_json: { file_path: 'src/score.js' },
493        });
494        // Completed status should use checkmark icon
495        assert.ok(ctx.historyContext.includes('✓') || ctx.historyContext.includes('completed'));
496      } finally {
497        teardown();
498        await cleanup();
499      }
500    });
501  
502    test('related task with failed status shows X icon', async () => {
503      await initDb();
504      setTestDb();
505  
506      try {
507        const db = new Database(dbPath);
508        db.prepare(
509          `INSERT INTO agent_tasks (task_type, assigned_to, status, error_message, context_json, completed_at)
510           VALUES (?, ?, ?, ?, ?, datetime('now'))`
511        ).run(
512          'fix_bug',
513          'developer',
514          'failed',
515          'Failed hard',
516          JSON.stringify({ file_path: 'src/enrich.js' })
517        );
518        db.close();
519  
520        clearCache();
521  
522        const ctx = await buildAgentContext('developer', ['base.md'], {
523          context_json: { file_path: 'src/enrich.js' },
524        });
525        assert.ok(ctx.historyContext.includes('✗') || ctx.historyContext.includes('failed'));
526      } finally {
527        teardown();
528        await cleanup();
529      }
530    });
531  
532    test('related task insight shows "what worked" for completed task with approach', async () => {
533      await initDb();
534      setTestDb();
535  
536      try {
537        const taskId = await createAgentTask({
538          task_type: 'fix_bug',
539          assigned_to: 'developer',
540          context: { file_path: 'src/proposals.js' },
541        });
542        completeTask(taskId, { approach: 'Refactored proposal generator' });
543  
544        const db = new Database(dbPath);
545        db.prepare(
546          'INSERT INTO agent_outcomes (task_id, agent_name, task_type, outcome, result_json) VALUES (?, ?, ?, ?, ?)'
547        ).run(
548          taskId,
549          'developer',
550          'fix_bug',
551          'success',
552          JSON.stringify({ approach: 'Refactored proposal generator' })
553        );
554        db.close();
555  
556        clearCache();
557  
558        const ctx = await buildAgentContext('developer', ['base.md'], {
559          context_json: { file_path: 'src/proposals.js' },
560        });
561        // extractRelatedInsight should produce "What worked:" text for completed tasks with approach
562        assert.ok(
563          ctx.historyContext.includes('What worked') || ctx.historyContext.includes('Refactored'),
564          'Should show what worked insight'
565        );
566      } finally {
567        teardown();
568        await cleanup();
569      }
570    });
571  
572    test('related task insight shows "what failed" for failed task with error in context', async () => {
573      await initDb();
574      setTestDb();
575  
576      try {
577        const db = new Database(dbPath);
578        db.prepare(
579          `INSERT INTO agent_tasks (task_type, assigned_to, status, error_message, context_json, completed_at)
580           VALUES (?, ?, ?, ?, ?, datetime('now'))`
581        ).run(
582          'fix_bug',
583          'developer',
584          'failed',
585          'Network error',
586          JSON.stringify({ file_path: 'src/outreach/email.js', error: 'SMTP connection refused' })
587        );
588        db.close();
589  
590        clearCache();
591  
592        const ctx = await buildAgentContext('developer', ['base.md'], {
593          context_json: { file_path: 'src/outreach/email.js' },
594        });
595        assert.ok(
596          ctx.historyContext.includes('What failed') || ctx.historyContext.includes('SMTP'),
597          'Should show what failed insight'
598        );
599      } finally {
600        teardown();
601        await cleanup();
602      }
603    });
604  });
605  
606  // ── Cache behavior ─────────────────────────────────────────────────────────
607  
608  describe('buildAgentContext - cache behavior', () => {
609    test('second call returns cached result (same stats)', async () => {
610      await initDb();
611      setTestDb();
612  
613      try {
614        const taskId = await createAgentTask({ task_type: 'fix_bug', assigned_to: 'developer' });
615        completeTask(taskId, { approach: 'Fixed' });
616  
617        clearCache();
618  
619        const ctx1 = await buildAgentContext('developer', ['base.md']);
620        const ctx2 = await buildAgentContext('developer', ['base.md']);
621  
622        assert.equal(
623          ctx1.metadata.historyStats.recentSuccesses,
624          ctx2.metadata.historyStats.recentSuccesses
625        );
626      } finally {
627        teardown();
628        await cleanup();
629      }
630    });
631  
632    test('clearCache() causes next call to re-query database', async () => {
633      await initDb();
634      setTestDb();
635  
636      try {
637        clearCache();
638        const ctx1 = await buildAgentContext('developer', ['base.md']);
639  
640        // Add a new task after first call
641        const taskId = await createAgentTask({ task_type: 'fix_bug', assigned_to: 'developer' });
642        completeTask(taskId, { approach: 'New fix' });
643  
644        // Without clear - still uses cache (should return same count)
645        const ctx2 = await buildAgentContext('developer', ['base.md']);
646        assert.equal(
647          ctx1.metadata.historyStats.recentSuccesses,
648          ctx2.metadata.historyStats.recentSuccesses
649        );
650  
651        // After clear - fresh query includes new task
652        clearCache();
653        const ctx3 = await buildAgentContext('developer', ['base.md']);
654        assert.ok(
655          ctx3.metadata.historyStats.recentSuccesses >= ctx1.metadata.historyStats.recentSuccesses
656        );
657      } finally {
658        teardown();
659        await cleanup();
660      }
661    });
662  
663    test('cache TTL expiry causes re-query', async () => {
664      const { mock } = await import('node:test');
665  
666      await initDb();
667      setTestDb();
668      mock.timers.enable({ apis: ['Date'] });
669  
670      try {
671        clearCache();
672        const ctx1 = await buildAgentContext('developer', ['base.md']);
673  
674        // Advance time past 30-minute TTL
675        mock.timers.tick(31 * 60 * 1000);
676  
677        const ctx2 = await buildAgentContext('developer', ['base.md']);
678        assert.ok(ctx2.fullContext, 'Should succeed after cache expiry');
679      } finally {
680        mock.timers.reset();
681        teardown();
682        await cleanup();
683      }
684    });
685  });
686  
687  // ── resetDb ────────────────────────────────────────────────────────────────
688  
689  describe('resetDb', () => {
690    test('can be called multiple times without error', async () => {
691      await initDb();
692      setTestDb();
693  
694      try {
695        await buildAgentContext('developer', ['base.md']);
696        resetDb();
697        resetDb(); // Second call when db is null
698        assert.ok(true, 'resetDb twice should not throw');
699      } finally {
700        teardown();
701        await cleanup();
702      }
703    });
704  
705    test('resetDb when db is null does nothing', () => {
706      resetDb(); // Should not throw even when not initialized
707      assert.ok(true, 'resetDb on uninitialized db should not throw');
708    });
709  });
710  
711  // ── Mixed success and failure ──────────────────────────────────────────────
712  
713  describe('buildAgentContext - mixed history', () => {
714    test('shows both success and failure sections when both exist', async () => {
715      await initDb();
716      setTestDb();
717  
718      try {
719        // 2 successes
720        for (let i = 0; i < 2; i++) {
721          const id = await createAgentTask({ task_type: 'fix_bug', assigned_to: 'developer' });
722          completeTask(id, { approach: `Fix ${i}` });
723          const db = new Database(dbPath);
724          db.prepare(
725            'INSERT INTO agent_outcomes (task_id, agent_name, task_type, outcome) VALUES (?, ?, ?, ?)'
726          ).run(id, 'developer', 'fix_bug', 'success');
727          db.close();
728        }
729  
730        // 1 failure
731        const failId = await createAgentTask({ task_type: 'fix_bug', assigned_to: 'developer' });
732        failTask(failId, 'Something went wrong completely');
733        const db = new Database(dbPath);
734        db.prepare(
735          'INSERT INTO agent_outcomes (task_id, agent_name, task_type, outcome) VALUES (?, ?, ?, ?)'
736        ).run(failId, 'developer', 'fix_bug', 'failure');
737        db.close();
738  
739        clearCache();
740  
741        const ctx = await buildAgentContext('developer', ['base.md']);
742        assert.equal(ctx.metadata.historyStats.recentSuccesses, 2);
743        assert.equal(ctx.metadata.historyStats.recentFailures, 1);
744        assert.ok(ctx.historyContext.includes('Recent Successful Approaches'));
745        assert.ok(ctx.historyContext.includes('Past Failures to Avoid'));
746      } finally {
747        teardown();
748        await cleanup();
749      }
750    });
751  
752    test('buildAgentContext without currentTask argument has 0 related tasks', async () => {
753      await initDb();
754      setTestDb();
755  
756      try {
757        clearCache();
758        const ctx = await buildAgentContext('developer', ['base.md']); // no currentTask
759        assert.equal(ctx.metadata.historyStats.relatedTasks, 0);
760      } finally {
761        teardown();
762        await cleanup();
763      }
764    });
765  
766    test('buildAgentContext with currentTask=null has 0 related tasks', async () => {
767      await initDb();
768      setTestDb();
769  
770      try {
771        clearCache();
772        const ctx = await buildAgentContext('developer', ['base.md'], null);
773        assert.equal(ctx.metadata.historyStats.relatedTasks, 0);
774      } finally {
775        teardown();
776        await cleanup();
777      }
778    });
779  });
780  
781  // ── extractFilePathFromContext edge cases ──────────────────────────────────
782  
783  describe('buildAgentContext - extractFilePathFromContext variants', () => {
784    test('uses filePath (camelCase) field in current task context', async () => {
785      await initDb();
786      setTestDb();
787  
788      try {
789        // Stored task that can be related
790        const id = await createAgentTask({
791          task_type: 'fix_bug',
792          assigned_to: 'developer',
793          context: { filePath: 'src/capture.js' },
794        });
795        completeTask(id, { approach: 'Fixed it' });
796  
797        clearCache();
798  
799        // Current task uses camelCase filePath
800        const ctx = await buildAgentContext('developer', ['base.md'], {
801          context_json: { filePath: 'src/capture.js' },
802        });
803        assert.ok(typeof ctx.metadata.historyStats.relatedTasks === 'number');
804      } finally {
805        teardown();
806        await cleanup();
807      }
808    });
809  
810    test('uses affected_file field in current task context', async () => {
811      await initDb();
812      setTestDb();
813  
814      try {
815        const id = await createAgentTask({
816          task_type: 'fix_bug',
817          assigned_to: 'developer',
818          context: { affected_file: 'src/proposals.js' },
819        });
820        completeTask(id, { approach: 'Fixed proposals' });
821  
822        clearCache();
823  
824        const ctx = await buildAgentContext('developer', ['base.md'], {
825          context_json: { affected_file: 'src/proposals.js' },
826        });
827        assert.ok(typeof ctx.metadata.historyStats.relatedTasks === 'number');
828      } finally {
829        teardown();
830        await cleanup();
831      }
832    });
833  
834    test('uses files_changed[0] field in current task context', async () => {
835      await initDb();
836      setTestDb();
837  
838      try {
839        const id = await createAgentTask({
840          task_type: 'fix_bug',
841          assigned_to: 'developer',
842          context: { files_changed: ['src/enrich.js', 'src/score.js'] },
843        });
844        completeTask(id, { approach: 'Fixed enrichment' });
845  
846        clearCache();
847  
848        const ctx = await buildAgentContext('developer', ['base.md'], {
849          context_json: { files_changed: ['src/enrich.js'] },
850        });
851        assert.ok(typeof ctx.metadata.historyStats.relatedTasks === 'number');
852      } finally {
853        teardown();
854        await cleanup();
855      }
856    });
857  
858    test('extracts path from error_message in current task context', async () => {
859      await initDb();
860      setTestDb();
861  
862      try {
863        const id = await createAgentTask({
864          task_type: 'fix_bug',
865          assigned_to: 'developer',
866          context: { error_message: 'Error at /home/user/src/assets.js:10' },
867        });
868        completeTask(id, { approach: 'Fixed assets' });
869  
870        clearCache();
871  
872        const ctx = await buildAgentContext('developer', ['base.md'], {
873          context_json: { error_message: 'Error at /home/user/src/assets.js:42:3' },
874        });
875        assert.ok(typeof ctx.metadata.historyStats.relatedTasks === 'number');
876      } finally {
877        teardown();
878        await cleanup();
879      }
880    });
881  
882    test('extracts path from stack_trace in current task context', async () => {
883      await initDb();
884      setTestDb();
885  
886      try {
887        const id = await createAgentTask({
888          task_type: 'fix_bug',
889          assigned_to: 'developer',
890          context: { stack_trace: 'at fn (/home/user/src/outreach.js:5:3)' },
891        });
892        completeTask(id, { approach: 'Fixed outreach' });
893  
894        clearCache();
895  
896        const ctx = await buildAgentContext('developer', ['base.md'], {
897          context_json: { stack_trace: 'at handler (/home/user/src/outreach.js:20:7)' },
898        });
899        assert.ok(typeof ctx.metadata.historyStats.relatedTasks === 'number');
900      } finally {
901        teardown();
902        await cleanup();
903      }
904    });
905  });
906  
907  // ── Invalid JSON handling ──────────────────────────────────────────────────
908  
909  describe('buildAgentContext - invalid JSON in task records', () => {
910    test('gracefully handles malformed context_json in completed tasks', async () => {
911      await initDb();
912      setTestDb();
913  
914      try {
915        const db = new Database(dbPath);
916        db.prepare(
917          `INSERT INTO agent_tasks (task_type, assigned_to, status, context_json, completed_at)
918           VALUES (?, ?, ?, ?, datetime('now'))`
919        ).run('fix_bug', 'developer', 'completed', '{invalid json here}');
920        db.close();
921  
922        clearCache();
923  
924        const ctx = await buildAgentContext('developer', ['base.md']);
925        assert.ok(ctx.fullContext, 'Should build context despite malformed JSON');
926      } finally {
927        teardown();
928        await cleanup();
929      }
930    });
931  
932    test('gracefully handles malformed context_json in failed tasks', async () => {
933      await initDb();
934      setTestDb();
935  
936      try {
937        const db = new Database(dbPath);
938        db.prepare(
939          `INSERT INTO agent_tasks (task_type, assigned_to, status, error_message, context_json, completed_at)
940           VALUES (?, ?, ?, ?, ?, datetime('now'))`
941        ).run('fix_bug', 'developer', 'failed', 'Error message', 'not-json');
942        db.close();
943  
944        clearCache();
945  
946        const ctx = await buildAgentContext('developer', ['base.md']);
947        assert.ok(ctx.fullContext, 'Should build context despite malformed JSON in failed task');
948      } finally {
949        teardown();
950        await cleanup();
951      }
952    });
953  });