"use strict";
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;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_test_1 = require("node:test");
const strict_1 = __importDefault(require("node:assert/strict"));
const fs = __importStar(require("node:fs"));
const path = __importStar(require("node:path"));
const os = __importStar(require("node:os"));
const run_1 = require("../commands/run");
function stubTask(overrides = {}) {
    return {
        id: 'TASK-1',
        name: 'Implement feature X',
        description: 'Build the X feature with sub-systems A and B.',
        status: 'open',
        url: 'https://example.test/t/TASK-1',
        tags: ['thinking'],
        ...overrides,
    };
}
function stubRunner(result = {}) {
    return {
        name: 'stub',
        isAvailable: () => result.available ?? true,
        run: async () => ({
            success: result.success ?? true,
            output: result.output ?? '',
            error: result.error ?? '',
        }),
    };
}
// ─── formatSubtaskId ──────────────────────────────────────────────────────────
(0, node_test_1.describe)('formatSubtaskId', () => {
    (0, node_test_1.it)('appends a trailing dot to numeric IDs', () => {
        strict_1.default.equal((0, run_1.formatSubtaskId)(3), '3.');
    });
    (0, node_test_1.it)('returns string IDs verbatim', () => {
        strict_1.default.equal((0, run_1.formatSubtaskId)('3.1'), '3.1');
        strict_1.default.equal((0, run_1.formatSubtaskId)('3.1.2'), '3.1.2');
    });
});
// ─── buildThinkingSubtaskPrompt ───────────────────────────────────────────────
(0, node_test_1.describe)('buildThinkingSubtaskPrompt', () => {
    const sub = {
        id: 1,
        title: 'Add handler',
        description: 'Edit src/foo.ts and add the route.',
        status: 'pending',
        attempts: 0,
    };
    const basePlan = {
        taskId: 'T-1',
        taskName: 'Feature',
        subtasks: [sub],
    };
    (0, node_test_1.it)('uses full task description and full instructions when not compact', () => {
        const task = stubTask({ description: 'Full long description of the feature.' });
        const text = (0, run_1.buildThinkingSubtaskPrompt)(sub, task, basePlan, '## Section\nDo the thing.', undefined, undefined, { compact: false });
        strict_1.default.match(text, /Task description:\nFull long description/);
        strict_1.default.match(text, /## Full implementation instructions/);
        strict_1.default.match(text, /## Section\nDo the thing\./);
    });
    (0, node_test_1.it)('uses taskSummary and truncated instructions when compact', () => {
        const task = stubTask({ description: 'X'.repeat(10000) });
        const plan = {
            ...basePlan,
            taskSummary: 'Build feature X with safe defaults.',
        };
        const longInstr = 'Y'.repeat(20000);
        const text = (0, run_1.buildThinkingSubtaskPrompt)(sub, task, plan, longInstr, undefined, undefined, { compact: true });
        strict_1.default.match(text, /Goal \(concise\):\nBuild feature X/);
        strict_1.default.match(text, /## Implementation plan \(truncated\)/);
        strict_1.default.ok(text.length < longInstr.length);
        strict_1.default.match(text, /T-1\.aidev\.instructions\.md/);
    });
    (0, node_test_1.it)('truncates task description in compact mode when taskSummary is absent', () => {
        const longDesc = 'Z'.repeat(run_1.SUBTASK_PROMPT_COMPACT_DESCRIPTION_FALLBACK_MAX + 500);
        const task = stubTask({ description: longDesc });
        const text = (0, run_1.buildThinkingSubtaskPrompt)(sub, task, basePlan, '', undefined, undefined, { compact: true });
        strict_1.default.match(text, /Task description \(truncated\)/);
        strict_1.default.match(text, /… \(truncated\)/);
    });
    (0, node_test_1.it)('includes previous-attempt diagnostics when previousError is set and not __git__', () => {
        const text = (0, run_1.buildThinkingSubtaskPrompt)(sub, stubTask(), basePlan, '', undefined, 'TypeError: oops', { compact: true });
        strict_1.default.match(text, /Previous attempt failure diagnostics/);
        strict_1.default.match(text, /TypeError: oops/);
    });
    (0, node_test_1.it)('omits previous-attempt section for __git__ sentinel', () => {
        const text = (0, run_1.buildThinkingSubtaskPrompt)(sub, stubTask(), basePlan, '', undefined, '__git__', { compact: true });
        strict_1.default.doesNotMatch(text, /Previous attempt failure diagnostics/);
    });
});
// ─── truncateForSubtaskPrompt ─────────────────────────────────────────────────
(0, node_test_1.describe)('truncateForSubtaskPrompt', () => {
    (0, node_test_1.it)('returns the string unchanged when under the limit', () => {
        strict_1.default.equal((0, run_1.truncateForSubtaskPrompt)('abc', 10), 'abc');
    });
    (0, node_test_1.it)('appends an ellipsis marker when over the limit', () => {
        const t = 'a'.repeat(200);
        const out = (0, run_1.truncateForSubtaskPrompt)(t, 50);
        strict_1.default.match(out, /… \(truncated\)/);
        strict_1.default.ok(out.length < t.length);
    });
});
// ─── subtaskDepth ─────────────────────────────────────────────────────────────
(0, node_test_1.describe)('subtaskDepth', () => {
    (0, node_test_1.it)('returns 0 for plain numeric IDs', () => {
        strict_1.default.equal((0, run_1.subtaskDepth)(3), 0);
    });
    (0, node_test_1.it)('counts dots for string IDs', () => {
        strict_1.default.equal((0, run_1.subtaskDepth)('3.1'), 1);
        strict_1.default.equal((0, run_1.subtaskDepth)('3.1.1'), 2);
    });
});
// ─── formatSubtaskList ────────────────────────────────────────────────────────
(0, node_test_1.describe)('formatSubtaskList', () => {
    (0, node_test_1.it)('renders mixed numeric and decimal IDs with status icons', () => {
        const plan = {
            taskId: 'T',
            taskName: 'demo',
            subtasks: [
                { id: 1, title: 'foundations', description: '', status: 'done', attempts: 1 },
                { id: '2.1', title: 'split-a', description: '', status: 'pending', attempts: 0 },
                { id: '2.2', title: 'split-b', description: '', status: 'failed', attempts: 1 },
            ],
        };
        const text = (0, run_1.formatSubtaskList)(plan);
        strict_1.default.match(text, /✅ \*\*1\.\*\* foundations — \*done\*/);
        strict_1.default.match(text, /⬜ \*\*2\.1\*\* split-a — \*pending\*/);
        strict_1.default.match(text, /❌ \*\*2\.2\*\* split-b — \*failed\*/);
    });
});
// ─── readTaskPlan / writeTaskPlan ─────────────────────────────────────────────
(0, node_test_1.describe)('readTaskPlan', () => {
    let tmpDir;
    let prevCwd;
    (0, node_test_1.beforeEach)(() => {
        tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-thinking-test-'));
        prevCwd = process.cwd();
        process.chdir(tmpDir);
    });
    (0, node_test_1.afterEach)(() => {
        process.chdir(prevCwd);
        fs.rmSync(tmpDir, { recursive: true, force: true });
    });
    (0, node_test_1.it)('returns null when no plan file exists', () => {
        strict_1.default.equal((0, run_1.readTaskPlan)('NONE'), null);
    });
    (0, node_test_1.it)('round-trips a plan', () => {
        const plan = {
            taskId: 'TASK-1',
            taskName: 'demo',
            subtasks: [
                { id: 1, title: 'first', description: 'do thing', status: 'done', attempts: 1 },
                { id: 2, title: 'second', description: 'do other', status: 'pending', attempts: 0 },
            ],
        };
        (0, run_1.writeTaskPlan)(plan);
        const loaded = (0, run_1.readTaskPlan)('TASK-1');
        strict_1.default.ok(loaded);
        strict_1.default.equal(loaded.subtasks.length, 2);
        strict_1.default.equal(loaded.subtasks[0].attempts, 1);
        strict_1.default.equal(loaded.subtasks[1].status, 'pending');
    });
    (0, node_test_1.it)('defaults missing attempts to 0 when reading legacy plans', () => {
        // Simulate a plan written by an older aidev version (no `attempts` field).
        const legacy = {
            taskId: 'OLD',
            taskName: 'legacy',
            subtasks: [
                { id: 1, title: 'a', description: 'a', status: 'done' },
                { id: 2, title: 'b', description: 'b', status: 'failed' },
            ],
        };
        fs.writeFileSync(path.join(tmpDir, 'OLD.aidev.task.json'), JSON.stringify(legacy), 'utf8');
        const loaded = (0, run_1.readTaskPlan)('OLD');
        strict_1.default.ok(loaded);
        strict_1.default.equal(loaded.subtasks[0].attempts, 0);
        strict_1.default.equal(loaded.subtasks[1].attempts, 0);
        strict_1.default.equal(loaded.subtasks[1].status, 'failed');
    });
    (0, node_test_1.it)('preserves string IDs across round-trip', () => {
        const plan = {
            taskId: 'STR',
            taskName: 'demo',
            subtasks: [
                { id: '3.1', title: 'a', description: 'a', status: 'pending', attempts: 0 },
                { id: '3.2', title: 'b', description: 'b', status: 'pending', attempts: 0 },
            ],
        };
        (0, run_1.writeTaskPlan)(plan);
        const loaded = (0, run_1.readTaskPlan)('STR');
        strict_1.default.ok(loaded);
        strict_1.default.equal(loaded.subtasks[0].id, '3.1');
        strict_1.default.equal(loaded.subtasks[1].id, '3.2');
    });
    (0, node_test_1.it)('round-trips taskSummary', () => {
        const plan = {
            taskId: 'SUM',
            taskName: 'demo',
            taskSummary: 'Ship the widget; keep API stable.',
            subtasks: [{ id: 1, title: 'a', description: 'a', status: 'pending', attempts: 0 }],
        };
        (0, run_1.writeTaskPlan)(plan);
        const loaded = (0, run_1.readTaskPlan)('SUM');
        strict_1.default.ok(loaded);
        strict_1.default.equal(loaded.taskSummary, 'Ship the widget; keep API stable.');
    });
});
// ─── cleanupStaleThinkingArtifacts ────────────────────────────────────────────
(0, node_test_1.describe)('cleanupStaleThinkingArtifacts', () => {
    let tmpDir;
    let prevCwd;
    (0, node_test_1.beforeEach)(() => {
        tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-stale-thinking-'));
        prevCwd = process.cwd();
        process.chdir(tmpDir);
    });
    (0, node_test_1.afterEach)(() => {
        process.chdir(prevCwd);
        fs.rmSync(tmpDir, { recursive: true, force: true });
    });
    (0, node_test_1.it)('does not remove recent artifact files', () => {
        fs.writeFileSync(path.join(tmpDir, 'X.aidev.task.json'), '{}', 'utf8');
        fs.writeFileSync(path.join(tmpDir, 'X.aidev.instructions.md'), 'x', 'utf8');
        (0, run_1.cleanupStaleThinkingArtifacts)(tmpDir, Date.now());
        strict_1.default.ok(fs.existsSync(path.join(tmpDir, 'X.aidev.task.json')));
        strict_1.default.ok(fs.existsSync(path.join(tmpDir, 'X.aidev.instructions.md')));
    });
    (0, node_test_1.it)('removes artifact files older than the max age', () => {
        fs.writeFileSync(path.join(tmpDir, 'OLD.aidev.task.json'), '{}', 'utf8');
        fs.writeFileSync(path.join(tmpDir, 'OLD.aidev.instructions.md'), 'x', 'utf8');
        const futureNow = Date.now() + run_1.THINKING_ARTIFACT_MAX_AGE_MS + 60000;
        (0, run_1.cleanupStaleThinkingArtifacts)(tmpDir, futureNow);
        strict_1.default.ok(!fs.existsSync(path.join(tmpDir, 'OLD.aidev.task.json')));
        strict_1.default.ok(!fs.existsSync(path.join(tmpDir, 'OLD.aidev.instructions.md')));
    });
    (0, node_test_1.it)('ignores unrelated filenames', () => {
        fs.writeFileSync(path.join(tmpDir, 'notes.txt'), 'x', 'utf8');
        (0, run_1.cleanupStaleThinkingArtifacts)(tmpDir, Date.now() + run_1.THINKING_ARTIFACT_MAX_AGE_MS + 60000);
        strict_1.default.ok(fs.existsSync(path.join(tmpDir, 'notes.txt')));
    });
});
// ─── splitFailedSubtask ───────────────────────────────────────────────────────
(0, node_test_1.describe)('splitFailedSubtask', () => {
    const failedSubtask = {
        id: 3,
        title: 'Hard step',
        description: 'Build a complicated thing',
        status: 'failed',
        attempts: 2,
        lastError: 'TypeError: cannot read property foo of undefined',
    };
    function planWith(failed) {
        return {
            taskId: 'TASK-1',
            taskName: 'demo',
            subtasks: [
                { id: 1, title: 'setup', description: '', status: 'done', attempts: 1 },
                { id: 2, title: 'middle', description: '', status: 'done', attempts: 1 },
                failed,
                { id: 4, title: 'finish', description: '', status: 'pending', attempts: 0 },
            ],
        };
    }
    (0, node_test_1.it)('returns null when no runner is available', async () => {
        const result = await (0, run_1.splitFailedSubtask)(stubTask(), planWith(failedSubtask), failedSubtask, [
            stubRunner({ available: false }),
        ]);
        strict_1.default.equal(result, null);
    });
    (0, node_test_1.it)('returns null when the runner reports failure', async () => {
        const result = await (0, run_1.splitFailedSubtask)(stubTask(), planWith(failedSubtask), failedSubtask, [
            stubRunner({ success: false, output: '', error: 'no api key' }),
        ]);
        strict_1.default.equal(result, null);
    });
    (0, node_test_1.it)('returns null when the runner output has no JSON', async () => {
        const result = await (0, run_1.splitFailedSubtask)(stubTask(), planWith(failedSubtask), failedSubtask, [
            stubRunner({ output: 'sorry, I cannot do that' }),
        ]);
        strict_1.default.equal(result, null);
    });
    (0, node_test_1.it)('returns null when JSON contains the wrong number of subtasks', async () => {
        const result = await (0, run_1.splitFailedSubtask)(stubTask(), planWith(failedSubtask), failedSubtask, [
            stubRunner({
                output: JSON.stringify({
                    subtasks: [{ title: 'only one', description: 'oops' }],
                }),
            }),
        ]);
        strict_1.default.equal(result, null);
    });
    (0, node_test_1.it)('returns null when a sub-task has empty title or description', async () => {
        const result = await (0, run_1.splitFailedSubtask)(stubTask(), planWith(failedSubtask), failedSubtask, [
            stubRunner({
                output: JSON.stringify({
                    subtasks: [
                        { title: '', description: 'ok' },
                        { title: 'b', description: 'ok' },
                    ],
                }),
            }),
        ]);
        strict_1.default.equal(result, null);
    });
    (0, node_test_1.it)('produces 2 sub-tasks with .1/.2 IDs when given valid JSON', async () => {
        const canned = {
            subtasks: [
                { title: 'Foundations', description: 'Lay the groundwork' },
                { title: 'Wiring', description: 'Connect the pieces' },
            ],
        };
        const result = await (0, run_1.splitFailedSubtask)(stubTask(), planWith(failedSubtask), failedSubtask, [
            stubRunner({ output: JSON.stringify(canned) }),
        ]);
        strict_1.default.ok(result);
        strict_1.default.equal(result.length, 2);
        strict_1.default.equal(result[0].id, '3.1');
        strict_1.default.equal(result[1].id, '3.2');
        strict_1.default.equal(result[0].title, 'Foundations');
        strict_1.default.equal(result[1].title, 'Wiring');
        strict_1.default.equal(result[0].status, 'pending');
        strict_1.default.equal(result[0].attempts, 0);
        strict_1.default.equal(result[1].attempts, 0);
    });
    (0, node_test_1.it)('produces nested IDs (3.1.1, 3.1.2) when splitting a decimal-ID step', async () => {
        const nestedFailed = { ...failedSubtask, id: '3.1' };
        const canned = {
            subtasks: [
                { title: 'a', description: 'a' },
                { title: 'b', description: 'b' },
            ],
        };
        const result = await (0, run_1.splitFailedSubtask)(stubTask(), planWith(nestedFailed), nestedFailed, [
            stubRunner({ output: JSON.stringify(canned) }),
        ]);
        strict_1.default.ok(result);
        strict_1.default.equal(result[0].id, '3.1.1');
        strict_1.default.equal(result[1].id, '3.1.2');
    });
    (0, node_test_1.it)('extracts JSON when wrapped in extraneous prose', async () => {
        const wrapped = `Sure! Here is the split:\n${JSON.stringify({
            subtasks: [
                { title: 'one', description: 'first' },
                { title: 'two', description: 'second' },
            ],
        })}\nHope that helps.`;
        const result = await (0, run_1.splitFailedSubtask)(stubTask(), planWith(failedSubtask), failedSubtask, [
            stubRunner({ output: wrapped }),
        ]);
        strict_1.default.ok(result);
        strict_1.default.equal(result.length, 2);
    });
    (0, node_test_1.it)('uses the first available runner', async () => {
        let secondCalled = false;
        const runners = [
            { name: 'unavail', isAvailable: () => false, run: async () => ({ success: false, output: '', error: '' }) },
            {
                name: 'real',
                isAvailable: () => true,
                run: async () => {
                    secondCalled = true;
                    return {
                        success: true,
                        output: JSON.stringify({
                            subtasks: [
                                { title: 'x', description: 'x' },
                                { title: 'y', description: 'y' },
                            ],
                        }),
                        error: '',
                    };
                },
            },
        ];
        const result = await (0, run_1.splitFailedSubtask)(stubTask(), planWith(failedSubtask), failedSubtask, runners);
        strict_1.default.ok(result);
        strict_1.default.ok(secondCalled);
    });
    (0, node_test_1.it)('passes diagnostics into the runner prompt', async () => {
        let capturedPrompt = '';
        const runner = {
            name: 'spy',
            isAvailable: () => true,
            run: async (prompt) => {
                capturedPrompt = prompt;
                return {
                    success: true,
                    output: JSON.stringify({
                        subtasks: [
                            { title: 'a', description: 'a' },
                            { title: 'b', description: 'b' },
                        ],
                    }),
                    error: '',
                };
            },
        };
        await (0, run_1.splitFailedSubtask)(stubTask(), planWith(failedSubtask), failedSubtask, [runner]);
        strict_1.default.match(capturedPrompt, /TypeError: cannot read property foo of undefined/);
        strict_1.default.match(capturedPrompt, /Hard step/);
    });
    (0, node_test_1.it)('omits the __git__ sentinel from diagnostics in the prompt', async () => {
        let capturedPrompt = '';
        const runner = {
            name: 'spy',
            isAvailable: () => true,
            run: async (prompt) => {
                capturedPrompt = prompt;
                return {
                    success: true,
                    output: JSON.stringify({
                        subtasks: [
                            { title: 'a', description: 'a' },
                            { title: 'b', description: 'b' },
                        ],
                    }),
                    error: '',
                };
            },
        };
        const gitFailed = { ...failedSubtask, lastError: '__git__' };
        await (0, run_1.splitFailedSubtask)(stubTask(), planWith(gitFailed), gitFailed, [runner]);
        strict_1.default.doesNotMatch(capturedPrompt, /__git__/);
        strict_1.default.match(capturedPrompt, /\(no diagnostics captured\)/);
    });
});
//# sourceMappingURL=thinking-recovery.test.js.map