testSessionsLeftBehindAreClearedAway
What sessions leave is cleared away: a session's server starting takes the branches servers made for themselves
that no running server works on, if main has all they hold (as their servers would have on stopping, had they
not been killed), and session directories whose branch is gone, unless they hold unsaved work, which tcm status
lists. tcm branch <b> --delete takes the branch's directory too (unless it holds unsaved work), and won't delete
a running session's branch. The status says what the session's own branch has that main doesn't.
import { okIn, plainEnv, tcmBin, tcmIn, tempDir, withChildren } from "#codebase/tcmts/cli/test/e2e/harness"; import { session } from "#codebase/tcmts/cli/test/e2e/mcp"; import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; export const testSessionsLeftBehindAreClearedAway = () => withChildren(async (keep) => { const dir = tempDir("mcp sweep"); const tcm = (...args: string[]) => okIn(dir, ...args); tcm("init"); fs.rmSync(path.join(dir, "scratch.ts"), { force: true }); const sessions = path.join(dir, ".tcm", "sessions"); const dirOf = (b: string) => path.join(sessions, b.replace(/[^\w.-]/g, "_")); // a session whose server was killed: nothing main doesn't have, nothing unsaved tcm("branch", "session/2026-01-01-aaaaaa"); fs.mkdirSync(dirOf("session/2026-01-01-aaaaaa"), { recursive: true }); // one whose work didn't land tcm("branch", "session/2026-01-01-bbbbbb"); fs.writeFileSync(path.join(dir, "work.ts"), "// tcm: namespace lib\nexport const work = 1;\n"); const added = spawnSync(process.execPath, [tcmBin(), "add", "./work.ts"], { cwd: dir, encoding: "utf8", env: { ...plainEnv(), TCM_BRANCH: "session/2026-01-01-bbbbbb" }, }); assert.equal(added.status, 0, added.stderr); // directories whose branch is gone: one empty, one holding unsaved work fs.mkdirSync(path.join(sessions, "agent_gone"), { recursive: true }); fs.mkdirSync(path.join(sessions, "agent_kept"), { recursive: true }); fs.writeFileSync(path.join(sessions, "agent_kept", "scratch.ts"), "export const kept = 1;\n"); // a branch someone named stays, whatever it holds tcm("branch", "agent/named"); const s = session(dir, keep); await s.request("initialize", { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "0" }, }); const branches = tcm("branch"); assert.doesNotMatch(branches, /aaaaaa/); assert.match(branches, /session\/2026-01-01-bbbbbb/); assert.match(branches, /agent\/named/); assert.ok(!fs.existsSync(dirOf("session/2026-01-01-aaaaaa"))); assert.ok(!fs.existsSync(path.join(sessions, "agent_gone"))); assert.ok(fs.existsSync(path.join(sessions, "agent_kept", "scratch.ts")), "unsaved work stays"); let status = (await s.call("tcm_status")).text; const mine = /^branch\s+(\S+)/m.exec(status)![1]!; assert.match( status, /session directories whose branch is gone, holding unsaved work\n\s+\S*agent_kept\s+scratch\.ts/, ); assert.match(status, /branches with work main doesn't have\n\s+session\/2026-01-01-bbbbbb\n/); assert.match( status, new RegExp(`this session's branch, ${mine}\\n\\s+nothing main doesn't have`), ); await s.call("tcm_add", { code: "export const mine = 2;\n", namespace: "lib" }); status = (await s.call("tcm_status")).text; assert.match( status, new RegExp( `this session's branch, ${mine}\\n\\s+1 history entry main doesn't have, changing 1 name\\n\\s+\\(it lands on main with tcm_merge, when the user says to\\)`, ), ); // a branch deleted takes its session's directory, unless that holds unsaved work tcm("branch", "agent/done"); fs.mkdirSync(dirOf("agent/done"), { recursive: true }); assert.match(tcm("branch", "agent/done", "--delete"), /^deleted branch agent\/done$/m); assert.ok(!fs.existsSync(dirOf("agent/done"))); fs.mkdirSync(dirOf("agent/named"), { recursive: true }); fs.writeFileSync(path.join(dirOf("agent/named"), "scratch.ts"), "export const x = 1;\n"); assert.match( tcm("branch", "agent/named", "--delete"), /deleted branch agent\/named; its session's directory stays, holding unsaved work: \S*scratch\.ts/, ); // nor is a running session's branch deleted const refused = tcmIn(dir, "branch", mine, "--delete"); assert.notEqual(refused.status, 0); assert.match(refused.out, /a session's server \(process \d+\) works on/); s.close(); })