Files
AgentHub/server/__tests__/pipelines-dormant.test.js
nntrivi2001 2b8f83d2dd refactor(lanes): drop the workflow skills and Superpowers link; pipelines go dormant
The plugin stops shipping its workflow layer:
- skills/ship-feature and skills/ship-feature-lane, the 18 stage/ship slash
  commands (plugin.json keeps agenthub-doctor/update/open), and all four
  agents/ (qc-local, senior-gate-reviewer, backend-reviewer, frontend-reviewer).
- CLI `agenthub skills install`, `agenthub lanes agents install`,
  `agenthub ship-full|ship-quick`; routes POST /api/skills/install and
  POST /api/lanes/:id/agents/install; the Settings button, the lane Tools
  menu item and the Add-lane setup step that called them.
- The Superpowers auto-install: server/lib/skill-plugins.js, the bootstrap's
  autoInstallSkillPlugins / skillPluginsSettled gate and the doctor row.

Pipelines are kept but dormant: server/data/pipelines/ is deleted, so no
template ships. `default` resolves to EMPTY_PIPELINE (no nodes, progress 0),
createLane still accepts it, POST /api/lanes/:id/stage answers 400
ENOPIPELINE, and the Workspace hides the stage timer, pickers, PipelineMap
and strip stage chip for a lane with no nodes. A template dropped into
DASHBOARD_PIPELINES_DIR turns it all back on. The server suite loads its
templates from server/__tests__/fixtures/pipelines/.

Docs: CLAUDE.md (marker, retired rules), README(.vi), INSTALL, ARCHITECTURE,
docs/LANES|PLUGINS|CLI|API, agenthub-introduction.html.
2026-09-28 17:49:05 +07:00

83 lines
3.3 KiB
JavaScript

/**
* @file With NO pipeline template installed (the shipped state since the
* built-in templates were removed), lanes must still be created, rendered and
* reported on: getPipeline() answers an empty pipeline, `default` stays a
* valid id, and a stage declaration is refused with a plain explanation.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
const os = require("node:os");
const fs = require("node:fs");
const path = require("node:path");
const ROOT = fs.mkdtempSync(path.join(os.tmpdir(), "pipelines-dormant-"));
process.env.DASHBOARD_DB_PATH = path.join(ROOT, "t.db");
process.env.DASHBOARD_DATA_DIR = path.join(ROOT, "data");
process.env.CLAUDE_HOME = path.join(ROOT, ".claude");
process.env.AGENTHUB_SHELL_PATH = "0";
const savedDir = process.env.DASHBOARD_PIPELINES_DIR;
delete process.env.DASHBOARD_PIPELINES_DIR;
const { describe, it, before, after } = require("node:test");
const assert = require("node:assert/strict");
const http = require("node:http");
const pipelines = require("../lib/pipelines");
pipelines.reload();
const lanes = require("../lib/lanes");
const { createApp } = require("../index");
let app;
let base;
const request = (method, p, body) =>
fetch(`${base}${p}`, {
method,
headers: { "Content-Type": "application/json" },
body: body === undefined ? undefined : JSON.stringify(body),
}).then(async (r) => ({ status: r.status, body: await r.json().catch(() => null) }));
before(async () => {
app = http.createServer(createApp());
await new Promise((r) => app.listen(0, "127.0.0.1", r));
base = `http://127.0.0.1:${app.address().port}`;
});
after(async () => {
await new Promise((r) => app.close(r));
if (savedDir !== undefined) process.env.DASHBOARD_PIPELINES_DIR = savedDir;
pipelines.reload();
fs.rmSync(ROOT, { recursive: true, force: true });
});
describe("no pipeline template installed", () => {
it("ships no template and answers an empty default pipeline", () => {
assert.deepEqual(pipelines.listPipelines(), []);
const p = pipelines.getPipeline("default");
assert.equal(p.id, "default");
assert.deepEqual(p.nodes, []);
assert.equal(pipelines.getPipeline("anything").nodes.length, 0);
assert.deepEqual(pipelines.nodeStates(p, { stage: "plan", stages: {} }), []);
assert.equal(pipelines.progressPct(p, { stage: "plan" }), 0);
});
it("still creates a lane on the default pipeline and renders it", () => {
const lane = lanes.createLane({ cwd: path.join(ROOT, "a"), title: "a" });
assert.equal(lane.pipeline, "default");
const payload = lanes.lanePayload(lane);
assert.deepEqual(payload.pipeline_nodes, []);
assert.equal(payload.progress, 0);
});
it("refuses a stage declaration with a plain reason", async () => {
const lane = lanes.createLane({ cwd: path.join(ROOT, "b"), title: "b" });
const r = await request("POST", `/api/lanes/${lane.id}/stage`, { stage: "plan" });
assert.equal(r.status, 400);
assert.equal(r.body.error.code, "ENOPIPELINE");
assert.match(r.body.error.message, /no pipeline template is installed/);
});
it("lists no pipelines over the API and still serves the lane list", async () => {
assert.deepEqual((await request("GET", "/api/lanes/pipelines")).body, { pipelines: [] });
assert.equal((await request("GET", "/api/lanes")).status, 200);
});
});