feat: add host-driven GitHub Actions CI watcher (Level 3)
Replace LLM-per-tick polling with direct `gh api` calls for GitHub Actions watchers. New `ci_provider` discriminator column routes tasks to either the host-driven path (zero LLM tokens) or the existing generic LLM path. GitHub watchers poll at 15s intervals (min 10s) via `checkGitHubActionsRun()` in the scheduler, with completion messages sent directly to chat on terminal state. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
149
src/github-ci.test.ts
Normal file
149
src/github-ci.test.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const { execFileMock } = vi.hoisted(() => ({
|
||||
execFileMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('child_process', () => ({
|
||||
execFile: execFileMock,
|
||||
}));
|
||||
|
||||
import {
|
||||
checkGitHubActionsRun,
|
||||
parseGitHubCiMetadata,
|
||||
serializeGitHubCiMetadata,
|
||||
} from './github-ci.js';
|
||||
|
||||
describe('github-ci helpers', () => {
|
||||
afterEach(() => {
|
||||
execFileMock.mockReset();
|
||||
});
|
||||
|
||||
it('round-trips GitHub CI metadata', () => {
|
||||
const raw = serializeGitHubCiMetadata({
|
||||
repo: 'owner/repo',
|
||||
run_id: 123456,
|
||||
poll_count: 1,
|
||||
});
|
||||
|
||||
expect(parseGitHubCiMetadata(raw)).toEqual({
|
||||
repo: 'owner/repo',
|
||||
run_id: 123456,
|
||||
poll_count: 1,
|
||||
consecutive_errors: undefined,
|
||||
});
|
||||
expect(parseGitHubCiMetadata('{"repo":"","run_id":0}')).toBeNull();
|
||||
});
|
||||
|
||||
it('returns non-terminal status for in-progress runs', async () => {
|
||||
execFileMock.mockImplementation(
|
||||
(
|
||||
_cmd: string,
|
||||
_args: string[],
|
||||
_opts: unknown,
|
||||
cb: (error: Error | null, stdout: string, stderr: string) => void,
|
||||
) => {
|
||||
cb(
|
||||
null,
|
||||
JSON.stringify({
|
||||
status: 'in_progress',
|
||||
conclusion: null,
|
||||
}),
|
||||
'',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
await expect(
|
||||
checkGitHubActionsRun({
|
||||
prompt: `
|
||||
[BACKGROUND CI WATCH]
|
||||
|
||||
Watch target:
|
||||
GitHub Actions run 123456
|
||||
|
||||
Task ID:
|
||||
task-github
|
||||
|
||||
Check instructions:
|
||||
Managed by host-driven watcher.
|
||||
`.trim(),
|
||||
ci_metadata: JSON.stringify({
|
||||
repo: 'owner/repo',
|
||||
run_id: 123456,
|
||||
}),
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
terminal: false,
|
||||
resultSummary: 'GitHub Actions run 123456 is in_progress',
|
||||
});
|
||||
});
|
||||
|
||||
it('renders a concise completion message for terminal runs', async () => {
|
||||
execFileMock
|
||||
.mockImplementationOnce(
|
||||
(
|
||||
_cmd: string,
|
||||
_args: string[],
|
||||
_opts: unknown,
|
||||
cb: (error: Error | null, stdout: string, stderr: string) => void,
|
||||
) => {
|
||||
cb(
|
||||
null,
|
||||
JSON.stringify({
|
||||
status: 'completed',
|
||||
conclusion: 'failure',
|
||||
name: 'CI',
|
||||
html_url: 'https://github.com/owner/repo/actions/runs/654321',
|
||||
head_branch: 'main',
|
||||
}),
|
||||
'',
|
||||
);
|
||||
},
|
||||
)
|
||||
.mockImplementationOnce(
|
||||
(
|
||||
_cmd: string,
|
||||
_args: string[],
|
||||
_opts: unknown,
|
||||
cb: (error: Error | null, stdout: string, stderr: string) => void,
|
||||
) => {
|
||||
cb(
|
||||
null,
|
||||
JSON.stringify({
|
||||
jobs: [
|
||||
{ name: 'build', conclusion: 'success' },
|
||||
{ name: 'test', conclusion: 'failure' },
|
||||
],
|
||||
}),
|
||||
'',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const result = await checkGitHubActionsRun({
|
||||
prompt: `
|
||||
[BACKGROUND CI WATCH]
|
||||
|
||||
Watch target:
|
||||
GitHub Actions run 654321
|
||||
|
||||
Task ID:
|
||||
task-github
|
||||
|
||||
Check instructions:
|
||||
Managed by host-driven watcher.
|
||||
`.trim(),
|
||||
ci_metadata: JSON.stringify({
|
||||
repo: 'owner/repo',
|
||||
run_id: 654321,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result.terminal).toBe(true);
|
||||
expect(result.resultSummary).toContain('실패');
|
||||
expect(result.completionMessage).toContain('CI 완료: GitHub Actions run 654321');
|
||||
expect(result.completionMessage).toContain('판정: 실패');
|
||||
expect(result.completionMessage).toContain('- 실패 job: test');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user