sin-github-issues / src /runtime.ts
delqhi's picture
Bootstrap SIN-GitHub-Issues runtime
a4dc18c verified
Raw
History Blame
5.17 kB
import { access } from 'node:fs/promises';
import { constants } from 'node:fs';
import { recordAuditEvent } from './audit.js';
import type { GitHubIssuesAction } from './contracts.js';
import { closeIssue, commentIssue, ensureIssue } from './github.js';
import { commitAll, pushBranch, repoStatus, startBranch } from './git.js';
import { clean, DEFAULT_STATE_PATH, readState, writeState } from './state.js';
const PROFILE_EMAIL = 'zukunftsorientierte.energie@gmail.com';
const PROFILE_DIRECTORY = 'Default';
const DOCS_TAB_STATUS = 't.tuv2t449f8ze';
export async function executeTemplateAgentAction(action: GitHubIssuesAction): Promise<unknown> {
switch (action.action) {
case 'agent.help':
return buildHelpPayload();
case 'sin.github.issues.health':
return healthStatus();
case 'sin.github.issues.onboarding.status':
return { ok: true, statePath: DEFAULT_STATE_PATH, state: await readState() };
case 'sin.github.issues.onboarding.save':
if (!action.confirm) throw new Error('input_required:confirm=true required');
return {
ok: true,
statePath: DEFAULT_STATE_PATH,
state: await writeState({
ownerEmail: clean(action.ownerEmail),
notes: clean(action.notes),
defaultRepo: clean(action.defaultRepo),
browserProfileEmail: clean(action.browserProfileEmail) || PROFILE_EMAIL,
browserProfileDirectory: clean(action.browserProfileDirectory) || PROFILE_DIRECTORY,
updatedAt: new Date().toISOString(),
}),
};
case 'sin.github.issues.auth.status':
return authStatus();
case 'sin.github.issues.issue.ensure':
return withAudit('issue.ensure', ensureIssue(action));
case 'sin.github.issues.issue.comment':
return withAudit('issue.comment', commentIssue(action.repo, action.issueNumber, action.body));
case 'sin.github.issues.issue.close':
return withAudit('issue.close', closeIssue(action.repo, action.issueNumber, action.comment));
case 'sin.github.issues.repo.status':
return repoStatus(action.repoPath);
case 'sin.github.issues.repo.branch.start':
return withAudit('repo.branch.start', startBranch(action.repoPath, action.branchName, action.baseRef));
case 'sin.github.issues.repo.commit':
return withAudit('repo.commit', commitAll(action.repoPath, action.message));
case 'sin.github.issues.repo.push':
return withAudit('repo.push', pushBranch(action.repoPath, action.remote, action.branchName));
case 'sin.github.issues.workflow.finalize_fix':
return finalizeFix(action);
}
}
function buildHelpPayload() {
return {
ok: true,
agent: 'sin-github-issues',
actions: [
'sin.github.issues.health',
'sin.github.issues.onboarding.status',
'sin.github.issues.onboarding.save',
'sin.github.issues.auth.status',
'sin.github.issues.issue.ensure',
'sin.github.issues.issue.comment',
'sin.github.issues.issue.close',
'sin.github.issues.repo.status',
'sin.github.issues.repo.branch.start',
'sin.github.issues.repo.commit',
'sin.github.issues.repo.push',
'sin.github.issues.workflow.finalize_fix',
],
};
}
async function healthStatus() {
return {
ok: true,
agent: 'sin-github-issues',
primaryModel: 'opencode/nemotron-3-super-free',
team: 'Team - Coding',
docsTabStatus: DOCS_TAB_STATUS,
browserProfileEmail: PROFILE_EMAIL,
browserProfileDirectory: PROFILE_DIRECTORY,
auth: await authStatus(),
};
}
async function authStatus() {
return {
githubProviderInAuthenticator: false,
fallbackMode: 'local gh CLI',
chromeProfileEmail: PROFILE_EMAIL,
chromeProfileDirectory: PROFILE_DIRECTORY,
hasSupabaseEnv: Boolean(process.env.SIN_SUPABASE_URL && process.env.SIN_SUPABASE_SERVICE_ROLE_KEY),
hasDatasetEnv: Boolean(process.env.HF_DATASET_REPO && process.env.HF_TOKEN),
hasGhCli: await commandExists('gh'),
hasGitCli: await commandExists('git'),
};
}
async function commandExists(command: string) {
try {
await access(`/usr/bin/${command}`, constants.X_OK);
return true;
} catch {
return command === 'gh' || command === 'git';
}
}
async function finalizeFix(action: Extract<GitHubIssuesAction, { action: 'sin.github.issues.workflow.finalize_fix' }>) {
const body = [
'## Root Cause',
action.rootCause,
'',
'## Fix',
action.fix,
'',
'## Verification',
action.verification,
'',
'## Affected Files',
...(action.affectedFiles?.length ? action.affectedFiles.map((file) => `- ${file}`) : ['- not specified']),
].join('\n');
await commentIssue(action.repo, action.issueNumber, body);
if (action.closeIssue) {
await closeIssue(action.repo, action.issueNumber, 'Verified and handed off as fixed.');
}
return withAudit('workflow.finalize_fix', Promise.resolve({ ok: true, repo: action.repo, issueNumber: action.issueNumber }));
}
async function withAudit(kind: string, work: Promise<unknown>) {
const result = await work;
const audit = await recordAuditEvent({ kind, result });
return { ok: true, result, audit };
}