feat: AI-powered merge via pi agent

This commit is contained in:
Dustin Byrne
2026-03-25 18:43:49 -04:00
parent 22d806d436
commit 16e9043aee
7 changed files with 308 additions and 12 deletions

View File

@@ -1 +1 @@
export { createServer } from "./server.js";
export { createServer, type ServerOptions } from "./server.js";

View File

@@ -1,8 +1,9 @@
import { Router } from "express";
import type { TaskStore, Column } from "@hai/core";
import type { TaskStore, Column, MergeResult } from "@hai/core";
import { COLUMNS } from "@hai/core";
import type { ServerOptions } from "./server.js";
export function createApiRoutes(store: TaskStore): Router {
export function createApiRoutes(store: TaskStore, options?: ServerOptions): Router {
const router = Router();
// List all tasks
@@ -54,13 +55,15 @@ export function createApiRoutes(store: TaskStore): Router {
});
// Merge task (in-review → done, merges branch + cleans worktree)
// Uses AI merge handler if provided, falls back to store.mergeTask
router.post("/tasks/:id/merge", async (req, res) => {
try {
const result = await store.mergeTask(req.params.id);
const merge = options?.onMerge ?? ((id: string) => store.mergeTask(id));
const result = await merge(req.params.id);
res.json(result);
} catch (err: any) {
const status = err.message.includes("Cannot merge") ? 400
: err.message.includes("Merge conflict") ? 409
: err.message.includes("conflict") ? 409
: 500;
res.status(status).json({ error: err.message });
}

View File

@@ -2,13 +2,18 @@ import express from "express";
import { join, dirname } from "node:path";
import { existsSync } from "node:fs";
import { fileURLToPath } from "node:url";
import type { TaskStore } from "@hai/core";
import type { TaskStore, MergeResult } from "@hai/core";
import { createApiRoutes } from "./routes.js";
import { createSSE } from "./sse.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
export function createServer(store: TaskStore): ReturnType<typeof express> {
export interface ServerOptions {
/** Custom merge handler — when provided, used instead of store.mergeTask */
onMerge?: (taskId: string) => Promise<MergeResult>;
}
export function createServer(store: TaskStore, options?: ServerOptions): ReturnType<typeof express> {
const app = express();
app.use(express.json());
@@ -25,7 +30,7 @@ export function createServer(store: TaskStore): ReturnType<typeof express> {
app.get("/api/events", createSSE(store));
// REST API
app.use("/api", createApiRoutes(store));
app.use("/api", createApiRoutes(store, options));
// SPA fallback
app.get("/{*splat}", (_req, res) => {