testSessionsLeftBehindAreClearedAway

tcmts.cli.test.e2e.mcp.testSessionsLeftBehindAreClearedAway · #r6gkcie2 · term

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();
  })

uses