diff --git a/app/git.test.js b/app/git.test.js index e766a0d..173bca6 100644 --- a/app/git.test.js +++ b/app/git.test.js @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'; import fs from 'fs'; import os from 'os'; import path from 'path'; -import { commitAndPush, cloneRepo, verifyRemoteAccess, SYNC_PATHS, BOT_COMMIT_MARKER, getHeadCommitMessage, isBotAutoCommit, mergeInstructionText } from './git.js'; +import { commitAndPush, cloneRepo, verifyRemoteAccess, BOT_COMMIT_MARKER, getHeadCommitMessage, isBotAutoCommit } from './git.js'; // --- helpers --- function makeTmpWorkspace() { @@ -13,13 +13,7 @@ function makeTmpWorkspace() { } function makeActionSource() { - const sourceRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'git-source-')); - for (const relPath of SYNC_PATHS) { - const fullPath = path.join(sourceRoot, relPath); - fs.mkdirSync(path.dirname(fullPath), { recursive: true }); - fs.writeFileSync(fullPath, relPath); - } - return sourceRoot; + return fs.mkdtempSync(path.join(os.tmpdir(), 'git-source-')); } // Default stub: all commands succeed, status returns changes @@ -125,7 +119,7 @@ describe('commitAndPush', () => { assert.equal(commitCalled, false, 'commit should not run when there are no changes'); }); - it('adds skill and entry files together with findings', async () => { + it('adds only generated review files', async () => { const repoDir = path.join(workspace, 'repo'); fs.mkdirSync(path.join(workspace, '.gitea/ai-review'), { recursive: true }); fs.writeFileSync(path.join(workspace, '.gitea/ai-review/findings.json'), '[]\n'); @@ -136,123 +130,36 @@ describe('commitAndPush', () => { const spawn = makeSpawn(); await commitAndPush(workspace, repoDir, spawn, sourceRoot); const addCalls = spawn.calls.filter(c => c.args[0] === 'add'); - const skillAddCall = addCalls.find(c => c.args.includes('.github/skills/triage-findings/SKILL.md')); const generatedAddCall = addCalls.find(c => c.args.includes('.gitea/ai-review/exclusions.json')); - assert.ok(skillAddCall, 'expected git add for synced skill files'); assert.ok(generatedAddCall, 'expected git add for generated review files'); - assert.ok(skillAddCall.args.includes('.codex/skills/triage-findings/SKILL.md')); - assert.ok(skillAddCall.args.includes('.codex/skills/triage-findings/agents/openai.yaml')); - assert.ok(skillAddCall.args.includes('.agents/skills/triage-findings/SKILL.md')); - assert.ok(skillAddCall.args.includes('.claude/skills/triage-findings/SKILL.md')); - assert.ok(skillAddCall.args.includes('.gemini/skills/triage-findings/SKILL.md')); - assert.ok(skillAddCall.args.includes('.antigravity/skills/triage-findings/SKILL.md')); - assert.ok(skillAddCall.args.includes('.github/copilot-instructions.md')); - assert.ok(skillAddCall.args.includes('.amazonq/rules/triage-findings.md')); - assert.ok(skillAddCall.args.includes('AGENTS.md')); - assert.ok(skillAddCall.args.includes('ANTIGRAVITY.md')); - assert.ok(skillAddCall.args.includes('CLAUDE.md')); - assert.ok(skillAddCall.args.includes('GEMINI.md')); - assert.ok(!skillAddCall.args.includes('README.md')); assert.ok(generatedAddCall.args.includes('.gitea/ai-review/findings.json')); assert.ok(generatedAddCall.args.includes('.gitea/ai-review/exclusions.json')); + assert.equal(addCalls.length, 1, 'expected only generated review files to be staged'); }); - it('keeps repo copies when the source sync file is missing', async () => { - const missingPath = path.join(sourceRoot, '.amazonq/rules/triage-findings.md'); - fs.rmSync(missingPath, { force: true }); - const repoPath = path.join(workspace, 'repo', '.amazonq/rules/triage-findings.md'); - fs.writeFileSync(repoPath, 'stale'); + it('does not overwrite or add action source files', async () => { + const repoDir = path.join(workspace, 'repo'); + const sourceDocPath = path.join(sourceRoot, 'docs/source-only.md'); + const repoDocPath = path.join(repoDir, 'docs/source-only.md'); + const repoConfigPath = path.join(repoDir, 'project-notes.md'); + fs.mkdirSync(path.join(workspace, '.gitea/ai-review'), { recursive: true }); + fs.writeFileSync(path.join(workspace, '.gitea/ai-review/findings.json'), '[]\n'); + fs.writeFileSync(path.join(workspace, '.gitea/ai-review/exclusions.json'), '[]\n'); + + fs.mkdirSync(path.dirname(sourceDocPath), { recursive: true }); + fs.mkdirSync(path.dirname(repoDocPath), { recursive: true }); + fs.writeFileSync(sourceDocPath, 'fresh action source doc'); + fs.writeFileSync(repoDocPath, 'existing repo doc'); + fs.writeFileSync(repoConfigPath, 'existing repo notes'); + const spawn = makeSpawn(); + await commitAndPush(workspace, repoDir, spawn, sourceRoot); + const addedArgs = spawn.calls.filter(c => c.args[0] === 'add').flatMap(c => c.args); - await commitAndPush(workspace, path.join(workspace, 'repo'), spawn, sourceRoot); - - const rmCall = spawn.calls.find(c => c.args[0] === 'rm'); - assert.equal(rmCall, undefined, 'git rm should not run for missing source files'); - assert.equal(fs.readFileSync(repoPath, 'utf8'), 'stale'); - }); - - it('merges existing repo copies with workspace files', async () => { - const repoDir = path.join(workspace, 'repo'); - fs.writeFileSync(path.join(repoDir, 'AGENTS.md'), 'repo agents doc'); - fs.writeFileSync(path.join(repoDir, 'ANTIGRAVITY.md'), 'repo antigravity doc'); - fs.writeFileSync(path.join(repoDir, 'CLAUDE.md'), 'repo claude doc'); - fs.writeFileSync(path.join(repoDir, 'GEMINI.md'), 'repo gemini doc'); - - await commitAndPush(workspace, repoDir, makeSpawn(), sourceRoot); - - const agentsDoc = fs.readFileSync(path.join(repoDir, 'AGENTS.md'), 'utf8'); - const antigravityDoc = fs.readFileSync(path.join(repoDir, 'ANTIGRAVITY.md'), 'utf8'); - const claudeDoc = fs.readFileSync(path.join(repoDir, 'CLAUDE.md'), 'utf8'); - const geminiDoc = fs.readFileSync(path.join(repoDir, 'GEMINI.md'), 'utf8'); - - assert.ok(agentsDoc.includes('repo agents doc')); - assert.ok(agentsDoc.includes('AGENTS.md')); - assert.ok(antigravityDoc.includes('repo antigravity doc')); - assert.ok(antigravityDoc.includes('ANTIGRAVITY.md')); - assert.ok(claudeDoc.includes('repo claude doc')); - assert.ok(claudeDoc.includes('CLAUDE.md')); - assert.ok(geminiDoc.includes('repo gemini doc')); - assert.ok(geminiDoc.includes('GEMINI.md')); - assert.ok(agentsDoc.includes('repo agents doc')); - }); - - it('accepts AI merged instruction text when all unique blocks are preserved', async () => { - const calls = []; - const aiMergeAssistant = async payload => { - calls.push(payload); - return ['repo block', 'source block', 'extra block'].join('\n\n'); - }; - - const result = await mergeInstructionText('repo block', 'source block', 'AGENTS.md', aiMergeAssistant); - - assert.equal(calls.length, 1); - assert.ok(result.includes('repo block')); - assert.ok(result.includes('source block')); - assert.ok(result.includes('extra block')); - }); - - it('uses deterministic instruction merge when AI returns no usable result', async () => { - const aiMergeAssistant = async () => null; - - const result = await mergeInstructionText('repo block', 'source block', 'AGENTS.md', aiMergeAssistant); - - assert.ok(result.includes('repo block')); - assert.ok(result.includes('source block')); - }); - - it('exits when AI output drops a block', async () => { - const originalExit = process.exit; - let exitCode = null; - process.exit = code => { exitCode = code; }; - try { - const aiMergeAssistant = async () => 'source block only'; - await assert.rejects(() => mergeInstructionText('repo block', 'source block', 'AGENTS.md', aiMergeAssistant)); - assert.equal(exitCode, 1); - } finally { - process.exit = originalExit; - } - }); - - it('overwrites non-merge sync files with workspace files', async () => { - const repoDir = path.join(workspace, 'repo'); - const sourceSkillPath = path.join(sourceRoot, '.github/skills/triage-findings/SKILL.md'); - const repoSkillPath = path.join(repoDir, '.github/skills/triage-findings/SKILL.md'); - const sourceNestedPath = path.join(sourceRoot, '.codex/skills/triage-findings/assets/example.txt'); - const repoNestedPath = path.join(repoDir, '.codex/skills/triage-findings/assets/example.txt'); - - fs.writeFileSync(sourceSkillPath, 'fresh github skill'); - fs.writeFileSync(repoSkillPath, 'stale github skill'); - fs.mkdirSync(path.dirname(sourceNestedPath), { recursive: true }); - fs.writeFileSync(sourceNestedPath, 'fresh nested'); - fs.mkdirSync(path.dirname(repoNestedPath), { recursive: true }); - fs.writeFileSync(repoNestedPath, 'stale nested'); - fs.writeFileSync(path.join(repoDir, '.github/copilot-instructions.md'), 'stale copilot'); - - await commitAndPush(workspace, repoDir, makeSpawn(), sourceRoot); - - assert.equal(fs.readFileSync(repoSkillPath, 'utf8'), 'fresh github skill'); - assert.equal(fs.readFileSync(repoNestedPath, 'utf8'), 'fresh nested'); - assert.equal(fs.readFileSync(path.join(repoDir, '.github/copilot-instructions.md'), 'utf8'), '.github/copilot-instructions.md'); + assert.equal(fs.readFileSync(repoDocPath, 'utf8'), 'existing repo doc'); + assert.equal(fs.readFileSync(repoConfigPath, 'utf8'), 'existing repo notes'); + assert.ok(!addedArgs.includes('docs/source-only.md')); + assert.ok(!addedArgs.includes('project-notes.md')); }); it('does not throw when git command fails', async () => { diff --git a/app/gitea.test.js b/app/gitea.test.js index 8dc7749..89c5a3a 100644 --- a/app/gitea.test.js +++ b/app/gitea.test.js @@ -129,10 +129,10 @@ describe('filterDiff', () => { const block = (file) => `diff --git a/${file} b/${file}\n--- a/${file}\n+++ b/${file}\n@@ -1 +1 @@\n-old\n+new\n`; it('filters out configured folder blocks', () => { - const diff = block('.gitea/workflows/review.yaml') + block('.amazonq/rules/triage-findings.md') + block('src/index.js'); - const result = filterDiff(diff, ['.gitea/', '.amazonq/']); + const diff = block('.gitea/workflows/review.yaml') + block('.github/workflows/review.yaml') + block('src/index.js'); + const result = filterDiff(diff, ['.gitea/', '.github/']); assert.ok(!result.includes('.gitea/')); - assert.ok(!result.includes('.amazonq/')); + assert.ok(!result.includes('.github/')); assert.ok(result.includes('src/index.js')); }); @@ -144,8 +144,8 @@ describe('filterDiff', () => { }); it('returns empty string when all blocks are excluded', () => { - const diff = block('.gitea/workflows/review.yaml') + block('.gitea/ai-review/findings.json') + block('.agents/skills/triage-findings/SKILL.md'); - const result = filterDiff(diff, ['.gitea/', '.agents/']); + const diff = block('.gitea/workflows/review.yaml') + block('.gitea/ai-review/findings.json'); + const result = filterDiff(diff, ['.gitea/']); assert.equal(result, ''); });