src/runner/runnable.ts
Rama main · réplica de lectura del repositorio original.
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { hasHumanReplies } from './human-feedback.ts';
import { readState, statePath } from './state.ts';
import { patchpawPaths } from '../config/paths.ts';
import { readUnfinishedConflictApproval } from './conflict-approval.ts';
import { readUnfinishedApprovalPlanClaim } from './approval-plans.ts';
async function json(path: string) {
try { return JSON.parse(await readFile(path, 'utf8')); }
catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null; throw error; }
}
// One wake predicate for PR-local mechanical work. Pure outbound retries are
// handled by the outbox scheduler, while this predicate lets legacy review,
// notice and close recovery run without requiring another human comment.
export async function hasRunnableWork(root: string, repo: string, number: number) {
const path = statePath(patchpawPaths(root).state, repo, number);
if (await hasHumanReplies(path)) return true;
const state = await readState(path);
if (!state) return false;
// An approval claim/repair is durable work even after the source comment has been
// marked handled. This lets a webhook redelivery or a later human entry wake the
// mechanical recovery path without consuming the approval a second time.
if (await readUnfinishedConflictApproval(path)) return true;
if (await readUnfinishedApprovalPlanClaim(path)) return true;
if (state.completion_notice_status === 'pending' || state.pending_close_refusal) return true;
const journal = await json(`${path}.close.json`);
if (journal?.status === 'closing') return true;
const runDir = join(patchpawPaths(root).runs, state.run_id);
const review = await json(join(runDir, 'review.json'));
const publication = await json(join(runDir, 'review-publication.json'));
if (review && !publication && ['review_ready', 'review_publishing'].includes(state.phase)) return true;
const notice = await json(join(runDir, 'run-notice.json'));
const notification = await json(join(runDir, 'notification.json'));
return !!notice && notification?.status !== 'published';
}