build: add biome formatter and linter

This commit is contained in:
2026-03-23 08:17:27 +05:30
parent 068db04355
commit 7a164c705c
21 changed files with 1620 additions and 510 deletions
+228 -66
View File
@@ -1,28 +1,39 @@
import { expect, test, mock, spyOn, beforeEach, afterEach, describe } from "bun:test";
import {
afterEach,
beforeEach,
describe,
expect,
mock,
spyOn,
test,
} from "bun:test";
import * as fs from "node:fs";
import * as os from "node:os";
import { openMultiModel } from "../src/core/open";
import {
shellQuote,
normalizeModels,
findDuplicates,
createWindowBaseName,
createWindowPlans,
levenshtein,
suggestModels,
findDuplicates,
formatInvalidModelError,
sanitizeName,
runCommand,
getWorktreePath,
getSessionPath,
undoWorktree,
launchModelInWindow,
getWorktreesForSession,
getBinaryName,
getSessionPath,
getWorktreePath,
getWorktreesForSession,
launchModelInWindow,
levenshtein,
normalizeModels,
runCommand,
sanitizeName,
shellQuote,
suggestModels,
undoWorktree,
} from "../src/core/utils";
let originalBun$: typeof Bun.$ = (globalThis as any).Bun?.$;
let mockCommandResponses: Record<string, { ok: boolean; stdout: string; stderr: string }> = {};
const originalBun$: typeof Bun.$ = (globalThis as any).Bun?.$;
let mockCommandResponses: Record<
string,
{ ok: boolean; stdout: string; stderr: string }
> = {};
let executedCommands: string[] = [];
function setupMockBun$() {
@@ -35,7 +46,11 @@ function setupMockBun$() {
return {
quiet: () => ({
nothrow: async () => {
const response = mockCommandResponses[commandSignature] ?? { ok: true, stdout: "", stderr: "" };
const response = mockCommandResponses[commandSignature] ?? {
ok: true,
stdout: "",
stderr: "",
};
return {
exitCode: response.ok ? 0 : 1,
stdout: { toString: () => response.stdout },
@@ -65,12 +80,29 @@ describe("multi-model launch", () => {
executedCommands = [];
mockCommandResponses = {
"git rev-parse --is-inside-work-tree": { ok: true, stdout: "true", stderr: "" },
"git rev-parse --is-inside-work-tree": {
ok: true,
stdout: "true",
stderr: "",
},
"command -v tmux": { ok: true, stdout: "/usr/bin/tmux", stderr: "" },
"command -v opencode": { ok: true, stdout: "/usr/bin/opencode", stderr: "" },
"opencode models": { ok: true, stdout: "openai/gpt-5.2\nanthropic/claude-3-5-sonnet", stderr: "" },
"tmux has-session -t test-session": { ok: false, stdout: "", stderr: "session not found" },
"git show-ref --verify --quiet refs/heads/opencode/test-session/gpt-5-2": { ok: false, stdout: "", stderr: "" },
"command -v opencode": {
ok: true,
stdout: "/usr/bin/opencode",
stderr: "",
},
"opencode models": {
ok: true,
stdout: "openai/gpt-5.2\nanthropic/claude-3-5-sonnet",
stderr: "",
},
"tmux has-session -t test-session": {
ok: false,
stdout: "",
stderr: "session not found",
},
"git show-ref --verify --quiet refs/heads/opencode/test-session/gpt-5-2":
{ ok: false, stdout: "", stderr: "" },
};
});
@@ -112,7 +144,11 @@ describe("multi-model launch", () => {
});
test("fails if not inside a git repository", async () => {
mockCommandResponses["git rev-parse --is-inside-work-tree"] = { ok: false, stdout: "", stderr: "not a git repo" };
mockCommandResponses["git rev-parse --is-inside-work-tree"] = {
ok: false,
stdout: "",
stderr: "not a git repo",
};
const result = await openMultiModel({
sessionName: "test-session",
@@ -120,12 +156,18 @@ describe("multi-model launch", () => {
});
expect(result.success).toBe(false);
expect(result.error).toContain("Error: `multi-model` requires a git repository.");
expect(result.error).toContain(
"Error: `multi-model` requires a git repository.",
);
expect(executedCommands).toEqual(["git rev-parse --is-inside-work-tree"]);
});
test("fails if tmux is not installed", async () => {
mockCommandResponses["command -v tmux"] = { ok: false, stdout: "", stderr: "" };
mockCommandResponses["command -v tmux"] = {
ok: false,
stdout: "",
stderr: "",
};
const result = await openMultiModel({
sessionName: "test-session",
@@ -133,7 +175,9 @@ describe("multi-model launch", () => {
});
expect(result.success).toBe(false);
expect(result.error).toContain("Error: `tmux` is not installed or not on `PATH`.");
expect(result.error).toContain(
"Error: `tmux` is not installed or not on `PATH`.",
);
expect(executedCommands).toEqual([
"git rev-parse --is-inside-work-tree",
"command -v tmux",
@@ -141,7 +185,11 @@ describe("multi-model launch", () => {
});
test("fails if opencode is not installed", async () => {
mockCommandResponses["command -v opencode"] = { ok: false, stdout: "", stderr: "" };
mockCommandResponses["command -v opencode"] = {
ok: false,
stdout: "",
stderr: "",
};
const result = await openMultiModel({
sessionName: "test-session",
@@ -149,7 +197,9 @@ describe("multi-model launch", () => {
});
expect(result.success).toBe(false);
expect(result.error).toContain("Error: `opencode` is not installed or not on `PATH`.");
expect(result.error).toContain(
"Error: `opencode` is not installed or not on `PATH`.",
);
expect(executedCommands).toEqual([
"git rev-parse --is-inside-work-tree",
"command -v tmux",
@@ -164,7 +214,9 @@ describe("multi-model launch", () => {
});
expect(result.success).toBe(false);
expect(result.error).toContain("Error: Model name 'invalid/model' not found");
expect(result.error).toContain(
"Error: Model name 'invalid/model' not found",
);
expect(executedCommands).toEqual([
"git rev-parse --is-inside-work-tree",
"command -v tmux",
@@ -174,7 +226,11 @@ describe("multi-model launch", () => {
});
test("fails if session already exists", async () => {
mockCommandResponses["tmux has-session -t test-session"] = { ok: true, stdout: "", stderr: "" };
mockCommandResponses["tmux has-session -t test-session"] = {
ok: true,
stdout: "",
stderr: "",
};
const result = await openMultiModel({
sessionName: "test-session",
@@ -199,13 +255,25 @@ describe("multi-model launch", () => {
});
expect(result.success).toBe(true);
expect(result.instructions).toContain("Use `tmux attach -t test-session` to join session");
expect(executedCommands.some(c => c.startsWith("tmux new-session -d -s test-session -n gpt-5-2"))).toBe(true);
expect(executedCommands.some(c => c.startsWith("tmux send-keys -t test-session:gpt-5-2"))).toBe(true);
expect(result.instructions).toContain(
"Use `tmux attach -t test-session` to join session",
);
expect(
executedCommands.some((c) =>
c.startsWith("tmux new-session -d -s test-session -n gpt-5-2"),
),
).toBe(true);
expect(
executedCommands.some((c) =>
c.startsWith("tmux send-keys -t test-session:gpt-5-2"),
),
).toBe(true);
});
test("successfully launches multiple models", async () => {
mockCommandResponses["git show-ref --verify --quiet refs/heads/opencode/test-session/claude-3-5-sonnet"] = { ok: false, stdout: "", stderr: "" };
mockCommandResponses[
"git show-ref --verify --quiet refs/heads/opencode/test-session/claude-3-5-sonnet"
] = { ok: false, stdout: "", stderr: "" };
const result = await openMultiModel({
sessionName: "test-session",
@@ -213,14 +281,30 @@ describe("multi-model launch", () => {
});
expect(result.success).toBe(true);
expect(result.instructions).toContain("Use `tmux attach -t test-session` to join session");
expect(executedCommands.some(c => c.startsWith("tmux new-session -d -s test-session -n gpt-5-2"))).toBe(true);
expect(executedCommands.some(c => c.startsWith("tmux new-window -d -t test-session -n claude-3-5-sonnet"))).toBe(true);
expect(result.instructions).toContain(
"Use `tmux attach -t test-session` to join session",
);
expect(
executedCommands.some((c) =>
c.startsWith("tmux new-session -d -s test-session -n gpt-5-2"),
),
).toBe(true);
expect(
executedCommands.some((c) =>
c.startsWith("tmux new-window -d -t test-session -n claude-3-5-sonnet"),
),
).toBe(true);
});
test("sanitizes session name", async () => {
mockCommandResponses["tmux has-session -t test session!"] = { ok: false, stdout: "", stderr: "" };
mockCommandResponses["git show-ref --verify --quiet refs/heads/opencode/test-session/gpt-5-2"] = { ok: false, stdout: "", stderr: "" };
mockCommandResponses["tmux has-session -t test session!"] = {
ok: false,
stdout: "",
stderr: "",
};
mockCommandResponses[
"git show-ref --verify --quiet refs/heads/opencode/test-session/gpt-5-2"
] = { ok: false, stdout: "", stderr: "" };
const result = await openMultiModel({
sessionName: "test session!",
@@ -228,18 +312,36 @@ describe("multi-model launch", () => {
});
expect(result.success).toBe(true);
expect(result.instructions).toContain("Use `tmux attach -t test session!` to join session");
expect(executedCommands.some(c => c.startsWith("git worktree add -b opencode/test-session/gpt-5-2"))).toBe(true);
expect(result.instructions).toContain(
"Use `tmux attach -t test session!` to join session",
);
expect(
executedCommands.some((c) =>
c.startsWith("git worktree add -b opencode/test-session/gpt-5-2"),
),
).toBe(true);
});
test("uses sessionName for tmux commands, not safeSessionName", async () => {
const unsanitized = "my awesome session!!";
const sanitized = "my-awesome-session";
mockCommandResponses[`tmux has-session -t ${unsanitized}`] = { ok: false, stdout: "", stderr: "" };
mockCommandResponses[`git show-ref --verify --quiet refs/heads/opencode/${sanitized}/gpt-5-2`] = { ok: false, stdout: "", stderr: "" };
mockCommandResponses[`git worktree add -b opencode/${sanitized}/gpt-5-2 /mock/home/.local/share/opencode/multi-model/${sanitized}/gpt-5-2`] = { ok: true, stdout: "", stderr: "" };
mockCommandResponses[`tmux new-session -d -s ${unsanitized} -n gpt-5-2 -c /mock/home/.local/share/opencode/multi-model/${sanitized}/gpt-5-2`] = { ok: true, stdout: "", stderr: "" };
mockCommandResponses[`tmux send-keys -t ${unsanitized}:gpt-5-2 opencode --model 'openai/gpt-5.2' C-m`] = { ok: true, stdout: "", stderr: "" };
mockCommandResponses[`tmux has-session -t ${unsanitized}`] = {
ok: false,
stdout: "",
stderr: "",
};
mockCommandResponses[
`git show-ref --verify --quiet refs/heads/opencode/${sanitized}/gpt-5-2`
] = { ok: false, stdout: "", stderr: "" };
mockCommandResponses[
`git worktree add -b opencode/${sanitized}/gpt-5-2 /mock/home/.local/share/opencode/multi-model/${sanitized}/gpt-5-2`
] = { ok: true, stdout: "", stderr: "" };
mockCommandResponses[
`tmux new-session -d -s ${unsanitized} -n gpt-5-2 -c /mock/home/.local/share/opencode/multi-model/${sanitized}/gpt-5-2`
] = { ok: true, stdout: "", stderr: "" };
mockCommandResponses[
`tmux send-keys -t ${unsanitized}:gpt-5-2 opencode --model 'openai/gpt-5.2' C-m`
] = { ok: true, stdout: "", stderr: "" };
const result = await openMultiModel({
sessionName: unsanitized,
@@ -248,19 +350,45 @@ describe("multi-model launch", () => {
expect(result.success).toBe(true);
// tmux commands should use unsanitized name
expect(executedCommands.some(c => c.startsWith(`tmux has-session -t ${unsanitized}`))).toBe(true);
expect(executedCommands.some(c => c.startsWith(`tmux new-session -d -s ${unsanitized}`))).toBe(true);
expect(executedCommands.some(c => c.startsWith(`tmux send-keys -t ${unsanitized}:`))).toBe(true);
expect(
executedCommands.some((c) =>
c.startsWith(`tmux has-session -t ${unsanitized}`),
),
).toBe(true);
expect(
executedCommands.some((c) =>
c.startsWith(`tmux new-session -d -s ${unsanitized}`),
),
).toBe(true);
expect(
executedCommands.some((c) =>
c.startsWith(`tmux send-keys -t ${unsanitized}:`),
),
).toBe(true);
// sanitized name should NOT appear in tmux commands
expect(executedCommands.some(c => c.includes(`-t ${sanitized}`))).toBe(false);
expect(executedCommands.some((c) => c.includes(`-t ${sanitized}`))).toBe(
false,
);
// git branches and paths should use sanitized name
expect(executedCommands.some(c => c.startsWith(`git worktree add -b opencode/${sanitized}/gpt-5-2`))).toBe(true);
expect(
executedCommands.some((c) =>
c.startsWith(`git worktree add -b opencode/${sanitized}/gpt-5-2`),
),
).toBe(true);
});
test("fails if tmux new-session fails for the first model and triggers undo", async () => {
mockCommandResponses["tmux new-session -d -s test-session -n gpt-5-2 -c /mock/home/.local/share/opencode/multi-model/test-session/gpt-5-2"] = { ok: false, stdout: "", stderr: "tmux error" };
mockCommandResponses["git worktree remove -f /mock/home/.local/share/opencode/multi-model/test-session/gpt-5-2"] = { ok: true, stdout: "", stderr: "" };
mockCommandResponses["git branch -D opencode/test-session/gpt-5-2"] = { ok: true, stdout: "", stderr: "" };
mockCommandResponses[
"tmux new-session -d -s test-session -n gpt-5-2 -c /mock/home/.local/share/opencode/multi-model/test-session/gpt-5-2"
] = { ok: false, stdout: "", stderr: "tmux error" };
mockCommandResponses[
"git worktree remove -f /mock/home/.local/share/opencode/multi-model/test-session/gpt-5-2"
] = { ok: true, stdout: "", stderr: "" };
mockCommandResponses["git branch -D opencode/test-session/gpt-5-2"] = {
ok: true,
stdout: "",
stderr: "",
};
const result = await openMultiModel({
sessionName: "test-session",
@@ -305,7 +433,9 @@ describe("multi-model launch", () => {
});
test("fails if git branch exists for first model", async () => {
mockCommandResponses["git show-ref --verify --quiet refs/heads/opencode/test-session/gpt-5-2"] = { ok: true, stdout: "", stderr: "" };
mockCommandResponses[
"git show-ref --verify --quiet refs/heads/opencode/test-session/gpt-5-2"
] = { ok: true, stdout: "", stderr: "" };
const result = await openMultiModel({
sessionName: "test-session",
@@ -313,7 +443,9 @@ describe("multi-model launch", () => {
});
expect(result.success).toBe(false);
expect(result.error).toContain("Branch 'opencode/test-session/gpt-5-2' already exists");
expect(result.error).toContain(
"Branch 'opencode/test-session/gpt-5-2' already exists",
);
expect(executedCommands).toEqual([
"git rev-parse --is-inside-work-tree",
"command -v tmux",
@@ -325,7 +457,9 @@ describe("multi-model launch", () => {
});
test("fails if git worktree add fails for first model", async () => {
mockCommandResponses["git worktree add -b opencode/test-session/gpt-5-2 /mock/home/.local/share/opencode/multi-model/test-session/gpt-5-2"] = { ok: false, stdout: "", stderr: "git error" };
mockCommandResponses[
"git worktree add -b opencode/test-session/gpt-5-2 /mock/home/.local/share/opencode/multi-model/test-session/gpt-5-2"
] = { ok: false, stdout: "", stderr: "git error" };
const result = await openMultiModel({
sessionName: "test-session",
@@ -346,9 +480,17 @@ describe("multi-model launch", () => {
});
test("model name collision creates unique window names", async () => {
mockCommandResponses["opencode models"] = { ok: true, stdout: "openai/gpt-5.2\nanthropic/gpt-5.2", stderr: "" };
mockCommandResponses["git show-ref --verify --quiet refs/heads/opencode/test-session/gpt-5-2"] = { ok: false, stdout: "", stderr: "" };
mockCommandResponses["git show-ref --verify --quiet refs/heads/opencode/test-session/gpt-5-2-2"] = { ok: false, stdout: "", stderr: "" };
mockCommandResponses["opencode models"] = {
ok: true,
stdout: "openai/gpt-5.2\nanthropic/gpt-5.2",
stderr: "",
};
mockCommandResponses[
"git show-ref --verify --quiet refs/heads/opencode/test-session/gpt-5-2"
] = { ok: false, stdout: "", stderr: "" };
mockCommandResponses[
"git show-ref --verify --quiet refs/heads/opencode/test-session/gpt-5-2-2"
] = { ok: false, stdout: "", stderr: "" };
const result = await openMultiModel({
sessionName: "test-session",
@@ -356,15 +498,31 @@ describe("multi-model launch", () => {
});
expect(result.success).toBe(true);
expect(result.instructions).toContain("Use `tmux attach -t test-session` to join session");
expect(executedCommands.some(c => c.startsWith("tmux new-session -d -s test-session -n gpt-5-2"))).toBe(true);
expect(executedCommands.some(c => c.startsWith("tmux new-window -d -t test-session -n gpt-5-2-2"))).toBe(true);
expect(result.instructions).toContain(
"Use `tmux attach -t test-session` to join session",
);
expect(
executedCommands.some((c) =>
c.startsWith("tmux new-session -d -s test-session -n gpt-5-2"),
),
).toBe(true);
expect(
executedCommands.some((c) =>
c.startsWith("tmux new-window -d -t test-session -n gpt-5-2-2"),
),
).toBe(true);
});
test("long model name is truncated in window name", async () => {
const longModel = "verylongmodelfrontexampleprovider";
mockCommandResponses["opencode models"] = { ok: true, stdout: longModel, stderr: "" };
mockCommandResponses[`git show-ref --verify --quiet refs/heads/opencode/test-session/${longModel.slice(0, 24)}`] = { ok: false, stdout: "", stderr: "" };
mockCommandResponses["opencode models"] = {
ok: true,
stdout: longModel,
stderr: "",
};
mockCommandResponses[
`git show-ref --verify --quiet refs/heads/opencode/test-session/${longModel.slice(0, 24)}`
] = { ok: false, stdout: "", stderr: "" };
const result = await openMultiModel({
sessionName: "test-session",
@@ -372,8 +530,12 @@ describe("multi-model launch", () => {
});
expect(result.success).toBe(true);
expect(result.instructions).toContain("Use `tmux attach -t test-session` to join session");
const windowName = executedCommands.find(c => c.includes("tmux new-session"))?.match(/-n (\S+)/)?.[1];
expect(result.instructions).toContain(
"Use `tmux attach -t test-session` to join session",
);
const windowName = executedCommands
.find((c) => c.includes("tmux new-session"))
?.match(/-n (\S+)/)?.[1];
expect(windowName?.length).toBeLessThanOrEqual(24);
});
});