diff --git a/server/routes/lanes.js b/server/routes/lanes.js index 75fe584..557badf 100644 --- a/server/routes/lanes.js +++ b/server/routes/lanes.js @@ -33,6 +33,7 @@ const { } = require("../lib/worktree"); const { withLaneLock } = require("../lib/lane-lock"); const { checkSync, mergeSync, continueSync } = require("../lib/lane-sync"); +const { installAgents } = require("../lib/lane-agents"); const { HOOKS, runHook, resolveProfile } = require("../lib/lane-profile"); const { slotDirs } = require("../lib/lane-slots"); const { @@ -328,6 +329,26 @@ router.post("/:id/proof-link", sameOriginGuard, (req, res) => { res.json(proofLib.ensureProofLink(lane)); }); +/** + * Install the ship-feature-lane pipeline's agent templates (qc-local, + * senior-gate-reviewer) into this lane's own .claude/agents/. Static file + * copy — no templating, no credentials to inject (this repo has no + * seed-account system yet; see the E3 design spec's Decisions table). + * Never automatic, same as proof-link: a session calls this explicitly. + */ +router.post("/:id/agents/install", sameOriginGuard, async (req, res) => { + const lane = lanesLib.getLane(req.params.id); + if (!lane) return res.status(404).json({ error: { code: "ENOLANE", message: "lane not found" } }); + try { + res.json(await installAgents(lane)); + } catch (err) { + if (err.code === "ENOTGITREPO") { + return res.status(400).json({ error: { code: err.code, message: err.message } }); + } + res.status(500).json({ error: { code: err.code || "ERUNTIME", message: err.message } }); + } +}); + /** * A lane's working-copy facts: branch, short HEAD, that commit's subject, and * the uncommitted counts. Read-only, so no same-origin guard — that guard