refactor(HAI-116): rename kb to hai across all packages, CLI, and docs
- Rename npm packages from @kb/* to @hai/* and update all workspace references - Rename CLI binary from kb to hai and config directory from .kb to .hai - Update dashboard UI branding, titles, and references from kb to hai - Update all test files, CI workflows, and documentation to reflect new naming - Run comprehensive grep verification to ensure no stale kb references remain
This commit is contained in:
@@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
|
||||
// Mock external dependencies
|
||||
vi.mock("./pi.js", () => ({
|
||||
createHaiAgent: vi.fn(),
|
||||
createKbAgent: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("node:child_process", () => ({
|
||||
@@ -14,23 +14,23 @@ vi.mock("node:fs", () => ({
|
||||
}));
|
||||
|
||||
import { aiMergeTask, findWorktreeUser } from "./merger.js";
|
||||
import { createHaiAgent } from "./pi.js";
|
||||
import { createKbAgent } from "./pi.js";
|
||||
import { execSync } from "node:child_process";
|
||||
import { type TaskStore, type Task, type MergeResult, DEFAULT_SETTINGS } from "@hai/core";
|
||||
import { type TaskStore, type Task, type MergeResult, DEFAULT_SETTINGS } from "@kb/core";
|
||||
|
||||
const mockedCreateHaiAgent = vi.mocked(createHaiAgent);
|
||||
const mockedCreateHaiAgent = vi.mocked(createKbAgent);
|
||||
const mockedExecSync = vi.mocked(execSync);
|
||||
const { existsSync: mockedExistsSyncRaw } = await import("node:fs");
|
||||
const mockedExistsSync = vi.mocked(mockedExistsSyncRaw);
|
||||
|
||||
function createMockStore(taskOverrides: Partial<Task> = {}, allTasks: Task[] = []) {
|
||||
const baseTask: Task = {
|
||||
id: "HAI-050",
|
||||
id: "KB-050",
|
||||
title: "Test task",
|
||||
description: "Test",
|
||||
column: "in-review",
|
||||
dependencies: [],
|
||||
worktree: "/tmp/root/.worktrees/HAI-050",
|
||||
worktree: "/tmp/root/.worktrees/KB-050",
|
||||
steps: [],
|
||||
currentStep: 0,
|
||||
log: [],
|
||||
@@ -73,27 +73,27 @@ function setupHappyPathExecSync() {
|
||||
describe("findWorktreeUser", () => {
|
||||
it("returns null when no other task uses the worktree", async () => {
|
||||
const store = createMockStore({}, [
|
||||
{ id: "HAI-050", worktree: "/tmp/wt", column: "done" } as Task,
|
||||
{ id: "KB-050", worktree: "/tmp/wt", column: "done" } as Task,
|
||||
]);
|
||||
const result = await findWorktreeUser(store, "/tmp/wt", "HAI-050");
|
||||
const result = await findWorktreeUser(store, "/tmp/wt", "KB-050");
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("returns task ID when another non-done task uses the worktree", async () => {
|
||||
const store = createMockStore({}, [
|
||||
{ id: "HAI-050", worktree: "/tmp/wt", column: "done" } as Task,
|
||||
{ id: "HAI-051", worktree: "/tmp/wt", column: "in-progress" } as Task,
|
||||
{ id: "KB-050", worktree: "/tmp/wt", column: "done" } as Task,
|
||||
{ id: "KB-051", worktree: "/tmp/wt", column: "in-progress" } as Task,
|
||||
]);
|
||||
const result = await findWorktreeUser(store, "/tmp/wt", "HAI-050");
|
||||
expect(result).toBe("HAI-051");
|
||||
const result = await findWorktreeUser(store, "/tmp/wt", "KB-050");
|
||||
expect(result).toBe("KB-051");
|
||||
});
|
||||
|
||||
it("ignores done tasks", async () => {
|
||||
const store = createMockStore({}, [
|
||||
{ id: "HAI-050", worktree: "/tmp/wt", column: "done" } as Task,
|
||||
{ id: "HAI-051", worktree: "/tmp/wt", column: "done" } as Task,
|
||||
{ id: "KB-050", worktree: "/tmp/wt", column: "done" } as Task,
|
||||
{ id: "KB-051", worktree: "/tmp/wt", column: "done" } as Task,
|
||||
]);
|
||||
const result = await findWorktreeUser(store, "/tmp/wt", "HAI-050");
|
||||
const result = await findWorktreeUser(store, "/tmp/wt", "KB-050");
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -112,16 +112,16 @@ describe("aiMergeTask — conditional worktree cleanup", () => {
|
||||
});
|
||||
|
||||
it("does NOT remove worktree when another task references the same path", async () => {
|
||||
const worktreePath = "/tmp/root/.worktrees/HAI-050";
|
||||
const worktreePath = "/tmp/root/.worktrees/KB-050";
|
||||
const store = createMockStore(
|
||||
{ id: "HAI-050", worktree: worktreePath },
|
||||
{ id: "KB-050", worktree: worktreePath },
|
||||
[
|
||||
{ id: "HAI-050", worktree: worktreePath, column: "in-review" } as Task,
|
||||
{ id: "HAI-051", worktree: worktreePath, column: "in-progress" } as Task,
|
||||
{ id: "KB-050", worktree: worktreePath, column: "in-review" } as Task,
|
||||
{ id: "KB-051", worktree: worktreePath, column: "in-progress" } as Task,
|
||||
],
|
||||
);
|
||||
|
||||
const result = await aiMergeTask(store, "/tmp/root", "HAI-050");
|
||||
const result = await aiMergeTask(store, "/tmp/root", "KB-050");
|
||||
|
||||
// Worktree should NOT be removed
|
||||
const removeCall = mockedExecSync.mock.calls.find(
|
||||
@@ -132,15 +132,15 @@ describe("aiMergeTask — conditional worktree cleanup", () => {
|
||||
});
|
||||
|
||||
it("removes worktree when no other task references it", async () => {
|
||||
const worktreePath = "/tmp/root/.worktrees/HAI-050";
|
||||
const worktreePath = "/tmp/root/.worktrees/KB-050";
|
||||
const store = createMockStore(
|
||||
{ id: "HAI-050", worktree: worktreePath },
|
||||
{ id: "KB-050", worktree: worktreePath },
|
||||
[
|
||||
{ id: "HAI-050", worktree: worktreePath, column: "in-review" } as Task,
|
||||
{ id: "KB-050", worktree: worktreePath, column: "in-review" } as Task,
|
||||
],
|
||||
);
|
||||
|
||||
const result = await aiMergeTask(store, "/tmp/root", "HAI-050");
|
||||
const result = await aiMergeTask(store, "/tmp/root", "KB-050");
|
||||
|
||||
const removeCall = mockedExecSync.mock.calls.find(
|
||||
(call) => String(call[0]).includes("worktree remove"),
|
||||
@@ -150,16 +150,16 @@ describe("aiMergeTask — conditional worktree cleanup", () => {
|
||||
});
|
||||
|
||||
it("always deletes the branch regardless of worktree sharing", async () => {
|
||||
const worktreePath = "/tmp/root/.worktrees/HAI-050";
|
||||
const worktreePath = "/tmp/root/.worktrees/KB-050";
|
||||
const store = createMockStore(
|
||||
{ id: "HAI-050", worktree: worktreePath },
|
||||
{ id: "KB-050", worktree: worktreePath },
|
||||
[
|
||||
{ id: "HAI-050", worktree: worktreePath, column: "in-review" } as Task,
|
||||
{ id: "HAI-051", worktree: worktreePath, column: "in-progress" } as Task,
|
||||
{ id: "KB-050", worktree: worktreePath, column: "in-review" } as Task,
|
||||
{ id: "KB-051", worktree: worktreePath, column: "in-progress" } as Task,
|
||||
],
|
||||
);
|
||||
|
||||
const result = await aiMergeTask(store, "/tmp/root", "HAI-050");
|
||||
const result = await aiMergeTask(store, "/tmp/root", "KB-050");
|
||||
|
||||
// Branch should be deleted even though worktree is shared
|
||||
const branchDeleteCall = mockedExecSync.mock.calls.find(
|
||||
@@ -170,16 +170,16 @@ describe("aiMergeTask — conditional worktree cleanup", () => {
|
||||
});
|
||||
|
||||
it("result.worktreeRemoved is false when worktree is retained", async () => {
|
||||
const worktreePath = "/tmp/root/.worktrees/HAI-050";
|
||||
const worktreePath = "/tmp/root/.worktrees/KB-050";
|
||||
const store = createMockStore(
|
||||
{ id: "HAI-050", worktree: worktreePath },
|
||||
{ id: "KB-050", worktree: worktreePath },
|
||||
[
|
||||
{ id: "HAI-050", worktree: worktreePath, column: "in-review" } as Task,
|
||||
{ id: "HAI-051", worktree: worktreePath, column: "todo" } as Task,
|
||||
{ id: "KB-050", worktree: worktreePath, column: "in-review" } as Task,
|
||||
{ id: "KB-051", worktree: worktreePath, column: "todo" } as Task,
|
||||
],
|
||||
);
|
||||
|
||||
const result = await aiMergeTask(store, "/tmp/root", "HAI-050");
|
||||
const result = await aiMergeTask(store, "/tmp/root", "KB-050");
|
||||
expect(result.worktreeRemoved).toBe(false);
|
||||
expect(result.merged).toBe(true);
|
||||
});
|
||||
@@ -200,11 +200,11 @@ describe("aiMergeTask — includeTaskIdInCommit setting", () => {
|
||||
|
||||
it("includes task ID in system prompt by default (includeTaskIdInCommit: true)", async () => {
|
||||
const store = createMockStore(
|
||||
{ id: "HAI-050", worktree: "/tmp/root/.worktrees/HAI-050" },
|
||||
[{ id: "HAI-050", worktree: "/tmp/root/.worktrees/HAI-050", column: "in-review" } as Task],
|
||||
{ id: "KB-050", worktree: "/tmp/root/.worktrees/KB-050" },
|
||||
[{ id: "KB-050", worktree: "/tmp/root/.worktrees/KB-050", column: "in-review" } as Task],
|
||||
);
|
||||
|
||||
await aiMergeTask(store, "/tmp/root", "HAI-050");
|
||||
await aiMergeTask(store, "/tmp/root", "KB-050");
|
||||
|
||||
const agentCall = mockedCreateHaiAgent.mock.calls[0][0] as any;
|
||||
expect(agentCall.systemPrompt).toContain("<type>(<scope>): <summary>");
|
||||
@@ -213,15 +213,15 @@ describe("aiMergeTask — includeTaskIdInCommit setting", () => {
|
||||
|
||||
it("omits task ID scope in system prompt when includeTaskIdInCommit is false", async () => {
|
||||
const store = createMockStore(
|
||||
{ id: "HAI-050", worktree: "/tmp/root/.worktrees/HAI-050" },
|
||||
[{ id: "HAI-050", worktree: "/tmp/root/.worktrees/HAI-050", column: "in-review" } as Task],
|
||||
{ id: "KB-050", worktree: "/tmp/root/.worktrees/KB-050" },
|
||||
[{ id: "KB-050", worktree: "/tmp/root/.worktrees/KB-050", column: "in-review" } as Task],
|
||||
);
|
||||
(store.getSettings as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
...DEFAULT_SETTINGS,
|
||||
includeTaskIdInCommit: false,
|
||||
});
|
||||
|
||||
await aiMergeTask(store, "/tmp/root", "HAI-050");
|
||||
await aiMergeTask(store, "/tmp/root", "KB-050");
|
||||
|
||||
const agentCall = mockedCreateHaiAgent.mock.calls[0][0] as any;
|
||||
expect(agentCall.systemPrompt).toContain("<type>: <summary>");
|
||||
@@ -245,17 +245,17 @@ describe("aiMergeTask — includeTaskIdInCommit setting", () => {
|
||||
});
|
||||
|
||||
const store = createMockStore(
|
||||
{ id: "HAI-050", worktree: "/tmp/root/.worktrees/HAI-050" },
|
||||
[{ id: "HAI-050", worktree: "/tmp/root/.worktrees/HAI-050", column: "in-review" } as Task],
|
||||
{ id: "KB-050", worktree: "/tmp/root/.worktrees/KB-050" },
|
||||
[{ id: "KB-050", worktree: "/tmp/root/.worktrees/KB-050", column: "in-review" } as Task],
|
||||
);
|
||||
|
||||
await aiMergeTask(store, "/tmp/root", "HAI-050");
|
||||
await aiMergeTask(store, "/tmp/root", "KB-050");
|
||||
|
||||
const commitCall = mockedExecSync.mock.calls.find(
|
||||
(call) => String(call[0]).includes("git commit"),
|
||||
);
|
||||
expect(commitCall).toBeDefined();
|
||||
expect(String(commitCall![0])).toContain("feat(HAI-050):");
|
||||
expect(String(commitCall![0])).toContain("feat(KB-050):");
|
||||
});
|
||||
|
||||
it("fallback commit omits task ID when includeTaskIdInCommit is false", async () => {
|
||||
@@ -273,22 +273,22 @@ describe("aiMergeTask — includeTaskIdInCommit setting", () => {
|
||||
});
|
||||
|
||||
const store = createMockStore(
|
||||
{ id: "HAI-050", worktree: "/tmp/root/.worktrees/HAI-050" },
|
||||
[{ id: "HAI-050", worktree: "/tmp/root/.worktrees/HAI-050", column: "in-review" } as Task],
|
||||
{ id: "KB-050", worktree: "/tmp/root/.worktrees/KB-050" },
|
||||
[{ id: "KB-050", worktree: "/tmp/root/.worktrees/KB-050", column: "in-review" } as Task],
|
||||
);
|
||||
(store.getSettings as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
...DEFAULT_SETTINGS,
|
||||
includeTaskIdInCommit: false,
|
||||
});
|
||||
|
||||
await aiMergeTask(store, "/tmp/root", "HAI-050");
|
||||
await aiMergeTask(store, "/tmp/root", "KB-050");
|
||||
|
||||
const commitCall = mockedExecSync.mock.calls.find(
|
||||
(call) => String(call[0]).includes("git commit"),
|
||||
);
|
||||
expect(commitCall).toBeDefined();
|
||||
expect(String(commitCall![0])).toContain("feat: merge");
|
||||
expect(String(commitCall![0])).not.toContain("feat(HAI-050)");
|
||||
expect(String(commitCall![0])).not.toContain("feat(KB-050)");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -305,10 +305,10 @@ describe("aiMergeTask — model settings threading", () => {
|
||||
} as any);
|
||||
});
|
||||
|
||||
it("passes defaultProvider and defaultModelId from settings to createHaiAgent", async () => {
|
||||
it("passes defaultProvider and defaultModelId from settings to createKbAgent", async () => {
|
||||
const store = createMockStore(
|
||||
{ id: "HAI-050", worktree: "/tmp/root/.worktrees/HAI-050" },
|
||||
[{ id: "HAI-050", worktree: "/tmp/root/.worktrees/HAI-050", column: "in-review" } as Task],
|
||||
{ id: "KB-050", worktree: "/tmp/root/.worktrees/KB-050" },
|
||||
[{ id: "KB-050", worktree: "/tmp/root/.worktrees/KB-050", column: "in-review" } as Task],
|
||||
);
|
||||
(store.getSettings as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
...DEFAULT_SETTINGS,
|
||||
@@ -316,7 +316,7 @@ describe("aiMergeTask — model settings threading", () => {
|
||||
defaultModelId: "gpt-4o",
|
||||
});
|
||||
|
||||
await aiMergeTask(store, "/tmp/root", "HAI-050");
|
||||
await aiMergeTask(store, "/tmp/root", "KB-050");
|
||||
|
||||
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(1);
|
||||
const opts = mockedCreateHaiAgent.mock.calls[0][0] as any;
|
||||
@@ -326,11 +326,11 @@ describe("aiMergeTask — model settings threading", () => {
|
||||
|
||||
it("does not set model fields when settings omit them", async () => {
|
||||
const store = createMockStore(
|
||||
{ id: "HAI-050", worktree: "/tmp/root/.worktrees/HAI-050" },
|
||||
[{ id: "HAI-050", worktree: "/tmp/root/.worktrees/HAI-050", column: "in-review" } as Task],
|
||||
{ id: "KB-050", worktree: "/tmp/root/.worktrees/KB-050" },
|
||||
[{ id: "KB-050", worktree: "/tmp/root/.worktrees/KB-050", column: "in-review" } as Task],
|
||||
);
|
||||
|
||||
await aiMergeTask(store, "/tmp/root", "HAI-050");
|
||||
await aiMergeTask(store, "/tmp/root", "KB-050");
|
||||
|
||||
const opts = mockedCreateHaiAgent.mock.calls[0][0] as any;
|
||||
expect(opts.defaultProvider).toBeUndefined();
|
||||
@@ -361,15 +361,15 @@ describe("aiMergeTask — agent log persistence", () => {
|
||||
} as any;
|
||||
});
|
||||
|
||||
const worktreePath = "/tmp/root/.worktrees/HAI-050";
|
||||
const worktreePath = "/tmp/root/.worktrees/KB-050";
|
||||
const store = createMockStore(
|
||||
{ id: "HAI-050", worktree: worktreePath },
|
||||
[{ id: "HAI-050", worktree: worktreePath, column: "in-review" } as Task],
|
||||
{ id: "KB-050", worktree: worktreePath },
|
||||
[{ id: "KB-050", worktree: worktreePath, column: "in-review" } as Task],
|
||||
);
|
||||
|
||||
await aiMergeTask(store, "/tmp/root", "HAI-050");
|
||||
await aiMergeTask(store, "/tmp/root", "KB-050");
|
||||
|
||||
expect(store.appendAgentLog).toHaveBeenCalledWith("HAI-050", "Hello merge", "text");
|
||||
expect(store.appendAgentLog).toHaveBeenCalledWith("KB-050", "Hello merge", "text");
|
||||
});
|
||||
|
||||
it("logs tool invocations to store.appendAgentLog", async () => {
|
||||
@@ -387,15 +387,15 @@ describe("aiMergeTask — agent log persistence", () => {
|
||||
} as any;
|
||||
});
|
||||
|
||||
const worktreePath = "/tmp/root/.worktrees/HAI-050";
|
||||
const worktreePath = "/tmp/root/.worktrees/KB-050";
|
||||
const store = createMockStore(
|
||||
{ id: "HAI-050", worktree: worktreePath },
|
||||
[{ id: "HAI-050", worktree: worktreePath, column: "in-review" } as Task],
|
||||
{ id: "KB-050", worktree: worktreePath },
|
||||
[{ id: "KB-050", worktree: worktreePath, column: "in-review" } as Task],
|
||||
);
|
||||
|
||||
await aiMergeTask(store, "/tmp/root", "HAI-050");
|
||||
await aiMergeTask(store, "/tmp/root", "KB-050");
|
||||
|
||||
expect(store.appendAgentLog).toHaveBeenCalledWith("HAI-050", "Bash", "tool", "git status");
|
||||
expect(store.appendAgentLog).toHaveBeenCalledWith("KB-050", "Bash", "tool", "git status");
|
||||
});
|
||||
|
||||
it("still fires onAgentText callback alongside logging", async () => {
|
||||
@@ -414,15 +414,15 @@ describe("aiMergeTask — agent log persistence", () => {
|
||||
} as any;
|
||||
});
|
||||
|
||||
const worktreePath = "/tmp/root/.worktrees/HAI-050";
|
||||
const worktreePath = "/tmp/root/.worktrees/KB-050";
|
||||
const store = createMockStore(
|
||||
{ id: "HAI-050", worktree: worktreePath },
|
||||
[{ id: "HAI-050", worktree: worktreePath, column: "in-review" } as Task],
|
||||
{ id: "KB-050", worktree: worktreePath },
|
||||
[{ id: "KB-050", worktree: worktreePath, column: "in-review" } as Task],
|
||||
);
|
||||
|
||||
await aiMergeTask(store, "/tmp/root", "HAI-050", { onAgentText });
|
||||
await aiMergeTask(store, "/tmp/root", "KB-050", { onAgentText });
|
||||
|
||||
expect(onAgentText).toHaveBeenCalledWith("hi");
|
||||
expect(store.appendAgentLog).toHaveBeenCalledWith("HAI-050", "hi", "text");
|
||||
expect(store.appendAgentLog).toHaveBeenCalledWith("KB-050", "hi", "text");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user