"use strict";
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 run_1 = require("../commands/run");
const github_1 = require("../github");
const baseConfig = {
    provider: 'clickup',
    githubRepo: 'owner/repo',
    githubBaseBranch: 'main',
    clickupInReviewStatus: 'review',
    commentPrefix: '[aidev]',
};
// ─── buildPRUrl ───────────────────────────────────────────────────────────────
(0, node_test_1.describe)('buildPRUrl', () => {
    (0, node_test_1.it)('returns a GitHub compare URL', () => {
        const url = (0, run_1.buildPRUrl)(baseConfig, 'abc123/fix-login');
        strict_1.default.ok(url.startsWith('https://github.com/owner/repo/compare/'));
        strict_1.default.ok(url.includes('main...'));
        strict_1.default.ok(url.includes('expand=1'));
    });
    (0, node_test_1.it)('URL-encodes the branch name', () => {
        const url = (0, run_1.buildPRUrl)(baseConfig, 'abc/fix login');
        strict_1.default.ok(url.includes('fix%20login'));
    });
    (0, node_test_1.it)('returns empty string when githubRepo is not set', () => {
        const url = (0, run_1.buildPRUrl)({ ...baseConfig, githubRepo: '' }, 'abc/branch');
        strict_1.default.equal(url, '');
    });
});
// ─── buildPRBody ─────────────────────────────────────────────────────────────
(0, node_test_1.describe)('buildPRBody', () => {
    const task = {
        id: 'abc123',
        name: 'Fix bug',
        description: 'Fix the login bug',
        status: 'open',
        url: 'https://app.clickup.com/t/abc123',
        tags: ['myproject'],
    };
    (0, node_test_1.it)('includes the task URL', () => {
        const body = (0, run_1.buildPRBody)(task);
        strict_1.default.ok(body.includes('https://app.clickup.com/t/abc123'));
    });
    (0, node_test_1.it)('uses default signature when PR_SIGNATURE is not set', () => {
        const original = process.env.PR_SIGNATURE;
        delete process.env.PR_SIGNATURE;
        const body = (0, run_1.buildPRBody)(task);
        strict_1.default.ok(body.includes('Automated PR by aidev.'));
        if (original !== undefined)
            process.env.PR_SIGNATURE = original;
    });
    (0, node_test_1.it)('uses PR_SIGNATURE env var when set', () => {
        const original = process.env.PR_SIGNATURE;
        process.env.PR_SIGNATURE = 'Custom signature from CI';
        const body = (0, run_1.buildPRBody)(task);
        strict_1.default.ok(body.includes('Custom signature from CI'));
        strict_1.default.ok(!body.includes('Automated PR by aidev.'));
        if (original !== undefined) {
            process.env.PR_SIGNATURE = original;
        }
        else {
            delete process.env.PR_SIGNATURE;
        }
    });
    (0, node_test_1.it)('formats as "Implements: <url>\\n\\n<signature>"', () => {
        const original = process.env.PR_SIGNATURE;
        delete process.env.PR_SIGNATURE;
        const body = (0, run_1.buildPRBody)(task);
        strict_1.default.equal(body, 'Implements: https://app.clickup.com/t/abc123\n\nAutomated PR by aidev.');
        if (original !== undefined)
            process.env.PR_SIGNATURE = original;
    });
});
// ─── buildCompletionComment ───────────────────────────────────────────────────
(0, node_test_1.describe)('buildCompletionComment', () => {
    (0, node_test_1.it)('includes the branch name', () => {
        const comment = (0, run_1.buildCompletionComment)('abc123/fix-login', '', baseConfig);
        strict_1.default.ok(comment.includes('abc123/fix-login'));
    });
    (0, node_test_1.it)('includes the in-review status', () => {
        const comment = (0, run_1.buildCompletionComment)('abc/branch', '', baseConfig);
        strict_1.default.ok(comment.includes('review'));
    });
    (0, node_test_1.it)('includes PR link when provided', () => {
        const comment = (0, run_1.buildCompletionComment)('abc/branch', 'https://github.com/pr/1', baseConfig);
        strict_1.default.ok(comment.includes('https://github.com/pr/1'));
    });
    (0, node_test_1.it)('omits PR link when empty', () => {
        const comment = (0, run_1.buildCompletionComment)('abc/branch', '', baseConfig);
        strict_1.default.ok(!comment.includes('https://'));
    });
    (0, node_test_1.it)('uses custom prefix when configured', () => {
        const customConfig = { ...baseConfig, commentPrefix: '[mybot]' };
        const comment = (0, run_1.buildCompletionComment)('abc/branch', '', customConfig);
        strict_1.default.ok(comment.startsWith('[mybot]'));
        strict_1.default.ok(!comment.includes('[aidev]'));
    });
});
// ─── buildImplementPrompt ─────────────────────────────────────────────────────
(0, node_test_1.describe)('buildImplementPrompt', () => {
    const task = {
        id: 'abc123',
        name: 'Fix login bug',
        description: 'Users cannot log in with email.',
        status: 'open',
        url: 'https://app.clickup.com/t/abc123',
        tags: ['myproject'],
    };
    (0, node_test_1.it)('includes the task name', () => {
        const prompt = (0, run_1.buildImplementPrompt)(task, '');
        strict_1.default.ok(prompt.includes('Fix login bug'));
    });
    (0, node_test_1.it)('includes the description', () => {
        const prompt = (0, run_1.buildImplementPrompt)(task, '');
        strict_1.default.ok(prompt.includes('Users cannot log in with email.'));
    });
    (0, node_test_1.it)('includes context when provided', () => {
        const prompt = (0, run_1.buildImplementPrompt)(task, 'Use JWT tokens.');
        strict_1.default.ok(prompt.includes('Use JWT tokens.'));
    });
    (0, node_test_1.it)('handles missing description gracefully', () => {
        const prompt = (0, run_1.buildImplementPrompt)({ ...task, description: '' }, '');
        strict_1.default.ok(prompt.includes('no description provided'));
    });
});
// ─── hasHumanReply ────────────────────────────────────────────────────────────
function makeComment(text) {
    return { id: '1', text, author: 'user', authorId: '100', date: Date.now() };
}
(0, node_test_1.describe)('hasHumanReply', () => {
    (0, node_test_1.it)('returns false when fewer than 2 comments', () => {
        strict_1.default.equal((0, run_1.hasHumanReply)([]), false);
        strict_1.default.equal((0, run_1.hasHumanReply)([makeComment('hello')]), false);
    });
    (0, node_test_1.it)('returns true when last comment is from a human', () => {
        const comments = [
            makeComment('[aidev] Starting implementation'),
            makeComment('Please also fix the tests'),
        ];
        strict_1.default.equal((0, run_1.hasHumanReply)(comments), true);
    });
    (0, node_test_1.it)('returns false when last comment is from aidev', () => {
        const comments = [
            makeComment('[aidev] Starting implementation'),
            makeComment('[aidev] All AI runners failed.'),
        ];
        strict_1.default.equal((0, run_1.hasHumanReply)(comments), false);
    });
    (0, node_test_1.it)('uses custom prefix when provided', () => {
        const comments = [
            makeComment('[mybot] Starting implementation'),
            makeComment('Please also fix the tests'),
        ];
        strict_1.default.equal((0, run_1.hasHumanReply)(comments, '[mybot]'), true);
    });
    (0, node_test_1.it)('returns false when last comment has custom prefix', () => {
        const comments = [
            makeComment('[mybot] Starting implementation'),
            makeComment('[mybot] All AI runners failed.'),
        ];
        strict_1.default.equal((0, run_1.hasHumanReply)(comments, '[mybot]'), false);
    });
    (0, node_test_1.it)('returns true when human comment quotes the aidev prefix mid-text', () => {
        const comments = [
            makeComment('[aidev] Starting implementation'),
            makeComment('Re: [aidev] please retry — the change you proposed is wrong'),
        ];
        strict_1.default.equal((0, run_1.hasHumanReply)(comments), true);
    });
    (0, node_test_1.it)('treats aidev comments as such even with leading whitespace', () => {
        const comments = [
            makeComment('Initial human request'),
            makeComment('   [aidev] Implementation complete!'),
        ];
        strict_1.default.equal((0, run_1.hasHumanReply)(comments), false);
    });
    (0, node_test_1.it)('returns true when a human commented after aidev even if a later comment is again from aidev', () => {
        const comments = [
            makeComment('[aidev] Implementation complete!'),
            makeComment('Please address review feedback'),
            makeComment('[aidev] Merge conflicts resolved automatically.'),
        ];
        strict_1.default.equal((0, run_1.hasHumanReply)(comments), true);
    });
});
(0, node_test_1.describe)('hasHumanComment', () => {
    (0, node_test_1.it)('returns false when there are no comments', () => {
        strict_1.default.equal((0, run_1.hasHumanComment)([]), false);
    });
    (0, node_test_1.it)('returns false when all comments are aidev comments', () => {
        const comments = [
            makeComment('[aidev] Starting implementation'),
            makeComment('   [aidev] Need clarification'),
        ];
        strict_1.default.equal((0, run_1.hasHumanComment)(comments), false);
    });
    (0, node_test_1.it)('returns true when at least one human comment exists', () => {
        const comments = [
            makeComment('[aidev] Starting implementation'),
            makeComment('Please proceed'),
        ];
        strict_1.default.equal((0, run_1.hasHumanComment)(comments), true);
    });
    (0, node_test_1.it)('supports custom prefixes', () => {
        const comments = [
            makeComment('[mybot] Status update'),
            makeComment('Follow-up from user'),
        ];
        strict_1.default.equal((0, run_1.hasHumanComment)(comments, '[mybot]'), true);
    });
});
// ─── hasTriggerWord ───────────────────────────────────────────────────────────
(0, node_test_1.describe)('hasTriggerWord', () => {
    (0, node_test_1.it)('returns false on empty comments', () => {
        strict_1.default.equal((0, run_1.hasTriggerWord)([], 'aidev-continue'), false);
    });
    (0, node_test_1.it)('returns true when last comment contains the trigger word', () => {
        const comments = [makeComment('aidev-continue please re-run')];
        strict_1.default.equal((0, run_1.hasTriggerWord)(comments, 'aidev-continue'), true);
    });
    (0, node_test_1.it)('is case-insensitive', () => {
        const comments = [makeComment('AIDEV-CONTINUE')];
        strict_1.default.equal((0, run_1.hasTriggerWord)(comments, 'aidev-continue'), true);
    });
    (0, node_test_1.it)('returns false when trigger word is absent', () => {
        const comments = [makeComment('Please fix the tests')];
        strict_1.default.equal((0, run_1.hasTriggerWord)(comments, 'aidev-continue'), false);
    });
    (0, node_test_1.it)('returns false when trigger word is empty and no default trigger present', () => {
        const comments = [makeComment('any text')];
        strict_1.default.equal((0, run_1.hasTriggerWord)(comments, ''), false);
    });
    // ── Default trigger word always works regardless of configured triggerWord ──
    (0, node_test_1.it)('DEFAULT_TRIGGER_WORD is "aidev-continue"', () => {
        strict_1.default.equal(run_1.DEFAULT_TRIGGER_WORD, 'aidev-continue');
    });
    (0, node_test_1.it)('aidev-continue works even when triggerWord is empty (no env config)', () => {
        const comments = [makeComment('aidev-continue')];
        strict_1.default.equal((0, run_1.hasTriggerWord)(comments, ''), true);
    });
    (0, node_test_1.it)('aidev-continue works even when a different triggerWord is configured', () => {
        const comments = [makeComment('aidev-continue')];
        strict_1.default.equal((0, run_1.hasTriggerWord)(comments, 'my-custom-word'), true);
    });
    (0, node_test_1.it)('configured custom trigger word also works', () => {
        const comments = [makeComment('my-custom-word go ahead')];
        strict_1.default.equal((0, run_1.hasTriggerWord)(comments, 'my-custom-word'), true);
    });
    (0, node_test_1.it)('only checks the last comment — earlier trigger word is ignored', () => {
        const comments = [
            makeComment('aidev-continue'),
            makeComment('Please review my changes'),
        ];
        // last comment has no trigger word
        strict_1.default.equal((0, run_1.hasTriggerWord)(comments, 'aidev-continue'), false);
    });
});
// ─── checkNeedsClarification ──────────────────────────────────────────────────
function makeRunner(name, success, output) {
    return {
        name,
        isAvailable: () => true,
        run: async (_prompt) => ({ success, output, error: success ? '' : 'error' }),
    };
}
const clarificationTask = {
    id: 'abc123',
    name: 'Add dark mode',
    description: 'Support a dark color scheme.',
    status: 'open',
    url: 'https://app.clickup.com/t/abc123',
    tags: ['myproject'],
};
const clarificationConfig = {
    devNotesMode: 'smart',
    clickupPendingStatus: 'pending',
};
const mockProvider = {
    fetchTasks: async () => [],
    fetchTasksByStatus: async () => [],
    getComments: async () => [],
    postComment: async () => { },
    updateStatus: async () => { },
    createTask: async () => ({ id: '', url: '' }),
};
(0, node_test_1.describe)('checkNeedsClarification', () => {
    (0, node_test_1.it)('returns a question when runner says task is not clear', async () => {
        const runner = makeRunner('claude', true, JSON.stringify({ clear: false, question: 'Which color scheme?' }));
        const q = await (0, run_1.checkNeedsClarification)(clarificationTask, clarificationConfig, mockProvider, [runner]);
        strict_1.default.equal(q, 'Which color scheme?');
    });
    (0, node_test_1.it)('returns null when runner says task is clear', async () => {
        const runner = makeRunner('claude', true, JSON.stringify({ clear: true, question: null }));
        const q = await (0, run_1.checkNeedsClarification)(clarificationTask, clarificationConfig, mockProvider, [runner]);
        strict_1.default.equal(q, null);
    });
    (0, node_test_1.it)('falls back to next runner when first runner fails', async () => {
        const failing = makeRunner('cursor', false, '');
        const working = makeRunner('claude', true, JSON.stringify({ clear: false, question: 'Any preferences?' }));
        const q = await (0, run_1.checkNeedsClarification)(clarificationTask, clarificationConfig, mockProvider, [failing, working]);
        strict_1.default.equal(q, 'Any preferences?');
    });
    (0, node_test_1.it)('falls back to next runner when first runner returns unparseable JSON', async () => {
        const bad = makeRunner('cursor', true, 'not json at all');
        const good = makeRunner('claude', true, JSON.stringify({ clear: true, question: null }));
        const q = await (0, run_1.checkNeedsClarification)(clarificationTask, clarificationConfig, mockProvider, [bad, good]);
        strict_1.default.equal(q, null);
    });
    (0, node_test_1.it)('returns null when all runners fail', async () => {
        const a = makeRunner('cursor', false, '');
        const b = makeRunner('windsurf', false, '');
        const q = await (0, run_1.checkNeedsClarification)(clarificationTask, clarificationConfig, mockProvider, [a, b]);
        strict_1.default.equal(q, null);
    });
    (0, node_test_1.it)('returns null when no runners are available', async () => {
        const unavailable = { name: 'cursor', isAvailable: () => false, run: async () => ({ success: false, output: '', error: '' }) };
        const q = await (0, run_1.checkNeedsClarification)(clarificationTask, clarificationConfig, mockProvider, [unavailable]);
        strict_1.default.equal(q, null);
    });
    (0, node_test_1.it)('returns the always-ask clarification prompt when devNotesMode is "always"', async () => {
        const config = { ...clarificationConfig, devNotesMode: 'always' };
        const q = await (0, run_1.checkNeedsClarification)(clarificationTask, config, mockProvider, []);
        strict_1.default.ok(q !== null && q.includes('Add dark mode'));
    });
});
// ─── hasAidevComment ──────────────────────────────────────────────────────────
(0, node_test_1.describe)('hasAidevComment', () => {
    (0, node_test_1.it)('returns false on empty comments', () => {
        strict_1.default.equal((0, run_1.hasAidevComment)([]), false);
    });
    (0, node_test_1.it)('returns true when a comment contains [aidev]', () => {
        const comments = [
            makeComment('[aidev] Starting implementation'),
            makeComment('Some human reply'),
        ];
        strict_1.default.equal((0, run_1.hasAidevComment)(comments), true);
    });
    (0, node_test_1.it)('returns false when no comment contains [aidev]', () => {
        const comments = [
            makeComment('Please fix the tests'),
            makeComment('I agree, this needs work'),
        ];
        strict_1.default.equal((0, run_1.hasAidevComment)(comments), false);
    });
    (0, node_test_1.it)('does not treat human comments quoting the prefix as aidev comments', () => {
        const comments = [
            makeComment('Earlier you said: "[aidev] foo" — that was wrong'),
        ];
        strict_1.default.equal((0, run_1.hasAidevComment)(comments), false);
    });
    (0, node_test_1.it)('returns true when only the last comment is from aidev', () => {
        const comments = [
            makeComment('Human wrote this'),
            makeComment('[aidev] Non-code task complete!'),
        ];
        strict_1.default.equal((0, run_1.hasAidevComment)(comments), true);
    });
    (0, node_test_1.it)('detects custom prefix', () => {
        const comments = [
            makeComment('[mybot] Starting implementation'),
        ];
        strict_1.default.equal((0, run_1.hasAidevComment)(comments, '[mybot]'), true);
    });
    (0, node_test_1.it)('returns false when custom prefix is not present', () => {
        const comments = [
            makeComment('[aidev] Starting implementation'),
        ];
        strict_1.default.equal((0, run_1.hasAidevComment)(comments, '[mybot]'), false);
    });
});
// ─── filterAutomatedComments ──────────────────────────────────────────────────
(0, node_test_1.describe)('filterAutomatedComments', () => {
    (0, node_test_1.it)('returns empty array for empty input', () => {
        strict_1.default.deepEqual((0, run_1.filterAutomatedComments)([]), []);
    });
    (0, node_test_1.it)('removes comments containing [aidev]', () => {
        const human = makeComment('Please fix the tests');
        const automated = makeComment('[aidev] Starting implementation on branch foo');
        const result = (0, run_1.filterAutomatedComments)([human, automated]);
        strict_1.default.deepEqual(result, [human]);
    });
    (0, node_test_1.it)('keeps all comments when none are automated', () => {
        const comments = [
            makeComment('Please fix the tests'),
            makeComment('I agree, this needs work'),
        ];
        strict_1.default.deepEqual((0, run_1.filterAutomatedComments)(comments), comments);
    });
    (0, node_test_1.it)('removes all comments when all are automated', () => {
        const comments = [
            makeComment('[aidev] Starting implementation'),
            makeComment('[aidev] Merge conflicts resolved automatically.'),
        ];
        strict_1.default.deepEqual((0, run_1.filterAutomatedComments)(comments), []);
    });
    (0, node_test_1.it)('filters by custom prefix', () => {
        const human = makeComment('Please fix the tests');
        const automated = makeComment('[mybot] Starting implementation on branch foo');
        const result = (0, run_1.filterAutomatedComments)([human, automated], '[mybot]');
        strict_1.default.deepEqual(result, [human]);
    });
    (0, node_test_1.it)('keeps human comments that quote the aidev prefix', () => {
        const quotingHuman = makeComment('You said "[aidev] xyz" — please redo it');
        const automated = makeComment('[aidev] Starting implementation');
        const result = (0, run_1.filterAutomatedComments)([quotingHuman, automated]);
        strict_1.default.deepEqual(result, [quotingHuman]);
    });
});
// ─── buildNonCodeCompletionComment ────────────────────────────────────────────
(0, node_test_1.describe)('buildNonCodeCompletionComment', () => {
    (0, node_test_1.it)('includes the in-review status', () => {
        const comment = (0, run_1.buildNonCodeCompletionComment)(baseConfig);
        strict_1.default.ok(comment.includes('review'));
    });
    (0, node_test_1.it)('starts with the [aidev] prefix', () => {
        const comment = (0, run_1.buildNonCodeCompletionComment)(baseConfig);
        strict_1.default.ok(comment.startsWith('[aidev]'));
    });
    (0, node_test_1.it)('mentions non-code task', () => {
        const comment = (0, run_1.buildNonCodeCompletionComment)(baseConfig);
        strict_1.default.ok(comment.includes('Non-code task complete'));
    });
    (0, node_test_1.it)('does not include branch or PR info', () => {
        const comment = (0, run_1.buildNonCodeCompletionComment)(baseConfig);
        strict_1.default.ok(!comment.includes('Branch:'));
        strict_1.default.ok(!comment.includes('PR'));
    });
    (0, node_test_1.it)('includes agent response when provided', () => {
        const comment = (0, run_1.buildNonCodeCompletionComment)(baseConfig, 'The answer to your question is 42.');
        strict_1.default.ok(comment.includes('The answer to your question is 42.'));
        strict_1.default.ok(comment.includes('[aidev] Non-code task complete'));
    });
    (0, node_test_1.it)('omits agent response section when not provided', () => {
        const comment = (0, run_1.buildNonCodeCompletionComment)(baseConfig);
        strict_1.default.ok(!comment.includes('---'));
    });
    (0, node_test_1.it)('uses custom prefix when configured', () => {
        const customConfig = { ...baseConfig, commentPrefix: '[mybot]' };
        const comment = (0, run_1.buildNonCodeCompletionComment)(customConfig);
        strict_1.default.ok(comment.startsWith('[mybot]'));
        strict_1.default.ok(!comment.includes('[aidev]'));
    });
    (0, node_test_1.it)('filters out instructional text from agent response', () => {
        const agentResponse = `Here's text you can paste as the **task ticket comment** (addresses your latest ask: do it + push):

---

**Done — publish + push**

I published **one English post** from the LinkedIn drafts, using the **oldest draft by git history** (first commit that added the file): \`qelos-plugins-microfrontends.md\` (committed 2026-03-11 00:51, before \`qelos-netlify-plugin.md\`). The body sent to LinkedIn was the **\`## Post (English)\`** section, **without markdown** (no \`**\`, \`\`\`, or \`#\` headings) so it matches what the text webhook expects.`;
        const comment = (0, run_1.buildNonCodeCompletionComment)(baseConfig, agentResponse);
        // Should not include the instructional text
        strict_1.default.ok(!comment.includes('Here\'s text you can paste as the'));
        strict_1.default.ok(!comment.includes('task ticket comment'));
        strict_1.default.ok(!comment.includes('addresses your latest ask'));
        // Should include the actual content
        strict_1.default.ok(comment.includes('Done — publish + push'));
        strict_1.default.ok(comment.includes('I published **one English post** from the LinkedIn drafts'));
        strict_1.default.ok(comment.includes('qelos-plugins-microfrontends.md'));
    });
});
// ─── buildNonCodePrompt ──────────────────────────────────────────────────────
(0, node_test_1.describe)('buildNonCodePrompt', () => {
    const task = { id: '1', name: 'Research question', description: 'What is X?', status: 'open', url: 'http://example.com', tags: [] };
    (0, node_test_1.it)('includes task name and description', () => {
        const prompt = (0, run_1.buildNonCodePrompt)(task, '');
        strict_1.default.ok(prompt.includes('Research question'));
        strict_1.default.ok(prompt.includes('What is X?'));
    });
    (0, node_test_1.it)('includes conversation context when provided', () => {
        const prompt = (0, run_1.buildNonCodePrompt)(task, '\n\nConversation context:\nAlice: Please clarify');
        strict_1.default.ok(prompt.includes('Alice: Please clarify'));
    });
    (0, node_test_1.it)('instructs AI to focus on latest comment when comments exist', () => {
        const prompt = (0, run_1.buildNonCodePrompt)(task, '\n\nConversation context:\nAlice: Change the env file');
        strict_1.default.ok(prompt.includes('LATEST comment'));
        strict_1.default.ok(prompt.includes('Original description'));
        strict_1.default.ok(prompt.includes('CRITICAL'));
        strict_1.default.ok(prompt.includes('FOLLOW-UP request'));
        strict_1.default.ok(prompt.includes('DO NOT repeat what was already done'));
    });
    (0, node_test_1.it)('does not mention latest comment when no comments', () => {
        const prompt = (0, run_1.buildNonCodePrompt)(task, '');
        strict_1.default.ok(!prompt.includes('LATEST comment'));
        strict_1.default.ok(!prompt.includes('Original description'));
    });
});
// ─── buildNonCodeAnalysisPrompt ──────────────────────────────────────────────
(0, node_test_1.describe)('buildNonCodeAnalysisPrompt', () => {
    const task = {
        id: '1', name: 'Investigate flaky CI',
        description: 'Find why the build flakes on macOS runners.',
        status: 'open', url: 'http://example.com', tags: [],
    };
    (0, node_test_1.it)('includes the task name and description', () => {
        const prompt = (0, run_1.buildNonCodeAnalysisPrompt)(task, '');
        strict_1.default.ok(prompt.includes('Investigate flaky CI'));
        strict_1.default.ok(prompt.includes('Find why the build flakes on macOS runners.'));
    });
    (0, node_test_1.it)('includes the conversation context when provided', () => {
        const prompt = (0, run_1.buildNonCodeAnalysisPrompt)(task, '\n\nConversation context:\nAlice: Please prioritize this');
        strict_1.default.ok(prompt.includes('Alice: Please prioritize this'));
    });
    (0, node_test_1.it)('asks for sequential sub-tasks with a JSON schema', () => {
        const prompt = (0, run_1.buildNonCodeAnalysisPrompt)(task, '');
        strict_1.default.ok(prompt.includes('sequential'));
        strict_1.default.ok(prompt.includes('"subtasks"'));
        strict_1.default.ok(prompt.includes('"taskSummary"'));
    });
    (0, node_test_1.it)('mentions that no code changes are expected', () => {
        const prompt = (0, run_1.buildNonCodeAnalysisPrompt)(task, '');
        strict_1.default.ok(prompt.toLowerCase().includes('no code'));
    });
    (0, node_test_1.it)('mentions that later sub-tasks receive earlier summaries', () => {
        const prompt = (0, run_1.buildNonCodeAnalysisPrompt)(task, '');
        strict_1.default.ok(prompt.toLowerCase().includes('summary') || prompt.toLowerCase().includes('summaries'));
    });
    (0, node_test_1.it)('handles missing description gracefully', () => {
        const prompt = (0, run_1.buildNonCodeAnalysisPrompt)({ ...task, description: '' }, '');
        strict_1.default.ok(prompt.includes('no description provided'));
    });
});
// ─── buildNonCodeSubtaskPrompt ───────────────────────────────────────────────
(0, node_test_1.describe)('buildNonCodeSubtaskPrompt', () => {
    const task = {
        id: '1', name: 'Investigate flaky CI',
        description: 'Find why the build flakes on macOS runners.',
        status: 'open', url: 'http://example.com', tags: [],
    };
    function makePlan(taskSummary) {
        return {
            taskId: task.id,
            taskName: task.name,
            ...(taskSummary ? { taskSummary } : {}),
            subtasks: [
                { id: 1, title: 'Collect failure samples', description: 'Pull last 50 macOS runs', status: 'done', attempts: 1 },
                { id: 2, title: 'Identify common stack traces', description: 'Group by error message', status: 'pending', attempts: 0 },
            ],
        };
    }
    const previousResults = [
        { id: 1, title: 'Collect failure samples', summary: 'Pulled 50 failing runs; 60% on macos-13.' },
    ];
    (0, node_test_1.it)('mentions the current step ID and title', () => {
        const subtask = makePlan().subtasks[1];
        const prompt = (0, run_1.buildNonCodeSubtaskPrompt)(subtask, task, makePlan(), previousResults, undefined);
        strict_1.default.ok(prompt.includes('Identify common stack traces'));
        strict_1.default.ok(prompt.includes('2'));
    });
    (0, node_test_1.it)('embeds previous sub-task summaries when provided', () => {
        const subtask = makePlan().subtasks[1];
        const prompt = (0, run_1.buildNonCodeSubtaskPrompt)(subtask, task, makePlan(), previousResults, undefined);
        strict_1.default.ok(prompt.includes('Pulled 50 failing runs'));
        strict_1.default.ok(prompt.includes('Collect failure samples'));
        strict_1.default.ok(prompt.includes('Summaries from previous sub-tasks'));
    });
    (0, node_test_1.it)('omits the previous summaries section when there are none', () => {
        const subtask = makePlan().subtasks[0];
        const prompt = (0, run_1.buildNonCodeSubtaskPrompt)(subtask, task, makePlan(), [], undefined);
        strict_1.default.ok(!prompt.includes('Summaries from previous sub-tasks'));
    });
    (0, node_test_1.it)('uses the plan task summary when available', () => {
        const plan = makePlan('Goal: fix macOS flakes. Constraint: no infra changes.');
        const subtask = plan.subtasks[1];
        const prompt = (0, run_1.buildNonCodeSubtaskPrompt)(subtask, task, plan, previousResults, undefined);
        strict_1.default.ok(prompt.includes('Goal: fix macOS flakes.'));
    });
    (0, node_test_1.it)('falls back to the task description when no summary is available', () => {
        const plan = makePlan();
        const subtask = plan.subtasks[1];
        const prompt = (0, run_1.buildNonCodeSubtaskPrompt)(subtask, task, plan, previousResults, undefined);
        strict_1.default.ok(prompt.includes('Find why the build flakes on macOS runners.'));
    });
    (0, node_test_1.it)('lists completed steps under Progress', () => {
        const plan = makePlan();
        const subtask = plan.subtasks[1];
        const prompt = (0, run_1.buildNonCodeSubtaskPrompt)(subtask, task, plan, previousResults, undefined);
        strict_1.default.ok(prompt.includes('Progress'));
        strict_1.default.ok(prompt.includes('Collect failure samples'));
    });
    (0, node_test_1.it)('tells the AI to write output suitable for a ticket comment', () => {
        const subtask = makePlan().subtasks[0];
        const prompt = (0, run_1.buildNonCodeSubtaskPrompt)(subtask, task, makePlan(), [], undefined);
        strict_1.default.ok(prompt.toLowerCase().includes('ticket comment'));
        strict_1.default.ok(prompt.toLowerCase().includes('do not include preambles'));
    });
});
// ─── buildNonCodeThinkingCompletionComment ───────────────────────────────────
(0, node_test_1.describe)('buildNonCodeThinkingCompletionComment', () => {
    (0, node_test_1.it)('starts with the configured prefix', () => {
        const out = (0, run_1.buildNonCodeThinkingCompletionComment)(baseConfig);
        strict_1.default.ok(out.startsWith('[aidev]'));
    });
    (0, node_test_1.it)('mentions the in-review status', () => {
        const out = (0, run_1.buildNonCodeThinkingCompletionComment)(baseConfig);
        strict_1.default.ok(out.includes('review'));
    });
    (0, node_test_1.it)('honours a custom comment prefix', () => {
        const out = (0, run_1.buildNonCodeThinkingCompletionComment)({ ...baseConfig, commentPrefix: '[mybot]' });
        strict_1.default.ok(out.startsWith('[mybot]'));
        strict_1.default.ok(!out.includes('[aidev]'));
    });
});
// ─── sortTasksByPriority ─────────────────────────────────────────────────────
function makeTask(id, priority) {
    return { id, name: `task-${id}`, description: '', status: 'open', url: '', tags: [], priority };
}
(0, node_test_1.describe)('sortTasksByPriority', () => {
    (0, node_test_1.it)('sorts tasks by priority ascending (urgent first)', () => {
        const tasks = [makeTask('a', 4), makeTask('b', 1), makeTask('c', 2)];
        const sorted = (0, run_1.sortTasksByPriority)(tasks);
        strict_1.default.deepEqual(sorted.map((t) => t.id), ['b', 'c', 'a']);
    });
    (0, node_test_1.it)('puts tasks without priority last', () => {
        const tasks = [makeTask('a'), makeTask('b', 2), makeTask('c', 1)];
        const sorted = (0, run_1.sortTasksByPriority)(tasks);
        strict_1.default.deepEqual(sorted.map((t) => t.id), ['c', 'b', 'a']);
    });
    (0, node_test_1.it)('preserves relative order among tasks with the same priority', () => {
        const tasks = [makeTask('a', 2), makeTask('b', 2), makeTask('c', 2)];
        const sorted = (0, run_1.sortTasksByPriority)(tasks);
        strict_1.default.deepEqual(sorted.map((t) => t.id), ['a', 'b', 'c']);
    });
    (0, node_test_1.it)('preserves relative order among tasks without priority', () => {
        const tasks = [makeTask('a'), makeTask('b'), makeTask('c')];
        const sorted = (0, run_1.sortTasksByPriority)(tasks);
        strict_1.default.deepEqual(sorted.map((t) => t.id), ['a', 'b', 'c']);
    });
    (0, node_test_1.it)('does not mutate the original array', () => {
        const tasks = [makeTask('a', 3), makeTask('b', 1)];
        const sorted = (0, run_1.sortTasksByPriority)(tasks);
        strict_1.default.equal(tasks[0].id, 'a');
        strict_1.default.equal(sorted[0].id, 'b');
    });
    (0, node_test_1.it)('handles empty array', () => {
        strict_1.default.deepEqual((0, run_1.sortTasksByPriority)([]), []);
    });
    (0, node_test_1.it)('handles single task', () => {
        const sorted = (0, run_1.sortTasksByPriority)([makeTask('a', 1)]);
        strict_1.default.equal(sorted.length, 1);
        strict_1.default.equal(sorted[0].id, 'a');
    });
});
// ─── getRunSkipReason ─────────────────────────────────────────────────────────
(0, node_test_1.describe)('getRunSkipReason', () => {
    (0, node_test_1.it)('allows open tasks for the default all filter', () => {
        strict_1.default.equal((0, run_1.getRunSkipReason)('open', 'all', 'pending'), null);
    });
    (0, node_test_1.it)('allows configured pending tasks for the default all filter', () => {
        strict_1.default.equal((0, run_1.getRunSkipReason)('Pending Review', 'all', 'pending review'), null);
    });
    (0, node_test_1.it)('skips statuses that are neither open nor pending', () => {
        strict_1.default.equal((0, run_1.getRunSkipReason)('failed', 'all', 'pending'), 'status "failed" is not open or pending');
    });
    (0, node_test_1.it)('skips pending tasks for the open filter', () => {
        strict_1.default.equal((0, run_1.getRunSkipReason)('pending', 'open', 'pending'), 'filter=open but task is pending');
    });
    (0, node_test_1.it)('skips open tasks for the pending filter', () => {
        strict_1.default.equal((0, run_1.getRunSkipReason)('open', 'pending', 'pending'), 'filter=pending but task is not pending');
    });
});
// ─── buildConflictResolutionPrompt ───────────────────────────────────────────
(0, node_test_1.describe)('buildConflictResolutionPrompt', () => {
    const task = {
        id: 'abc123',
        name: 'Add user settings page',
        description: 'Create a settings page where users can update their profile.',
        status: 'pending',
        url: 'https://app.clickup.com/t/abc123',
        tags: ['myproject'],
    };
    (0, node_test_1.it)('includes the task name and description so the agent understands the task', () => {
        const prompt = (0, run_1.buildConflictResolutionPrompt)(task, ['src/app.ts'], '');
        strict_1.default.ok(prompt.includes('Add user settings page'));
        strict_1.default.ok(prompt.includes('Create a settings page'));
    });
    (0, node_test_1.it)('lists all conflicting files', () => {
        const files = ['src/app.ts', 'src/config.ts', 'package.json'];
        const prompt = (0, run_1.buildConflictResolutionPrompt)(task, files, '');
        for (const f of files) {
            strict_1.default.ok(prompt.includes(f));
        }
    });
    (0, node_test_1.it)('includes conversation context when provided', () => {
        const prompt = (0, run_1.buildConflictResolutionPrompt)(task, ['src/app.ts'], '\n\nConversation context:\nAlice: Use tabs not spaces');
        strict_1.default.ok(prompt.includes('Alice: Use tabs not spaces'));
    });
    (0, node_test_1.it)('instructs to preserve the task intent', () => {
        const prompt = (0, run_1.buildConflictResolutionPrompt)(task, ['src/app.ts'], '');
        strict_1.default.ok(prompt.includes("task's intent") || prompt.includes("task's changes"));
    });
    (0, node_test_1.it)('instructs to remove conflict markers', () => {
        const prompt = (0, run_1.buildConflictResolutionPrompt)(task, ['src/app.ts'], '');
        strict_1.default.ok(prompt.includes('<<<<<<'));
        strict_1.default.ok(prompt.includes('>>>>>>>'));
    });
    (0, node_test_1.it)('handles missing description gracefully', () => {
        const prompt = (0, run_1.buildConflictResolutionPrompt)({ ...task, description: '' }, ['a.ts'], '');
        strict_1.default.ok(prompt.includes('no description provided'));
    });
});
// ─── buildReviewPrompt ──────────────────────────────────────────────────────
(0, node_test_1.describe)('buildReviewPrompt', () => {
    const reviewTask = {
        id: 'rev1',
        name: 'Add caching layer',
        description: 'Add Redis caching to the API endpoints.',
        status: 'review',
        url: 'https://app.clickup.com/t/rev1',
        tags: ['myproject'],
    };
    const sampleThreads = [
        {
            id: 'thread_1',
            path: 'src/cache.ts',
            line: 42,
            comments: [
                { author: 'alice', body: 'This TTL should be configurable' },
                { author: 'bob', body: 'Agreed, hardcoded values are fragile' },
            ],
        },
        {
            id: 'thread_2',
            path: 'src/api.ts',
            line: 10,
            comments: [
                { author: 'alice', body: 'Missing error handling for cache miss' },
            ],
        },
    ];
    (0, node_test_1.it)('includes task name and description', () => {
        const prompt = (0, run_1.buildReviewPrompt)(reviewTask, sampleThreads);
        strict_1.default.ok(prompt.includes('Add caching layer'));
        strict_1.default.ok(prompt.includes('Add Redis caching to the API endpoints.'));
    });
    (0, node_test_1.it)('includes all thread file paths and line numbers', () => {
        const prompt = (0, run_1.buildReviewPrompt)(reviewTask, sampleThreads);
        strict_1.default.ok(prompt.includes('`src/cache.ts` (line 42)'));
        strict_1.default.ok(prompt.includes('`src/api.ts` (line 10)'));
    });
    (0, node_test_1.it)('includes all comment bodies', () => {
        const prompt = (0, run_1.buildReviewPrompt)(reviewTask, sampleThreads);
        strict_1.default.ok(prompt.includes('This TTL should be configurable'));
        strict_1.default.ok(prompt.includes('Agreed, hardcoded values are fragile'));
        strict_1.default.ok(prompt.includes('Missing error handling for cache miss'));
    });
    (0, node_test_1.it)('includes comment authors', () => {
        const prompt = (0, run_1.buildReviewPrompt)(reviewTask, sampleThreads);
        strict_1.default.ok(prompt.includes('**alice**'));
        strict_1.default.ok(prompt.includes('**bob**'));
    });
    (0, node_test_1.it)('includes thread IDs', () => {
        const prompt = (0, run_1.buildReviewPrompt)(reviewTask, sampleThreads);
        strict_1.default.ok(prompt.includes('Thread thread_1'));
        strict_1.default.ok(prompt.includes('Thread thread_2'));
    });
    (0, node_test_1.it)('handles threads with no line number', () => {
        const threads = [
            {
                id: 'thread_no_line',
                path: 'README.md',
                line: null,
                comments: [{ author: 'alice', body: 'Update the docs' }],
            },
        ];
        const prompt = (0, run_1.buildReviewPrompt)(reviewTask, threads);
        strict_1.default.ok(prompt.includes('`README.md`'));
        strict_1.default.ok(!prompt.includes('(line'));
    });
    (0, node_test_1.it)('handles empty threads array', () => {
        const prompt = (0, run_1.buildReviewPrompt)(reviewTask, []);
        strict_1.default.ok(prompt.includes('Add caching layer'));
        // Should still produce a valid prompt, just with no thread sections
        strict_1.default.ok(!prompt.includes('Thread '));
    });
    (0, node_test_1.it)('handles missing description gracefully', () => {
        const prompt = (0, run_1.buildReviewPrompt)({ ...reviewTask, description: '' }, sampleThreads);
        strict_1.default.ok(prompt.includes('no description provided'));
    });
    (0, node_test_1.it)('includes AIDEV-REPLY instruction', () => {
        const prompt = (0, run_1.buildReviewPrompt)(reviewTask, sampleThreads);
        strict_1.default.ok(prompt.includes('AIDEV-REPLY'));
    });
});
// ─── buildReviewCompletionComment ───────────────────────────────────────────
(0, node_test_1.describe)('buildReviewCompletionComment', () => {
    (0, node_test_1.it)('includes resolved count', () => {
        const comment = (0, run_1.buildReviewCompletionComment)(baseConfig, 3, 0);
        strict_1.default.ok(comment.includes('Resolved 3 thread(s)'));
    });
    (0, node_test_1.it)('includes replied count', () => {
        const comment = (0, run_1.buildReviewCompletionComment)(baseConfig, 0, 2);
        strict_1.default.ok(comment.includes('Replied to 2 thread(s)'));
    });
    (0, node_test_1.it)('includes both resolved and replied counts', () => {
        const comment = (0, run_1.buildReviewCompletionComment)(baseConfig, 2, 1);
        strict_1.default.ok(comment.includes('Resolved 2 thread(s)'));
        strict_1.default.ok(comment.includes('Replied to 1 thread(s)'));
    });
    (0, node_test_1.it)('uses correct commentPrefix', () => {
        const comment = (0, run_1.buildReviewCompletionComment)(baseConfig, 1, 0);
        strict_1.default.ok(comment.startsWith('[aidev]'));
    });
    (0, node_test_1.it)('uses custom commentPrefix', () => {
        const customConfig = { ...baseConfig, commentPrefix: '[mybot]' };
        const comment = (0, run_1.buildReviewCompletionComment)(customConfig, 1, 1);
        strict_1.default.ok(comment.startsWith('[mybot]'));
        strict_1.default.ok(!comment.includes('[aidev]'));
    });
    (0, node_test_1.it)('mentions code review in the message', () => {
        const comment = (0, run_1.buildReviewCompletionComment)(baseConfig, 1, 0);
        strict_1.default.ok(comment.includes('Code review comments addressed'));
    });
    (0, node_test_1.it)('omits resolved line when count is zero', () => {
        const comment = (0, run_1.buildReviewCompletionComment)(baseConfig, 0, 2);
        strict_1.default.ok(!comment.includes('Resolved'));
    });
    (0, node_test_1.it)('omits replied line when count is zero', () => {
        const comment = (0, run_1.buildReviewCompletionComment)(baseConfig, 3, 0);
        strict_1.default.ok(!comment.includes('Replied'));
    });
});
// ─── parseReplyDirectives ───────────────────────────────────────────────────
(0, node_test_1.describe)('parseReplyDirectives', () => {
    (0, node_test_1.it)('extracts a single AIDEV-REPLY block', () => {
        const output = 'Some text\n<!-- AIDEV-REPLY thread_abc -->This is my reply<!-- /AIDEV-REPLY -->\nMore text';
        const replies = (0, run_1.parseReplyDirectives)(output);
        strict_1.default.equal(replies.length, 1);
        strict_1.default.equal(replies[0].threadId, 'thread_abc');
        strict_1.default.equal(replies[0].body, 'This is my reply');
    });
    (0, node_test_1.it)('extracts multiple AIDEV-REPLY blocks', () => {
        const output = `<!-- AIDEV-REPLY id1 -->Reply one<!-- /AIDEV-REPLY -->
Some middle text
<!-- AIDEV-REPLY id2 -->Reply two<!-- /AIDEV-REPLY -->`;
        const replies = (0, run_1.parseReplyDirectives)(output);
        strict_1.default.equal(replies.length, 2);
        strict_1.default.equal(replies[0].threadId, 'id1');
        strict_1.default.equal(replies[0].body, 'Reply one');
        strict_1.default.equal(replies[1].threadId, 'id2');
        strict_1.default.equal(replies[1].body, 'Reply two');
    });
    (0, node_test_1.it)('returns empty array when no AIDEV-REPLY blocks present', () => {
        const output = 'Just regular agent output with no reply directives';
        const replies = (0, run_1.parseReplyDirectives)(output);
        strict_1.default.equal(replies.length, 0);
    });
    (0, node_test_1.it)('trims whitespace from reply body', () => {
        const output = '<!-- AIDEV-REPLY thread_x -->\n  This has whitespace  \n<!-- /AIDEV-REPLY -->';
        const replies = (0, run_1.parseReplyDirectives)(output);
        strict_1.default.equal(replies.length, 1);
        strict_1.default.equal(replies[0].body, 'This has whitespace');
    });
    (0, node_test_1.it)('handles multiline reply bodies', () => {
        const output = '<!-- AIDEV-REPLY thread_y -->Line one\nLine two\nLine three<!-- /AIDEV-REPLY -->';
        const replies = (0, run_1.parseReplyDirectives)(output);
        strict_1.default.equal(replies.length, 1);
        strict_1.default.ok(replies[0].body.includes('Line one'));
        strict_1.default.ok(replies[0].body.includes('Line three'));
    });
    (0, node_test_1.it)('handles thread IDs with base64 characters', () => {
        const output = '<!-- AIDEV-REPLY abc+/def= -->Reply<!-- /AIDEV-REPLY -->';
        const replies = (0, run_1.parseReplyDirectives)(output);
        strict_1.default.equal(replies.length, 1);
        strict_1.default.equal(replies[0].threadId, 'abc+/def=');
    });
});
// ─── filterUnresolvedByNonAidev ─────────────────────────────────────────────
(0, node_test_1.describe)('filterUnresolvedByNonAidev', () => {
    function makeThread(id, comments) {
        return { id, path: 'src/file.ts', line: 1, comments };
    }
    (0, node_test_1.it)('includes thread where last comment is from a human', () => {
        const threads = [
            makeThread('t1', [
                { author: 'alice', body: 'Fix this bug' },
            ]),
        ];
        const result = (0, github_1.filterUnresolvedByNonAidev)(threads, '[aidev]');
        strict_1.default.equal(result.length, 1);
        strict_1.default.equal(result[0].id, 't1');
    });
    (0, node_test_1.it)('filters out thread where last comment is from aidev', () => {
        const threads = [
            makeThread('t1', [
                { author: 'alice', body: 'Fix this bug' },
                { author: 'bot', body: '[aidev] I have addressed this issue' },
            ]),
        ];
        const result = (0, github_1.filterUnresolvedByNonAidev)(threads, '[aidev]');
        strict_1.default.equal(result.length, 0);
    });
    (0, node_test_1.it)('handles mixed threads — keeps human-last, filters aidev-last', () => {
        const threads = [
            makeThread('t1', [
                { author: 'alice', body: 'Please refactor this' },
            ]),
            makeThread('t2', [
                { author: 'alice', body: 'Fix the typo' },
                { author: 'bot', body: '[aidev] Fixed the typo' },
            ]),
            makeThread('t3', [
                { author: 'bot', body: '[aidev] Done' },
                { author: 'bob', body: 'Actually, this is still wrong' },
            ]),
        ];
        const result = (0, github_1.filterUnresolvedByNonAidev)(threads, '[aidev]');
        strict_1.default.equal(result.length, 2);
        strict_1.default.deepEqual(result.map((t) => t.id), ['t1', 't3']);
    });
    (0, node_test_1.it)('includes thread with empty comments array', () => {
        const threads = [makeThread('t1', [])];
        const result = (0, github_1.filterUnresolvedByNonAidev)(threads, '[aidev]');
        strict_1.default.equal(result.length, 1);
    });
    (0, node_test_1.it)('works with custom comment prefix', () => {
        const threads = [
            makeThread('t1', [
                { author: 'bot', body: '[mybot] I fixed this' },
            ]),
            makeThread('t2', [
                { author: 'alice', body: 'Needs work' },
            ]),
        ];
        const result = (0, github_1.filterUnresolvedByNonAidev)(threads, '[mybot]');
        strict_1.default.equal(result.length, 1);
        strict_1.default.equal(result[0].id, 't2');
    });
    (0, node_test_1.it)('does not filter when aidev prefix appears mid-comment (not at start)', () => {
        const threads = [
            makeThread('t1', [
                { author: 'alice', body: 'The [aidev] tool did something wrong' },
            ]),
        ];
        const result = (0, github_1.filterUnresolvedByNonAidev)(threads, '[aidev]');
        strict_1.default.equal(result.length, 1);
    });
});
//# sourceMappingURL=run.test.js.map