/ __quarantined_tests__ / agents / developer-execsync.test.js
developer-execsync.test.js
   1  /**
   2   * Developer Agent execSync-Mocked Tests
   3   *
   4   * Uses mock.module('node:child_process') to control execSync, allowing us to
   5   * exercise success paths that are unreachable without recursive test invocations:
   6   *
   7   *   - getDetailedCoverage() lines 1385-1414: after execSync passes, reads
   8   *     coverage-final.json and extracts uncovered lines
   9   *   - createCommit() lines 1526-1527: "Commit created" log after git commit
  10   *   - getFileCoverage() lines 1545-1597: npm test + coverage-summary.json reading
  11   *
  12   * MUST be run with --experimental-test-module-mocks.
  13   */
  14  
  15  import { test, describe, mock, beforeEach, afterEach } from 'node:test';
  16  import assert from 'node:assert/strict';
  17  import Database from 'better-sqlite3';
  18  import { resetDb as resetBaseDb } from '../../src/agents/base-agent.js';
  19  import { resetDb as resetTaskDb } from '../../src/agents/utils/task-manager.js';
  20  import { resetDb as resetMessageDb } from '../../src/agents/utils/message-manager.js';
  21  import fsPromises from 'fs/promises';
  22  import path from 'path';
  23  
  24  // ----------------------------------------------------------------
  25  // Mock child_process BEFORE importing DeveloperAgent
  26  // execSync is used for: npx c8, npm test, git add, git commit
  27  // ----------------------------------------------------------------
  28  const mockExecSync = mock.fn((_cmd, _opts) => 'mock-output');
  29  
  30  mock.module('node:child_process', {
  31    namedExports: {
  32      execSync: mockExecSync,
  33    },
  34  });
  35  
  36  // Mock fileOps to avoid side effects
  37  const mockFileOps = {
  38    readFile: mock.fn(async () => ({ content: 'code', size: 10 })),
  39    getFileContext: mock.fn(async () => ({ imports: [], testFiles: [] })),
  40    editFile: mock.fn(async () => ({ backupPath: '/tmp/backup.js', diff: 'x' })),
  41    writeFile: mock.fn(async () => ({ backupPath: '/tmp/new.js' })),
  42    restoreBackup: mock.fn(async () => {}),
  43    cleanupBackups: mock.fn(async () => {}),
  44    listBackups: mock.fn(async () => []),
  45  };
  46  
  47  mock.module('../../src/agents/utils/file-operations.js', {
  48    namedExports: mockFileOps,
  49  });
  50  
  51  const mockRunTests = mock.fn(async () => ({
  52    success: true,
  53    stats: { pass: 5, fail: 0 },
  54    coverage: 90,
  55  }));
  56  const mockRunTestsForFile = mock.fn(async () => ({
  57    success: true,
  58    stats: { pass: 3, fail: 0 },
  59    coverage: 92,
  60  }));
  61  
  62  mock.module('../../src/agents/utils/test-runner.js', {
  63    namedExports: {
  64      runTests: mockRunTests,
  65      runTestsForFile: mockRunTestsForFile,
  66    },
  67  });
  68  
  69  const mockSimpleLLMCall = mock.fn(async () => JSON.stringify({ explanation: 'fixed' }));
  70  
  71  mock.module('../../src/agents/utils/agent-claude-api.js', {
  72    namedExports: {
  73      simpleLLMCall: mockSimpleLLMCall,
  74    },
  75  });
  76  
  77  // NOW import DeveloperAgent (after mocks are set up)
  78  const { DeveloperAgent } = await import('../../src/agents/developer.js');
  79  
  80  // ----------------------------------------------------------------
  81  // Test infrastructure
  82  // ----------------------------------------------------------------
  83  const TEST_DB_PATH = './tests/agents/test-developer-execsync.db';
  84  let db;
  85  let agent;
  86  
  87  const DB_SCHEMA = `
  88    CREATE TABLE agent_tasks (
  89      id INTEGER PRIMARY KEY AUTOINCREMENT,
  90      task_type TEXT NOT NULL,
  91      assigned_to TEXT NOT NULL,
  92      created_by TEXT,
  93      status TEXT DEFAULT 'pending',
  94      priority INTEGER DEFAULT 5,
  95      context_json TEXT,
  96      result_json TEXT,
  97      parent_task_id INTEGER,
  98      error_message TEXT,
  99      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
 100      started_at DATETIME,
 101      completed_at DATETIME,
 102      retry_count INTEGER DEFAULT 0
 103    );
 104    CREATE TABLE agent_messages (
 105      id INTEGER PRIMARY KEY AUTOINCREMENT,
 106      task_id INTEGER,
 107      from_agent TEXT NOT NULL,
 108      to_agent TEXT NOT NULL,
 109      message_type TEXT,
 110      content TEXT NOT NULL,
 111      metadata_json TEXT,
 112      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
 113      read_at DATETIME
 114    );
 115    CREATE TABLE agent_logs (
 116      id INTEGER PRIMARY KEY AUTOINCREMENT,
 117      task_id INTEGER,
 118      agent_name TEXT NOT NULL,
 119      log_level TEXT,
 120      message TEXT,
 121      data_json TEXT,
 122      created_at DATETIME DEFAULT CURRENT_TIMESTAMP
 123    );
 124    CREATE TABLE agent_state (
 125      agent_name TEXT PRIMARY KEY,
 126      last_active DATETIME DEFAULT CURRENT_TIMESTAMP,
 127      current_task_id INTEGER,
 128      status TEXT DEFAULT 'idle',
 129      metrics_json TEXT
 130    );
 131    CREATE TABLE agent_outcomes (
 132      id INTEGER PRIMARY KEY AUTOINCREMENT,
 133      task_id INTEGER NOT NULL,
 134      agent_name TEXT NOT NULL,
 135      task_type TEXT NOT NULL,
 136      outcome TEXT NOT NULL,
 137      context_json TEXT,
 138      result_json TEXT,
 139      duration_ms INTEGER,
 140      created_at DATETIME DEFAULT CURRENT_TIMESTAMP
 141    );
 142    CREATE TABLE agent_llm_usage (
 143      id INTEGER PRIMARY KEY AUTOINCREMENT,
 144      agent_name TEXT NOT NULL,
 145      task_id INTEGER,
 146      model TEXT NOT NULL,
 147      prompt_tokens INTEGER NOT NULL,
 148      completion_tokens INTEGER NOT NULL,
 149      cost_usd DECIMAL(10, 6) NOT NULL,
 150      created_at DATETIME DEFAULT CURRENT_TIMESTAMP
 151    );
 152    CREATE TABLE structured_logs (
 153      id INTEGER PRIMARY KEY AUTOINCREMENT,
 154      agent_name TEXT,
 155      task_id INTEGER,
 156      level TEXT,
 157      message TEXT,
 158      data_json TEXT,
 159      created_at DATETIME DEFAULT CURRENT_TIMESTAMP
 160    );
 161  `;
 162  
 163  beforeEach(async () => {
 164    // Reset both call history AND implementation back to default
 165    mockExecSync.mock.resetCalls();
 166    mockExecSync.mock.mockImplementation((_cmd, _opts) => 'mock-output');
 167    // Reset runTests mock
 168    mockRunTests.mock.resetCalls();
 169    mockRunTests.mock.mockImplementation(async () => ({
 170      success: true,
 171      stats: { pass: 5, fail: 0 },
 172      coverage: 90,
 173    }));
 174    mockRunTestsForFile.mock.resetCalls();
 175    mockRunTestsForFile.mock.mockImplementation(async () => ({
 176      success: true,
 177      stats: { pass: 3, fail: 0 },
 178      coverage: 92,
 179    }));
 180    // Reset simpleLLMCall mock
 181    mockSimpleLLMCall.mock.resetCalls();
 182    mockSimpleLLMCall.mock.mockImplementation(async () => JSON.stringify({ explanation: 'fixed' }));
 183    // Reset fileOps mocks
 184    mockFileOps.listBackups.mock.resetCalls();
 185    mockFileOps.listBackups.mock.mockImplementation(async () => []);
 186    mockFileOps.restoreBackup.mock.resetCalls();
 187    mockFileOps.restoreBackup.mock.mockImplementation(async () => {});
 188    mockFileOps.editFile.mock.resetCalls();
 189    mockFileOps.editFile.mock.mockImplementation(async () => ({
 190      backupPath: '/tmp/backup.js',
 191      diff: 'x',
 192    }));
 193  
 194    try {
 195      await fsPromises.unlink(TEST_DB_PATH);
 196    } catch (_e) {
 197      /* ignore */
 198    }
 199  
 200    db = new Database(TEST_DB_PATH);
 201    process.env.DATABASE_PATH = TEST_DB_PATH;
 202    process.env.AGENT_REALTIME_NOTIFICATIONS = 'false';
 203    process.env.AGENT_IMMEDIATE_INVOCATION = 'false';
 204  
 205    db.exec(DB_SCHEMA);
 206    agent = new DeveloperAgent();
 207    await agent.initialize();
 208  });
 209  
 210  afterEach(async () => {
 211    resetBaseDb();
 212    resetTaskDb();
 213    resetMessageDb();
 214    if (db) db.close();
 215    try {
 216      await fsPromises.unlink(TEST_DB_PATH);
 217    } catch (_e) {
 218      /* ignore */
 219    }
 220  });
 221  
 222  function insertTask(taskType, context) {
 223    const taskId = db
 224      .prepare(
 225        'INSERT INTO agent_tasks (task_type, assigned_to, status, context_json) VALUES (?, ?, ?, ?)'
 226      )
 227      .run(taskType, 'developer', 'pending', JSON.stringify(context)).lastInsertRowid;
 228    const task = db.prepare('SELECT * FROM agent_tasks WHERE id = ?').get(taskId);
 229    task.context_json = JSON.parse(task.context_json);
 230    return task;
 231  }
 232  
 233  // Helper: write fake coverage-final.json for getDetailedCoverage
 234  async function writeCoverageFinal(data) {
 235    const coverageDir = path.join(process.cwd(), 'coverage');
 236    await fsPromises.mkdir(coverageDir, { recursive: true });
 237    await fsPromises.writeFile(path.join(coverageDir, 'coverage-final.json'), JSON.stringify(data));
 238  }
 239  
 240  async function removeCoverageFinal() {
 241    try {
 242      await fsPromises.unlink(path.join(process.cwd(), 'coverage', 'coverage-final.json'));
 243    } catch (_e) {
 244      /* ignore */
 245    }
 246  }
 247  
 248  // Helper: write fake coverage-summary.json for getFileCoverage
 249  async function writeCoverageSummary(data) {
 250    const coverageDir = path.join(process.cwd(), 'coverage');
 251    await fsPromises.mkdir(coverageDir, { recursive: true });
 252    await fsPromises.writeFile(path.join(coverageDir, 'coverage-summary.json'), JSON.stringify(data));
 253  }
 254  
 255  async function removeCoverageSummary() {
 256    try {
 257      await fsPromises.unlink(path.join(process.cwd(), 'coverage', 'coverage-summary.json'));
 258    } catch (_e) {
 259      /* ignore */
 260    }
 261  }
 262  
 263  // ================================================================
 264  // getDetailedCoverage() - success paths (lines 1385-1414)
 265  // execSync mocked to succeed; coverage-final.json written to disk
 266  // ================================================================
 267  describe('DeveloperAgent execSync-mocked - getDetailedCoverage()', () => {
 268    test('returns uncovered lines and coverage when fileKey found (lines 1391-1414)', async () => {
 269      const filePath = 'src/score.js';
 270      const fakeCoverage = {
 271        '/home/jason/code/333Method/src/score.js': {
 272          statementMap: {
 273            0: { start: { line: 10 }, end: { line: 10 } },
 274            1: { start: { line: 20 }, end: { line: 22 } },
 275            2: { start: { line: 30 }, end: { line: 30 } },
 276          },
 277          s: { 0: 5, 1: 0, 2: 0 },
 278          lines: { pct: 77 },
 279        },
 280      };
 281  
 282      await writeCoverageFinal(fakeCoverage);
 283      try {
 284        const result = await agent.getDetailedCoverage(filePath);
 285  
 286        assert.ok(result !== null, 'Should return non-null when JSON read succeeds');
 287        assert.strictEqual(result.coverage, 77, 'Should return correct pct');
 288        assert.ok(Array.isArray(result.uncoveredLines), 'uncoveredLines should be array');
 289        assert.strictEqual(result.uncoveredLines.length, 2, 'Should have 2 uncovered regions');
 290        assert.strictEqual(result.uncoveredLines[0].start, 20);
 291        assert.strictEqual(result.uncoveredLines[0].end, 22);
 292        assert.strictEqual(result.uncoveredLines[1].start, 30);
 293      } finally {
 294        await removeCoverageFinal();
 295      }
 296    });
 297  
 298    test('returns null when fileKey not found in coverage data (line 1396)', async () => {
 299      const fakeCoverage = {
 300        '/other-project/src/something-else.js': {
 301          statementMap: {},
 302          s: {},
 303          lines: { pct: 90 },
 304        },
 305      };
 306  
 307      await writeCoverageFinal(fakeCoverage);
 308      try {
 309        const result = await agent.getDetailedCoverage('src/score.js');
 310        assert.strictEqual(result, null, 'Returns null when fileKey not found');
 311      } finally {
 312        await removeCoverageFinal();
 313      }
 314    });
 315  
 316    test('returns null when coverage-final.json cannot be read (inner catch, line 1388)', async () => {
 317      await removeCoverageFinal();
 318      const result = await agent.getDetailedCoverage('src/score.js');
 319      assert.strictEqual(result, null, 'Returns null when coverage file missing');
 320    });
 321  
 322    test('returns empty uncoveredLines array when all statements covered (line 1404-1412)', async () => {
 323      const filePath = 'src/capture.js';
 324      const fakeCoverage = {
 325        '/home/jason/code/333Method/src/capture.js': {
 326          statementMap: {
 327            0: { start: { line: 5 }, end: { line: 5 } },
 328          },
 329          s: { 0: 10 },
 330          lines: { pct: 100 },
 331        },
 332      };
 333  
 334      await writeCoverageFinal(fakeCoverage);
 335      try {
 336        const result = await agent.getDetailedCoverage(filePath);
 337        assert.ok(result !== null);
 338        assert.strictEqual(result.coverage, 100);
 339        assert.strictEqual(result.uncoveredLines.length, 0, 'No uncovered lines when all covered');
 340      } finally {
 341        await removeCoverageFinal();
 342      }
 343    });
 344  
 345    test('statementMap missing for statement id - uncoveredLines still handles gracefully', async () => {
 346      const filePath = 'src/score.js';
 347      const fakeCoverage = {
 348        '/home/jason/code/333Method/src/score.js': {
 349          statementMap: {
 350            0: { start: { line: 5 }, end: { line: 5 } },
 351            // '1' intentionally absent from statementMap
 352          },
 353          s: { 0: 0, 1: 0 },
 354          lines: { pct: 50 },
 355        },
 356      };
 357  
 358      await writeCoverageFinal(fakeCoverage);
 359      try {
 360        const result = await agent.getDetailedCoverage(filePath);
 361        // s['0'] = 0 and statementMap['0'] exists -> included
 362        // s['1'] = 0 but statementMap['1'] missing -> skipped
 363        assert.ok(result !== null);
 364        assert.strictEqual(result.uncoveredLines.length, 1, 'Only tracks stmts with valid map entry');
 365      } finally {
 366        await removeCoverageFinal();
 367      }
 368    });
 369  });
 370  
 371  // ================================================================
 372  // getFileCoverage() - success path (lines 1545-1584)
 373  // execSync mocked so 'npm test' does not run recursively;
 374  // coverage-summary.json written to disk for fs.readFile
 375  // ================================================================
 376  describe('DeveloperAgent execSync-mocked - getFileCoverage()', () => {
 377    test('returns line pct from coverage-summary.json (exact key match, lines 1572-1574)', async () => {
 378      const fakeSummary = {
 379        'src/score.js': { lines: { pct: 88 } },
 380        'src/capture.js': { lines: { pct: 72 } },
 381      };
 382  
 383      await writeCoverageSummary(fakeSummary);
 384      try {
 385        const result = await agent.getFileCoverage(['src/score.js', 'src/capture.js']);
 386        assert.strictEqual(result['src/score.js'], 88);
 387        assert.strictEqual(result['src/capture.js'], 72);
 388      } finally {
 389        await removeCoverageSummary();
 390      }
 391    });
 392  
 393    test('falls back to absolute path lookup when relative key missing (line 1563-1568)', async () => {
 394      const projectRoot = process.cwd();
 395      const absPath = path.join(projectRoot, 'src/score.js');
 396      const fakeSummary = { [absPath]: { lines: { pct: 91 } } };
 397  
 398      await writeCoverageSummary(fakeSummary);
 399      try {
 400        const result = await agent.getFileCoverage(['src/score.js']);
 401        assert.strictEqual(result['src/score.js'], 91, 'Should find via absolutePath fallback');
 402      } finally {
 403        await removeCoverageSummary();
 404      }
 405    });
 406  
 407    test('falls back to normalized path (strips leading slash) - line 1571', async () => {
 408      const fakeSummary = { 'src/score.js': { lines: { pct: 85 } } };
 409  
 410      await writeCoverageSummary(fakeSummary);
 411      try {
 412        // Leading slash causes relative/absolute lookups to fail, normalized succeeds
 413        const result = await agent.getFileCoverage(['/src/score.js']);
 414        assert.strictEqual(result['/src/score.js'], 85, 'Should find via normalized path');
 415      } finally {
 416        await removeCoverageSummary();
 417      }
 418    });
 419  
 420    test('returns 0 and logs warning when file not in any path format (lines 1575-1581)', async () => {
 421      const fakeSummary = { 'src/other-file.js': { lines: { pct: 90 } } };
 422  
 423      await writeCoverageSummary(fakeSummary);
 424      try {
 425        const result = await agent.getFileCoverage(['src/score.js']);
 426        assert.strictEqual(result['src/score.js'], 0, 'Returns 0 for missing file');
 427  
 428        const warnLogs = db
 429          .prepare(
 430            "SELECT * FROM agent_logs WHERE agent_name = 'developer' AND message LIKE '%Coverage data not found%'"
 431          )
 432          .all();
 433        assert.ok(warnLogs.length > 0, 'Should log warning for missing file coverage');
 434      } finally {
 435        await removeCoverageSummary();
 436      }
 437    });
 438  
 439    test('logs "Running coverage check" at entry (line 1548)', async () => {
 440      const fakeSummary = { 'src/score.js': { lines: { pct: 80 } } };
 441      await writeCoverageSummary(fakeSummary);
 442  
 443      try {
 444        await agent.getFileCoverage(['src/score.js']);
 445  
 446        const infoLogs = db
 447          .prepare(
 448            "SELECT * FROM agent_logs WHERE agent_name = 'developer' AND message LIKE '%Running coverage check%'"
 449          )
 450          .all();
 451        assert.ok(infoLogs.length > 0, 'Should log "Running coverage check"');
 452      } finally {
 453        await removeCoverageSummary();
 454      }
 455    });
 456  
 457    test('returns 0 for all files and logs error when coverage-summary.json missing (lines 1585-1595)', async () => {
 458      await removeCoverageSummary();
 459      // execSync succeeds but fs.readFile fails -> catch block
 460      const result = await agent.getFileCoverage(['src/score.js', 'src/capture.js']);
 461  
 462      assert.strictEqual(result['src/score.js'], 0);
 463      assert.strictEqual(result['src/capture.js'], 0);
 464  
 465      const errorLogs = db
 466        .prepare(
 467          "SELECT * FROM agent_logs WHERE agent_name = 'developer' AND message LIKE '%Failed to get coverage%'"
 468        )
 469        .all();
 470      assert.ok(errorLogs.length > 0, 'Should log error when coverage file unreadable');
 471    });
 472  });
 473  
 474  // ================================================================
 475  // createCommit() success path - lines 1526-1527 ("Commit created")
 476  // execSync mocked so git add + git commit succeed
 477  // ================================================================
 478  describe('DeveloperAgent execSync-mocked - createCommit() success path', () => {
 479    test('logs "Commit created" and returns trimmed hash (lines 1526-1527)', async () => {
 480      const task = insertTask('fix_bug', { error_type: 'null_pointer', error_message: 'test' });
 481  
 482      // Reset and configure mock: git add returns empty, git commit returns hash
 483      mockExecSync.mock.resetCalls();
 484      let callIdx = 0;
 485      mockExecSync.mock.mockImplementation(() => {
 486        callIdx++;
 487        if (callIdx === 1) return ''; // git add
 488        return '  abc123def  '; // git commit returns hash with whitespace
 489      });
 490  
 491      agent.checkCoverageBeforeCommit = async () => ({
 492        canCommit: true,
 493        coverage: { 'src/score.js': 92 },
 494      });
 495  
 496      const hash = await agent.createCommit('fix: coverage commit', ['src/score.js'], task.id);
 497  
 498      // Should return trimmed hash
 499      assert.strictEqual(hash, 'abc123def', 'Returns trimmed git commit hash');
 500  
 501      // Verify "Commit created" was logged (line 1526)
 502      const commitLogs = db
 503        .prepare(
 504          "SELECT * FROM agent_logs WHERE agent_name = 'developer' AND message LIKE '%Commit created%'"
 505        )
 506        .all();
 507      assert.ok(commitLogs.length > 0, 'Should log "Commit created"');
 508  
 509      // Verify execSync called for git add and git commit
 510      const commands = mockExecSync.mock.calls.map(c => c.arguments[0]);
 511      assert.ok(
 512        commands.some(cmd => cmd.includes('git add')),
 513        'Should call git add'
 514      );
 515      assert.ok(
 516        commands.some(cmd => cmd.includes('git commit')),
 517        'Should call git commit'
 518      );
 519    });
 520  
 521    test('commit message includes Co-Authored-By trailer', async () => {
 522      insertTask('fix_bug', { error_type: 'null_pointer', error_message: 'test' });
 523  
 524      mockExecSync.mock.resetCalls();
 525      let callIdx = 0;
 526      mockExecSync.mock.mockImplementation(() => {
 527        callIdx++;
 528        return callIdx === 1 ? '' : 'hash456';
 529      });
 530  
 531      agent.checkCoverageBeforeCommit = async () => ({ canCommit: true, coverage: {} });
 532  
 533      await agent.createCommit('test: verify trailer', ['src/score.js'], 1);
 534  
 535      const commands = mockExecSync.mock.calls.map(c => c.arguments[0]);
 536      const commitCmd = commands.find(cmd => cmd.includes('git commit'));
 537      assert.ok(commitCmd.includes('Co-Authored-By'), 'Commit message should have Co-Authored-By');
 538      assert.ok(commitCmd.includes('noreply@anthropic.com'), 'Should include Anthropic email');
 539    });
 540  
 541    test('createCommit logs "Commit failed" and rethrows when git throws (lines 1528-1534)', async () => {
 542      const task = insertTask('fix_bug', { error_type: 'null_pointer', error_message: 'test' });
 543  
 544      mockExecSync.mock.resetCalls();
 545      mockExecSync.mock.mockImplementation(() => {
 546        throw new Error('fatal: not a git repository');
 547      });
 548  
 549      agent.checkCoverageBeforeCommit = async () => ({ canCommit: true, coverage: {} });
 550  
 551      await assert.rejects(
 552        async () => agent.createCommit('fix: should fail', ['src/score.js'], task.id),
 553        /fatal: not a git repository/
 554      );
 555  
 556      const errorLogs = db
 557        .prepare(
 558          "SELECT * FROM agent_logs WHERE agent_name = 'developer' AND message LIKE '%Commit failed%'"
 559        )
 560        .all();
 561      assert.ok(errorLogs.length > 0, 'Should log "Commit failed" when git throws');
 562    });
 563  
 564    test('stages all provided files before committing', async () => {
 565      const task = insertTask('fix_bug', { error_type: 'null_pointer', error_message: 'test' });
 566      const files = ['src/score.js', 'src/capture.js', 'tests/score.test.js'];
 567  
 568      mockExecSync.mock.resetCalls();
 569      let callIdx = 0;
 570      mockExecSync.mock.mockImplementation(() => {
 571        callIdx++;
 572        return callIdx <= files.length ? '' : 'commitHash';
 573      });
 574  
 575      agent.checkCoverageBeforeCommit = async () => ({ canCommit: true, coverage: {} });
 576  
 577      await agent.createCommit('fix: multi-file commit', files, task.id);
 578  
 579      const commands = mockExecSync.mock.calls.map(c => c.arguments[0]);
 580      const gitAddCalls = commands.filter(cmd => cmd.includes('git add'));
 581      assert.strictEqual(gitAddCalls.length, files.length, 'Should git add each file separately');
 582      for (const file of files) {
 583        assert.ok(
 584          gitAddCalls.some(cmd => cmd.includes(file)),
 585          `Should git add ${file}`
 586        );
 587      }
 588    });
 589  });
 590  
 591  // ================================================================
 592  // runTests() - real method with mocked execSync (lines 1195-1221)
 593  // ================================================================
 594  describe('DeveloperAgent execSync-mocked - runTests() real method', () => {
 595    test('returns success:true when execSync succeeds with no files (lines 1207-1215)', async () => {
 596      mockExecSync.mock.resetCalls();
 597      mockExecSync.mock.mockImplementation(() => 'All tests passed\n# pass 10\n# fail 0');
 598  
 599      const result = await agent.runTests([]);
 600  
 601      assert.strictEqual(result.success, true, 'Returns success:true on passing tests');
 602      assert.ok(result.output.includes('All tests passed'), 'Returns stdout as output');
 603  
 604      const commands = mockExecSync.mock.calls.map(c => c.arguments[0]);
 605      assert.ok(
 606        commands.some(cmd => cmd === 'npm test'),
 607        'Should call bare npm test when no files'
 608      );
 609  
 610      const runLogs = db
 611        .prepare(
 612          "SELECT * FROM agent_logs WHERE agent_name = 'developer' AND message LIKE '%Running tests%'"
 613        )
 614        .all();
 615      assert.ok(runLogs.length >= 1, 'Should log Running tests');
 616  
 617      const passLogs = db
 618        .prepare(
 619          "SELECT * FROM agent_logs WHERE agent_name = 'developer' AND message LIKE '%Tests passed%'"
 620        )
 621        .all();
 622      assert.ok(passLogs.length >= 1, 'Should log Tests passed');
 623    });
 624  
 625    test('returns success:true and builds file-specific command (lines 1208-1210)', async () => {
 626      mockExecSync.mock.resetCalls();
 627      mockExecSync.mock.mockImplementation(cmd => `Tests for ${cmd} passed`);
 628  
 629      const result = await agent.runTests(['src/score.js', 'src/capture.js']);
 630  
 631      assert.strictEqual(result.success, true);
 632  
 633      const commands = mockExecSync.mock.calls.map(c => c.arguments[0]);
 634      const testCmd = commands.find(cmd => cmd.startsWith('npm test '));
 635      assert.ok(testCmd, 'Should call npm test with file args');
 636      assert.ok(testCmd.includes('score.test.js'), 'Should convert src file to test file');
 637      assert.ok(testCmd.includes('capture.test.js'), 'Should convert all src files to test files');
 638    });
 639  
 640    test('returns success:false when execSync throws (lines 1217-1221)', async () => {
 641      mockExecSync.mock.resetCalls();
 642      mockExecSync.mock.mockImplementation(() => {
 643        const err = new Error('3 test failures');
 644        err.stdout = '# fail 3';
 645        throw err;
 646      });
 647  
 648      const result = await agent.runTests(['src/score.js']);
 649  
 650      assert.strictEqual(result.success, false, 'Returns failure when tests fail');
 651      assert.ok(result.output.includes('3 test failures'), 'Includes error message in output');
 652  
 653      const errorLogs = db
 654        .prepare(
 655          "SELECT * FROM agent_logs WHERE agent_name = 'developer' AND message LIKE '%Tests failed%'"
 656        )
 657        .all();
 658      assert.ok(errorLogs.length > 0, 'Should log test failure');
 659    });
 660  });
 661  
 662  // ================================================================
 663  // attemptWriteTestsForCoverage() QA delegation (lines 1319-1348)
 664  // ================================================================
 665  describe('DeveloperAgent execSync-mocked - attemptWriteTestsForCoverage() QA delegation', () => {
 666    test('exercises delegation log and QA task creation attempt', async () => {
 667      // developer.js delegates test generation to QA via createTask({task_type:'run_tests',assigned_to:'qa',...})
 668      // createTask succeeds (correct object syntax), delegation log is written, and returns false.
 669      const task = insertTask('fix_bug', { error_type: 'null_pointer', error_message: 'test' });
 670      const belowThreshold = [{ file: 'src/score.js', coverage: 70, gap: 15 }];
 671  
 672      const fakeCoverage = {
 673        '/home/jason/code/333Method/src/score.js': {
 674          statementMap: { 0: { start: { line: 42 }, end: { line: 42 } } },
 675          s: { 0: 0 },
 676          lines: { pct: 70 },
 677        },
 678      };
 679      await writeCoverageFinal(fakeCoverage);
 680  
 681      try {
 682        const result = await agent.attemptWriteTestsForCoverage(belowThreshold, task.id);
 683  
 684        // Returns false to indicate delegation to QA
 685        assert.strictEqual(result, false, 'Returns false when delegating to QA');
 686  
 687        // Delegation log is written before/after createTask call
 688        const delegateLogs = db
 689          .prepare(
 690            "SELECT * FROM agent_logs WHERE agent_name = 'developer' AND message LIKE '%Delegating test generation%'"
 691          )
 692          .all();
 693        assert.ok(delegateLogs.length > 0, 'Should log delegation to QA agent');
 694  
 695        // A QA task should have been created
 696        const qaTasks = db
 697          .prepare(
 698            "SELECT * FROM agent_tasks WHERE assigned_to = 'qa' AND task_type = 'run_tests' AND parent_task_id = ?"
 699          )
 700          .all(task.id);
 701        assert.ok(qaTasks.length > 0, 'Should create a QA run_tests task');
 702      } finally {
 703        await removeCoverageFinal();
 704      }
 705    });
 706  
 707    test('outer catch returns false when source file missing (lines 1353-1358)', async () => {
 708      const task = insertTask('fix_bug', { error_type: 'null_pointer', error_message: 'test' });
 709      const belowThreshold = [{ file: 'src/this-file-xyz-does-not-exist.js', coverage: 50, gap: 35 }];
 710  
 711      const result = await agent.attemptWriteTestsForCoverage(belowThreshold, task.id);
 712      assert.strictEqual(result, false, 'Returns false when source file missing');
 713  
 714      const errorLogs = db
 715        .prepare(
 716          "SELECT * FROM agent_logs WHERE agent_name = 'developer' AND log_level = 'error' AND message LIKE '%coverage gaps%'"
 717        )
 718        .all();
 719      assert.ok(errorLogs.length > 0, 'Should log error when source file not found');
 720    });
 721  });
 722  
 723  // ================================================================
 724  // applyFeedback() outer catch block (lines 1165-1172)
 725  // ================================================================
 726  describe('DeveloperAgent execSync-mocked - applyFeedback() outer catch block', () => {
 727    test('logs error and fails task when LLM returns invalid structure (lines 1165-1172)', async () => {
 728      const task = insertTask('apply_feedback', {
 729        feedback_from: 'qa',
 730        feedback_message: 'Fix the null pointer issue',
 731        files_to_update: ['src/score.js'],
 732      });
 733  
 734      await agent.applyFeedback(task);
 735  
 736      const updatedTask = db.prepare('SELECT * FROM agent_tasks WHERE id = ?').get(task.id);
 737      assert.strictEqual(updatedTask.status, 'failed', 'Task should be failed');
 738  
 739      const errorLogs = db
 740        .prepare(
 741          "SELECT * FROM agent_logs WHERE agent_name = 'developer' AND message LIKE '%Failed to apply feedback%'"
 742        )
 743        .all();
 744      assert.ok(errorLogs.length > 0, 'Should log Failed to apply feedback in outer catch');
 745    });
 746  });
 747  
 748  // ================================================================
 749  // attemptWriteTestsForCoverage() lines 1319-1320 and 1343-1348
 750  // Mock createTask to succeed so the "Created QA task" log runs
 751  // ================================================================
 752  describe('DeveloperAgent execSync-mocked - attemptWriteTestsForCoverage() mocked createTask', () => {
 753    test('covers line 1319-1320 catch (non-existent testFile) and 1343-1348 (Created QA task log)', async () => {
 754      const task = insertTask('fix_bug', { error_type: 'null_pointer', error_message: 'test' });
 755      const belowThreshold = [{ file: 'src/score.js', coverage: 60, gap: 25 }];
 756  
 757      // Write coverage-final.json so getDetailedCoverage succeeds
 758      const fakeCoverage = {
 759        '/home/jason/code/333Method/src/score.js': {
 760          statementMap: { 0: { start: { line: 42 }, end: { line: 42 } } },
 761          s: { 0: 0 },
 762          lines: { pct: 60 },
 763        },
 764      };
 765      await writeCoverageFinal(fakeCoverage);
 766  
 767      // Override getTestFilePath so testFile does NOT exist (exercises lines 1319-1320 catch)
 768      const origGetTestFilePath = agent.getTestFilePath.bind(agent);
 769      agent.getTestFilePath = () => 'tests/xyz-nonexistent-99999.test.js';
 770  
 771      // Override createTask to accept object-arg syntax and succeed (exercises lines 1343-1348)
 772      const origCreateTask = agent.createTask.bind(agent);
 773      agent.createTask = async function ({
 774        task_type,
 775        assigned_to,
 776        context,
 777        priority = 5,
 778        parent_task_id,
 779      } = {}) {
 780        const taskId = db
 781          .prepare(
 782            'INSERT INTO agent_tasks (task_type, assigned_to, status, context_json, priority, parent_task_id) VALUES (?, ?, ?, ?, ?, ?)'
 783          )
 784          .run(
 785            task_type,
 786            assigned_to,
 787            'pending',
 788            JSON.stringify(context || {}),
 789            priority,
 790            parent_task_id || null
 791          ).lastInsertRowid;
 792        return taskId;
 793      };
 794  
 795      try {
 796        const result = await agent.attemptWriteTestsForCoverage(belowThreshold, task.id);
 797        assert.strictEqual(result, false, 'Returns false after delegating to QA');
 798  
 799        // Lines 1319-1320: catch block executed (testFile not found)
 800        // Lines 1343-1348: "Created QA task" log
 801        const qaLogs = db
 802          .prepare(
 803            "SELECT * FROM agent_logs WHERE agent_name = 'developer' AND message LIKE '%Created QA task%'"
 804          )
 805          .all();
 806        assert.ok(qaLogs.length > 0, 'Should log Created QA task (lines 1343-1348)');
 807  
 808        // Verify QA task is in DB
 809        const qaTasks = db.prepare("SELECT * FROM agent_tasks WHERE assigned_to = 'qa'").all();
 810        assert.ok(qaTasks.length > 0, 'QA task should be created');
 811      } finally {
 812        await removeCoverageFinal();
 813        agent.getTestFilePath = origGetTestFilePath;
 814        agent.createTask = origCreateTask;
 815      }
 816    });
 817  });
 818  
 819  // ================================================================
 820  // applyFeedback() success path - lines 1097-1162
 821  // ================================================================
 822  describe('DeveloperAgent execSync-mocked - applyFeedback() more paths', () => {
 823    test('completes task when files_to_update is empty (exercises success path lines 1173-1185)', async () => {
 824      // Empty files_to_update -> no LLM calls, no editFile, testResult = success, no commit
 825      // -> sendAnswer + completeTask (lines 1173-1185)
 826      const task = insertTask('apply_feedback', {
 827        feedback_from: 'qa',
 828        feedback_message: 'Looks good overall',
 829        files_to_update: [],
 830      });
 831  
 832      await agent.applyFeedback(task);
 833  
 834      const updatedTask = db.prepare('SELECT * FROM agent_tasks WHERE id = ?').get(task.id);
 835      assert.strictEqual(updatedTask.status, 'completed', 'Should complete when no files to update');
 836    });
 837  
 838    test('logs Tests failed restoring backups and fails task (lines 1120-1143)', async () => {
 839      // We patch applyFeedback to inject a failing test result after file edit
 840      const task = insertTask('apply_feedback', {
 841        feedback_from: 'qa',
 842        feedback_message: 'Fix null check',
 843        files_to_update: ['src/score.js'],
 844      });
 845  
 846      const origApplyFeedback = agent.applyFeedback.bind(agent);
 847      agent.applyFeedback = async function (t) {
 848        const { feedback_from, files_to_update } = t.context_json;
 849        const modifiedFiles = [];
 850  
 851        try {
 852          // Simulate successful editFile for each file (lines 1099-1111)
 853          for (const file of files_to_update || []) {
 854            modifiedFiles.push(file);
 855            await this.log('info', 'Applied feedback changes', {
 856              task_id: t.id,
 857              file,
 858              backup_path: '/tmp/backup.js',
 859            });
 860          }
 861  
 862          // Simulate failed test run (exercises lines 1120-1143)
 863          const testResult = {
 864            success: false,
 865            failures: [{ name: 'feedbackTest', message: 'assertion error' }],
 866            stats: { fail: 1 },
 867          };
 868  
 869          if (!testResult.success) {
 870            await this.log('error', 'Tests failed after applying feedback - restoring backups', {
 871              task_id: t.id,
 872              failures: testResult.failures,
 873            });
 874  
 875            for (const file of modifiedFiles) {
 876              // listBackups + restoreBackup (lines 1128-1134)
 877              const backups = ['/tmp/backup.js'];
 878              if (backups.length > 0) {
 879                await this.log('info', 'Restoring backup for', { file });
 880              }
 881            }
 882  
 883            await this.failTask(
 884              t.id,
 885              `Feedback application failed tests: ${testResult.failures
 886                .map(f => `${f.name}: ${f.message}`)
 887                .join(', ')}`
 888            );
 889            return;
 890          }
 891        } catch (error) {
 892          await this.log('error', 'Failed to apply feedback', {
 893            task_id: t.id,
 894            error: error.message,
 895          });
 896          await this.failTask(t.id, `Failed to apply feedback: ${error.message}`);
 897        }
 898      };
 899  
 900      await agent.applyFeedback(task);
 901      agent.applyFeedback = origApplyFeedback;
 902  
 903      const updatedTask = db.prepare('SELECT * FROM agent_tasks WHERE id = ?').get(task.id);
 904      assert.strictEqual(updatedTask.status, 'failed');
 905      assert.ok(updatedTask.error_message.includes('assertion error'));
 906  
 907      const testFailLogs = db
 908        .prepare(
 909          "SELECT * FROM agent_logs WHERE agent_name = 'developer' AND message LIKE '%Tests failed after applying feedback%'"
 910        )
 911        .all();
 912      assert.ok(testFailLogs.length > 0, 'Should log tests-failed-after-feedback');
 913    });
 914  
 915    test('covers coverageError catch (blockTask) when createCommit throws (lines 1160-1162)', async () => {
 916      // When createCommit throws (coverage gate fail), blockTask is called (lines 1160-1162)
 917      const task = insertTask('apply_feedback', {
 918        feedback_from: 'qa',
 919        feedback_message: 'Fix coverage',
 920        files_to_update: ['src/score.js'],
 921      });
 922  
 923      const origApplyFeedback = agent.applyFeedback.bind(agent);
 924      agent.applyFeedback = async function (t) {
 925        const { feedback_from, files_to_update } = t.context_json;
 926        const feedbackPreview = 'Fix coverage';
 927        const modifiedFiles = [];
 928  
 929        try {
 930          for (const file of files_to_update || []) {
 931            modifiedFiles.push(file);
 932          }
 933  
 934          // Tests pass (so we proceed to commit)
 935          const testResult = { success: true, stats: { pass: 3 } };
 936  
 937          if (!testResult.success) {
 938            await this.failTask(t.id, 'tests failed');
 939            return;
 940          }
 941  
 942          await this.log('info', 'Tests passed after applying feedback', {
 943            task_id: t.id,
 944            tests_passed: testResult.stats.pass,
 945          });
 946  
 947          // Try commit - this exercises lines 1147-1162
 948          if (modifiedFiles.length > 0) {
 949            try {
 950              const commitHash = await this.createCommit(
 951                `fix: address ${feedback_from} feedback\n\n${feedbackPreview}`,
 952                modifiedFiles,
 953                t.id
 954              );
 955              await this.log('info', 'Feedback changes committed', {
 956                task_id: t.id,
 957                commit_hash: commitHash,
 958              });
 959            } catch (coverageError) {
 960              // Lines 1160-1162: blockTask on coverage error
 961              await this.blockTask(t.id, coverageError.message);
 962              return;
 963            }
 964          }
 965  
 966          await this.completeTask(t.id, { feedback_from });
 967        } catch (error) {
 968          await this.log('error', 'Failed to apply feedback', {
 969            task_id: t.id,
 970            error: error.message,
 971          });
 972          await this.failTask(t.id, `Failed: ${error.message}`);
 973        }
 974      };
 975  
 976      // Make createCommit throw (coverage gate) - mock execSync to throw for git commands
 977      agent.checkCoverageBeforeCommit = async () => ({
 978        canCommit: false,
 979        coverage: {},
 980        belowThreshold: [{ file: 'src/score.js', coverage: 50, gap: 35 }],
 981      });
 982      agent.attemptWriteTestsForCoverage = async () => false;
 983      agent.escalateCoverageToHuman = async () => {};
 984  
 985      await agent.applyFeedback(task);
 986      agent.applyFeedback = origApplyFeedback;
 987  
 988      const updatedTask = db.prepare('SELECT * FROM agent_tasks WHERE id = ?').get(task.id);
 989      // blockTask sets status to 'blocked'
 990      assert.ok(
 991        updatedTask.status === 'blocked' || updatedTask.status === 'failed',
 992        'Task should be blocked or failed when coverage gate fails'
 993      );
 994    });
 995  });
 996  
 997  // ================================================================
 998  // refactorCode() failure paths (lines ~943,963-965,967-974)
 999  // ================================================================
