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:
Dustin Byrne
2026-03-26 22:25:30 -04:00
parent 1c5eb494db
commit c802108a02
84 changed files with 792 additions and 1363 deletions

View File

@@ -1,12 +1,12 @@
import { describe, it, expect } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import { AgentLogViewer } from "../AgentLogViewer";
import type { AgentLogEntry } from "@hai/core";
import type { AgentLogEntry } from "@kb/core";
function makeEntry(overrides: Partial<AgentLogEntry> = {}): AgentLogEntry {
return {
timestamp: "2026-01-01T00:00:00Z",
taskId: "HAI-001",
taskId: "KB-001",
text: "Hello world",
type: "text",
...overrides,

View File

@@ -1,7 +1,7 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { Board } from "../Board";
import { COLUMNS } from "@hai/core";
import { COLUMNS } from "@kb/core";
// Mock child components so we only test Board's own rendering
vi.mock("../Column", () => ({

View File

@@ -1,7 +1,7 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { Column } from "../Column";
import type { Task, Column as ColumnType } from "@hai/core";
import type { Task, Column as ColumnType } from "@kb/core";
// Mock child components to keep tests focused on the Column badge behavior
vi.mock("../TaskCard", () => ({
@@ -43,7 +43,7 @@ const defaultProps = {
describe("Column count-flash", () => {
it("does not apply count-flash class on initial render", () => {
const tasks = [makeTask("HAI-001")];
const tasks = [makeTask("KB-001")];
render(<Column {...defaultProps} tasks={tasks} />);
const badge = screen.getByText("1");
@@ -52,10 +52,10 @@ describe("Column count-flash", () => {
});
it("applies count-flash class when task count increases", () => {
const tasks = [makeTask("HAI-001")];
const tasks = [makeTask("KB-001")];
const { rerender } = render(<Column {...defaultProps} tasks={tasks} />);
const moreTasks = [makeTask("HAI-001"), makeTask("HAI-002")];
const moreTasks = [makeTask("KB-001"), makeTask("KB-002")];
rerender(<Column {...defaultProps} tasks={moreTasks} />);
const badge = screen.getByText("2");
@@ -63,10 +63,10 @@ describe("Column count-flash", () => {
});
it("does not apply count-flash class when task count decreases", () => {
const tasks = [makeTask("HAI-001"), makeTask("HAI-002")];
const tasks = [makeTask("KB-001"), makeTask("KB-002")];
const { rerender } = render(<Column {...defaultProps} tasks={tasks} />);
const fewerTasks = [makeTask("HAI-001")];
const fewerTasks = [makeTask("KB-001")];
rerender(<Column {...defaultProps} tasks={fewerTasks} />);
const badge = screen.getByText("1");

View File

@@ -5,7 +5,7 @@ import { Header } from "../Header";
describe("Header", () => {
it("renders a logo image with correct src and alt", () => {
render(<Header />);
const logo = screen.getByAltText("hai logo");
const logo = screen.getByAltText("kb logo");
expect(logo).toBeDefined();
expect(logo.tagName).toBe("IMG");
expect((logo as HTMLImageElement).src).toContain("/logo.svg");
@@ -13,7 +13,7 @@ describe("Header", () => {
it("renders the logo before the h1 element", () => {
render(<Header />);
const logo = screen.getByAltText("hai logo");
const logo = screen.getByAltText("kb logo");
const h1 = screen.getByRole("heading", { level: 1 });
// Logo should be a preceding sibling of the h1
expect(logo.compareDocumentPosition(h1) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();

View File

@@ -15,7 +15,7 @@ vi.mock("../../api", () => ({
function renderCard() {
const props = {
tasks: [],
onSubmit: vi.fn().mockResolvedValue({ id: "HAI-001" }),
onSubmit: vi.fn().mockResolvedValue({ id: "KB-001" }),
onCancel: vi.fn(),
addToast: vi.fn(),
};

View File

@@ -1,7 +1,7 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { SettingsModal } from "../SettingsModal";
import type { Settings } from "@hai/core";
import type { Settings } from "@kb/core";
const defaultSettings: Settings = {
maxConcurrent: 2,

View File

@@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest";
import type { Column } from "@hai/core";
import type { Column } from "@kb/core";
/**
* Tests for the agent-active class logic in TaskCard.
@@ -139,11 +139,11 @@ describe("TaskCard dependency tooltip", () => {
}
it("returns comma-separated dependency IDs when dependencies are present", () => {
expect(computeDepTooltip(["HAI-001", "HAI-042"])).toBe("HAI-001, HAI-042");
expect(computeDepTooltip(["KB-001", "KB-042"])).toBe("KB-001, KB-042");
});
it("returns single dependency ID when only one dependency", () => {
expect(computeDepTooltip(["HAI-010"])).toBe("HAI-010");
expect(computeDepTooltip(["KB-010"])).toBe("KB-010");
});
it("returns undefined when dependencies array is empty", () => {
@@ -151,12 +151,12 @@ describe("TaskCard dependency tooltip", () => {
});
it("handles many dependencies", () => {
const deps = ["HAI-001", "HAI-002", "HAI-003", "HAI-004"];
expect(computeDepTooltip(deps)).toBe("HAI-001, HAI-002, HAI-003, HAI-004");
const deps = ["KB-001", "KB-002", "KB-003", "KB-004"];
expect(computeDepTooltip(deps)).toBe("KB-001, KB-002, KB-003, KB-004");
});
it("data-tooltip attribute contains dependency IDs as a readable string", () => {
const deps = ["HAI-005", "HAI-012"];
const deps = ["KB-005", "KB-012"];
const tooltip = computeDepTooltip(deps);
expect(tooltip).toBeDefined();
// Each dependency ID should appear in the tooltip
@@ -179,7 +179,7 @@ describe("TaskCard file-scope overlap badge logic", () => {
}
it("shows scope badge when blockedBy is set", () => {
expect(shouldShowScopeBadge("HAI-003")).toBe(true);
expect(shouldShowScopeBadge("KB-003")).toBe(true);
});
it("does NOT show scope badge when blockedBy is undefined", () => {
@@ -187,7 +187,7 @@ describe("TaskCard file-scope overlap badge logic", () => {
});
it("shows card-meta when blockedBy is set even with no deps or queued status", () => {
expect(shouldShowCardMeta({ blockedBy: "HAI-003" })).toBe(true);
expect(shouldShowCardMeta({ blockedBy: "KB-003" })).toBe(true);
});
it("does NOT show card-meta when no deps, not queued, and no blockedBy", () => {
@@ -200,7 +200,7 @@ describe("TaskCard file-scope overlap badge logic", () => {
}
it("generates correct tooltip text", () => {
expect(computeScopeTooltip("HAI-005")).toBe("Blocked by HAI-005 (file overlap)");
expect(computeScopeTooltip("KB-005")).toBe("Blocked by KB-005 (file overlap)");
});
});

View File

@@ -1,7 +1,7 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen, fireEvent, act, waitFor } from "@testing-library/react";
import { TaskDetailModal } from "../TaskDetailModal";
import type { TaskDetail, Column, MergeResult, Task } from "@hai/core";
import type { TaskDetail, Column, MergeResult, Task } from "@kb/core";
vi.mock("../../api", () => ({
uploadAttachment: vi.fn(),
@@ -16,7 +16,7 @@ vi.mock("../../hooks/useAgentLogs", () => ({
function makeTask(overrides: Partial<TaskDetail> = {}): TaskDetail {
return {
id: "HAI-099",
id: "KB-099",
description: "Test task",
column: "in-progress" as Column,
dependencies: [],
@@ -157,7 +157,7 @@ describe("TaskDetailModal", () => {
task={makeTask({
title: undefined,
description: "Fix the login bug",
prompt: "# HAI-099\n\nFix the login bug\n",
prompt: "# KB-099\n\nFix the login bug\n",
})}
onClose={noop}
onMoveTask={noopMove}
@@ -167,13 +167,13 @@ describe("TaskDetailModal", () => {
/>,
);
// The heading "HAI-099" should be stripped from the markdown
// The heading "KB-099" should be stripped from the markdown
const markdownBody = container.querySelector(".markdown-body");
expect(markdownBody?.innerHTML).not.toContain("HAI-099");
expect(markdownBody?.innerHTML).not.toContain("KB-099");
// Description appears in the markdown body
expect(markdownBody?.textContent).toContain("Fix the login bug");
// The detail header shows the ID (not duplicated as markdown heading)
expect(container.querySelector(".detail-id")?.textContent).toBe("HAI-099");
expect(container.querySelector(".detail-id")?.textContent).toBe("KB-099");
// The h2 title shows description, not the task ID
const h2 = container.querySelector("h2.detail-title");
expect(h2?.textContent).toBe("Fix the login bug");
@@ -210,7 +210,7 @@ describe("TaskDetailModal", () => {
addToast={noop}
/>,
);
expect(withTitle.querySelector(".detail-id")?.textContent).toBe("HAI-099");
expect(withTitle.querySelector(".detail-id")?.textContent).toBe("KB-099");
// Without title
const { container: withoutTitle } = render(
@@ -223,7 +223,7 @@ describe("TaskDetailModal", () => {
addToast={noop}
/>,
);
expect(withoutTitle.querySelector(".detail-id")?.textContent).toBe("HAI-099");
expect(withoutTitle.querySelector(".detail-id")?.textContent).toBe("KB-099");
});
describe("paste image upload", () => {
@@ -267,7 +267,7 @@ describe("TaskDetailModal", () => {
});
await waitFor(() => {
expect(mockUpload).toHaveBeenCalledWith("HAI-099", imageFile);
expect(mockUpload).toHaveBeenCalledWith("KB-099", imageFile);
expect(addToast).toHaveBeenCalledWith("Screenshot attached", "success");
});
});
@@ -394,7 +394,7 @@ describe("TaskDetailModal", () => {
});
await waitFor(() => {
expect(mockUpload).toHaveBeenCalledWith("HAI-099", imageFile);
expect(mockUpload).toHaveBeenCalledWith("KB-099", imageFile);
expect(addToast).toHaveBeenCalledWith("Screenshot attached", "success");
});
});
@@ -418,7 +418,7 @@ describe("TaskDetailModal", () => {
it("renders dependency list when dependencies exist", () => {
render(
<TaskDetailModal
task={makeTask({ dependencies: ["HAI-001", "HAI-002"] })}
task={makeTask({ dependencies: ["KB-001", "KB-002"] })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -427,16 +427,16 @@ describe("TaskDetailModal", () => {
/>,
);
expect(screen.getByText("HAI-001")).toBeTruthy();
expect(screen.getByText("HAI-002")).toBeTruthy();
expect(screen.getByText("KB-001")).toBeTruthy();
expect(screen.getByText("KB-002")).toBeTruthy();
expect(screen.queryByText("(no dependencies)")).toBeNull();
});
it("can add a dependency via the dropdown", async () => {
const { updateTask } = await import("../../api");
const allTasks: Task[] = [
{ id: "HAI-001", description: "Dep 1", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "", updatedAt: "" },
{ id: "HAI-099", description: "Self", column: "in-progress" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "", updatedAt: "" },
{ id: "KB-001", description: "Dep 1", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "", updatedAt: "" },
{ id: "KB-099", description: "Self", column: "in-progress" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "", updatedAt: "" },
];
render(
@@ -452,16 +452,16 @@ describe("TaskDetailModal", () => {
);
fireEvent.click(screen.getByText("Add Dependency"));
// Should show HAI-001 in the dropdown but not HAI-099 (self is excluded)
// Should show KB-001 in the dropdown but not KB-099 (self is excluded)
const dropdown = document.querySelector(".dep-dropdown")!;
expect(dropdown).toBeTruthy();
expect(dropdown.textContent).toContain("HAI-001");
expect(dropdown.textContent).toContain("KB-001");
expect(dropdown.querySelectorAll(".dep-dropdown-item")).toHaveLength(1);
fireEvent.click(screen.getByText("HAI-001"));
fireEvent.click(screen.getByText("KB-001"));
await waitFor(() => {
expect(updateTask).toHaveBeenCalledWith("HAI-099", { dependencies: ["HAI-001"] });
expect(updateTask).toHaveBeenCalledWith("KB-099", { dependencies: ["KB-001"] });
});
});
@@ -470,7 +470,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ dependencies: ["HAI-001", "HAI-002"] })}
task={makeTask({ dependencies: ["KB-001", "KB-002"] })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -480,10 +480,10 @@ describe("TaskDetailModal", () => {
);
const removeButtons = screen.getAllByTitle(/Remove dependency/);
fireEvent.click(removeButtons[0]); // Remove HAI-001
fireEvent.click(removeButtons[0]); // Remove KB-001
await waitFor(() => {
expect(updateTask).toHaveBeenCalledWith("HAI-099", { dependencies: ["HAI-002"] });
expect(updateTask).toHaveBeenCalledWith("KB-099", { dependencies: ["KB-002"] });
});
});