testSessionsReadOtherBranches
A session reads another session's branch with the reading tools' branch option, as that branch has it, staying on
its own: its files untouched, main too. The tools that change things have no such option, and the CLI's
--branch refuses them.
import { okIn, 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 fs from "node:fs"; import path from "node:path"; export const testSessionsReadOtherBranches = () => withChildren(async (keep) => { const dir = tempDir("read other branches"); const tcm = (...args: string[]) => okIn(dir, ...args); tcm("init"); fs.writeFileSync( path.join(dir, "scratch.ts"), `// tcm: namespace lib\nexport const square = (x: number): number => x * x;\n`, ); tcm("add"); const a = session(dir, keep, "--branch", "agent/a"); const b = session(dir, keep, "--branch", "agent/b"); const init = await b.request("initialize", { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "0" }, }); assert.match(String(init.result!.instructions), /To look at another branch[\s\S]*`branch`/); // a's work: a change, and a new definition assert.equal( (await a.call("tcm_patch", { name: "lib.square", edits: [{ old: "x * x", new: "x ** 2" }] })) .isError, false, ); assert.equal( ( await a.call("tcm_add", { namespace: "lib", code: "export const cube = (x: number): number => x ** 3;\n", }) ).isError, false, ); // b, with a scratch file and `#codebase` files of its own assert.match((await b.call("tcm_edit", { names: ["lib.square"] })).text, /x \* x/); assert.equal((await b.call("tcm_check")).isError, false); const own = path.join(dir, ".tcm", "sessions", "agent_b"); const shim = path.join(own, "codebase", "lib.ts"); const files = () => [ fs.readFileSync(path.join(own, "scratch.ts"), "utf8"), fs.readFileSync(shim, "utf8"), ]; const before = files(); assert.match(before[1]!, /square/); // b reads a's branch const theirs = { branch: "agent/a" }; assert.match( (await b.call("tcm_view", { names: ["lib.square", "lib.cube"], ...theirs })).text, /x \*\* 2[\s\S]*x \*\* 3/, ); assert.match((await b.call("tcm_search", { text: "x ** 3", ...theirs })).text, /lib\.cube/); assert.match((await b.call("tcm_find", { query: "cube", ...theirs })).text, /lib\.cube/); // a line of its view (its own, rendered as it reads there: nothing's written for a read) const view = path.join(".tcm", "sessions", "agent_a", "src", "lib", "square.ts"); assert.match((await b.call("tcm_at", { place: `${view}:2`, ...theirs })).text, /^lib\.square /); assert.match((await b.call("tcm_at", { place: `${view}:2` })).text, /--branch agent\/a/); assert.match((await b.call("tcm_history", { ...theirs })).text, /lib\.cube/); assert.match((await b.call("tcm_todo", { ...theirs })).text, /nothing to do/); assert.match( (await b.call("tcm_status", { ...theirs })).text, /branch\s+agent\/a\s+\(read with --branch/, ); // tools that change things have no `branch`; the CLI refuses --branch for them const tools = (await b.request("tools/list")).result as { tools: { name: string; inputSchema: { properties: Record<string, unknown> } }[]; }; const takes = (name: string) => "branch" in tools.tools.find((t) => t.name === name)!.inputSchema.properties; assert.ok(takes("tcm_view") && takes("tcm_history") && takes("tcm_status") && takes("tcm_at")); assert.ok(!takes("tcm_patch") && !takes("tcm_update") && !takes("tcm_edit")); const patched = await b.call("tcm_patch", { name: "lib.square", edits: [{ old: "x ** 2", new: "x" }], ...theirs, }); assert.equal(patched.isError, true); assert.match(patched.text, /tcm_patch has no `branch` option/); const cli = await b.call("tcm_cli", { args: ["rm", "lib.cube", "--branch", "agent/a"] }); assert.equal(cli.isError, true); assert.match(cli.text, /--branch reads another branch, changing nothing/); // b is still on its own branch, its files as they were; a's branch and main are as they were assert.match((await b.call("tcm_view", { names: ["lib.square"] })).text, /x \* x/); assert.match((await b.call("tcm_status")).text, /branch\s+agent\/b/); assert.deepEqual(files(), before); assert.doesNotMatch(fs.readFileSync(shim, "utf8"), /cube/); assert.match((await a.call("tcm_view", { names: ["lib.cube"] })).text, /x \*\* 3/); assert.doesNotMatch(tcm("ls", "lib"), /lib\.cube/); a.close(); b.close(); })