Add reviewer host evidence and verification shims

This commit is contained in:
Eyejoker
2026-04-04 20:51:24 +09:00
parent 3019d9bf31
commit e9c400424d
19 changed files with 2351 additions and 97 deletions

View File

@@ -0,0 +1,69 @@
import fs from 'fs';
import os from 'os';
import path from 'path';
import { describe, expect, it } from 'vitest';
import {
formatHostEvidenceResponse,
normalizeHostEvidenceTailLines,
resolveHostEvidenceResponsesDir,
waitForHostEvidenceResponse,
} from '../src/host-evidence.js';
describe('runner host evidence helpers', () => {
it('normalizes log tail lines for the MCP request', () => {
expect(normalizeHostEvidenceTailLines(undefined)).toBe(20);
expect(normalizeHostEvidenceTailLines(0)).toBe(1);
expect(normalizeHostEvidenceTailLines(999)).toBe(200);
expect(normalizeHostEvidenceTailLines(25)).toBe(25);
});
it('reads and removes the host evidence response file', async () => {
const ipcDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-host-ipc-'));
const responseDir = resolveHostEvidenceResponsesDir(ipcDir);
fs.mkdirSync(responseDir, { recursive: true });
const responsePath = path.join(responseDir, 'req-1.json');
fs.writeFileSync(
responsePath,
JSON.stringify({
requestId: 'req-1',
ok: true,
action: 'ejclaw_service_status',
command: 'systemctl --user show ejclaw',
stdout: 'ActiveState=active\n',
stderr: '',
exitCode: 0,
}),
);
const response = await waitForHostEvidenceResponse(responseDir, 'req-1', {
timeoutMs: 100,
pollMs: 10,
});
expect(response.ok).toBe(true);
expect(response.stdout).toContain('ActiveState=active');
expect(fs.existsSync(responsePath)).toBe(false);
});
it('formats the response into a compact MCP-friendly text block', () => {
const text = formatHostEvidenceResponse({
requestId: 'req-2',
ok: false,
action: 'ejclaw_service_logs',
command: 'journalctl --user -u ejclaw --no-pager -n 10',
stdout: 'line 1\nline 2\n',
stderr: 'No journal files were found.\n',
exitCode: 1,
error: 'command failed',
});
expect(text).toContain('Host evidence action: ejclaw_service_logs');
expect(text).toContain('Exit code: 1');
expect(text).toContain('$ journalctl --user -u ejclaw --no-pager -n 10');
expect(text).toContain('[stderr]');
expect(text).toContain('[error] command failed');
});
});

View File

@@ -0,0 +1,84 @@
import fs from 'fs';
import os from 'os';
import path from 'path';
import { describe, expect, it } from 'vitest';
import {
computeVerificationSnapshotId,
formatVerificationResponse,
resolveVerificationResponsesDir,
waitForVerificationResponse,
} from '../src/verification.js';
describe('runner verification helpers', () => {
it('computes the same snapshot when excluded files change', () => {
const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-runner-snapshot-'));
fs.mkdirSync(path.join(repoDir, 'src'), { recursive: true });
fs.mkdirSync(path.join(repoDir, 'node_modules'), { recursive: true });
fs.writeFileSync(path.join(repoDir, 'src', 'index.ts'), 'export const x = 1;\n');
fs.writeFileSync(path.join(repoDir, '.env'), 'SECRET=1\n');
const first = computeVerificationSnapshotId(repoDir);
fs.writeFileSync(path.join(repoDir, '.env'), 'SECRET=2\n');
const second = computeVerificationSnapshotId(repoDir);
expect(second).toBe(first);
});
it('reads and removes the verification response file', async () => {
const ipcDir = fs.mkdtempSync(
path.join(os.tmpdir(), 'ejclaw-verification-ipc-'),
);
const responseDir = resolveVerificationResponsesDir(ipcDir);
fs.mkdirSync(responseDir, { recursive: true });
const responsePath = path.join(responseDir, 'req-1.json');
fs.writeFileSync(
responsePath,
JSON.stringify({
requestId: 'req-1',
ok: true,
profile: 'typecheck',
command: 'npm run typecheck',
stdout: '',
stderr: '',
exitCode: 0,
snapshotId: 'fs:abc123',
runtimeVersion: 'ejclaw-reviewer:latest@sha256:test',
}),
);
const response = await waitForVerificationResponse(responseDir, 'req-1', {
timeoutMs: 100,
pollMs: 10,
});
expect(response.ok).toBe(true);
expect(response.snapshotId).toBe('fs:abc123');
expect(fs.existsSync(responsePath)).toBe(false);
});
it('formats the response into a compact MCP-friendly text block', () => {
const text = formatVerificationResponse({
requestId: 'req-2',
ok: false,
profile: 'build',
command: 'npm run build',
stdout: 'tsc output\n',
stderr: 'build failed\n',
exitCode: 1,
snapshotId: 'fs:def456',
runtimeVersion: 'ejclaw-reviewer:latest@sha256:test',
error: 'command failed',
});
expect(text).toContain('Verification profile: build');
expect(text).toContain('Snapshot: fs:def456');
expect(text).toContain('Runtime: ejclaw-reviewer:latest@sha256:test');
expect(text).toContain('Exit code: 1');
expect(text).toContain('$ npm run build');
expect(text).toContain('[stderr]');
expect(text).toContain('[error] command failed');
});
});