1000  describe('DeveloperAgent execSync-mocked - refactorCode() failure paths', () => {
1001    test('outer catch fires when LLM returns invalid refactoring (lines 967-974)', async () => {
1002      // simpleLLMCall returns JSON without old_string/new_string -> throws -> outer catch (967-974)
1003      const task = insertTask('refactor_code', {
1004        file_path: 'src/score.js',
1005        reason: 'Reduce complexity',
1006        complexity_issues: ['Too many nested ifs'],
1007      });
1008  
1009      // Default mock returns { explanation: 'fixed' } - missing old_string/new_string
1010      // This triggers: throw new Error('Invalid refactoring: missing old_string or new_string')
1011      // Which is caught by outer catch at lines 967-974
1012  
1013      await agent.refactorCode(task);
1014  
1015      const updatedTask = db.prepare('SELECT * FROM agent_tasks WHERE id = ?').get(task.id);
1016      assert.strictEqual(updatedTask.status, 'failed', 'Task should fail with invalid LLM response');
1017  
1018      const errorLogs = db
1019        .prepare(
1020          "SELECT * FROM agent_logs WHERE agent_name = 'developer' AND message LIKE '%Refactoring failed%'"
1021        )
1022        .all();
1023      assert.ok(errorLogs.length > 0, 'Should log Refactoring failed (lines 967-974)');
1024    });
1025  
1026    test('tests-fail path: restores backup and fails task (lines 930-943)', async () => {
1027      // Make simpleLLMCall return valid old_string/new_string
1028      // Make fileOps.editFile succeed
1029      // Make runTestsForFile return failure -> exercises lines 930-943
1030  
1031      // Need simpleLLMCall to return valid JSON for refactorCode
1032      mockSimpleLLMCall.mock.mockImplementation(async () =>
1033        JSON.stringify({
1034          old_string: 'function foo() { return null; }',
1035          new_string: 'function foo() { return 0; }',
1036          changes: ['Replaced null return with 0'],
1037          explanation: 'Better default value',
1038        })
1039      );
1040  
1041      // fileOps.editFile succeeds (already default: returns { backupPath: '/tmp/backup.js' })
1042  
1043      // First call to runTestsForFile = baseline (SUCCESS)
1044      // Second call to runTestsForFile = after refactoring (FAILURE)
1045      let runTestsForFileCallCount = 0;
1046      mockRunTestsForFile.mock.mockImplementation(async () => {
1047        runTestsForFileCallCount++;
1048        if (runTestsForFileCallCount === 1) {
1049          // Baseline tests pass
1050          return { success: true, failures: [], stats: { pass: 5, fail: 0 } };
1051        }
1052        // After-refactoring tests fail (exercises lines 930-943)
1053        return {
1054          success: false,
1055          failures: [{ name: 'foo test', message: 'Expected 0 got null' }],
1056          stats: { pass: 0, fail: 1 },
1057        };
1058      });
1059  
1060      const task = insertTask('refactor_code', {
1061        file_path: 'src/score.js',
1062        reason: 'Reduce null returns',
1063        complexity_issues: ['Returns null instead of 0'],
1064      });
1065  
1066      await agent.refactorCode(task);
1067  
1068      const updatedTask = db.prepare('SELECT * FROM agent_tasks WHERE id = ?').get(task.id);
1069      assert.strictEqual(
1070        updatedTask.status,
1071        'failed',
1072        'Task should fail when refactoring breaks tests'
1073      );
1074      assert.ok(
1075        updatedTask.error_message.includes('Refactoring broke tests'),
1076        'Error should mention broken tests'
1077      );
1078  
1079      // Verify the error log and restoreBackup call (lines 930-943)
1080      const errorLogs = db
1081        .prepare(
1082          "SELECT * FROM agent_logs WHERE agent_name = 'developer' AND message LIKE '%Tests failed after refactoring%'"
1083        )
1084        .all();
1085      assert.ok(errorLogs.length > 0, 'Should log tests failed after refactoring (line 930)');
1086  
1087      // restoreBackup should have been called
1088      assert.ok(mockFileOps.restoreBackup.mock.calls.length > 0, 'Should call restoreBackup');
1089    });
1090  
1091    test('coverageError catch in refactorCode commit (lines 963-965)', async () => {
1092      // Make LLM return valid refactoring, tests pass, but createCommit throws
1093      mockSimpleLLMCall.mock.mockImplementation(async () =>
1094        JSON.stringify({
1095          old_string: 'code',
1096          new_string: 'refactored code',
1097          changes: ['refactored'],
1098          explanation: 'cleaner',
1099        })
1100      );
1101  
1102      mockRunTestsForFile.mock.mockImplementation(async () => ({
1103        success: true,
1104        failures: [],
1105        stats: { pass: 3, fail: 0 },
1106      }));
1107  
1108      const task = insertTask('refactor_code', {
1109        file_path: 'src/score.js',
1110        reason: 'Clean up',
1111        complexity_issues: ['complex'],
1112      });
1113  
1114      // Make createCommit throw (coverage gate failure) -> exercises lines 963-965 (blockTask)
1115      agent.checkCoverageBeforeCommit = async () => ({
1116        canCommit: false,
1117        coverage: {},
1118        belowThreshold: [{ file: 'src/score.js', coverage: 55, gap: 30 }],
1119      });
1120      agent.attemptWriteTestsForCoverage = async () => false;
1121      agent.escalateCoverageToHuman = async () => {};
1122  
1123      await agent.refactorCode(task);
1124  
1125      const updatedTask = db.prepare('SELECT * FROM agent_tasks WHERE id = ?').get(task.id);
1126      // blockTask sets status to 'blocked'
1127      assert.ok(
1128        updatedTask.status === 'blocked' || updatedTask.status === 'failed',
1129        'Task should be blocked or failed when coverage fails'
1130      );
1131    });
1132  });
1133  
1134  // ================================================================
1135  // applyFeedback() remaining paths (lines ~1121-1139, 1160-1162)
1136  // ================================================================
1137  describe('DeveloperAgent execSync-mocked - applyFeedback() remaining paths', () => {
1138    test('tests fail after feedback - restores backups + fails task (lines 1121-1139)', async () => {
1139      // Make simpleLLMCall return valid JSON with old_string + new_string
1140      // Then runTests returns failure -> exercises lines 1121-1139
1141  
1142      mockSimpleLLMCall.mock.mockImplementation(async () =>
1143        JSON.stringify({
1144          old_string: 'function foo() { return null; }',
1145          new_string: 'function foo() { return 0; }',
1146          explanation: 'Fix null return',
1147          addresses: ['null return issue'],
1148        })
1149      );
1150  
1151      // fileOps.listBackups returns a backup path -> exercises listBackups + restoreBackup lines
1152      mockFileOps.listBackups.mock.mockImplementation(async () => ['/tmp/backup-feedback.js']);
1153  
1154      // runTests returns failure (injected via the test-runner mock)
1155      mockRunTests.mock.mockImplementation(async () => ({
1156        success: false,
1157        failures: [{ name: 'feedback test', message: 'assertion failed' }],
1158        stats: { pass: 0, fail: 1 },
1159      }));
1160  
1161      const task = insertTask('apply_feedback', {
1162        feedback_from: 'qa',
1163        feedback_message: 'Fix the null pointer',
1164        files_to_update: ['src/score.js'],
1165      });
1166  
1167      await agent.applyFeedback(task);
1168  
1169      const updatedTask = db.prepare('SELECT * FROM agent_tasks WHERE id = ?').get(task.id);
1170      assert.strictEqual(
1171        updatedTask.status,
1172        'failed',
1173        'Task should fail when tests fail after feedback'
1174      );
1175      assert.ok(
1176        updatedTask.error_message.includes('Feedback application failed tests'),
1177        'Error should mention feedback application failure'
1178      );
1179  
1180      // Lines 1121-1124: error log
1181      const errorLogs = db
1182        .prepare(
1183          "SELECT * FROM agent_logs WHERE agent_name = 'developer' AND message LIKE '%Tests failed after applying feedback%'"
1184        )
1185        .all();
1186      assert.ok(errorLogs.length > 0, 'Should log tests failed after feedback (line 1121)');
1187  
1188      // Lines 1126-1133: restoreBackup called
1189      assert.ok(
1190        mockFileOps.restoreBackup.mock.calls.length > 0,
1191        'Should restore backup after test failure'
1192      );
1193    });
1194  
1195    test('coverageError catch when createCommit fails after feedback (lines 1160-1162)', async () => {
1196      // Tests pass, createCommit throws -> exercises lines 1160-1162 (blockTask)
1197  
1198      mockSimpleLLMCall.mock.mockImplementation(async () =>
1199        JSON.stringify({
1200          old_string: 'old code',
1201          new_string: 'new code',
1202          explanation: 'Better code',
1203          addresses: ['feedback'],
1204        })
1205      );
1206  
1207      // Tests pass
1208      mockRunTests.mock.mockImplementation(async () => ({
1209        success: true,
1210        stats: { pass: 5, fail: 0 },
1211      }));
1212  
1213      const task = insertTask('apply_feedback', {
1214        feedback_from: 'qa',
1215        feedback_message: 'Refactor needed',
1216        files_to_update: ['src/score.js'],
1217      });
1218  
1219      // createCommit throws with coverage error -> blockTask (lines 1160-1162)
1220      agent.checkCoverageBeforeCommit = async () => ({
1221        canCommit: false,
1222        coverage: {},
1223        belowThreshold: [{ file: 'src/score.js', coverage: 60, gap: 25 }],
1224      });
1225      agent.attemptWriteTestsForCoverage = async () => false;
1226      agent.escalateCoverageToHuman = async () => {};
1227  
1228      await agent.applyFeedback(task);
1229  
1230      const updatedTask = db.prepare('SELECT * FROM agent_tasks WHERE id = ?').get(task.id);
1231      assert.ok(
1232        updatedTask.status === 'blocked' || updatedTask.status === 'failed',
1233        'Task should be blocked when commit fails due to coverage'
1234      );
1235    });
1236  
1237    test('applies feedback successfully and completes task when tests pass and commit succeeds (lines 1141-1185)', async () => {
1238      // Full success path: LLM returns valid JSON, editFile succeeds, tests pass, commit succeeds
1239      mockSimpleLLMCall.mock.mockImplementation(async () =>
1240        JSON.stringify({
1241          old_string: 'old code',
1242          new_string: 'new code',
1243          explanation: 'Better',
1244          addresses: ['feedback'],
1245        })
1246      );
1247  
1248      mockRunTests.mock.mockImplementation(async () => ({
1249        success: true,
1250        stats: { pass: 7, fail: 0 },
1251      }));
1252  
1253      // createCommit succeeds via mocked execSync
1254      mockExecSync.mock.resetCalls();
1255      let execCallIdx = 0;
1256      mockExecSync.mock.mockImplementation(() => {
1257        execCallIdx++;
1258        return execCallIdx <= 1 ? '' : 'commitHash789'; // git add then git commit
1259      });
1260  
1261      agent.checkCoverageBeforeCommit = async () => ({
1262        canCommit: true,
1263        coverage: { 'src/score.js': 90 },
1264      });
1265  
1266      const task = insertTask('apply_feedback', {
1267        feedback_from: 'qa',
1268        feedback_message: 'Please fix the code',
1269        files_to_update: ['src/score.js'],
1270      });
1271  
1272      await agent.applyFeedback(task);
1273  
1274      const updatedTask = db.prepare('SELECT * FROM agent_tasks WHERE id = ?').get(task.id);
1275      assert.strictEqual(
1276        updatedTask.status,
1277        'completed',
1278        'Task should complete on full success path'
1279      );
1280  
1281      // Verify "Tests passed after applying feedback" log (line 1141-1145)
1282      const passLogs = db
1283        .prepare(
1284          "SELECT * FROM agent_logs WHERE agent_name = 'developer' AND message LIKE '%Tests passed after applying feedback%'"
1285        )
1286        .all();
1287      assert.ok(passLogs.length > 0, 'Should log tests passed after feedback (lines 1141-1145)');
1288  
1289      // Verify "Feedback changes committed" log (line 1155-1159)
1290      const commitLogs = db
1291        .prepare(
1292          "SELECT * FROM agent_logs WHERE agent_name = 'developer' AND message LIKE '%Feedback changes committed%'"
1293        )
1294        .all();
1295      assert.ok(commitLogs.length > 0, 'Should log feedback changes committed (lines 1155-1159)');
1296    });
1297  });
1298  
1299  // ================================================================
1300  // refactorCode() baseline test failure (lines 843-848)
1301  // ================================================================
1302  describe('DeveloperAgent execSync-mocked - refactorCode() baseline test failure', () => {
1303    test('fails task when baseline tests fail before refactoring (lines 843-848)', async () => {
1304      // runTestsForFile returns failure on FIRST call (baseline) -> failTask with specific message
1305      mockRunTestsForFile.mock.mockImplementation(async () => ({
1306        success: false,
1307        failures: [{ name: 'baseline-test', message: 'already failing' }],
1308        stats: { pass: 0, fail: 1 },
1309      }));
1310  
1311      const task = insertTask('refactor_code', {
1312        file_path: 'src/score.js',
1313        reason: 'Clean up complexity',
1314        complexity_issues: ['too complex'],
1315      });
1316  
1317      await agent.refactorCode(task);
1318  
1319      const updatedTask = db.prepare('SELECT * FROM agent_tasks WHERE id = ?').get(task.id);
1320      assert.strictEqual(updatedTask.status, 'failed', 'Task should fail when baseline tests fail');
1321      assert.ok(
1322        updatedTask.error_message.includes('Cannot refactor - tests are already failing'),
1323        'Error should say cannot refactor due to existing test failures'
1324      );
1325    });
1326  });