feat(api): add bevy playtest, worktree review, and drop seeding
- bevy.ts: spawn `cargo run` in a run's worktree to playtest its branch, batching build/runtime output as `bevy` events (capped at 2000/run) - events.ts: shared appendRunEvent/nextSeq so the runner, the agent-driven internal mutations, and the bevy launcher all persist through one path - runs.ts: track per-run bevy processes; stop them before worktree teardown - worktrees.ts: review/merge ops (isWorktreePresent, mainDirtySummary, commitsAheadOfHead, diffPatch/stat, mergeBranch) - orchestrator routes: diff, merge, bevy start/stop, bevy-status endpoints - remove seeding: drop seed.ts + data/kanbanCards.ts, the /reset endpoint, and boot-time seeding; cards are plain persisted DB records now
This commit is contained in:
@@ -13,6 +13,14 @@ import { db } from '../db.js';
|
||||
import type { Card } from '../types.js';
|
||||
import { hydrateCard } from './kanban.js';
|
||||
import { RunNotFoundError, runManager } from '../orchestrator/runs.js';
|
||||
import {
|
||||
commitsAheadOfHead,
|
||||
diffPatch,
|
||||
diffStat,
|
||||
mainDirtySummary,
|
||||
isWorktreePresent,
|
||||
mergeBranch,
|
||||
} from '../orchestrator/worktrees.js';
|
||||
|
||||
export const orchestrator = new Hono();
|
||||
|
||||
@@ -101,6 +109,65 @@ orchestrator.delete('/runs/:id', (c) => {
|
||||
}
|
||||
});
|
||||
|
||||
// --- worktree review & playtesting ----------------------------------------
|
||||
|
||||
/** Diff of a run's branch vs main (commits, --stat, and the capped patch). */
|
||||
orchestrator.get('/runs/:id/diff', (c) => {
|
||||
const run = runManager.get(c.req.param('id'));
|
||||
if (!run) return c.json({ error: 'run not found' }, 404);
|
||||
if (!run.branch || !run.worktreePath || !isWorktreePresent(run.worktreePath)) {
|
||||
return c.json({ error: 'run has no worktree to diff' }, 409);
|
||||
}
|
||||
const { patch, truncated } = diffPatch(run.branch);
|
||||
return c.json({
|
||||
branch: run.branch,
|
||||
commits: commitsAheadOfHead(run.branch),
|
||||
stat: diffStat(run.branch),
|
||||
patch,
|
||||
truncated,
|
||||
});
|
||||
});
|
||||
|
||||
/** Merge a run's branch into the main worktree's checked-out branch. */
|
||||
orchestrator.post('/runs/:id/merge', (c) => {
|
||||
const run = runManager.get(c.req.param('id'));
|
||||
if (!run) return c.json({ error: 'run not found' }, 404);
|
||||
if (!run.branch) return c.json({ error: 'run has no branch to merge' }, 409);
|
||||
// Refuse if the main worktree is dirty — merging would be unsafe.
|
||||
const dirty = mainDirtySummary();
|
||||
if (dirty) return c.json({ error: `Cannot merge: ${dirty}` }, 409);
|
||||
// The merge outcome (success or conflict-aborted) is in the body; only hard
|
||||
// preconditions (above) throw HTTP errors so the UI can read `ok` directly.
|
||||
return c.json(mergeBranch(run.branch));
|
||||
});
|
||||
|
||||
/** Whether a Bevy playtest is running for a run. */
|
||||
orchestrator.get('/runs/:id/bevy', (c) => {
|
||||
const run = runManager.get(c.req.param('id'));
|
||||
if (!run) return c.json({ error: 'run not found' }, 404);
|
||||
return c.json({ running: runManager.bevyRunning(run.id) });
|
||||
});
|
||||
|
||||
/** Launch a Bevy playtest (`cargo run`) in a run's worktree. */
|
||||
orchestrator.post('/runs/:id/bevy', (c) => {
|
||||
const id = c.req.param('id');
|
||||
try {
|
||||
runManager.startBevy(id);
|
||||
return c.json({ ok: true });
|
||||
} catch (err) {
|
||||
if (err instanceof RunNotFoundError) return c.json({ error: 'run not found' }, 404);
|
||||
const message = err instanceof Error ? err.message : 'failed to start Bevy';
|
||||
const status = message.includes('no worktree') || message.includes('already running') ? 409 : 500;
|
||||
return c.json({ error: message }, status);
|
||||
}
|
||||
});
|
||||
|
||||
/** Stop a run's Bevy playtest. */
|
||||
orchestrator.post('/runs/:id/bevy/stop', (c) => {
|
||||
runManager.stopBevy(c.req.param('id'));
|
||||
return c.json({ ok: true });
|
||||
});
|
||||
|
||||
/** Live event stream for a run (Server-Sent Events). */
|
||||
orchestrator.get('/runs/:id/stream', (c) => {
|
||||
const id = c.req.param('id');
|
||||
|
||||
Reference in New Issue
Block a user