feat: add repo evidence mcp tool
This commit is contained in:
144
runners/agent-runner/test/repo-evidence.test.ts
Normal file
144
runners/agent-runner/test/repo-evidence.test.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { execFileSync } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
buildRepoEvidenceCommand,
|
||||
formatRepoEvidenceResponse,
|
||||
normalizeRepoEvidenceLimit,
|
||||
normalizeRepoEvidenceRef,
|
||||
runRepoEvidenceRequestDirect,
|
||||
} from '../src/repo-evidence.js';
|
||||
|
||||
describe('repo evidence helpers', () => {
|
||||
let repoDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-repo-evidence-'));
|
||||
execFileSync('git', ['init'], { cwd: repoDir, stdio: 'ignore' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], {
|
||||
cwd: repoDir,
|
||||
stdio: 'ignore',
|
||||
});
|
||||
execFileSync('git', ['config', 'user.name', 'Test User'], {
|
||||
cwd: repoDir,
|
||||
stdio: 'ignore',
|
||||
});
|
||||
fs.writeFileSync(path.join(repoDir, 'README.md'), 'initial\n');
|
||||
execFileSync('git', ['add', 'README.md'], {
|
||||
cwd: repoDir,
|
||||
stdio: 'ignore',
|
||||
});
|
||||
execFileSync('git', ['commit', '-m', 'initial commit'], {
|
||||
cwd: repoDir,
|
||||
stdio: 'ignore',
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(repoDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('normalizes bounded log limits', () => {
|
||||
expect(normalizeRepoEvidenceLimit()).toBe(10);
|
||||
expect(normalizeRepoEvidenceLimit(0)).toBe(1);
|
||||
expect(normalizeRepoEvidenceLimit(3.8)).toBe(3);
|
||||
expect(normalizeRepoEvidenceLimit(100)).toBe(30);
|
||||
});
|
||||
|
||||
it('rejects refs that can change command behavior or request ranges', () => {
|
||||
expect(normalizeRepoEvidenceRef()).toBe('HEAD');
|
||||
expect(normalizeRepoEvidenceRef(' HEAD~1 ')).toBe('HEAD~1');
|
||||
expect(() => normalizeRepoEvidenceRef('--help')).toThrow(
|
||||
'Unsupported git ref',
|
||||
);
|
||||
expect(() => normalizeRepoEvidenceRef('main..HEAD')).toThrow(
|
||||
'Unsupported git ref',
|
||||
);
|
||||
expect(() => normalizeRepoEvidenceRef('HEAD;rm')).toThrow(
|
||||
'Unsupported git ref',
|
||||
);
|
||||
});
|
||||
|
||||
it('builds fixed git commands without invoking a shell', () => {
|
||||
expect(buildRepoEvidenceCommand(repoDir, { action: 'git_status' })).toEqual(
|
||||
{
|
||||
file: 'git',
|
||||
args: ['-C', repoDir, 'status', '--short', '--branch'],
|
||||
commandText: `git -C ${repoDir} status --short --branch`,
|
||||
},
|
||||
);
|
||||
|
||||
expect(
|
||||
buildRepoEvidenceCommand(repoDir, {
|
||||
action: 'git_recent_log',
|
||||
limit: 100,
|
||||
}).args,
|
||||
).toContain('-30');
|
||||
|
||||
expect(
|
||||
buildRepoEvidenceCommand(repoDir, {
|
||||
action: 'git_show_ref',
|
||||
ref: 'HEAD',
|
||||
}).args,
|
||||
).toEqual([
|
||||
'-C',
|
||||
repoDir,
|
||||
'show',
|
||||
'--stat',
|
||||
'--oneline',
|
||||
'--decorate',
|
||||
'--no-ext-diff',
|
||||
'--no-renames',
|
||||
'HEAD',
|
||||
]);
|
||||
});
|
||||
|
||||
it('reads read-only git evidence from the target repository', async () => {
|
||||
fs.appendFileSync(path.join(repoDir, 'README.md'), 'dirty\n');
|
||||
|
||||
const status = await runRepoEvidenceRequestDirect(repoDir, {
|
||||
action: 'git_status',
|
||||
});
|
||||
expect(status.ok).toBe(true);
|
||||
expect(status.stdout).toContain('README.md');
|
||||
|
||||
const head = await runRepoEvidenceRequestDirect(repoDir, {
|
||||
action: 'git_head',
|
||||
});
|
||||
expect(head.ok).toBe(true);
|
||||
expect(head.stdout).toContain('initial commit');
|
||||
|
||||
const log = await runRepoEvidenceRequestDirect(repoDir, {
|
||||
action: 'git_recent_log',
|
||||
limit: 2,
|
||||
});
|
||||
expect(log.ok).toBe(true);
|
||||
expect(log.stdout).toContain('initial commit');
|
||||
|
||||
const shown = await runRepoEvidenceRequestDirect(repoDir, {
|
||||
action: 'git_show_ref',
|
||||
ref: 'HEAD',
|
||||
});
|
||||
expect(shown.ok).toBe(true);
|
||||
expect(shown.stdout).toContain('initial commit');
|
||||
expect(shown.stdout).toContain('README.md');
|
||||
});
|
||||
|
||||
it('formats failures into MCP-friendly evidence', async () => {
|
||||
const response = await runRepoEvidenceRequestDirect(repoDir, {
|
||||
action: 'git_show_ref',
|
||||
ref: '--help',
|
||||
});
|
||||
|
||||
expect(response.ok).toBe(false);
|
||||
const text = formatRepoEvidenceResponse(response);
|
||||
expect(text).toContain('Repo evidence action: git_show_ref');
|
||||
expect(text).toContain(`Workdir: ${repoDir}`);
|
||||
expect(text).toContain('Exit code: 1');
|
||||
expect(text).toContain('[error] Unsupported git ref');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user