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
+661
View File
@@ -0,0 +1,661 @@
/**
* @file Git worktree management for lanes: creation, reset, removal, and the
* three-check destroy guard that stands between a mis-click and a user's real
* project directory. Every destructive function (resetWorktree, removeWorktree)
* verifies the lane against all three safety checks before touching git.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const { execFile } = require("node:child_process");
const { promisify } = require("node:util");
const execFileAsync = promisify(execFile);
const LANES_ROOT = process.env.LANES_ROOT || path.join(os.homedir(), ".claude", "ccam-lanes");
const PROTECTED_BRANCHES = new Set(["main", "master"]);
/**
* Promisified git wrapper. On failure, throws an Error with err.git = { args, code, stderr }.
* Treats zero exit code as success even if stderr has hints.
*
* CRITICAL: Scrubs git hook environment variables (GIT_DIR, GIT_INDEX_FILE, etc.)
* that leak from parent processes. Without this, git operations on a worktree (where
* .git is a file, not a directory) fail with ".git/index: index file open failed:
* Not a directory" when run from within a git hook or from a shell that inherited
* these variables. This module's whole job is to run git safely against repos other
* than the one enclosing the current working directory.
*/
async function git(cwd, args) {
// Build a clean environment: copy process.env but scrub git hook variables
// that could point to the outer repo's git directory or index.
const env = { ...process.env };
delete env.GIT_DIR;
delete env.GIT_WORK_TREE;
delete env.GIT_INDEX_FILE;
delete env.GIT_COMMON_DIR;
delete env.GIT_OBJECT_DIRECTORY;
delete env.GIT_ALTERNATE_OBJECT_DIRECTORIES;
delete env.GIT_PREFIX;
delete env.GIT_NAMESPACE;
delete env.GIT_CONFIG_PARAMETERS;
// GIT_CONFIG_COUNT + GIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_n inject arbitrary git
// config into every invocation — including core.hooksPath, which would make an
// untrusted repo run our git commands' hooks. GIT_CONFIG_GLOBAL/SYSTEM do the
// same by redirecting which config files are read. All are scrubbed.
for (const name of Object.keys(env)) {
if (/^GIT_CONFIG_(COUNT|KEY_\d+|VALUE_\d+|GLOBAL|SYSTEM)$/.test(name)) delete env[name];
}
// Prevent credential prompts from hanging a background provisioning job
env.GIT_TERMINAL_PROMPT = "0";
try {
const result = await execFileAsync("git", args, {
cwd,
env,
maxBuffer: 8 * 1024 * 1024,
});
return { stdout: result.stdout, stderr: result.stderr };
} catch (err) {
const error = new Error(`git ${args[0]} failed`);
error.code = err.code;
error.git = { args, code: err.code, stderr: err.stderr };
throw error;
}
}
/**
* Check if a directory is a git repository.
*/
async function isGitRepo(dir) {
try {
await git(dir, ["rev-parse", "--git-dir"]);
return true;
} catch {
return false;
}
}
/**
* List a repo's local branches plus its current HEAD branch, so a caller can
* offer a real picker instead of asking someone to remember a branch name.
* Local branches only (not `origin/*` refs) — those are what `addWorktree`
* can actually check a new worktree out onto without a fetch first.
*/
async function listBranches(sourceRepo) {
const result = await git(sourceRepo, [
"for-each-ref",
"--format=%(refname:short)",
"refs/heads",
]);
const branches = result.stdout
.split("\n")
.map((line) => line.trim())
.filter(Boolean);
let current = null;
try {
const head = await git(sourceRepo, ["symbolic-ref", "--short", "HEAD"]);
current = head.stdout.trim();
} catch {
// Detached HEAD: no current branch, and that's fine - the caller still
// gets the full branch list to choose a base from.
}
return { branches, current };
}
/**
* Resolve the base branch: try origin/<wanted>, then <wanted>, then HEAD.
*/
async function resolveBase(sourceRepo, wanted) {
// Try origin/<wanted>
try {
await git(sourceRepo, ["rev-parse", "--verify", "--quiet", `origin/${wanted}`]);
return wanted;
} catch {
// Fall through to next attempt
}
// Try <wanted>
try {
await git(sourceRepo, ["rev-parse", "--verify", "--quiet", wanted]);
return wanted;
} catch {
// Fall through to next attempt
}
// Fall back to current HEAD
const result = await git(sourceRepo, ["rev-parse", "--abbrev-ref", "HEAD"]);
return result.stdout.trim();
}
/**
* Slugify a title into a safe branch-name segment: lowercase, non-alphanumerics to `-`,
* collapsed, trimmed, max 40 chars. Throws EBADSLUG if result is empty.
*/
function slugify(text) {
const result = text
.toLowerCase()
.normalize("NFKD")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "")
.slice(0, 40);
if (!result) {
const err = new Error("slug is empty");
err.code = "EBADSLUG";
throw err;
}
return result;
}
/**
* Parse `git worktree list --porcelain` output.
* Records are separated by blank lines, with keys like:
* - worktree <path>
* - branch refs/heads/<name>
* - locked (optional, bare line)
*/
async function listWorktrees(sourceRepo) {
const result = await git(sourceRepo, ["worktree", "list", "--porcelain"]);
const lines = result.stdout.split("\n");
const worktrees = [];
let current = {};
for (const line of lines) {
if (!line.trim()) {
if (current.path) {
worktrees.push(current);
current = {};
}
continue;
}
if (line.startsWith("worktree ")) {
current.path = line.slice("worktree ".length);
} else if (line.startsWith("branch ")) {
const branchPath = line.slice("branch ".length);
// Strip refs/heads/ prefix
current.branch = branchPath.replace(/^refs\/heads\//, "");
} else if (line === "locked") {
current.locked = true;
}
}
if (current.path) {
worktrees.push(current);
}
return worktrees;
}
/**
* Find which worktree (if any) has a given branch checked out.
*/
async function branchCheckedOutAt(sourceRepo, branch) {
const worktrees = await listWorktrees(sourceRepo);
const found = worktrees.find((w) => w.branch === branch);
return found ? found.path : null;
}
/**
* Create a new worktree, or add an existing branch to a new worktree.
* - If branch is already checked out elsewhere, throw EBRANCHBUSY.
* - If branch doesn't exist, create it with -b from base.
* - If branch exists, add without -b (reuse existing).
*/
async function addWorktree({ sourceRepo, dir, branch, base }) {
// Check if branch is already checked out elsewhere
const checkedOutAt = await branchCheckedOutAt(sourceRepo, branch);
if (checkedOutAt) {
const err = new Error(`branch ${branch} already checked out at ${checkedOutAt}`);
err.code = "EBRANCHBUSY";
err.checkedOutAt = checkedOutAt;
throw err;
}
// Ensure parent directory exists
fs.mkdirSync(path.dirname(dir), { recursive: true });
// Check if branch already exists
let branchExists = false;
try {
await git(sourceRepo, ["rev-parse", "--verify", "--quiet", branch]);
branchExists = true;
} catch {
// Branch doesn't exist, we'll create it with -b
}
if (branchExists) {
// Branch exists, use it
await git(sourceRepo, ["worktree", "add", dir, branch]);
return { dir, branch, created: false };
} else {
// Branch doesn't exist, create it from base
await git(sourceRepo, ["worktree", "add", "-b", branch, dir, base]);
return { dir, branch, created: true };
}
}
/**
* Delete a branch safely: never delete protected branches or falsy branch.
*/
async function deleteBranchSafely(sourceRepo, branch, baseBranch) {
if (!branch) {
return; // Branch is falsy, don't delete
}
if (PROTECTED_BRANCHES.has(branch)) {
return; // Protected branch
}
if (baseBranch && branch === baseBranch) {
return; // Never delete the base branch
}
try {
await git(sourceRepo, ["branch", "-D", branch]);
} catch {
// Ignore deletion failures
}
}
/**
* Check 1 on its own: only a dashboard-provisioned worktree may ever be
* destroyed. Shared with removeWorktree's prune path, which cannot run checks 2
* and 3 as written (there is no directory left to resolve) but must still refuse
* an adopted lane outright.
*/
function assertManaged(lane) {
if (lane.kind !== "managed") {
const err = new Error(`lane kind is ${lane.kind}, not managed`);
err.code = "ENOTMANAGED";
throw err;
}
}
/**
* Check 2 for a path that may no longer exist: is this lane's RECORDED cwd
* inside LANES_ROOT on a path boundary? Purely lexical after resolving
* LANES_ROOT itself, because a hand-deleted worktree cannot be realpath'd.
* assertDestroyable still realpaths a live cwd, which additionally defeats
* symlinks; this weaker form only ever gates operations that touch git
* bookkeeping, never a directory.
*/
function isInsideLanesRoot(cwd) {
let resolvedRoot;
try {
resolvedRoot = fs.realpathSync(LANES_ROOT);
} catch {
return false;
}
const relativePath = path.relative(resolvedRoot, path.resolve(cwd));
return !relativePath.startsWith("..") && !path.isAbsolute(relativePath);
}
/**
* Three checks for whether a lane can be safely destroyed:
* 1. kind must be "managed" (not "adopted")
* 2. cwd must resolve to a path inside LANES_ROOT
* 3. The path must be listed in git worktree list for the source repo
*/
async function assertDestroyable(lane) {
// Check 1: kind must be "managed"
assertManaged(lane);
// Check 2: cwd must be inside LANES_ROOT on a path boundary
let resolvedCwd;
let resolvedRoot;
try {
resolvedCwd = fs.realpathSync(lane.cwd);
} catch {
// Path doesn't exist, which means it's not a live worktree
const err = new Error(`lane cwd does not exist: ${lane.cwd}`);
err.code = "EOUTSIDEROOT";
throw err;
}
try {
resolvedRoot = fs.realpathSync(LANES_ROOT);
} catch {
// LANES_ROOT doesn't exist, so cwd can't be inside it
const err = new Error(`LANES_ROOT does not exist: ${LANES_ROOT}`);
err.code = "EOUTSIDEROOT";
throw err;
}
// Check that cwd is inside LANES_ROOT on a path boundary
const relativePath = path.relative(resolvedRoot, resolvedCwd);
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
const err = new Error(`lane cwd is outside LANES_ROOT: ${resolvedCwd} not in ${resolvedRoot}`);
err.code = "EOUTSIDEROOT";
throw err;
}
// Check 3: path must be in worktree list.
// git keeps listing a hand-deleted worktree (as `prunable`), so realpath must
// be tolerated per entry: an entry we cannot resolve is simply not this lane.
// Throwing here failed reset/remove for every OTHER lane in the same repo with
// an ENOENT naming an unrelated directory.
const worktrees = await listWorktrees(lane.source_repo);
const exists = worktrees.some((w) => {
try {
return fs.realpathSync(w.path) === resolvedCwd;
} catch {
return false;
}
});
if (!exists) {
const err = new Error(`lane is not listed as a worktree in ${lane.source_repo}`);
err.code = "ENOTWORKTREE";
throw err;
}
}
/**
* Reset a worktree to its base branch: checkout base, reset hard, clean files,
* then reset the feature branch to the base. This uses git branch -f from a
* separate working directory context to avoid worktree association restrictions.
*
* Verifies the base branch exists BEFORE any mutations, and verifies the final
* state ends on the feature branch (not left on base or detached).
*/
async function resetWorktree(lane) {
// Safety check
await assertDestroyable(lane);
const { cwd, branch, source_repo: sourceRepo, base_branch: baseBranch } = lane;
// CRITICAL: Verify base branch exists BEFORE any mutations.
// If base doesn't exist, we can't safely reset anything.
try {
await git(cwd, ["rev-parse", "--verify", baseBranch]);
} catch {
const err = new Error(`base branch does not exist: ${baseBranch}`);
err.code = "ENOBASE";
throw err;
}
// Fetch and prune (tolerate failure if no remote)
try {
await git(cwd, ["fetch", "origin", "--prune"]);
} catch {
// Ignore: may not have a remote
}
// Checkout base. Should succeed now that we've verified it exists.
try {
await git(cwd, ["checkout", baseBranch]);
} catch {
// Doesn't exist locally, try to create from remote (may fail if no remote)
try {
await git(cwd, ["checkout", "-b", baseBranch, `origin/${baseBranch}`]);
} catch {
// If both failed but rev-parse passed, the branch exists but we can't check it out
// Try the reset anyway - it might work even if checkout failed
}
}
// Reset hard to base
await git(cwd, ["reset", "--hard", baseBranch]);
// Clean untracked files (but NOT ignored files, so -x is omitted)
await git(cwd, ["clean", "-fd"]);
// Reset the feature branch. Git worktrees prevent deletion/force-update of
// "their" branch, so we reset it in-place instead: checkout → reset hard.
// ponytail: worktree association blocks deletion, reset-in-place instead
try {
// Try to checkout the feature branch
await git(cwd, ["checkout", branch]);
// Reset the current branch (feat/branch) to base
await git(cwd, ["reset", "--hard", baseBranch]);
} catch {
// If checkout fails, the branch might not exist. Create it.
try {
await git(cwd, ["checkout", "-b", branch, baseBranch]);
} catch (err) {
// If both checkout and create failed, we're in trouble
throw err;
}
}
// CRITICAL: Verify we actually ended on the feature branch.
// If this fails, the reset succeeded but left us on the wrong branch.
const currentBranch = (await git(cwd, ["rev-parse", "--abbrev-ref", "HEAD"])).stdout.trim();
if (currentBranch !== branch) {
const err = new Error(`reset ended on wrong branch: expected ${branch}, got ${currentBranch}`);
err.code = "ERESETBRANCH";
throw err;
}
// Try to clean up by deleting the branch from source repo (may fail if still in use)
await deleteBranchSafely(sourceRepo, branch, baseBranch);
}
/**
* Locate a worktree's administrative directory under the source repo's common
* dir (`<common>/worktrees/<name>`) by matching the `gitdir` file each entry
* points at against the worktree's cwd. That file's content is the absolute
* path to the worktree's OWN `.git` file, so its dirname is the worktree path
* — this still works when that `.git` file is corrupt, since we only ever
* read it from the source repo's side. Returns null if no entry matches.
*/
async function findWorktreeAdminDir(sourceRepo, cwd) {
const common = (await git(sourceRepo, ["rev-parse", "--git-common-dir"])).stdout.trim();
const worktreesDir = path.join(path.resolve(sourceRepo, common), "worktrees");
let entries;
try {
entries = fs.readdirSync(worktreesDir);
} catch {
return null;
}
const resolvedCwd = path.resolve(cwd);
for (const name of entries) {
let pointer;
try {
pointer = fs.readFileSync(path.join(worktreesDir, name, "gitdir"), "utf8").trim();
} catch {
continue;
}
if (path.resolve(path.dirname(pointer)) === resolvedCwd) {
return path.join(worktreesDir, name);
}
}
return null;
}
/**
* Remove a worktree completely: unlock, remove, prune, delete branch.
*
* When the directory was deleted by hand there is nothing on disk to destroy,
* but git still registers the worktree and the branch — so that case takes the
* prune path instead of the full three checks, which cannot resolve a path that
* no longer exists. Checks 1 and 2 still hold there (an adopted lane is refused
* outright; the recorded cwd must still be inside LANES_ROOT), and the operation
* touches only git bookkeeping in the source repo. Check 3 is what the prune
* replaces: a worktree git no longer lists needs no removal at all.
*/
async function removeWorktree(lane) {
const { cwd, branch, source_repo: sourceRepo, base_branch: baseBranch } = lane;
if (!fs.existsSync(cwd)) {
assertManaged(lane);
if (!isInsideLanesRoot(cwd)) {
const err = new Error(`lane cwd is outside LANES_ROOT: ${cwd} not in ${LANES_ROOT}`);
err.code = "EOUTSIDEROOT";
throw err;
}
// Drops git's record of the vanished worktree. A no-op when git never knew
// it, which leaves only the branch to clean up.
await git(sourceRepo, ["worktree", "prune"]);
const stillListed = (await listWorktrees(sourceRepo)).some(
(w) => path.resolve(w.path) === path.resolve(cwd)
);
if (stillListed) {
await git(sourceRepo, ["worktree", "remove", "--force", cwd]);
}
await deleteBranchSafely(sourceRepo, branch, baseBranch);
return;
}
// Safety check
await assertDestroyable(lane);
// Unlock (ignore failure)
try {
await git(sourceRepo, ["worktree", "unlock", cwd]);
} catch {
// Ignore
}
// Remove the worktree (force). Git validates the worktree's OWN `.git`
// pointer before it will touch it, and refuses outright (even with a
// second --force) when that pointer is corrupt — the three checks above
// already proved this is a real, managed worktree of this repo, so fall
// back to deregistering it directly from the source repo's bookkeeping
// rather than leaving the lane permanently stuck. This never touches the
// worktree directory itself — only `<sourceRepo>/.git/worktrees/<name>`.
try {
await git(sourceRepo, ["worktree", "remove", "--force", cwd]);
} catch (removeErr) {
const adminDir = await findWorktreeAdminDir(sourceRepo, cwd);
if (!adminDir) throw removeErr;
fs.rmSync(adminDir, { recursive: true, force: true });
}
// Prune dead worktree entries
await git(sourceRepo, ["worktree", "prune"]);
// Delete the branch safely
await deleteBranchSafely(sourceRepo, branch, baseBranch);
}
/**
* Parse git status --porcelain to count dirty, untracked, and get HEAD commit.
* Lines starting with ?? are untracked; others are dirty.
*/
async function statusCounts(dir) {
const result = await git(dir, ["status", "--porcelain=v1", "--untracked-files=normal"]);
let dirty = 0;
let untracked = 0;
for (const line of result.stdout.split("\n")) {
if (!line.trim()) continue;
if (line.startsWith("??")) {
untracked++;
} else {
dirty++;
}
}
// Get short commit hash
const headResult = await git(dir, ["rev-parse", "--short", "HEAD"]);
const head = headResult.stdout.trim();
return { dirty, untracked, head };
}
/**
* What a lane's working copy looks like right now: which branch it is on, the
* short HEAD, that commit's subject, and how much is uncommitted.
*
* Read-only and cheap, but it is three subprocesses, which is why it lives
* behind its own endpoint rather than inside the polled `GET /api/lanes`
* payload. A detached HEAD reports the literal `HEAD` that git returns — the
* caller shows what git says rather than inventing a nicer word for it.
*/
async function gitFacts(dir) {
const { dirty, untracked, head } = await statusCounts(dir);
const branch = (await git(dir, ["rev-parse", "--abbrev-ref", "HEAD"])).stdout.trim();
const subject = (await git(dir, ["log", "-1", "--format=%s"])).stdout.trim();
return { branch, head, subject, dirty, untracked };
}
/**
* Count the commits a destructive action would actually discard.
*
* With remotes configured: commits on no remote (`--not --remotes HEAD`).
*
* With NO remotes: the commits ahead of the lane's base branch
* (`<base>..HEAD`) — the work that belongs to this lane. Counting the whole
* history instead made a freshly provisioned worktree in a local-only repo
* report every commit in the repo as unpushed and demand Force to discard
* commits a `reset --hard <base>` would never touch. The `no-remote` warning is
* what tells the user nothing is backed up.
*
* Falls back to the total commit count only when there is no usable base to
* measure against (an adopted lane has no `base_branch` at all).
* Returns 0 if the repository is corrupt or unborn.
*
* @param {string} dir - Working directory to count in.
* @param {string|null} [baseBranch] - The lane's base branch, when it has one.
*/
async function unpushedCount(dir, baseBranch = null) {
try {
// First check if there are any remotes
const remotesResult = await git(dir, ["remote"]);
const hasRemotes = !!remotesResult.stdout.trim();
if (hasRemotes) {
// Has remotes: count commits not on any remote
const result = await git(dir, ["rev-list", "--count", "--not", "--remotes", "HEAD"]);
return parseInt(result.stdout.trim(), 10);
}
if (baseBranch) {
try {
const result = await git(dir, ["rev-list", "--count", `${baseBranch}..HEAD`]);
return parseInt(result.stdout.trim(), 10);
} catch {
// Base branch is gone or never existed — fall through to the total.
}
}
// No remote and no usable base: every commit is at risk, so count them all.
const result = await git(dir, ["rev-list", "--count", "HEAD"]);
return parseInt(result.stdout.trim(), 10);
} catch {
// Repository error (unborn HEAD, corrupt, etc.)
return 0;
}
}
/**
* Check if the repository has no remotes configured at all.
* Returns true if `git remote` output is empty, false otherwise.
*/
async function hasNoRemotes(dir) {
try {
const result = await git(dir, ["remote"]);
return !result.stdout.trim();
} catch {
// Assume remotes exist if we can't query
return false;
}
}
module.exports = {
LANES_ROOT,
git,
isGitRepo,
listBranches,
resolveBase,
slugify,
listWorktrees,
branchCheckedOutAt,
addWorktree,
assertManaged,
isInsideLanesRoot,
assertDestroyable,
resetWorktree,
removeWorktree,
statusCounts,
gitFacts,
unpushedCount,
hasNoRemotes,
};