feat: Claude Code Monitor — lanes, pipelines and a merged workspace

Internal SmartGift build of a Claude Code monitoring dashboard.

Lanes: a durable unit of parallel agent work, one per working directory,
tracked across session restarts. Managed lanes are git worktrees the
dashboard provisions and can reset or remove behind a three-check destroy
guard and a counted preflight; adopted lanes are directories you already
own and are never destroyable.

Pipelines: a lane moves through pipeline stages. A stage the agent declares
with evidence renders green; a stage inferred from the tool-event stream
renders dashed amber and never counts as done. Detection is forward-only
within a 30-minute window, and never writes the declared stage.

Workspace: one page at /run with a lane grid, the selected lane's pipeline,
and a full Claude console behind a disclosure.
This commit is contained in:
2026-07-29 17:07:45 +07:00
commit 57dc91585d
783 changed files with 221743 additions and 0 deletions
+828
View File
@@ -0,0 +1,828 @@
/**
* @file Unit tests for pipeline templates and durable lane storage helpers,
* including lifecycle transitions, recovery, liveness, and worktree metadata.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
const os = require("node:os");
const pathMod = require("node:path");
// Every test in this file must hit THIS database. Migration tests below
// re-point DASHBOARD_DB_PATH at a throwaway file; their cleanup must restore
// this value rather than delete it - an unset DASHBOARD_DB_PATH resolves to the
// operator's REAL dashboard DB, so a later test would read and write live data.
const SUITE_DB_PATH = pathMod.join(
os.tmpdir(),
`dashboard-lanes-lib-${Date.now()}-${process.pid}.db`
);
process.env.DASHBOARD_DB_PATH = SUITE_DB_PATH;
const { describe, it } = require("node:test");
const assert = require("node:assert/strict");
const {
DEFAULT_PIPELINE_ID,
getPipeline,
listPipelines,
phaseIdx,
nodeStates,
progressPct,
} = require("../lib/pipelines");
describe("pipelines", () => {
it("exposes a default template and falls back to it for unknown ids", () => {
const def = getPipeline(DEFAULT_PIPELINE_ID);
assert.ok(def.nodes.length > 3);
assert.equal(getPipeline("does-not-exist").id, DEFAULT_PIPELINE_ID);
assert.ok(listPipelines().some((p) => p.id === DEFAULT_PIPELINE_ID));
});
it("resolves a stage through node id and through aliases", () => {
const p = getPipeline(DEFAULT_PIPELINE_ID);
assert.equal(
phaseIdx(p, "plan"),
p.nodes.findIndex((n) => n.id === "plan")
);
assert.equal(phaseIdx(p, "planning"), phaseIdx(p, "plan"));
assert.equal(phaseIdx(p, "totally-unknown-stage"), -1);
});
it("marks the current stage current, recorded-with-evidence done, recorded-without amber", () => {
const p = getPipeline(DEFAULT_PIPELINE_ID);
const lane = {
stage: "review",
stages: {
plan: { enteredAt: "2026-07-27T00:00:00Z", evidence: "docs/plan.md" },
implement: { enteredAt: "2026-07-27T01:00:00Z", evidence: null },
review: { enteredAt: "2026-07-27T02:00:00Z", evidence: null },
},
};
const byId = Object.fromEntries(nodeStates(p, lane).map((n) => [n.id, n.state]));
assert.equal(byId.plan, "done");
assert.equal(byId.implement, "passed-no-evidence");
assert.equal(byId.review, "current");
assert.equal(byId.done, "pending");
});
it("marks a failed stage failed even when it is the current stage", () => {
const p = getPipeline(DEFAULT_PIPELINE_ID);
const lane = { stage: "gate", stages: { gate: { enteredAt: "x", result: "fail" } } };
const byId = Object.fromEntries(nodeStates(p, lane).map((n) => [n.id, n.state]));
assert.equal(byId.gate, "failed");
});
it("treats skipped earlier nodes as passed-without-evidence, not done", () => {
const p = getPipeline(DEFAULT_PIPELINE_ID);
const lane = { stage: "review", stages: { review: { enteredAt: "x" } } };
const byId = Object.fromEntries(nodeStates(p, lane).map((n) => [n.id, n.state]));
assert.equal(byId.plan, "passed-no-evidence");
});
it("computes progress from node position, 0 for an unknown stage", () => {
const p = getPipeline(DEFAULT_PIPELINE_ID);
assert.equal(progressPct(p, { stage: p.nodes[0].id, stages: {} }), 0);
assert.equal(progressPct(p, { stage: p.nodes[p.nodes.length - 1].id, stages: {} }), 100);
assert.equal(progressPct(p, { stage: "nope", stages: {} }), 0);
});
});
const lanes = require("../lib/lanes");
describe("lanes lib", () => {
it("creates, lists, updates and deletes a lane", () => {
const l = lanes.createLane({ title: "Feature A", cwd: "/tmp/wt/a", branch: "feat/a" });
assert.equal(l.title, "Feature A");
assert.equal(l.stage, "idle");
assert.equal(lanes.getLane(l.id).cwd, "/tmp/wt/a");
assert.ok(lanes.listLanes().length >= 1);
assert.equal(lanes.updateLane(l.id, { ci_status: "green" }).ci_status, "green");
assert.equal(lanes.deleteLane(l.id), true);
assert.equal(lanes.getLane(l.id), null);
});
it("recovers only provisioning lanes interrupted by a restart", () => {
const provisioning = lanes.createLane({
cwd: "/tmp/wt/provisioning-recovery",
branch: "feat/provisioning-recovery",
kind: "managed",
});
lanes.updateLane(provisioning.id, { status: "provisioning" });
const idle = lanes.createLane({ cwd: "/tmp/wt/idle-recovery", branch: "feat/idle-recovery" });
const failed = lanes.createLane({
cwd: "/tmp/wt/failed-recovery",
branch: "feat/failed-recovery",
});
lanes.updateLane(failed.id, { status: "failed", notes: "existing failure" });
assert.equal(lanes.recoverInterruptedProvisioning(), 1);
const recovered = lanes.getLane(provisioning.id);
assert.equal(recovered.status, "failed");
assert.equal(recovered.notes, "Provisioning was interrupted by a server restart.");
assert.equal(recovered.kind, "managed");
assert.equal(recovered.cwd, "/tmp/wt/provisioning-recovery");
assert.equal(recovered.branch, "feat/provisioning-recovery");
assert.equal(lanes.getLane(idle.id).status, "idle");
assert.equal(lanes.getLane(failed.id).status, "failed");
assert.equal(lanes.getLane(failed.id).notes, "existing failure");
lanes.deleteLane(provisioning.id);
lanes.deleteLane(idle.id);
lanes.deleteLane(failed.id);
});
it("bumps stage_since only when the stage actually changes", async () => {
const l = lanes.createLane({ cwd: "/tmp/wt/b" });
const a = lanes.setStage(l.id, { stage: "plan" });
await new Promise((r) => setTimeout(r, 1100));
const b = lanes.setStage(l.id, { stage: "plan", note: "still planning" });
assert.equal(a.stage_since, b.stage_since);
const c = lanes.setStage(l.id, { stage: "implement" });
assert.notEqual(c.stage_since, b.stage_since);
lanes.deleteLane(l.id);
});
it("records evidence per stage so the map can tell done from amber", () => {
const l = lanes.createLane({ cwd: "/tmp/wt/c" });
lanes.setStage(l.id, { stage: "plan", evidence: "docs/plan.md" });
const after = lanes.setStage(l.id, { stage: "implement" });
assert.equal(after.stages.plan.evidence, "docs/plan.md");
assert.ok(after.stages.plan.enteredAt);
lanes.deleteLane(l.id);
});
it("resolves a lane from a session cwd by longest path-boundary prefix", () => {
const outer = lanes.createLane({ cwd: "/tmp/wt" });
const inner = lanes.createLane({ cwd: "/tmp/wt/inner" });
assert.equal(lanes.resolveLaneByCwd("/tmp/wt/inner/src").id, inner.id);
assert.equal(lanes.resolveLaneByCwd("/tmp/wt/other").id, outer.id);
assert.equal(lanes.resolveLaneByCwd("/tmp/wt-sibling"), null);
assert.equal(lanes.resolveLaneByCwd(null), null);
lanes.deleteLane(inner.id);
lanes.deleteLane(outer.id);
});
it("classifies liveness: silent watcher is dead, silent idle lane is not", () => {
const d = 300;
assert.equal(
lanes.classifyLiveness({ status: "running", stage: "implement", ageSec: 10 }, d),
"active"
);
assert.equal(
lanes.classifyLiveness({ status: "running", stage: "implement", ageSec: 999 }, d),
"dead"
);
assert.equal(
lanes.classifyLiveness({ status: "idle", stage: "watching-pr", ageSec: 999 }, d),
"dead"
);
assert.equal(
lanes.classifyLiveness({ status: "idle", stage: "done", ageSec: 99999 }, d),
"idle"
);
assert.equal(
lanes.classifyLiveness({ status: "idle", stage: "done", ageSec: null }, d),
"idle"
);
});
it("payload carries node states and progress", () => {
const l = lanes.createLane({ cwd: "/tmp/wt/d" });
lanes.setStage(l.id, { stage: "review" });
const p = lanes.lanePayload(lanes.getLane(l.id), 5);
assert.equal(p.pipeline_nodes.find((n) => n.id === "review").state, "current");
assert.ok(p.progress > 0 && p.progress < 100);
assert.equal(p.liveness, "idle");
lanes.deleteLane(l.id);
});
});
const { withLaneLock } = require("../lib/lane-lock");
describe("lane kind, worktree fields and purge", () => {
it("defaults to adopted and stores worktree fields when given", () => {
const a = lanes.createLane({ cwd: "/tmp/wt-kind-a" });
assert.equal(a.kind, "adopted");
const m = lanes.createLane({
cwd: "/tmp/wt-kind-b",
kind: "managed",
source_repo: "/tmp/src",
base_branch: "main",
slug: "b",
});
assert.equal(m.kind, "managed");
assert.equal(m.source_repo, "/tmp/src");
assert.equal(m.base_branch, "main");
assert.equal(m.slug, "b");
lanes.deleteLane(a.id);
lanes.deleteLane(m.id);
});
it("rejects an unknown kind", () => {
assert.throws(
() => lanes.createLane({ cwd: "/tmp/wt-kind-c", kind: "gremlin" }),
(e) => e.code === "EBADKIND"
);
});
it("purges a lane's sessions, their events and orphaned token rows, sparing the live one", () => {
const l = lanes.createLane({ cwd: "/tmp/wt-purge" });
const { db } = require("../db");
db.prepare("INSERT INTO sessions (id, cwd, status) VALUES (?, ?, 'completed')").run(
"purge-1",
"/tmp/wt-purge/sub"
);
db.prepare("INSERT INTO sessions (id, cwd, status) VALUES (?, ?, 'active')").run(
"purge-live",
"/tmp/wt-purge"
);
db.prepare("INSERT INTO events (session_id, event_type) VALUES (?, 'PostToolUse')").run(
"purge-1"
);
db.prepare("INSERT INTO token_usage (session_id, model, input_tokens) VALUES (?, 'm', 5)").run(
"purge-1"
);
lanes.updateLane(l.id, { session_id: "purge-live" });
const counts = lanes.purgeLaneSessions(l.id);
assert.equal(counts.sessions, 1);
assert.equal(counts.events, 1);
assert.equal(counts.tokenRows, 1);
assert.equal(db.prepare("SELECT COUNT(*) c FROM sessions WHERE id='purge-live'").get().c, 1);
assert.equal(db.prepare("SELECT COUNT(*) c FROM events WHERE session_id='purge-1'").get().c, 0);
assert.equal(
db.prepare("SELECT COUNT(*) c FROM token_usage WHERE session_id='purge-1'").get().c,
0
);
lanes.deleteLane(l.id);
});
it("never counts or deletes a sibling directory whose name differs only at an underscore", () => {
// Every managed lane directory is named `<repo>__<slug>` — two literal
// underscores, each of which LIKE treats as "any single character". Without
// ESCAPE, a lane at /root/myrepo__feat-foo purged /root/myrepoXXfeat-foo's
// sessions too, and the preflight count reported the victims, so the
// confirmation was consistently wrong rather than detectably wrong.
const { db } = require("../db");
const laneCwd = "/root/myrepo__feat-foo";
const siblingCwd = "/root/myrepoXXfeat-foo";
const l = lanes.createLane({ cwd: laneCwd, kind: "managed" });
db.prepare("INSERT INTO sessions (id, cwd, status) VALUES (?, ?, 'completed')").run(
"like-mine",
`${laneCwd}/sub`
);
db.prepare("INSERT INTO sessions (id, cwd, status) VALUES (?, ?, 'completed')").run(
"like-victim",
`${siblingCwd}/sub`
);
db.prepare("INSERT INTO sessions (id, cwd, status) VALUES (?, ?, 'active')").run(
"like-victim-active",
`${siblingCwd}/other`
);
db.prepare("INSERT INTO events (session_id, event_type) VALUES (?, 'Stop')").run("like-victim");
// The counter sees only this lane's own session.
const candidates = lanes.purgeCandidateSessions(lanes.getLane(l.id));
assert.deepEqual(
candidates.map((s) => s.id),
["like-mine"]
);
// ...and does not attribute the sibling's live session to this lane either.
assert.equal(lanes.hasActiveLaneSession(lanes.getLane(l.id)), false);
// The deleter agrees with the counter, exactly.
const counts = lanes.purgeLaneSessions(l.id);
assert.equal(counts.sessions, 1);
assert.equal(counts.events, 0);
assert.equal(db.prepare("SELECT COUNT(*) c FROM sessions WHERE id='like-victim'").get().c, 1);
assert.equal(
db.prepare("SELECT COUNT(*) c FROM sessions WHERE id='like-victim-active'").get().c,
1
);
assert.equal(
db.prepare("SELECT COUNT(*) c FROM events WHERE session_id='like-victim'").get().c,
1
);
lanes.deleteLane(l.id);
});
it("a PATCH-style updateLane cannot change kind, source_repo, slug or base_branch", () => {
// kind is check 1 of the destroy guard: a client that could flip it to
// "managed" could point the guard at a directory the user owns.
const l = lanes.createLane({
cwd: "/tmp/wt-patch-guard",
kind: "adopted",
source_repo: "/tmp/original-src",
base_branch: "main",
slug: "original",
});
const after = lanes.updateLane(l.id, {
kind: "managed",
source_repo: "/tmp/attacker-src",
base_branch: "attacker",
slug: "attacker",
title: "this one IS patchable",
});
assert.equal(after.kind, "adopted");
assert.equal(after.source_repo, "/tmp/original-src");
assert.equal(after.base_branch, "main");
assert.equal(after.slug, "original");
assert.equal(after.title, "this one IS patchable");
// Provisioning has its own internal writer for those facts.
const provisioned = lanes.setProvisioningFacts(l.id, { base_branch: "resolved-base" });
assert.equal(provisioned.base_branch, "resolved-base");
assert.equal(provisioned.kind, "adopted");
lanes.deleteLane(l.id);
});
it("serialises work per lane and releases the lock when the body throws", async () => {
const order = [];
const slow = withLaneLock(7, async () => {
order.push("a-start");
await new Promise((r) => setTimeout(r, 50));
order.push("a-end");
});
const fast = withLaneLock(7, async () => {
order.push("b");
});
await Promise.all([slow, fast]);
assert.deepEqual(order, ["a-start", "a-end", "b"]);
await assert.rejects(() =>
withLaneLock(7, async () => {
throw new Error("boom");
})
);
await withLaneLock(7, async () => order.push("c"));
assert.equal(order[order.length - 1], "c");
});
it("updateLane rejects an unknown kind with EBADKIND and leaves stored value unchanged", () => {
const l = lanes.createLane({ cwd: "/tmp/wt-kind-update", kind: "adopted" });
assert.equal(l.kind, "adopted");
assert.throws(
() => lanes.updateLane(l.id, { kind: "MANAGED" }),
(e) => e.code === "EBADKIND"
);
const after = lanes.getLane(l.id);
assert.equal(after.kind, "adopted");
lanes.deleteLane(l.id);
});
it("different lane ids have separate locks and run in parallel", async () => {
const order = [];
const lane1Slow = withLaneLock(10, async () => {
order.push("lane1-start");
await new Promise((r) => setTimeout(r, 50));
order.push("lane1-end");
});
const lane2 = withLaneLock(11, async () => {
order.push("lane2");
});
await Promise.all([lane1Slow, lane2]);
assert.ok(order.includes("lane2"), "lane 2 should run without waiting for lane 1");
assert.notDeepEqual(order, ["lane1-start", "lane1-end", "lane2"]);
});
it("migration: old schema database gains all four columns idempotently", () => {
const tmpPath = pathMod.join(
os.tmpdir(),
`test-lanes-migration-${Date.now()}-${process.pid}.db`
);
try {
// Create an old-schema lanes table with only id, cwd, title columns
const OldDatabase = require("better-sqlite3");
const oldDb = new OldDatabase(tmpPath);
oldDb.exec(`
CREATE TABLE lanes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL DEFAULT '',
cwd TEXT NOT NULL UNIQUE,
branch TEXT,
pipeline TEXT NOT NULL DEFAULT 'default',
session_id TEXT,
run_id TEXT,
stage TEXT NOT NULL DEFAULT 'idle',
stage_since TEXT,
status TEXT NOT NULL DEFAULT 'idle',
gate_decision TEXT,
ci_status TEXT,
needs_action TEXT,
links TEXT NOT NULL DEFAULT '{}',
stages TEXT NOT NULL DEFAULT '{}',
notes TEXT,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
)
`);
oldDb
.prepare(
"INSERT INTO lanes (title, cwd, branch, pipeline, stage_since) VALUES (?, ?, ?, ?, ?)"
)
.run("old-lane", "/tmp/old-lane-path", "main", "default", "2026-07-27T00:00:00Z");
oldDb.close();
// Load db.js against it, which should add the four columns
process.env.DASHBOARD_DB_PATH = tmpPath;
delete require.cache[require.resolve("../db")];
const { db: newDb } = require("../db");
// Verify all four columns exist
const checkCols = [
"SELECT kind FROM lanes LIMIT 1",
"SELECT source_repo FROM lanes LIMIT 1",
"SELECT base_branch FROM lanes LIMIT 1",
"SELECT slug FROM lanes LIMIT 1",
];
checkCols.forEach((sql) => {
assert.doesNotThrow(() => newDb.prepare(sql).get());
});
// Verify existing row has kind='adopted' and others NULL
const row = newDb.prepare("SELECT * FROM lanes WHERE cwd = ?").get("/tmp/old-lane-path");
assert.equal(row.kind, "adopted");
assert.equal(row.source_repo, null);
assert.equal(row.base_branch, null);
assert.equal(row.slug, null);
} finally {
process.env.DASHBOARD_DB_PATH = SUITE_DB_PATH;
delete require.cache[require.resolve("../db")];
try {
require("fs").rmSync(tmpPath, { force: true });
} catch {
/* cleanup best effort */
}
}
});
});
describe("stage detection", () => {
it("writes a forward detection", () => {
const l = lanes.createLane({ cwd: "/tmp/wt-detect-forward" });
const result = lanes.recordDetection(l.id, { nodeId: "implement", signal: "`Edit`" });
assert.deepEqual(result, { written: true });
const after = lanes.getLane(l.id);
assert.equal(after.detected_stage, "implement");
assert.equal(after.detected_signal, "`Edit`");
assert.ok(after.detected_at);
lanes.deleteLane(l.id);
});
it("drops a detection that does not advance past the current detected stage", () => {
const l = lanes.createLane({ cwd: "/tmp/wt-detect-backward" });
lanes.recordDetection(l.id, { nodeId: "tests" });
const result = lanes.recordDetection(l.id, { nodeId: "implement" });
assert.deepEqual(result, { written: false, reason: "behind-detected" });
assert.equal(lanes.getLane(l.id).detected_stage, "tests");
lanes.deleteLane(l.id);
});
it("drops a detection at or behind the declared stage", () => {
const l = lanes.createLane({ cwd: "/tmp/wt-detect-declared" });
lanes.setStage(l.id, { stage: "review" });
const atDeclared = lanes.recordDetection(l.id, { nodeId: "review" });
assert.deepEqual(atDeclared, { written: false, reason: "behind-declared" });
const behindDeclared = lanes.recordDetection(l.id, { nodeId: "implement" });
assert.deepEqual(behindDeclared, { written: false, reason: "behind-declared" });
assert.equal(lanes.getLane(l.id).detected_stage, null);
lanes.deleteLane(l.id);
});
it("reports unknown-node for a node id the pipeline does not have", () => {
const l = lanes.createLane({ cwd: "/tmp/wt-detect-unknown" });
const result = lanes.recordDetection(l.id, { nodeId: "not-a-real-node" });
assert.deepEqual(result, { written: false, reason: "unknown-node" });
assert.equal(lanes.getLane(l.id).detected_stage, null);
lanes.deleteLane(l.id);
});
it("PREMISE GUARD: a lane with only detections and no declarations has no done node", () => {
const l = lanes.createLane({ cwd: "/tmp/wt-detect-no-done" });
lanes.recordDetection(l.id, { nodeId: "review", signal: "`git diff`" });
const payload = lanes.lanePayload(lanes.getLane(l.id));
assert.ok(
!payload.pipeline_nodes.some((n) => n.state === "done"),
"inference must never render a node done — only a declared stage carrying evidence may"
);
// lane.stage is still the default "idle", which is not itself a pipeline
// node/alias, so nodeStates() (unaware of detection) reports "pending" for
// every node here — detection only ever adds the `detected` flag alongside
// whatever nodeStates() already computed from the declared stage.
const review = payload.pipeline_nodes.find((n) => n.id === "review");
assert.equal(review.state, "pending");
assert.equal(review.detected, true);
const implement = payload.pipeline_nodes.find((n) => n.id === "implement");
assert.equal(implement.detected, true);
const done = payload.pipeline_nodes.find((n) => n.id === "done");
assert.equal(done.detected, false);
lanes.deleteLane(l.id);
});
it("PREMISE GUARD: a node declared WITH evidence stays done and unflagged under a detection ahead of it", () => {
const l = lanes.createLane({ cwd: "/tmp/wt-detect-done-guard" });
// implement declared with evidence, then the lane declares tests; a review
// detection lands ahead of both. `implement` is the adversarial node: it is
// `done` (declared + evidence) and it sits before the detected index.
lanes.setStage(l.id, { stage: "implement", evidence: "server/lib/x.js" });
lanes.setStage(l.id, { stage: "tests" });
assert.deepEqual(lanes.recordDetection(l.id, { nodeId: "review", signal: "`git diff`" }), {
written: true,
});
const nodes = lanes.lanePayload(lanes.getLane(l.id)).pipeline_nodes;
const implement = nodes.find((n) => n.id === "implement");
assert.equal(implement.state, "done");
assert.equal(implement.detected, false);
lanes.deleteLane(l.id);
});
it("a stage declared by ALIAS keeps its current ring instead of reading as an inference", () => {
const l = lanes.createLane({ cwd: "/tmp/wt-detect-alias-current" });
// "coding" is an alias of the `implement` node, so `stages` gets no entry
// under "implement" — the declared current node must still not be flagged.
lanes.setStage(l.id, { stage: "coding", evidence: "server/lib/x.js" });
assert.deepEqual(lanes.recordDetection(l.id, { nodeId: "tests", signal: "`npm test`" }), {
written: true,
});
const nodes = lanes.lanePayload(lanes.getLane(l.id)).pipeline_nodes;
const implement = nodes.find((n) => n.id === "implement");
assert.equal(implement.state, "current");
assert.equal(implement.detected, false);
lanes.deleteLane(l.id);
});
it("never writes the declared stage: stage and stage_since are byte-identical after a detection", () => {
const l = lanes.createLane({ cwd: "/tmp/wt-detect-no-stage-write" });
lanes.setStage(l.id, { stage: "plan" });
const before = lanes.getLane(l.id);
assert.deepEqual(lanes.recordDetection(l.id, { nodeId: "tests", signal: "`npm test`" }), {
written: true,
});
const after = lanes.getLane(l.id);
assert.equal(after.stage, before.stage);
assert.equal(after.stage_since, before.stage_since);
lanes.deleteLane(l.id);
});
it("clearLane nulls the detection columns and lets a fresh detection land immediately", () => {
const l = lanes.createLane({ cwd: "/tmp/wt-detect-clear" });
lanes.recordDetection(l.id, { nodeId: "ship", signal: "`git push`" });
assert.equal(lanes.getLane(l.id).detected_stage, "ship");
const cleared = lanes.clearLane(l.id);
assert.equal(cleared.detected_stage, null);
assert.equal(cleared.detected_signal, null);
assert.equal(cleared.detected_at, null);
assert.ok(
!lanes.lanePayload(cleared).pipeline_nodes.some((n) => n.detected),
"a cleared lane must claim no inferred progress"
);
// Without the reset, `ship` would forever outrank every later detection.
assert.deepEqual(lanes.recordDetection(l.id, { nodeId: "implement", signal: "`Edit`" }), {
written: true,
});
assert.equal(lanes.getLane(l.id).detected_stage, "implement");
lanes.deleteLane(l.id);
});
it("migration: detection columns are added to a database holding an old-schema lanes row", () => {
const tmpPath = pathMod.join(
os.tmpdir(),
`test-lanes-detect-migration-${Date.now()}-${process.pid}.db`
);
try {
const OldDatabase = require("better-sqlite3");
const oldDb = new OldDatabase(tmpPath);
oldDb.exec(`
CREATE TABLE lanes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL DEFAULT '',
cwd TEXT NOT NULL UNIQUE,
branch TEXT,
pipeline TEXT NOT NULL DEFAULT 'default',
session_id TEXT,
run_id TEXT,
stage TEXT NOT NULL DEFAULT 'idle',
stage_since TEXT,
status TEXT NOT NULL DEFAULT 'idle',
gate_decision TEXT,
ci_status TEXT,
needs_action TEXT,
links TEXT NOT NULL DEFAULT '{}',
stages TEXT NOT NULL DEFAULT '{}',
notes TEXT,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
kind TEXT NOT NULL DEFAULT 'adopted',
source_repo TEXT,
base_branch TEXT,
slug TEXT
)
`);
oldDb
.prepare(
"INSERT INTO lanes (title, cwd, branch, pipeline, stage_since) VALUES (?, ?, ?, ?, ?)"
)
.run(
"pre-detect-lane",
"/tmp/pre-detect-lane-path",
"main",
"default",
"2026-07-27T00:00:00Z"
);
oldDb.close();
process.env.DASHBOARD_DB_PATH = tmpPath;
delete require.cache[require.resolve("../db")];
const { db: newDb } = require("../db");
["detected_stage", "detected_signal", "detected_at"].forEach((col) => {
assert.doesNotThrow(() => newDb.prepare(`SELECT ${col} FROM lanes LIMIT 1`).get());
});
const row = newDb
.prepare("SELECT * FROM lanes WHERE cwd = ?")
.get("/tmp/pre-detect-lane-path");
assert.equal(row.detected_stage, null);
assert.equal(row.detected_signal, null);
assert.equal(row.detected_at, null);
} finally {
process.env.DASHBOARD_DB_PATH = SUITE_DB_PATH;
delete require.cache[require.resolve("../db")];
try {
require("fs").rmSync(tmpPath, { force: true });
} catch {
/* cleanup best effort */
}
}
});
it("migration: a crash after only the first detection column self-heals on the next load", () => {
const tmpPath = pathMod.join(
os.tmpdir(),
`test-lanes-detect-crash-${Date.now()}-${process.pid}.db`
);
try {
const OldDatabase = require("better-sqlite3");
const oldDb = new OldDatabase(tmpPath);
oldDb.exec(`
CREATE TABLE lanes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL DEFAULT '',
cwd TEXT NOT NULL UNIQUE,
branch TEXT,
pipeline TEXT NOT NULL DEFAULT 'default',
session_id TEXT,
run_id TEXT,
stage TEXT NOT NULL DEFAULT 'idle',
stage_since TEXT,
status TEXT NOT NULL DEFAULT 'idle',
gate_decision TEXT,
ci_status TEXT,
needs_action TEXT,
links TEXT NOT NULL DEFAULT '{}',
stages TEXT NOT NULL DEFAULT '{}',
notes TEXT,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
kind TEXT NOT NULL DEFAULT 'adopted',
source_repo TEXT,
base_branch TEXT,
slug TEXT
)
`);
// Simulate a process death that landed only the first ALTER.
oldDb.exec("ALTER TABLE lanes ADD COLUMN detected_stage TEXT");
oldDb.close();
process.env.DASHBOARD_DB_PATH = tmpPath;
delete require.cache[require.resolve("../db")];
const { db: newDb } = require("../db");
["detected_stage", "detected_signal", "detected_at"].forEach((col) => {
assert.doesNotThrow(() => newDb.prepare(`SELECT ${col} FROM lanes LIMIT 1`).get());
});
} finally {
process.env.DASHBOARD_DB_PATH = SUITE_DB_PATH;
delete require.cache[require.resolve("../db")];
try {
require("fs").rmSync(tmpPath, { force: true });
} catch {
/* cleanup best effort */
}
}
});
it("detection expiry: backward detection inside window is rejected", () => {
const l = lanes.createLane({ cwd: "/tmp/wt-detect-expiry-window" });
// Set an initial forward detection
lanes.recordDetection(l.id, { nodeId: "tests" });
// Try backward detection - should still be rejected within window
const result = lanes.recordDetection(l.id, { nodeId: "implement" });
assert.deepEqual(result, { written: false, reason: "behind-detected" });
assert.equal(lanes.getLane(l.id).detected_stage, "tests");
lanes.deleteLane(l.id);
});
it("detection expiry: backward detection beyond TTL is accepted and written", () => {
const { db } = require("../db");
const l = lanes.createLane({ cwd: "/tmp/wt-detect-expiry-stale" });
// Set an initial forward detection with a stale timestamp
lanes.recordDetection(l.id, { nodeId: "tests" });
// Manually set detected_at to an old time (2 hours ago, assuming default TTL of 30 min)
const oldTime = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString();
db.prepare("UPDATE lanes SET detected_at = ? WHERE id = ?").run(oldTime, l.id);
// Try backward detection - should be accepted because detection is stale
const result = lanes.recordDetection(l.id, { nodeId: "implement" });
assert.deepEqual(result, { written: true });
assert.equal(lanes.getLane(l.id).detected_stage, "implement");
lanes.deleteLane(l.id);
});
it("detection expiry: stale detection behind declared stage still rejects", () => {
const { db } = require("../db");
const l = lanes.createLane({ cwd: "/tmp/wt-detect-expiry-declared" });
// Stand up the detection BEFORE declaring, otherwise declared-wins refuses
// it and there is no standing detection left to age.
lanes.recordDetection(l.id, { nodeId: "tests" });
lanes.setStage(l.id, { stage: "review" });
const oldTime = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString();
db.prepare("UPDATE lanes SET detected_at = ? WHERE id = ?").run(oldTime, l.id);
// Try backward detection to implement - should still be rejected because declared stage is ahead
const result = lanes.recordDetection(l.id, { nodeId: "implement" });
assert.deepEqual(result, { written: false, reason: "behind-declared" });
assert.equal(lanes.getLane(l.id).detected_stage, "tests");
lanes.deleteLane(l.id);
});
it("detection expiry: NULL detected_at is treated as stale", () => {
const { db } = require("../db");
const l = lanes.createLane({ cwd: "/tmp/wt-detect-expiry-null" });
// Set a detection and then nullify detected_at
lanes.recordDetection(l.id, { nodeId: "tests" });
db.prepare("UPDATE lanes SET detected_at = NULL WHERE id = ?").run(l.id);
// Try backward detection - should be accepted because detected_at is NULL (unknown age)
const result = lanes.recordDetection(l.id, { nodeId: "implement" });
assert.deepEqual(result, { written: true });
assert.equal(lanes.getLane(l.id).detected_stage, "implement");
lanes.deleteLane(l.id);
});
it("detection expiry: DETECTION_TTL_MS env var controls the window", () => {
const { db } = require("../db");
const oldTTL = process.env.DETECTION_TTL_MS;
try {
// Set a very short TTL (1 second)
process.env.DETECTION_TTL_MS = "1000";
const l = lanes.createLane({ cwd: "/tmp/wt-detect-expiry-ttl" });
// Set initial detection
lanes.recordDetection(l.id, { nodeId: "tests" });
// Set detected_at to 2 seconds ago (beyond the 1-second TTL)
const staleTime = new Date(Date.now() - 2000).toISOString();
db.prepare("UPDATE lanes SET detected_at = ? WHERE id = ?").run(staleTime, l.id);
// Try backward detection - should be accepted because we're beyond TTL
const result = lanes.recordDetection(l.id, { nodeId: "implement" });
assert.deepEqual(result, { written: true });
lanes.deleteLane(l.id);
} finally {
if (oldTTL === undefined) {
delete process.env.DETECTION_TTL_MS;
} else {
process.env.DETECTION_TTL_MS = oldTTL;
}
}
});
});
describe("the forward-only hold is short enough for one work session", () => {
it("lets a newer signal move the lane back within a few minutes", () => {
const { db } = require("../db");
const l = lanes.createLane({ cwd: "/tmp/wt-hold-window" });
// A real session cycles implement -> tests -> ship -> implement -> tests.
// With a 30-minute hold, one push pinned the lane at `ship` for half an
// hour while the agent was demonstrably back to running tests.
lanes.recordDetection(l.id, { nodeId: "ship" });
const sixMinutesAgo = new Date(Date.now() - 6 * 60 * 1000).toISOString();
db.prepare("UPDATE lanes SET detected_at = ? WHERE id = ?").run(sixMinutesAgo, l.id);
const result = lanes.recordDetection(l.id, { nodeId: "tests", signal: "node --test" });
assert.deepEqual(result, { written: true });
assert.equal(lanes.getLane(l.id).detected_stage, "tests");
lanes.deleteLane(l.id);
});
it("still smooths noise inside one burst of activity", () => {
const l = lanes.createLane({ cwd: "/tmp/wt-hold-burst" });
lanes.recordDetection(l.id, { nodeId: "tests" });
// Same second: a Read after an Edit must not drag the lane backwards.
const result = lanes.recordDetection(l.id, { nodeId: "implement" });
assert.deepEqual(result, { written: false, reason: "behind-detected" });
assert.equal(lanes.getLane(l.id).detected_stage, "tests");
lanes.deleteLane(l.id);
});
});