"use strict";
// src/test_story_executor.ts
// v1.17.0 — End-to-end test of the autonomous story loop using MockAdapter.
//
// Strategy:
//   - Create a real git repo with a real story file.
//   - Inject canned MockAdapter responses that produce structured output
//     parseable by ResponseStructureExtractor (file paths + AC checkboxes).
//   - Run the executor and assert the story file is mutated, commits are
//     created, and the lifecycle transitions happen.
//
// Skipped scenarios (need real LLM): real adapter spawn, complex prompt
// engineering, multi-persona coordination.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const child_process_1 = require("child_process");
const StoryExecutor_1 = require("./orchestrator/story/StoryExecutor");
const HostInvoker_1 = require("./orchestrator/story/HostInvoker");
const MockAdapter_1 = require("./orchestrator/story/adapters/MockAdapter");
function assert(cond, msg) {
    if (!cond)
        throw new Error(`assertion failed: ${msg}`);
}
function setEnv(key, value) {
    const prev = process.env[key];
    if (value === undefined)
        delete process.env[key];
    else
        process.env[key] = value;
    return () => { if (prev === undefined)
        delete process.env[key];
    else
        process.env[key] = prev; };
}
function mkProject() {
    const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'story-exec-'));
    (0, child_process_1.execFileSync)('git', ['init', '-q', '-b', 'feature-x'], { cwd: dir });
    (0, child_process_1.execFileSync)('git', ['config', 'user.email', 'test@test.com'], { cwd: dir });
    (0, child_process_1.execFileSync)('git', ['config', 'user.name', 'Test'], { cwd: dir });
    (0, child_process_1.execFileSync)('git', ['config', 'commit.gpgsign', 'false'], { cwd: dir });
    // Seed a docs/stories/epic-test/1.1.story.md with the canonical schema.
    const storyDir = path.join(dir, 'docs', 'stories', 'epic-test');
    fs.mkdirSync(storyDir, { recursive: true });
    const storyPath = path.join(storyDir, '1.1.story.md');
    fs.writeFileSync(storyPath, `---
storyId: 1.1
epic: epic-test
title: '[BUG] Fix the gateway'
status: InProgress
severity: P1
---

# Story 1.1 — [BUG] Fix the gateway

**Status:** InProgress

## Acceptance Criteria
- [ ] SIGTERM exits within 5s
- [ ] tests added

## Description

Simulated story for the executor test.

## Change Log

- 2026-05-22 dev: started
`);
    // Seed a fake source file the mock will "modify"
    fs.mkdirSync(path.join(dir, 'src', 'orchestrator'), { recursive: true });
    fs.writeFileSync(path.join(dir, 'src', 'orchestrator', 'Gateway.ts'), 'export class Gateway {}\n');
    // Initial commit so we have HEAD.
    (0, child_process_1.execFileSync)('git', ['add', '.'], { cwd: dir });
    (0, child_process_1.execFileSync)('git', ['commit', '-q', '-m', 'initial'], { cwd: dir });
    return { dir, storyPath };
}
function rm(d) {
    try {
        fs.rmSync(d, { recursive: true, force: true });
    }
    catch { /* */ }
}
async function scenarioPlanOnly() {
    const { dir, storyPath } = mkProject();
    const prev = process.cwd();
    try {
        process.chdir(dir);
        const executor = new StoryExecutor_1.StoryExecutor();
        const result = await executor.run('1.1', { planOnly: true });
        assert(result.ok, `plan-only ok, errors: ${result.errors?.join('|')}`);
        assert(result.status === 'plan-only', `status: ${result.status}`);
        assert(result.iterations === 0, 'no iterations');
        // Story file untouched
        const after = fs.readFileSync(storyPath, 'utf-8');
        assert(after.includes('- [ ] SIGTERM'), 'story unchanged');
    }
    finally {
        process.chdir(prev);
        rm(dir);
    }
    console.log('✅ scenario 1: --plan-only exits cleanly without mutating state');
}
async function scenarioHappyPath() {
    const { dir, storyPath } = mkProject();
    const prev = process.cwd();
    HostInvoker_1.HostInvoker._resetForTests();
    MockAdapter_1.MockAdapter._reset();
    // Adapter responses: iter 1 → check AC 0 + file path, iter 2 → check AC 1.
    const restoreScript = setEnv('OPENLIFE_MOCK_ADAPTER_SCRIPT', JSON.stringify({
        responses: [
            `I'll patch the gateway.\n\nModified \`src/orchestrator/Gateway.ts\`.\n\nAcceptance:\n- [x] SIGTERM exits within 5s\n- [ ] tests added\n`,
            `Tests added.\n\nModified \`src/orchestrator/Gateway.ts\`.\n\nAcceptance:\n- [x] SIGTERM exits within 5s\n- [x] tests added\n`,
            `QA pass.\n\nAcceptance:\n- [x] SIGTERM exits within 5s\n- [x] tests added\n`,
        ],
    }));
    const restoreAdapter = setEnv('OPENLIFE_HOST_ADAPTER', 'mock');
    try {
        process.chdir(dir);
        const executor = new StoryExecutor_1.StoryExecutor();
        // stop-after=builder so we don't try to run `npm run test:all` (no node_modules).
        const result = await executor.run('1.1', {
            maxIters: 5,
            stopAfter: 'builder',
            adapter: 'mock',
        });
        if (!result.ok) {
            console.error('errors:', result.errors);
        }
        assert(result.iterations >= 1, `at least one iteration ran, got ${result.iterations}`);
        assert(result.acsCheckedThisRun >= 1, `ACs checked: ${result.acsCheckedThisRun}`);
        // Story file should have updated checkboxes.
        const after = fs.readFileSync(storyPath, 'utf-8');
        assert(after.includes('- [x] SIGTERM exits within 5s'), `AC 0 marked: ${after.slice(0, 200)}`);
        assert(after.includes('## Dev Agent Record'), 'Dev Agent Record appended');
        assert(after.includes('## File List'), 'File List appended');
        // Commits made
        assert(result.commits.length >= 1, `commits: ${result.commits.length}`);
    }
    finally {
        restoreScript();
        restoreAdapter();
        HostInvoker_1.HostInvoker._resetForTests();
        MockAdapter_1.MockAdapter._reset();
        process.chdir(prev);
        rm(dir);
    }
    console.log('✅ scenario 2: happy path — ACs checked, files recorded, commits made');
}
async function scenarioStoryNotFound() {
    const { dir } = mkProject();
    const prev = process.cwd();
    try {
        process.chdir(dir);
        const executor = new StoryExecutor_1.StoryExecutor();
        const result = await executor.run('999.999');
        assert(!result.ok, 'fails on missing story');
        assert(result.status === 'failed', `status: ${result.status}`);
        assert(result.reason?.includes('story_not_found'), `reason: ${result.reason}`);
    }
    finally {
        process.chdir(prev);
        rm(dir);
    }
    console.log('✅ scenario 3: missing story id → failed cleanly');
}
async function scenarioDirtyTreeRefused() {
    const { dir, storyPath } = mkProject();
    const prev = process.cwd();
    try {
        process.chdir(dir);
        // Make working tree dirty
        fs.writeFileSync(path.join(dir, 'dirty.txt'), 'dirty');
        const executor = new StoryExecutor_1.StoryExecutor();
        const result = await executor.run('1.1');
        assert(!result.ok, 'dirty tree refused');
        assert(result.status === 'failed', `status: ${result.status}`);
        assert(result.reason?.includes('dirty_working_tree'), `reason: ${result.reason}`);
        // Verify story untouched
        const after = fs.readFileSync(storyPath, 'utf-8');
        assert(after.includes('- [ ] SIGTERM'), 'story unchanged');
    }
    finally {
        process.chdir(prev);
        rm(dir);
    }
    console.log('✅ scenario 4: dirty working tree refused without --force');
}
async function scenarioNoProgressHalt() {
    const { dir } = mkProject();
    const prev = process.cwd();
    HostInvoker_1.HostInvoker._resetForTests();
    MockAdapter_1.MockAdapter._reset();
    // All responses return NO AC checkboxes → executor halts after 3 no-progress iters.
    const restoreScript = setEnv('OPENLIFE_MOCK_ADAPTER_SCRIPT', JSON.stringify({
        responses: [
            `Thinking about it.`,
            `Still thinking.`,
            `Cannot make progress.`,
            `Stuck.`,
        ],
    }));
    const restoreAdapter = setEnv('OPENLIFE_HOST_ADAPTER', 'mock');
    try {
        process.chdir(dir);
        const executor = new StoryExecutor_1.StoryExecutor();
        const result = await executor.run('1.1', { maxIters: 10, stopAfter: 'builder', adapter: 'mock' });
        assert(result.status === 'halted', `expected halted, got ${result.status}`);
        assert(result.reason === 'no_progress_after_3_iters', `reason: ${result.reason}`);
        assert(result.iterations === 3, `should halt after 3 iters, got ${result.iterations}`);
    }
    finally {
        restoreScript();
        restoreAdapter();
        HostInvoker_1.HostInvoker._resetForTests();
        MockAdapter_1.MockAdapter._reset();
        process.chdir(prev);
        rm(dir);
    }
    console.log('✅ scenario 5: no-progress watchdog halts after 3 iterations');
}
(async () => {
    try {
        await scenarioPlanOnly();
        await scenarioHappyPath();
        await scenarioStoryNotFound();
        await scenarioDirtyTreeRefused();
        await scenarioNoProgressHalt();
        console.log('TEST_STORY_EXECUTOR_OK');
    }
    catch (err) {
        console.error('TEST_STORY_EXECUTOR_FAIL', err);
        process.exit(1);
    }
})();
