feat(KB-332): rename task prefix from KB to FN and branches from kb/ to fusion/
- Change default task prefix from KB to FN across all packages
- Rename branch naming pattern from kb/{id} to fusion/{id}
- Update all test assertions and fixtures to use new prefixes
- Add changeset for the breaking change
- Resolve merge conflicts in TaskCard.test.tsx touch gesture tests
This commit is contained in:
@@ -126,10 +126,10 @@ describe("kb pi extension", () => {
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
|
||||
expect(result.content[0].text).toContain("KB-001");
|
||||
expect(result.content[0].text).toContain("FN-001");
|
||||
expect(result.content[0].text).toContain("Fix the login button");
|
||||
expect(result.content[0].text).toContain("triage");
|
||||
expect(result.details.taskId).toBe("KB-001");
|
||||
expect(result.details.taskId).toBe("FN-001");
|
||||
expect(result.details.column).toBe("triage");
|
||||
});
|
||||
|
||||
@@ -145,14 +145,14 @@ describe("kb pi extension", () => {
|
||||
|
||||
const result = await tool.execute(
|
||||
"call-2",
|
||||
{ description: "Second task", depends: ["KB-001"] },
|
||||
{ description: "Second task", depends: ["FN-001"] },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
|
||||
expect(result.details.taskId).toBe("KB-002");
|
||||
expect(result.details.dependencies).toEqual(["KB-001"]);
|
||||
expect(result.details.taskId).toBe("FN-002");
|
||||
expect(result.details.dependencies).toEqual(["FN-001"]);
|
||||
expect(result.content[0].text).toContain("Dependencies: KB-001");
|
||||
});
|
||||
});
|
||||
@@ -199,8 +199,8 @@ describe("kb pi extension", () => {
|
||||
);
|
||||
|
||||
expect(result.content[0].text).toContain("Triage (2)");
|
||||
expect(result.content[0].text).toContain("KB-001");
|
||||
expect(result.content[0].text).toContain("KB-002");
|
||||
expect(result.content[0].text).toContain("FN-001");
|
||||
expect(result.content[0].text).toContain("FN-002");
|
||||
expect(result.details.count).toBe(2);
|
||||
});
|
||||
|
||||
@@ -223,7 +223,7 @@ describe("kb pi extension", () => {
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
expect(triageResult.content[0].text).toContain("Triage (1)");
|
||||
expect(triageResult.content[0].text).toContain("KB-001");
|
||||
expect(triageResult.content[0].text).toContain("FN-001");
|
||||
|
||||
const todoResult = await listTool.execute(
|
||||
"call-2",
|
||||
@@ -257,9 +257,9 @@ describe("kb pi extension", () => {
|
||||
);
|
||||
|
||||
expect(result.content[0].text).toContain("Triage (5)");
|
||||
expect(result.content[0].text).toContain("KB-001");
|
||||
expect(result.content[0].text).toContain("KB-002");
|
||||
expect(result.content[0].text).not.toContain("KB-003");
|
||||
expect(result.content[0].text).toContain("FN-001");
|
||||
expect(result.content[0].text).toContain("FN-002");
|
||||
expect(result.content[0].text).not.toContain("FN-003");
|
||||
expect(result.content[0].text).toContain("... and 3 more");
|
||||
});
|
||||
});
|
||||
@@ -278,17 +278,17 @@ describe("kb pi extension", () => {
|
||||
const showTool = api.tools.get("kb_task_show")!;
|
||||
const result = await showTool.execute(
|
||||
"call-1",
|
||||
{ id: "KB-001" },
|
||||
{ id: "FN-001" },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
|
||||
expect(result.content[0].text).toContain("KB-001");
|
||||
expect(result.content[0].text).toContain("FN-001");
|
||||
expect(result.content[0].text).toContain("Implement caching layer");
|
||||
expect(result.content[0].text).toContain("Triage");
|
||||
expect(result.details.task).toBeDefined();
|
||||
expect(result.details.task.id).toBe("KB-001");
|
||||
expect(result.details.task.id).toBe("FN-001");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -309,7 +309,7 @@ describe("kb pi extension", () => {
|
||||
const attachTool = api.tools.get("kb_task_attach")!;
|
||||
const result = await attachTool.execute(
|
||||
"call-1",
|
||||
{ id: "KB-001", path: "test.txt" },
|
||||
{ id: "FN-001", path: "test.txt" },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
@@ -338,7 +338,7 @@ describe("kb pi extension", () => {
|
||||
await expect(
|
||||
attachTool.execute(
|
||||
"call-1",
|
||||
{ id: "KB-001", path: "file.exe" },
|
||||
{ id: "FN-001", path: "file.exe" },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
@@ -361,7 +361,7 @@ describe("kb pi extension", () => {
|
||||
const pauseTool = api.tools.get("kb_task_pause")!;
|
||||
const pauseResult = await pauseTool.execute(
|
||||
"call-1",
|
||||
{ id: "KB-001" },
|
||||
{ id: "FN-001" },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
@@ -372,7 +372,7 @@ describe("kb pi extension", () => {
|
||||
const showTool = api.tools.get("kb_task_show")!;
|
||||
const show = await showTool.execute(
|
||||
"call-2",
|
||||
{ id: "KB-001" },
|
||||
{ id: "FN-001" },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
@@ -383,7 +383,7 @@ describe("kb pi extension", () => {
|
||||
const unpauseTool = api.tools.get("kb_task_unpause")!;
|
||||
const unpauseResult = await unpauseTool.execute(
|
||||
"call-3",
|
||||
{ id: "KB-001" },
|
||||
{ id: "FN-001" },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
|
||||
@@ -78,11 +78,11 @@ describe("runTaskPlan", () => {
|
||||
(TaskStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(() => ({
|
||||
init: vi.fn().mockResolvedValue(undefined),
|
||||
createTask: vi.fn().mockResolvedValue({
|
||||
id: "KB-042",
|
||||
id: "FN-042",
|
||||
title: "Test Task Title",
|
||||
description: "Test description",
|
||||
column: "triage",
|
||||
dependencies: ["KB-001"],
|
||||
dependencies: ["FN-001"],
|
||||
...overrides,
|
||||
}),
|
||||
}));
|
||||
@@ -370,11 +370,11 @@ describe("runTaskPlan", () => {
|
||||
|
||||
it("creates task after planning completes with --yes flag", async () => {
|
||||
const mockCreateTask = vi.fn().mockResolvedValue({
|
||||
id: "KB-042",
|
||||
id: "FN-042",
|
||||
title: "Planned Task",
|
||||
description: "A well-planned task",
|
||||
column: "triage",
|
||||
dependencies: ["KB-001"],
|
||||
dependencies: ["FN-001"],
|
||||
});
|
||||
|
||||
(TaskStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(() => ({
|
||||
@@ -398,7 +398,7 @@ describe("runTaskPlan", () => {
|
||||
title: "Planned Task",
|
||||
description: "A well-planned task",
|
||||
suggestedSize: "M",
|
||||
suggestedDependencies: ["KB-001"],
|
||||
suggestedDependencies: ["FN-001"],
|
||||
keyDeliverables: ["Code", "Tests"],
|
||||
},
|
||||
});
|
||||
@@ -419,7 +419,7 @@ describe("runTaskPlan", () => {
|
||||
title: "Planned Task",
|
||||
description: "A well-planned task",
|
||||
column: "triage",
|
||||
dependencies: ["KB-001"],
|
||||
dependencies: ["FN-001"],
|
||||
});
|
||||
|
||||
exitSpy.mockRestore();
|
||||
@@ -543,7 +543,7 @@ describe("runTaskPlan", () => {
|
||||
});
|
||||
|
||||
it("skips task creation when user declines confirmation", async () => {
|
||||
const mockCreateTask = vi.fn().mockResolvedValue({ id: "KB-042" });
|
||||
const mockCreateTask = vi.fn().mockResolvedValue({ id: "FN-042" });
|
||||
|
||||
(TaskStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(() => ({
|
||||
init: vi.fn().mockResolvedValue(undefined),
|
||||
@@ -824,7 +824,7 @@ describe("runTaskPlan", () => {
|
||||
title: "Complete Auth System",
|
||||
description: "Build a comprehensive authentication system with login, logout, and password reset functionality. Includes email verification and 2FA support.",
|
||||
suggestedSize: "L",
|
||||
suggestedDependencies: ["KB-001", "KB-002"],
|
||||
suggestedDependencies: ["FN-001", "FN-002"],
|
||||
keyDeliverables: [
|
||||
"User login with email/password",
|
||||
"Password reset via email",
|
||||
|
||||
@@ -58,13 +58,13 @@ describe("runTaskSteer", () => {
|
||||
it("adds steering comment with message argument", async () => {
|
||||
setupTaskStoreMock();
|
||||
mockAddSteeringComment.mockResolvedValueOnce({
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
title: "Test Task",
|
||||
});
|
||||
|
||||
await runTaskSteer("KB-001", "Focus on error handling");
|
||||
await runTaskSteer("FN-001", "Focus on error handling");
|
||||
|
||||
expect(mockAddSteeringComment).toHaveBeenCalledWith("KB-001", "Focus on error handling", "user");
|
||||
expect(mockAddSteeringComment).toHaveBeenCalledWith("FN-001", "Focus on error handling", "user");
|
||||
expect(mockConsoleLog).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Steering comment added to KB-001")
|
||||
);
|
||||
@@ -73,16 +73,16 @@ describe("runTaskSteer", () => {
|
||||
it("reads message from stdin when not provided as argument", async () => {
|
||||
setupTaskStoreMock();
|
||||
mockAddSteeringComment.mockResolvedValueOnce({
|
||||
id: "KB-002",
|
||||
id: "FN-002",
|
||||
title: "Another Task",
|
||||
});
|
||||
|
||||
mockQuestion.mockResolvedValueOnce("This is a steering comment from stdin");
|
||||
|
||||
await runTaskSteer("KB-002", undefined);
|
||||
await runTaskSteer("FN-002", undefined);
|
||||
|
||||
expect(mockQuestion).toHaveBeenCalledWith("Message: ");
|
||||
expect(mockAddSteeringComment).toHaveBeenCalledWith("KB-002", "This is a steering comment from stdin", "user");
|
||||
expect(mockAddSteeringComment).toHaveBeenCalledWith("FN-002", "This is a steering comment from stdin", "user");
|
||||
expect(mockClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -94,7 +94,7 @@ describe("runTaskSteer", () => {
|
||||
|
||||
const longMessage = "a".repeat(2001);
|
||||
|
||||
await expect(runTaskSteer("KB-003", longMessage)).rejects.toThrow();
|
||||
await expect(runTaskSteer("FN-003", longMessage)).rejects.toThrow();
|
||||
|
||||
expect(mockConsoleError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Message must be between 1 and 2000 characters")
|
||||
@@ -110,7 +110,7 @@ describe("runTaskSteer", () => {
|
||||
throw new Error(`Process.exit called with ${code}`);
|
||||
});
|
||||
|
||||
await expect(runTaskSteer("KB-004", "")).rejects.toThrow();
|
||||
await expect(runTaskSteer("FN-004", "")).rejects.toThrow();
|
||||
|
||||
expect(mockConsoleError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Message is required")
|
||||
@@ -126,7 +126,7 @@ describe("runTaskSteer", () => {
|
||||
throw new Error(`Process.exit called with ${code}`);
|
||||
});
|
||||
|
||||
await expect(runTaskSteer("KB-005", " ")).rejects.toThrow();
|
||||
await expect(runTaskSteer("FN-005", " ")).rejects.toThrow();
|
||||
|
||||
expect(mockConsoleError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Message is required")
|
||||
@@ -158,11 +158,11 @@ describe("runTaskSteer", () => {
|
||||
it("shows success output with preview for short messages", async () => {
|
||||
setupTaskStoreMock();
|
||||
mockAddSteeringComment.mockResolvedValueOnce({
|
||||
id: "KB-006",
|
||||
id: "FN-006",
|
||||
title: "Short Message Task",
|
||||
});
|
||||
|
||||
await runTaskSteer("KB-006", "Short comment");
|
||||
await runTaskSteer("FN-006", "Short comment");
|
||||
|
||||
expect(mockConsoleLog).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Short comment")
|
||||
@@ -172,12 +172,12 @@ describe("runTaskSteer", () => {
|
||||
it("truncates long messages in success preview", async () => {
|
||||
setupTaskStoreMock();
|
||||
mockAddSteeringComment.mockResolvedValueOnce({
|
||||
id: "KB-007",
|
||||
id: "FN-007",
|
||||
title: "Long Message Task",
|
||||
});
|
||||
|
||||
const longMessage = "a".repeat(100);
|
||||
await runTaskSteer("KB-007", longMessage);
|
||||
await runTaskSteer("FN-007", longMessage);
|
||||
|
||||
// Should show first 60 chars + ellipsis
|
||||
const expectedPreview = "a".repeat(60) + "…";
|
||||
@@ -189,38 +189,38 @@ describe("runTaskSteer", () => {
|
||||
it("trims whitespace from messages", async () => {
|
||||
setupTaskStoreMock();
|
||||
mockAddSteeringComment.mockResolvedValueOnce({
|
||||
id: "KB-008",
|
||||
id: "FN-008",
|
||||
title: "Trim Test Task",
|
||||
});
|
||||
|
||||
await runTaskSteer("KB-008", " Some message with whitespace ");
|
||||
await runTaskSteer("FN-008", " Some message with whitespace ");
|
||||
|
||||
expect(mockAddSteeringComment).toHaveBeenCalledWith("KB-008", "Some message with whitespace", "user");
|
||||
expect(mockAddSteeringComment).toHaveBeenCalledWith("FN-008", "Some message with whitespace", "user");
|
||||
});
|
||||
|
||||
it("accepts messages at boundary lengths (1 and 2000 chars)", async () => {
|
||||
setupTaskStoreMock();
|
||||
mockAddSteeringComment.mockResolvedValueOnce({
|
||||
id: "KB-009",
|
||||
id: "FN-009",
|
||||
title: "Boundary Test",
|
||||
});
|
||||
|
||||
// Test 1 character
|
||||
await runTaskSteer("KB-009", "x");
|
||||
expect(mockAddSteeringComment).toHaveBeenCalledWith("KB-009", "x", "user");
|
||||
await runTaskSteer("FN-009", "x");
|
||||
expect(mockAddSteeringComment).toHaveBeenCalledWith("FN-009", "x", "user");
|
||||
|
||||
// Reset mock for next test
|
||||
vi.clearAllMocks();
|
||||
setupTaskStoreMock();
|
||||
mockAddSteeringComment.mockResolvedValueOnce({
|
||||
id: "KB-010",
|
||||
id: "FN-010",
|
||||
title: "Boundary Test 2",
|
||||
});
|
||||
|
||||
// Test exactly 2000 characters
|
||||
const exact2000 = "b".repeat(2000);
|
||||
await runTaskSteer("KB-010", exact2000);
|
||||
expect(mockAddSteeringComment).toHaveBeenCalledWith("KB-010", exact2000, "user");
|
||||
await runTaskSteer("FN-010", exact2000);
|
||||
expect(mockAddSteeringComment).toHaveBeenCalledWith("FN-010", exact2000, "user");
|
||||
});
|
||||
|
||||
it("rethrows non-ENOENT errors", async () => {
|
||||
@@ -229,7 +229,7 @@ describe("runTaskSteer", () => {
|
||||
const error = new Error("Database error");
|
||||
mockAddSteeringComment.mockRejectedValueOnce(error);
|
||||
|
||||
await expect(runTaskSteer("KB-011", "Message")).rejects.toThrow("Database error");
|
||||
await expect(runTaskSteer("FN-011", "Message")).rejects.toThrow("Database error");
|
||||
});
|
||||
|
||||
it("treats empty string as validation error, not prompt trigger", async () => {
|
||||
@@ -239,7 +239,7 @@ describe("runTaskSteer", () => {
|
||||
});
|
||||
|
||||
// Empty string as argument is a validation error, not a prompt trigger
|
||||
await expect(runTaskSteer("KB-012", "")).rejects.toThrow();
|
||||
await expect(runTaskSteer("FN-012", "")).rejects.toThrow();
|
||||
|
||||
// Should NOT prompt, should error instead
|
||||
expect(mockQuestion).not.toHaveBeenCalled();
|
||||
|
||||
@@ -21,7 +21,7 @@ function makeMockStore() {
|
||||
pollIntervalMs: 60_000,
|
||||
}),
|
||||
listTasks: vi.fn().mockResolvedValue([]),
|
||||
getTask: vi.fn().mockResolvedValue({ id: "KB-TEST", column: "in-review", paused: false, description: "Test task", log: [] }),
|
||||
getTask: vi.fn().mockResolvedValue({ id: "FN-TEST", column: "in-review", paused: false, description: "Test task", log: [] }),
|
||||
moveTask: vi.fn().mockResolvedValue({}),
|
||||
updatePrInfo: vi.fn().mockResolvedValue({}),
|
||||
logEntry: vi.fn().mockResolvedValue(undefined),
|
||||
@@ -203,8 +203,8 @@ function resetGitHubMocks() {
|
||||
url: "https://github.com/owner/repo/pull/42",
|
||||
number: 42,
|
||||
status: "open",
|
||||
title: "KB-TEST",
|
||||
headBranch: "kb/kb-test",
|
||||
title: "FN-TEST",
|
||||
headBranch: "fusion/fn-test",
|
||||
baseBranch: "main",
|
||||
commentCount: 0,
|
||||
});
|
||||
@@ -213,8 +213,8 @@ function resetGitHubMocks() {
|
||||
url: "https://github.com/owner/repo/pull/42",
|
||||
number: 42,
|
||||
status: "open",
|
||||
title: "KB-TEST",
|
||||
headBranch: "kb/kb-test",
|
||||
title: "FN-TEST",
|
||||
headBranch: "fusion/fn-test",
|
||||
baseBranch: "main",
|
||||
commentCount: 0,
|
||||
},
|
||||
@@ -227,8 +227,8 @@ function resetGitHubMocks() {
|
||||
url: "https://github.com/owner/repo/pull/42",
|
||||
number: 42,
|
||||
status: "merged",
|
||||
title: "KB-TEST",
|
||||
headBranch: "kb/kb-test",
|
||||
title: "FN-TEST",
|
||||
headBranch: "fusion/fn-test",
|
||||
baseBranch: "main",
|
||||
commentCount: 0,
|
||||
});
|
||||
@@ -250,8 +250,8 @@ describe("PR merge helpers", () => {
|
||||
expect(getMergeStrategy({ mergeStrategy: "pull-request" })).toBe("pull-request");
|
||||
});
|
||||
|
||||
it("uses kb/{task-id-lower} branch naming for pull requests", () => {
|
||||
expect(getTaskBranchName("KB-093")).toBe("kb/kb-093");
|
||||
it("uses fusion/{task-id-lower} branch naming for pull requests", () => {
|
||||
expect(getTaskBranchName("FN-093")).toBe("fusion/fn-093");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -259,7 +259,7 @@ describe("processPullRequestMergeTask", () => {
|
||||
it("creates and links a PR when task.prInfo is missing", async () => {
|
||||
const store = makeMockStore();
|
||||
store.getTask.mockResolvedValue({
|
||||
id: "KB-093",
|
||||
id: "FN-093",
|
||||
title: "Add support for creating pull requests",
|
||||
description: "Implement PR automation",
|
||||
column: "in-review",
|
||||
@@ -268,7 +268,7 @@ describe("processPullRequestMergeTask", () => {
|
||||
log: [],
|
||||
});
|
||||
|
||||
const result = await processPullRequestMergeTask(store as any, "/repo", "KB-093", {
|
||||
const result = await processPullRequestMergeTask(store as any, "/repo", "FN-093", {
|
||||
findPrForBranch: mockFindPrForBranch,
|
||||
createPr: mockCreatePr,
|
||||
getPrMergeStatus: mockGetPrMergeStatus,
|
||||
@@ -276,17 +276,17 @@ describe("processPullRequestMergeTask", () => {
|
||||
} as any);
|
||||
|
||||
expect(result).toBe("waiting");
|
||||
expect(mockFindPrForBranch).toHaveBeenCalledWith({ head: "kb/kb-093", state: "all" });
|
||||
expect(mockFindPrForBranch).toHaveBeenCalledWith({ head: "fusion/fn-093", state: "all" });
|
||||
expect(mockCreatePr).toHaveBeenCalledWith({
|
||||
title: "KB-093: Add support for creating pull requests",
|
||||
title: "FN-093: Add support for creating pull requests",
|
||||
body: "Automated PR for KB-093.\n\nImplement PR automation",
|
||||
head: "kb/kb-093",
|
||||
head: "fusion/fn-093",
|
||||
});
|
||||
expect(store.updatePrInfo).toHaveBeenCalledWith(
|
||||
"KB-093",
|
||||
"FN-093",
|
||||
expect.objectContaining({ number: 42, status: "open" }),
|
||||
);
|
||||
expect(store.updateTask).toHaveBeenCalledWith("KB-093", { status: "awaiting-pr-checks" });
|
||||
expect(store.updateTask).toHaveBeenCalledWith("FN-093", { status: "awaiting-pr-checks" });
|
||||
});
|
||||
|
||||
it("links an existing PR instead of creating a duplicate", async () => {
|
||||
@@ -296,13 +296,13 @@ describe("processPullRequestMergeTask", () => {
|
||||
number: 7,
|
||||
status: "open" as const,
|
||||
title: "Existing PR",
|
||||
headBranch: "kb/kb-093",
|
||||
headBranch: "fusion/fn-093",
|
||||
baseBranch: "main",
|
||||
commentCount: 0,
|
||||
};
|
||||
mockFindPrForBranch.mockResolvedValue(existingPr);
|
||||
store.getTask.mockResolvedValue({
|
||||
id: "KB-093",
|
||||
id: "FN-093",
|
||||
title: "Task",
|
||||
description: "Description",
|
||||
column: "in-review",
|
||||
@@ -310,7 +310,7 @@ describe("processPullRequestMergeTask", () => {
|
||||
log: [],
|
||||
});
|
||||
|
||||
await processPullRequestMergeTask(store as any, "/repo", "KB-093", {
|
||||
await processPullRequestMergeTask(store as any, "/repo", "FN-093", {
|
||||
findPrForBranch: mockFindPrForBranch,
|
||||
createPr: mockCreatePr,
|
||||
getPrMergeStatus: mockGetPrMergeStatus,
|
||||
@@ -319,7 +319,7 @@ describe("processPullRequestMergeTask", () => {
|
||||
|
||||
expect(mockCreatePr).not.toHaveBeenCalled();
|
||||
expect(store.logEntry).toHaveBeenCalledWith(
|
||||
"KB-093",
|
||||
"FN-093",
|
||||
"Linked existing PR",
|
||||
"PR #7: https://github.com/owner/repo/pull/7",
|
||||
);
|
||||
@@ -328,7 +328,7 @@ describe("processPullRequestMergeTask", () => {
|
||||
it("merges a ready PR and finalizes task cleanup", async () => {
|
||||
const store = makeMockStore();
|
||||
store.getTask.mockResolvedValue({
|
||||
id: "KB-093",
|
||||
id: "FN-093",
|
||||
title: "Task",
|
||||
description: "Description",
|
||||
column: "in-review",
|
||||
@@ -339,7 +339,7 @@ describe("processPullRequestMergeTask", () => {
|
||||
number: 42,
|
||||
status: "open",
|
||||
title: "Task",
|
||||
headBranch: "kb/kb-093",
|
||||
headBranch: "fusion/fn-093",
|
||||
baseBranch: "main",
|
||||
commentCount: 0,
|
||||
},
|
||||
@@ -351,7 +351,7 @@ describe("processPullRequestMergeTask", () => {
|
||||
number: 42,
|
||||
status: "open",
|
||||
title: "Task",
|
||||
headBranch: "kb/kb-093",
|
||||
headBranch: "fusion/fn-093",
|
||||
baseBranch: "main",
|
||||
commentCount: 0,
|
||||
},
|
||||
@@ -361,7 +361,7 @@ describe("processPullRequestMergeTask", () => {
|
||||
blockingReasons: [],
|
||||
});
|
||||
|
||||
const result = await processPullRequestMergeTask(store as any, "/repo", "KB-093", {
|
||||
const result = await processPullRequestMergeTask(store as any, "/repo", "FN-093", {
|
||||
findPrForBranch: mockFindPrForBranch,
|
||||
createPr: mockCreatePr,
|
||||
getPrMergeStatus: mockGetPrMergeStatus,
|
||||
@@ -370,15 +370,15 @@ describe("processPullRequestMergeTask", () => {
|
||||
|
||||
expect(result).toBe("merged");
|
||||
expect(mockMergePr).toHaveBeenCalledWith({ number: 42, method: "squash" });
|
||||
expect(store.moveTask).toHaveBeenCalledWith("KB-093", "done");
|
||||
expect(store.moveTask).toHaveBeenCalledWith("FN-093", "done");
|
||||
expect(mockExecSync).toHaveBeenCalledWith('git worktree remove "/tmp/kb-093" --force', expect.any(Object));
|
||||
expect(mockExecSync).toHaveBeenCalledWith('git branch -d "kb/kb-093"', expect.any(Object));
|
||||
expect(mockExecSync).toHaveBeenCalledWith('git branch -d "fusion/fn-093"', expect.any(Object));
|
||||
});
|
||||
|
||||
it("does not merge when required checks or reviews are blocking", async () => {
|
||||
const store = makeMockStore();
|
||||
store.getTask.mockResolvedValue({
|
||||
id: "KB-093",
|
||||
id: "FN-093",
|
||||
title: "Task",
|
||||
description: "Description",
|
||||
column: "in-review",
|
||||
@@ -388,7 +388,7 @@ describe("processPullRequestMergeTask", () => {
|
||||
number: 42,
|
||||
status: "open",
|
||||
title: "Task",
|
||||
headBranch: "kb/kb-093",
|
||||
headBranch: "fusion/fn-093",
|
||||
baseBranch: "main",
|
||||
commentCount: 0,
|
||||
},
|
||||
@@ -400,7 +400,7 @@ describe("processPullRequestMergeTask", () => {
|
||||
number: 42,
|
||||
status: "open",
|
||||
title: "Task",
|
||||
headBranch: "kb/kb-093",
|
||||
headBranch: "fusion/fn-093",
|
||||
baseBranch: "main",
|
||||
commentCount: 0,
|
||||
},
|
||||
@@ -410,7 +410,7 @@ describe("processPullRequestMergeTask", () => {
|
||||
blockingReasons: ["changes requested review is active", "required checks not successful: ci (pending)"],
|
||||
});
|
||||
|
||||
const result = await processPullRequestMergeTask(store as any, "/repo", "KB-093", {
|
||||
const result = await processPullRequestMergeTask(store as any, "/repo", "FN-093", {
|
||||
findPrForBranch: mockFindPrForBranch,
|
||||
createPr: mockCreatePr,
|
||||
getPrMergeStatus: mockGetPrMergeStatus,
|
||||
@@ -420,7 +420,7 @@ describe("processPullRequestMergeTask", () => {
|
||||
expect(result).toBe("waiting");
|
||||
expect(mockMergePr).not.toHaveBeenCalled();
|
||||
expect(store.moveTask).not.toHaveBeenCalled();
|
||||
expect(store.updateTask).toHaveBeenCalledWith("KB-093", { status: "awaiting-pr-checks" });
|
||||
expect(store.updateTask).toHaveBeenCalledWith("FN-093", { status: "awaiting-pr-checks" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -441,10 +441,10 @@ describe("runDashboard — PR-first auto-merge queue", () => {
|
||||
globalPause: false,
|
||||
});
|
||||
mockStore.listTasks.mockResolvedValue([
|
||||
{ id: "KB-093", column: "in-review", paused: false },
|
||||
{ id: "FN-093", column: "in-review", paused: false },
|
||||
]);
|
||||
mockStore.getTask.mockResolvedValue({
|
||||
id: "KB-093",
|
||||
id: "FN-093",
|
||||
title: "Task",
|
||||
description: "Description",
|
||||
column: "in-review",
|
||||
@@ -463,9 +463,9 @@ describe("runDashboard — PR-first auto-merge queue", () => {
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
|
||||
expect(mockCreatePr).toHaveBeenCalledWith({
|
||||
title: "KB-093: Task",
|
||||
title: "FN-093: Task",
|
||||
body: "Automated PR for KB-093.\n\nDescription",
|
||||
head: "kb/kb-093",
|
||||
head: "fusion/fn-093",
|
||||
});
|
||||
expect(aiMergeTask).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -510,7 +510,7 @@ describe("runDashboard — WorktreePool wiring", () => {
|
||||
const serverOpts = createServerCall[1] as { onMerge: (taskId: string) => Promise<unknown> };
|
||||
|
||||
// Invoke the merge handler
|
||||
await serverOpts.onMerge("KB-TEST");
|
||||
await serverOpts.onMerge("FN-TEST");
|
||||
|
||||
expect(aiMergeTask).toHaveBeenCalled();
|
||||
const mergeCallOpts = (aiMergeTask as ReturnType<typeof vi.fn>).mock.calls[0][3];
|
||||
@@ -526,7 +526,7 @@ describe("runDashboard — WorktreePool wiring", () => {
|
||||
// Trigger merger via onMerge
|
||||
const createServerCall = (createServer as ReturnType<typeof vi.fn>).mock.calls[0];
|
||||
const serverOpts = createServerCall[1] as { onMerge: (taskId: string) => Promise<unknown> };
|
||||
await serverOpts.onMerge("KB-TEST");
|
||||
await serverOpts.onMerge("FN-TEST");
|
||||
|
||||
const executorPool = capturedExecutorOpts!.pool;
|
||||
const mergerPool = (aiMergeTask as ReturnType<typeof vi.fn>).mock.calls[0][3].pool;
|
||||
@@ -573,7 +573,7 @@ describe("runDashboard — auto-merge pause exclusion", () => {
|
||||
|
||||
// Emit task:moved with a paused task
|
||||
mockStore.emit("task:moved", {
|
||||
task: { id: "KB-PAUSED", column: "in-review", paused: true },
|
||||
task: { id: "FN-PAUSED", column: "in-review", paused: true },
|
||||
from: "in-progress",
|
||||
to: "in-review",
|
||||
});
|
||||
@@ -592,8 +592,8 @@ describe("runDashboard — auto-merge pause exclusion", () => {
|
||||
pollIntervalMs: 60_000,
|
||||
});
|
||||
mockStore.listTasks.mockResolvedValue([
|
||||
{ id: "KB-PAUSED", column: "in-review", paused: true },
|
||||
{ id: "KB-ACTIVE", column: "in-review", paused: false },
|
||||
{ id: "FN-PAUSED", column: "in-review", paused: true },
|
||||
{ id: "FN-ACTIVE", column: "in-review", paused: false },
|
||||
]);
|
||||
|
||||
const { aiMergeTask } = await import("@fusion/engine");
|
||||
@@ -611,7 +611,7 @@ describe("runDashboard — auto-merge pause exclusion", () => {
|
||||
const mergedIds = (aiMergeTask as ReturnType<typeof vi.fn>).mock.calls.map(
|
||||
(call: any[]) => call[2],
|
||||
);
|
||||
expect(mergedIds).not.toContain("KB-PAUSED");
|
||||
expect(mergedIds).not.toContain("FN-PAUSED");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -684,8 +684,8 @@ describe("runDashboard — immediate resume on unpause", () => {
|
||||
globalPause: false,
|
||||
});
|
||||
mockStore.listTasks.mockResolvedValue([
|
||||
{ id: "KB-MQ1", column: "in-review", paused: false },
|
||||
{ id: "KB-MQ2", column: "in-review", paused: false },
|
||||
{ id: "FN-MQ1", column: "in-review", paused: false },
|
||||
{ id: "FN-MQ2", column: "in-review", paused: false },
|
||||
]);
|
||||
// getTask is called inside drainMergeQueue to verify the task
|
||||
mockStore.getTask = vi.fn().mockImplementation(async (id: string) => ({
|
||||
@@ -716,8 +716,8 @@ describe("runDashboard — immediate resume on unpause", () => {
|
||||
const mergedIds = (aiMergeTask as ReturnType<typeof vi.fn>).mock.calls.map(
|
||||
(call: any[]) => call[2],
|
||||
);
|
||||
expect(mergedIds).toContain("KB-MQ1");
|
||||
expect(mergedIds).toContain("KB-MQ2");
|
||||
expect(mergedIds).toContain("FN-MQ1");
|
||||
expect(mergedIds).toContain("FN-MQ2");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -917,7 +917,7 @@ describe("runDashboard — enginePaused (soft pause)", () => {
|
||||
|
||||
// Emit task:moved
|
||||
mockStore.emit("task:moved", {
|
||||
task: { id: "KB-EP1", column: "in-review", paused: false },
|
||||
task: { id: "FN-EP1", column: "in-review", paused: false },
|
||||
from: "in-progress",
|
||||
to: "in-review",
|
||||
});
|
||||
@@ -963,7 +963,7 @@ describe("runDashboard — enginePaused (soft pause)", () => {
|
||||
globalPause: false,
|
||||
});
|
||||
mockStore.listTasks.mockResolvedValue([
|
||||
{ id: "KB-EP2", column: "in-review", paused: false },
|
||||
{ id: "FN-EP2", column: "in-review", paused: false },
|
||||
]);
|
||||
mockStore.getTask = vi.fn().mockImplementation(async (id: string) => ({
|
||||
id,
|
||||
@@ -990,7 +990,7 @@ describe("runDashboard — enginePaused (soft pause)", () => {
|
||||
const mergedIds = (aiMergeTask as ReturnType<typeof vi.fn>).mock.calls.map(
|
||||
(call: any[]) => call[2],
|
||||
);
|
||||
expect(mergedIds).toContain("KB-EP2");
|
||||
expect(mergedIds).toContain("FN-EP2");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1278,7 +1278,7 @@ describe("runDashboard — merge conflict retry logic", () => {
|
||||
});
|
||||
|
||||
mockStore.listTasks.mockResolvedValue([
|
||||
{ id: "KB-RETRY", column: "in-review", paused: false },
|
||||
{ id: "FN-RETRY", column: "in-review", paused: false },
|
||||
]);
|
||||
|
||||
await runDashboard(0, { open: false });
|
||||
@@ -1288,7 +1288,7 @@ describe("runDashboard — merge conflict retry logic", () => {
|
||||
|
||||
// Should have incremented mergeRetries
|
||||
expect(mockStore.updateTask).toHaveBeenCalledWith(
|
||||
"KB-RETRY",
|
||||
"FN-RETRY",
|
||||
expect.objectContaining({ mergeRetries: 1 }),
|
||||
);
|
||||
|
||||
@@ -1325,7 +1325,7 @@ describe("runDashboard — merge conflict retry logic", () => {
|
||||
}));
|
||||
|
||||
mockStore.listTasks.mockResolvedValue([
|
||||
{ id: "KB-MAX", column: "in-review", paused: false, mergeRetries: 3 },
|
||||
{ id: "FN-MAX", column: "in-review", paused: false, mergeRetries: 3 },
|
||||
]);
|
||||
|
||||
await runDashboard(0, { open: false });
|
||||
@@ -1341,7 +1341,7 @@ describe("runDashboard — merge conflict retry logic", () => {
|
||||
|
||||
// Should reset mergeRetries on the task
|
||||
expect(mockStore.updateTask).toHaveBeenCalledWith(
|
||||
"KB-MAX",
|
||||
"FN-MAX",
|
||||
expect.objectContaining({ status: null }),
|
||||
);
|
||||
});
|
||||
@@ -1364,7 +1364,7 @@ describe("runDashboard — merge conflict retry logic", () => {
|
||||
});
|
||||
|
||||
mockStore.listTasks.mockResolvedValue([
|
||||
{ id: "KB-NO-AUTO", column: "in-review", paused: false },
|
||||
{ id: "FN-NO-AUTO", column: "in-review", paused: false },
|
||||
]);
|
||||
|
||||
await runDashboard(0, { open: false });
|
||||
@@ -1404,7 +1404,7 @@ describe("runDashboard — merge conflict retry logic", () => {
|
||||
}));
|
||||
|
||||
mockStore.listTasks.mockResolvedValue([
|
||||
{ id: "KB-SUCCESS", column: "in-review", paused: false, mergeRetries: 2 },
|
||||
{ id: "FN-SUCCESS", column: "in-review", paused: false, mergeRetries: 2 },
|
||||
]);
|
||||
|
||||
await runDashboard(0, { open: false });
|
||||
@@ -1413,7 +1413,7 @@ describe("runDashboard — merge conflict retry logic", () => {
|
||||
|
||||
// Should clear mergeRetries on success
|
||||
expect(mockStore.updateTask).toHaveBeenCalledWith(
|
||||
"KB-SUCCESS",
|
||||
"FN-SUCCESS",
|
||||
expect.objectContaining({ mergeRetries: 0 }),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -71,7 +71,7 @@ export function getMergeStrategy(settings: Pick<Settings, "mergeStrategy">): Non
|
||||
}
|
||||
|
||||
export function getTaskBranchName(taskId: string): string {
|
||||
return `kb/${taskId.toLowerCase()}`;
|
||||
return `fusion/${taskId.toLowerCase()}`;
|
||||
}
|
||||
|
||||
function buildPullRequestTitle(task: Pick<TaskDetail, "id" | "title">): string {
|
||||
|
||||
@@ -58,7 +58,7 @@ import { GitHubClient } from "@fusion/dashboard";
|
||||
|
||||
function makeTask(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
description: "A short description",
|
||||
column: "triage",
|
||||
dependencies: [],
|
||||
@@ -91,16 +91,16 @@ describe("runTaskShow", () => {
|
||||
getTask: vi.fn().mockResolvedValue(task),
|
||||
}));
|
||||
|
||||
await runTaskShow("KB-001");
|
||||
await runTaskShow("FN-001");
|
||||
|
||||
const headerLine = logSpy.mock.calls.find(
|
||||
(call) => typeof call[0] === "string" && call[0].includes("KB-001:")
|
||||
(call) => typeof call[0] === "string" && call[0].includes("FN-001:")
|
||||
);
|
||||
expect(headerLine).toBeDefined();
|
||||
expect(headerLine![0]).toContain(longDesc);
|
||||
// Ensure no truncation happened
|
||||
expect(headerLine![0]).not.toContain(longDesc.slice(0, 60) + "…");
|
||||
expect(headerLine![0].length).toBeGreaterThan(60 + " KB-001: ".length);
|
||||
expect(headerLine![0].length).toBeGreaterThan(60 + " FN-001: ".length);
|
||||
});
|
||||
|
||||
it("displays the title when present instead of description", async () => {
|
||||
@@ -114,10 +114,10 @@ describe("runTaskShow", () => {
|
||||
getTask: vi.fn().mockResolvedValue(task),
|
||||
}));
|
||||
|
||||
await runTaskShow("KB-001");
|
||||
await runTaskShow("FN-001");
|
||||
|
||||
const headerLine = logSpy.mock.calls.find(
|
||||
(call) => typeof call[0] === "string" && call[0].includes("KB-001:")
|
||||
(call) => typeof call[0] === "string" && call[0].includes("FN-001:")
|
||||
);
|
||||
expect(headerLine).toBeDefined();
|
||||
expect(headerLine![0]).toContain("My Task Title");
|
||||
@@ -151,7 +151,7 @@ describe("runTaskCreate with --attach", () => {
|
||||
(TaskStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(() => ({
|
||||
init: vi.fn(),
|
||||
createTask: vi.fn().mockResolvedValue({
|
||||
id: "KB-002",
|
||||
id: "FN-002",
|
||||
description: "test task",
|
||||
column: "triage",
|
||||
dependencies: [],
|
||||
@@ -178,7 +178,7 @@ describe("runTaskCreate with --attach", () => {
|
||||
|
||||
expect(mockAddAttachment).toHaveBeenCalledOnce();
|
||||
expect(mockAddAttachment).toHaveBeenCalledWith(
|
||||
"KB-002",
|
||||
"FN-002",
|
||||
"test.png",
|
||||
expect.any(Buffer),
|
||||
"image/png",
|
||||
@@ -247,7 +247,7 @@ describe("runTaskCreate with --depends", () => {
|
||||
logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
||||
|
||||
mockCreateTask = vi.fn().mockImplementation((input: { description: string; dependencies?: string[] }) => ({
|
||||
id: "KB-003",
|
||||
id: "FN-003",
|
||||
description: input.description,
|
||||
column: "triage",
|
||||
dependencies: input.dependencies || [],
|
||||
@@ -269,28 +269,28 @@ describe("runTaskCreate with --depends", () => {
|
||||
});
|
||||
|
||||
it("passes dependencies to store.createTask when depends provided", async () => {
|
||||
await runTaskCreate("test task", undefined, ["KB-124"]);
|
||||
await runTaskCreate("test task", undefined, ["FN-124"]);
|
||||
|
||||
expect(mockCreateTask).toHaveBeenCalledWith({
|
||||
description: "test task",
|
||||
dependencies: ["KB-124"],
|
||||
dependencies: ["FN-124"],
|
||||
});
|
||||
});
|
||||
|
||||
it("passes multiple dependencies correctly", async () => {
|
||||
await runTaskCreate("test task", undefined, ["KB-124", "KB-100"]);
|
||||
await runTaskCreate("test task", undefined, ["FN-124", "FN-100"]);
|
||||
|
||||
expect(mockCreateTask).toHaveBeenCalledWith({
|
||||
description: "test task",
|
||||
dependencies: ["KB-124", "KB-100"],
|
||||
dependencies: ["FN-124", "FN-100"],
|
||||
});
|
||||
|
||||
const depsLine = logSpy.mock.calls.find(
|
||||
(call) => typeof call[0] === "string" && call[0].includes("Dependencies:"),
|
||||
);
|
||||
expect(depsLine).toBeDefined();
|
||||
expect(depsLine![0]).toContain("KB-124");
|
||||
expect(depsLine![0]).toContain("KB-100");
|
||||
expect(depsLine![0]).toContain("FN-124");
|
||||
expect(depsLine![0]).toContain("FN-100");
|
||||
});
|
||||
|
||||
it("works without dependencies (backward compatible)", async () => {
|
||||
@@ -417,7 +417,7 @@ describe("runTaskImportGitHubInteractive", () => {
|
||||
// Setup existing task with source URL
|
||||
mockListTasks.mockResolvedValueOnce([
|
||||
{
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
description: "Existing\n\nSource: https://github.com/owner/repo/issues/1",
|
||||
column: "triage",
|
||||
},
|
||||
@@ -764,7 +764,7 @@ describe("runTaskImportFromGitHub", () => {
|
||||
// Setup existing task with source URL
|
||||
mockListTasks.mockResolvedValueOnce([
|
||||
{
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
description: "Existing\n\nSource: https://github.com/owner/repo/issues/1",
|
||||
column: "triage",
|
||||
},
|
||||
@@ -877,7 +877,7 @@ describe("runTaskDuplicate", () => {
|
||||
errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
|
||||
mockDuplicateTask = vi.fn().mockResolvedValue({
|
||||
id: "KB-002",
|
||||
id: "FN-002",
|
||||
description: "Duplicated task",
|
||||
column: "triage",
|
||||
dependencies: [],
|
||||
@@ -899,17 +899,17 @@ describe("runTaskDuplicate", () => {
|
||||
});
|
||||
|
||||
it("duplicates task and prints success", async () => {
|
||||
await runTaskDuplicate("KB-001");
|
||||
await runTaskDuplicate("FN-001");
|
||||
|
||||
expect(mockDuplicateTask).toHaveBeenCalledOnce();
|
||||
expect(mockDuplicateTask).toHaveBeenCalledWith("KB-001");
|
||||
expect(mockDuplicateTask).toHaveBeenCalledWith("FN-001");
|
||||
|
||||
const successLine = logSpy.mock.calls.find(
|
||||
(call) => typeof call[0] === "string" && call[0].includes("✓ Duplicated"),
|
||||
);
|
||||
expect(successLine).toBeDefined();
|
||||
expect(successLine![0]).toContain("KB-001");
|
||||
expect(successLine![0]).toContain("KB-002");
|
||||
expect(successLine![0]).toContain("FN-001");
|
||||
expect(successLine![0]).toContain("FN-002");
|
||||
});
|
||||
|
||||
it("throws when task not found", async () => {
|
||||
@@ -941,10 +941,10 @@ describe("runTaskRefine", () => {
|
||||
});
|
||||
|
||||
mockRefineTask = vi.fn().mockResolvedValue({
|
||||
id: "KB-002",
|
||||
id: "FN-002",
|
||||
description: "Refinement of KB-001",
|
||||
column: "triage",
|
||||
dependencies: ["KB-001"],
|
||||
dependencies: ["FN-001"],
|
||||
steps: [],
|
||||
currentStep: 0,
|
||||
log: [],
|
||||
@@ -965,34 +965,34 @@ describe("runTaskRefine", () => {
|
||||
it("refines task with interactive feedback and prints success", async () => {
|
||||
mockRlQuestion.mockResolvedValue("Need to add more tests");
|
||||
|
||||
await runTaskRefine("KB-001");
|
||||
await runTaskRefine("FN-001");
|
||||
|
||||
expect(mockRlQuestion).toHaveBeenCalledWith("What needs to be refined? ");
|
||||
expect(mockRlClose).toHaveBeenCalled();
|
||||
expect(mockRefineTask).toHaveBeenCalledOnce();
|
||||
expect(mockRefineTask).toHaveBeenCalledWith("KB-001", "Need to add more tests");
|
||||
expect(mockRefineTask).toHaveBeenCalledWith("FN-001", "Need to add more tests");
|
||||
|
||||
const successLine = logSpy.mock.calls.find(
|
||||
(call) => typeof call[0] === "string" && call[0].includes("✓ Created refinement"),
|
||||
);
|
||||
expect(successLine).toBeDefined();
|
||||
expect(successLine![0]).toContain("KB-002");
|
||||
expect(successLine![0]).toContain("KB-001");
|
||||
expect(successLine![0]).toContain("FN-002");
|
||||
expect(successLine![0]).toContain("FN-001");
|
||||
|
||||
// Check that dependency is printed
|
||||
const depLine = logSpy.mock.calls.find(
|
||||
(call) => typeof call[0] === "string" && call[0].includes("Dependency:"),
|
||||
);
|
||||
expect(depLine).toBeDefined();
|
||||
expect(depLine![0]).toContain("KB-001");
|
||||
expect(depLine![0]).toContain("FN-001");
|
||||
});
|
||||
|
||||
it("refines task with provided feedback (non-interactive)", async () => {
|
||||
await runTaskRefine("KB-001", "Fix the error handling");
|
||||
await runTaskRefine("FN-001", "Fix the error handling");
|
||||
|
||||
expect(mockRlQuestion).not.toHaveBeenCalled();
|
||||
expect(mockRefineTask).toHaveBeenCalledOnce();
|
||||
expect(mockRefineTask).toHaveBeenCalledWith("KB-001", "Fix the error handling");
|
||||
expect(mockRefineTask).toHaveBeenCalledWith("FN-001", "Fix the error handling");
|
||||
});
|
||||
|
||||
it("exits when interactive feedback is empty", async () => {
|
||||
@@ -1000,7 +1000,7 @@ describe("runTaskRefine", () => {
|
||||
|
||||
const exitSpy = vi.spyOn(process, "exit").mockImplementation((() => {}) as (code?: number) => never);
|
||||
|
||||
await runTaskRefine("KB-001");
|
||||
await runTaskRefine("FN-001");
|
||||
|
||||
expect(mockRlClose).toHaveBeenCalled();
|
||||
expect(errorSpy).toHaveBeenCalledWith("Feedback is required");
|
||||
@@ -1012,7 +1012,7 @@ describe("runTaskRefine", () => {
|
||||
it("exits when provided feedback is empty", async () => {
|
||||
const exitSpy = vi.spyOn(process, "exit").mockImplementation((() => {}) as (code?: number) => never);
|
||||
|
||||
await runTaskRefine("KB-001", " ");
|
||||
await runTaskRefine("FN-001", " ");
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith("Feedback is required");
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
@@ -1023,7 +1023,7 @@ describe("runTaskRefine", () => {
|
||||
it("exits when feedback exceeds 2000 characters", async () => {
|
||||
const exitSpy = vi.spyOn(process, "exit").mockImplementation((() => {}) as (code?: number) => never);
|
||||
|
||||
await runTaskRefine("KB-001", "A".repeat(2001));
|
||||
await runTaskRefine("FN-001", "A".repeat(2001));
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith("Feedback must be 2000 characters or less");
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
@@ -1034,16 +1034,16 @@ describe("runTaskRefine", () => {
|
||||
it("allows feedback at exactly 2000 characters", async () => {
|
||||
const longFeedback = "A".repeat(2000);
|
||||
|
||||
await runTaskRefine("KB-001", longFeedback);
|
||||
await runTaskRefine("FN-001", longFeedback);
|
||||
|
||||
expect(mockRefineTask).toHaveBeenCalledOnce();
|
||||
expect(mockRefineTask).toHaveBeenCalledWith("KB-001", longFeedback);
|
||||
expect(mockRefineTask).toHaveBeenCalledWith("FN-001", longFeedback);
|
||||
});
|
||||
|
||||
it("throws when task not in done or in-review", async () => {
|
||||
mockRefineTask.mockRejectedValueOnce(new Error("Task must be in 'done' or 'in-review' column to refine"));
|
||||
|
||||
await expect(runTaskRefine("KB-001", "Some feedback")).rejects.toThrow("done' or 'in-review'");
|
||||
await expect(runTaskRefine("FN-001", "Some feedback")).rejects.toThrow("done' or 'in-review'");
|
||||
});
|
||||
|
||||
it("throws when task not found", async () => {
|
||||
@@ -1076,7 +1076,7 @@ describe("runTaskDelete", () => {
|
||||
});
|
||||
|
||||
mockGetTask = vi.fn().mockResolvedValue({
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
description: "Test task",
|
||||
column: "triage",
|
||||
dependencies: [],
|
||||
@@ -1088,7 +1088,7 @@ describe("runTaskDelete", () => {
|
||||
});
|
||||
|
||||
mockDeleteTask = vi.fn().mockResolvedValue({
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
description: "Test task",
|
||||
column: "triage",
|
||||
dependencies: [],
|
||||
@@ -1111,31 +1111,31 @@ describe("runTaskDelete", () => {
|
||||
});
|
||||
|
||||
it("deletes task successfully with force=true (no prompt)", async () => {
|
||||
await runTaskDelete("KB-001", true);
|
||||
await runTaskDelete("FN-001", true);
|
||||
|
||||
expect(mockGetTask).toHaveBeenCalledOnce();
|
||||
expect(mockGetTask).toHaveBeenCalledWith("KB-001");
|
||||
expect(mockGetTask).toHaveBeenCalledWith("FN-001");
|
||||
expect(mockRlQuestion).not.toHaveBeenCalled();
|
||||
expect(mockDeleteTask).toHaveBeenCalledOnce();
|
||||
expect(mockDeleteTask).toHaveBeenCalledWith("KB-001");
|
||||
expect(mockDeleteTask).toHaveBeenCalledWith("FN-001");
|
||||
|
||||
const successLine = logSpy.mock.calls.find(
|
||||
(call) => typeof call[0] === "string" && call[0].includes("✓ Deleted"),
|
||||
);
|
||||
expect(successLine).toBeDefined();
|
||||
expect(successLine![0]).toContain("KB-001");
|
||||
expect(successLine![0]).toContain("FN-001");
|
||||
});
|
||||
|
||||
it("deletes task after confirmation prompt with 'y'", async () => {
|
||||
mockRlQuestion.mockResolvedValue("y");
|
||||
|
||||
await runTaskDelete("KB-001", false);
|
||||
await runTaskDelete("FN-001", false);
|
||||
|
||||
expect(mockRlQuestion).toHaveBeenCalledOnce();
|
||||
expect(mockRlQuestion).toHaveBeenCalledWith("Are you sure you want to delete KB-001? [y/N] ");
|
||||
expect(mockRlClose).toHaveBeenCalled();
|
||||
expect(mockDeleteTask).toHaveBeenCalledOnce();
|
||||
expect(mockDeleteTask).toHaveBeenCalledWith("KB-001");
|
||||
expect(mockDeleteTask).toHaveBeenCalledWith("FN-001");
|
||||
|
||||
const successLine = logSpy.mock.calls.find(
|
||||
(call) => typeof call[0] === "string" && call[0].includes("✓ Deleted"),
|
||||
@@ -1146,7 +1146,7 @@ describe("runTaskDelete", () => {
|
||||
it("deletes task after confirmation prompt with 'yes'", async () => {
|
||||
mockRlQuestion.mockResolvedValue("yes");
|
||||
|
||||
await runTaskDelete("KB-001", false);
|
||||
await runTaskDelete("FN-001", false);
|
||||
|
||||
expect(mockDeleteTask).toHaveBeenCalledOnce();
|
||||
});
|
||||
@@ -1155,7 +1155,7 @@ describe("runTaskDelete", () => {
|
||||
mockRlQuestion.mockResolvedValue("n");
|
||||
const exitSpy = vi.spyOn(process, "exit").mockImplementation((() => {}) as (code?: number) => never);
|
||||
|
||||
await runTaskDelete("KB-001", false);
|
||||
await runTaskDelete("FN-001", false);
|
||||
|
||||
expect(mockRlQuestion).toHaveBeenCalledOnce();
|
||||
expect(mockRlClose).toHaveBeenCalled();
|
||||
@@ -1168,7 +1168,7 @@ describe("runTaskDelete", () => {
|
||||
mockRlQuestion.mockResolvedValue("");
|
||||
const exitSpy = vi.spyOn(process, "exit").mockImplementation((() => {}) as (code?: number) => never);
|
||||
|
||||
await runTaskDelete("KB-001", false);
|
||||
await runTaskDelete("FN-001", false);
|
||||
|
||||
expect(mockRlQuestion).toHaveBeenCalledOnce();
|
||||
expect(mockDeleteTask).not.toHaveBeenCalled();
|
||||
@@ -1193,9 +1193,9 @@ describe("runTaskDelete", () => {
|
||||
mockDeleteTask.mockRejectedValueOnce(new Error("Task has dependencies"));
|
||||
const exitSpy = vi.spyOn(process, "exit").mockImplementation((() => {}) as (code?: number) => never);
|
||||
|
||||
await runTaskDelete("KB-001", true);
|
||||
await runTaskDelete("FN-001", true);
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith("✗ Failed to delete KB-001: Task has dependencies");
|
||||
expect(errorSpy).toHaveBeenCalledWith("✗ Failed to delete FN-001: Task has dependencies");
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
|
||||
exitSpy.mockRestore();
|
||||
@@ -1223,10 +1223,10 @@ describe("runTaskComment", () => {
|
||||
addTaskComment: vi.fn().mockResolvedValue(makeTask({ comments: [{ id: "c1", text: "Hello", author: "alice", createdAt: new Date().toISOString() }] })),
|
||||
}));
|
||||
|
||||
await runTaskComment("KB-001", "Hello", "alice");
|
||||
await runTaskComment("FN-001", "Hello", "alice");
|
||||
|
||||
const store = (TaskStore as unknown as ReturnType<typeof vi.fn>).mock.results.at(-1)?.value;
|
||||
expect(store.addTaskComment).toHaveBeenCalledWith("KB-001", "Hello", "alice");
|
||||
expect(store.addTaskComment).toHaveBeenCalledWith("FN-001", "Hello", "alice");
|
||||
expect(logSpy).toHaveBeenCalledWith(" ✓ Comment added to KB-001");
|
||||
});
|
||||
|
||||
@@ -1236,9 +1236,9 @@ describe("runTaskComment", () => {
|
||||
getTask: vi.fn().mockResolvedValue(makeTask({ comments: [{ id: "c1", text: "Hello", author: "alice", createdAt: "2026-01-01T00:00:00.000Z" }] })),
|
||||
}));
|
||||
|
||||
await runTaskComments("KB-001");
|
||||
await runTaskComments("FN-001");
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith(" Comments for KB-001:");
|
||||
expect(logSpy).toHaveBeenCalledWith(" Comments for FN-001:");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1272,27 +1272,27 @@ describe("runTaskRetry", () => {
|
||||
|
||||
it("retries failed task successfully", async () => {
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
status: "failed",
|
||||
error: "Some error",
|
||||
column: "in-progress"
|
||||
}));
|
||||
mockUpdateTask.mockResolvedValueOnce(makeTask({ id: "KB-001", status: undefined, error: undefined }));
|
||||
mockMoveTask.mockResolvedValueOnce(makeTask({ id: "KB-001", column: "todo" }));
|
||||
mockLogEntry.mockResolvedValueOnce(makeTask({ id: "KB-001" }));
|
||||
mockUpdateTask.mockResolvedValueOnce(makeTask({ id: "FN-001", status: undefined, error: undefined }));
|
||||
mockMoveTask.mockResolvedValueOnce(makeTask({ id: "FN-001", column: "todo" }));
|
||||
mockLogEntry.mockResolvedValueOnce(makeTask({ id: "FN-001" }));
|
||||
|
||||
await runTaskRetry("KB-001");
|
||||
await runTaskRetry("FN-001");
|
||||
|
||||
expect(mockGetTask).toHaveBeenCalledWith("KB-001");
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("KB-001", { status: null, error: null });
|
||||
expect(mockMoveTask).toHaveBeenCalledWith("KB-001", "todo");
|
||||
expect(mockLogEntry).toHaveBeenCalledWith("KB-001", "Retry requested from CLI", "Task reset to todo for retry");
|
||||
expect(mockGetTask).toHaveBeenCalledWith("FN-001");
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("FN-001", { status: null, error: null });
|
||||
expect(mockMoveTask).toHaveBeenCalledWith("FN-001", "todo");
|
||||
expect(mockLogEntry).toHaveBeenCalledWith("FN-001", "Retry requested from CLI", "Task reset to todo for retry");
|
||||
|
||||
const successLine = logSpy.mock.calls.find(
|
||||
(call) => typeof call[0] === "string" && call[0].includes("✓ Retried"),
|
||||
);
|
||||
expect(successLine).toBeDefined();
|
||||
expect(successLine![0]).toContain("KB-001");
|
||||
expect(successLine![0]).toContain("FN-001");
|
||||
expect(successLine![0]).toContain("todo");
|
||||
});
|
||||
|
||||
@@ -1304,22 +1304,22 @@ describe("runTaskRetry", () => {
|
||||
|
||||
it("throws error when task is not failed", async () => {
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
status: undefined,
|
||||
column: "in-progress"
|
||||
}));
|
||||
|
||||
await expect(runTaskRetry("KB-001")).rejects.toThrow("Task KB-001 is not failed (status: none)");
|
||||
await expect(runTaskRetry("FN-001")).rejects.toThrow("Task KB-001 is not failed (status: none)");
|
||||
});
|
||||
|
||||
it("throws error with correct status when task has different status", async () => {
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
status: "paused",
|
||||
column: "in-progress"
|
||||
}));
|
||||
|
||||
await expect(runTaskRetry("KB-001")).rejects.toThrow("Task KB-001 is not failed (status: paused)");
|
||||
await expect(runTaskRetry("FN-001")).rejects.toThrow("Task KB-001 is not failed (status: paused)");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1340,7 +1340,7 @@ describe("runTaskLogs", () => {
|
||||
function makeAgentLogEntry(overrides: Record<string, unknown> = {}): import("@fusion/core").AgentLogEntry {
|
||||
return {
|
||||
timestamp: new Date().toISOString(),
|
||||
taskId: "KB-001",
|
||||
taskId: "FN-001",
|
||||
text: "Test message",
|
||||
type: "text" as const,
|
||||
...overrides,
|
||||
@@ -1389,7 +1389,7 @@ describe("runTaskLogs", () => {
|
||||
});
|
||||
|
||||
it("displays logs with various entry types", async () => {
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "KB-001" }));
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "FN-001" }));
|
||||
mockGetAgentLogs.mockResolvedValueOnce([
|
||||
makeAgentLogEntry({ type: "text", text: "Analyzing code" }),
|
||||
makeAgentLogEntry({ type: "thinking", text: "Let me think" }),
|
||||
@@ -1398,10 +1398,10 @@ describe("runTaskLogs", () => {
|
||||
makeAgentLogEntry({ type: "tool_error", text: "read", detail: "File not found" }),
|
||||
]);
|
||||
|
||||
await runTaskLogs("KB-001");
|
||||
await runTaskLogs("FN-001");
|
||||
|
||||
expect(mockGetTask).toHaveBeenCalledWith("KB-001");
|
||||
expect(mockGetAgentLogs).toHaveBeenCalledWith("KB-001");
|
||||
expect(mockGetTask).toHaveBeenCalledWith("FN-001");
|
||||
expect(mockGetAgentLogs).toHaveBeenCalledWith("FN-001");
|
||||
expect(logSpy).toHaveBeenCalledTimes(5);
|
||||
|
||||
// Check that each type is formatted
|
||||
@@ -1415,13 +1415,13 @@ describe("runTaskLogs", () => {
|
||||
});
|
||||
|
||||
it("displays agent role when present", async () => {
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "KB-001" }));
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "FN-001" }));
|
||||
mockGetAgentLogs.mockResolvedValueOnce([
|
||||
makeAgentLogEntry({ type: "text", text: "Starting execution", agent: "executor" }),
|
||||
makeAgentLogEntry({ type: "text", text: "Reviewing code", agent: "reviewer" }),
|
||||
]);
|
||||
|
||||
await runTaskLogs("KB-001");
|
||||
await runTaskLogs("FN-001");
|
||||
|
||||
const calls = logSpy.mock.calls.map((call) => call[0] as string);
|
||||
expect(calls[0]).toContain("[EXECUTOR]");
|
||||
@@ -1429,10 +1429,10 @@ describe("runTaskLogs", () => {
|
||||
});
|
||||
|
||||
it("shows 'no logs found' message when logs are empty", async () => {
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "KB-001" }));
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "FN-001" }));
|
||||
mockGetAgentLogs.mockResolvedValueOnce([]);
|
||||
|
||||
await runTaskLogs("KB-001");
|
||||
await runTaskLogs("FN-001");
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith("No agent logs found for KB-001");
|
||||
});
|
||||
@@ -1450,12 +1450,12 @@ describe("runTaskLogs", () => {
|
||||
});
|
||||
|
||||
it("respects --limit flag", async () => {
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "KB-001" }));
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "FN-001" }));
|
||||
mockGetAgentLogs.mockResolvedValueOnce(
|
||||
Array.from({ length: 200 }, (_, i) => makeAgentLogEntry({ text: `Line ${i + 1}` }))
|
||||
);
|
||||
|
||||
await runTaskLogs("KB-001", { limit: 50 });
|
||||
await runTaskLogs("FN-001", { limit: 50 });
|
||||
|
||||
// Should only show 50 entries (default is 100, we specified 50)
|
||||
expect(logSpy).toHaveBeenCalledTimes(50);
|
||||
@@ -1467,18 +1467,18 @@ describe("runTaskLogs", () => {
|
||||
});
|
||||
|
||||
it("enforces max limit of 1000", async () => {
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "KB-001" }));
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "FN-001" }));
|
||||
mockGetAgentLogs.mockResolvedValueOnce(
|
||||
Array.from({ length: 1500 }, (_, i) => makeAgentLogEntry({ text: `Line ${i + 1}` }))
|
||||
);
|
||||
|
||||
await runTaskLogs("KB-001", { limit: 2000 }); // Request 2000, should be capped at 1000
|
||||
await runTaskLogs("FN-001", { limit: 2000 }); // Request 2000, should be capped at 1000
|
||||
|
||||
expect(logSpy).toHaveBeenCalledTimes(1000);
|
||||
});
|
||||
|
||||
it("filters by --type flag", async () => {
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "KB-001" }));
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "FN-001" }));
|
||||
mockGetAgentLogs.mockResolvedValueOnce([
|
||||
makeAgentLogEntry({ type: "text", text: "Text 1" }),
|
||||
makeAgentLogEntry({ type: "tool", text: "tool1" }),
|
||||
@@ -1487,7 +1487,7 @@ describe("runTaskLogs", () => {
|
||||
makeAgentLogEntry({ type: "thinking", text: "Think 1" }),
|
||||
]);
|
||||
|
||||
await runTaskLogs("KB-001", { type: "text" });
|
||||
await runTaskLogs("FN-001", { type: "text" });
|
||||
|
||||
// Should only show 2 text entries
|
||||
expect(logSpy).toHaveBeenCalledTimes(2);
|
||||
@@ -1497,7 +1497,7 @@ describe("runTaskLogs", () => {
|
||||
});
|
||||
|
||||
it("filters by type with --limit combined", async () => {
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "KB-001" }));
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "FN-001" }));
|
||||
// Create 50 tool entries interspersed with text entries
|
||||
const entries: import("@fusion/core").AgentLogEntry[] = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
@@ -1508,7 +1508,7 @@ describe("runTaskLogs", () => {
|
||||
}
|
||||
mockGetAgentLogs.mockResolvedValueOnce(entries);
|
||||
|
||||
await runTaskLogs("KB-001", { type: "tool", limit: 10 });
|
||||
await runTaskLogs("FN-001", { type: "tool", limit: 10 });
|
||||
|
||||
// Should show 10 tool entries (the last 10 tool entries: #50, #52, #54, #56, #58, #60, #62, #64, #66, #68... wait, that's not right)
|
||||
// Actually it's #50, #52, #54, #56, #58, #60, #62, #64, #66, #68... no wait, tool entries are at indices 0, 2, 4...
|
||||
@@ -1518,13 +1518,13 @@ describe("runTaskLogs", () => {
|
||||
});
|
||||
|
||||
it("calls watchFile when --follow is set", async () => {
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "KB-001" }));
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "FN-001" }));
|
||||
mockGetAgentLogs.mockResolvedValueOnce([
|
||||
makeAgentLogEntry({ type: "text", text: "Existing log" }),
|
||||
]);
|
||||
|
||||
// Don't await - follow mode keeps the promise pending
|
||||
const logsPromise = runTaskLogs("KB-001", { follow: true });
|
||||
const logsPromise = runTaskLogs("FN-001", { follow: true });
|
||||
|
||||
// Give a tick for the async operations to start
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
@@ -1537,11 +1537,11 @@ describe("runTaskLogs", () => {
|
||||
});
|
||||
|
||||
it("calls unwatchFile in SIGINT handler", async () => {
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "KB-001" }));
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "FN-001" }));
|
||||
mockGetAgentLogs.mockResolvedValueOnce([]);
|
||||
|
||||
// Start follow mode
|
||||
runTaskLogs("KB-001", { follow: true });
|
||||
runTaskLogs("FN-001", { follow: true });
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
// Trigger SIGINT
|
||||
@@ -1551,11 +1551,11 @@ describe("runTaskLogs", () => {
|
||||
});
|
||||
|
||||
it("prints waiting message in follow mode when no log file exists", async () => {
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "KB-001" }));
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "FN-001" }));
|
||||
mockGetAgentLogs.mockResolvedValueOnce([]);
|
||||
mockExistsSync.mockReturnValue(false);
|
||||
|
||||
runTaskLogs("KB-001", { follow: true });
|
||||
runTaskLogs("FN-001", { follow: true });
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
const waitingMessage = logSpy.mock.calls.find(
|
||||
@@ -1568,7 +1568,7 @@ describe("runTaskLogs", () => {
|
||||
});
|
||||
|
||||
it("reads and prints new entries in follow mode", async () => {
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "KB-001" }));
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "FN-001" }));
|
||||
mockGetAgentLogs.mockResolvedValueOnce([
|
||||
makeAgentLogEntry({ type: "text", text: "Initial" }),
|
||||
]);
|
||||
@@ -1576,7 +1576,7 @@ describe("runTaskLogs", () => {
|
||||
// Mock file to exist with size 0 initially (no content read yet)
|
||||
mockStatSync.mockReturnValue({ size: 0 });
|
||||
|
||||
runTaskLogs("KB-001", { follow: true });
|
||||
runTaskLogs("FN-001", { follow: true });
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
// Get the watchFile callback
|
||||
@@ -1600,12 +1600,12 @@ describe("runTaskLogs", () => {
|
||||
});
|
||||
|
||||
it("applies type filter in follow mode", async () => {
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "KB-001" }));
|
||||
mockGetTask.mockResolvedValueOnce(makeTask({ id: "FN-001" }));
|
||||
mockGetAgentLogs.mockResolvedValueOnce([]);
|
||||
|
||||
mockStatSync.mockReturnValue({ size: 0 });
|
||||
|
||||
runTaskLogs("KB-001", { follow: true, type: "tool" });
|
||||
runTaskLogs("FN-001", { follow: true, type: "tool" });
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
const watchCallback = mockWatchFile.mock.calls[0][2] as () => void;
|
||||
@@ -1650,7 +1650,7 @@ describe("runTaskPrCreate", () => {
|
||||
|
||||
function makeInReviewTask(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
title: "Test Task Title",
|
||||
description: "Test task description for PR creation",
|
||||
column: "in-review",
|
||||
@@ -1671,7 +1671,7 @@ describe("runTaskPrCreate", () => {
|
||||
number: 42,
|
||||
status: "open" as const,
|
||||
title: "Test Task Title",
|
||||
headBranch: "kb/kb-001",
|
||||
headBranch: "fusion/fn-001",
|
||||
baseBranch: "main",
|
||||
commentCount: 0,
|
||||
...overrides,
|
||||
@@ -1725,26 +1725,26 @@ describe("runTaskPrCreate", () => {
|
||||
url: "https://github.com/owner/repo/pull/42",
|
||||
}));
|
||||
|
||||
await runTaskPrCreate("KB-001", {
|
||||
await runTaskPrCreate("FN-001", {
|
||||
title: "Custom PR Title",
|
||||
base: "develop",
|
||||
body: "PR description body"
|
||||
});
|
||||
|
||||
expect(mockGetTask).toHaveBeenCalledWith("KB-001");
|
||||
expect(mockGetTask).toHaveBeenCalledWith("FN-001");
|
||||
expect(mockCreatePr).toHaveBeenCalledWith({
|
||||
owner: "owner",
|
||||
repo: "repo",
|
||||
title: "Custom PR Title",
|
||||
body: "PR description body",
|
||||
head: "kb/kb-001",
|
||||
head: "fusion/fn-001",
|
||||
base: "develop",
|
||||
});
|
||||
expect(mockUpdatePrInfo).toHaveBeenCalledWith("KB-001", expect.objectContaining({
|
||||
expect(mockUpdatePrInfo).toHaveBeenCalledWith("FN-001", expect.objectContaining({
|
||||
number: 42,
|
||||
url: "https://github.com/owner/repo/pull/42",
|
||||
}));
|
||||
expect(mockLogEntry).toHaveBeenCalledWith("KB-001", "Created PR", "PR #42: https://github.com/owner/repo/pull/42");
|
||||
expect(mockLogEntry).toHaveBeenCalledWith("FN-001", "Created PR", "PR #42: https://github.com/owner/repo/pull/42");
|
||||
|
||||
const successLine = logSpy.mock.calls.find(
|
||||
(call) => typeof call[0] === "string" && call[0].includes("✓ Created PR")
|
||||
@@ -1757,11 +1757,11 @@ describe("runTaskPrCreate", () => {
|
||||
mockGetTask.mockResolvedValueOnce(task);
|
||||
mockCreatePr.mockResolvedValueOnce(makePrInfo({ title: "My Task Title" }));
|
||||
|
||||
await runTaskPrCreate("KB-001", {});
|
||||
await runTaskPrCreate("FN-001", {});
|
||||
|
||||
expect(mockCreatePr).toHaveBeenCalledWith(expect.objectContaining({
|
||||
title: "My Task Title",
|
||||
head: "kb/kb-001",
|
||||
head: "fusion/fn-001",
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -1770,7 +1770,7 @@ describe("runTaskPrCreate", () => {
|
||||
mockGetTask.mockResolvedValueOnce(task);
|
||||
mockCreatePr.mockResolvedValueOnce(makePrInfo());
|
||||
|
||||
await runTaskPrCreate("KB-001", {});
|
||||
await runTaskPrCreate("FN-001", {});
|
||||
|
||||
// Title should be first 50 chars of description, sentence-cased, with ellipsis if truncated
|
||||
expect(mockCreatePr).toHaveBeenCalledWith(expect.objectContaining({
|
||||
@@ -1802,7 +1802,7 @@ describe("runTaskPrCreate", () => {
|
||||
throw new Error("process.exit");
|
||||
}) as (code?: number) => never);
|
||||
|
||||
await expect(runTaskPrCreate("KB-001", {})).rejects.toThrow("process.exit");
|
||||
await expect(runTaskPrCreate("FN-001", {})).rejects.toThrow("process.exit");
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("must be in 'in-review' column"));
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
@@ -1816,7 +1816,7 @@ describe("runTaskPrCreate", () => {
|
||||
number: 10,
|
||||
status: "open",
|
||||
title: "Existing PR",
|
||||
headBranch: "kb/kb-001",
|
||||
headBranch: "fusion/fn-001",
|
||||
baseBranch: "main",
|
||||
commentCount: 0,
|
||||
},
|
||||
@@ -1827,7 +1827,7 @@ describe("runTaskPrCreate", () => {
|
||||
throw new Error("process.exit");
|
||||
}) as (code?: number) => never);
|
||||
|
||||
await expect(runTaskPrCreate("KB-001", {})).rejects.toThrow("process.exit");
|
||||
await expect(runTaskPrCreate("FN-001", {})).rejects.toThrow("process.exit");
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("already has PR #10"));
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
@@ -1845,7 +1845,7 @@ describe("runTaskPrCreate", () => {
|
||||
throw new Error("process.exit");
|
||||
}) as (code?: number) => never);
|
||||
|
||||
await expect(runTaskPrCreate("KB-001", {})).rejects.toThrow("process.exit");
|
||||
await expect(runTaskPrCreate("FN-001", {})).rejects.toThrow("process.exit");
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("Not authenticated with GitHub"));
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
@@ -1862,7 +1862,7 @@ describe("runTaskPrCreate", () => {
|
||||
|
||||
mockCreatePr.mockResolvedValueOnce(makePrInfo());
|
||||
|
||||
await runTaskPrCreate("KB-001", {});
|
||||
await runTaskPrCreate("FN-001", {});
|
||||
|
||||
expect(GitHubClient).toHaveBeenCalledWith("test-token");
|
||||
expect(mockUpdatePrInfo).toHaveBeenCalled();
|
||||
@@ -1879,7 +1879,7 @@ describe("runTaskPrCreate", () => {
|
||||
throw new Error("process.exit");
|
||||
}) as (code?: number) => never);
|
||||
|
||||
await expect(runTaskPrCreate("KB-001", {})).rejects.toThrow("process.exit");
|
||||
await expect(runTaskPrCreate("FN-001", {})).rejects.toThrow("process.exit");
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("Could not determine GitHub repository"));
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
@@ -1893,7 +1893,7 @@ describe("runTaskPrCreate", () => {
|
||||
mockGetTask.mockResolvedValueOnce(task);
|
||||
mockCreatePr.mockResolvedValueOnce(makePrInfo());
|
||||
|
||||
await runTaskPrCreate("KB-001", {});
|
||||
await runTaskPrCreate("FN-001", {});
|
||||
|
||||
expect(mockCreatePr).toHaveBeenCalledWith(expect.objectContaining({
|
||||
owner: "custom-owner",
|
||||
@@ -1911,7 +1911,7 @@ describe("runTaskPrCreate", () => {
|
||||
throw new Error("process.exit");
|
||||
}) as (code?: number) => never);
|
||||
|
||||
await expect(runTaskPrCreate("KB-001", {})).rejects.toThrow("process.exit");
|
||||
await expect(runTaskPrCreate("FN-001", {})).rejects.toThrow("process.exit");
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("GITHUB_REPOSITORY format is invalid"));
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
@@ -1921,15 +1921,15 @@ describe("runTaskPrCreate", () => {
|
||||
it("exits with error when PR already exists for branch", async () => {
|
||||
const task = makeInReviewTask();
|
||||
mockGetTask.mockResolvedValueOnce(task);
|
||||
mockCreatePr.mockRejectedValueOnce(new Error("A pull request already exists for owner/repo:kb/kb-001"));
|
||||
mockCreatePr.mockRejectedValueOnce(new Error("A pull request already exists for owner/repo:fusion/fn-001"));
|
||||
|
||||
const exitSpy = vi.spyOn(process, "exit").mockImplementation((() => {
|
||||
throw new Error("process.exit");
|
||||
}) as (code?: number) => never);
|
||||
|
||||
await expect(runTaskPrCreate("KB-001", {})).rejects.toThrow("process.exit");
|
||||
await expect(runTaskPrCreate("FN-001", {})).rejects.toThrow("process.exit");
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("already exists for owner/repo:kb/kb-001"));
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("already exists for owner/repo:fusion/fn-001"));
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
exitSpy.mockRestore();
|
||||
});
|
||||
@@ -1943,7 +1943,7 @@ describe("runTaskPrCreate", () => {
|
||||
throw new Error("process.exit");
|
||||
}) as (code?: number) => never);
|
||||
|
||||
await expect(runTaskPrCreate("KB-001", {})).rejects.toThrow("process.exit");
|
||||
await expect(runTaskPrCreate("FN-001", {})).rejects.toThrow("process.exit");
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("No commits between"));
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
@@ -1959,7 +1959,7 @@ describe("runTaskPrCreate", () => {
|
||||
throw new Error("process.exit");
|
||||
}) as (code?: number) => never);
|
||||
|
||||
await expect(runTaskPrCreate("KB-001", {})).rejects.toThrow("process.exit");
|
||||
await expect(runTaskPrCreate("FN-001", {})).rejects.toThrow("process.exit");
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("Network error"));
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
|
||||
@@ -1056,7 +1056,7 @@ export async function runTaskPrCreate(id: string, options: PrCreateOptions = {})
|
||||
}
|
||||
|
||||
// Build branch name
|
||||
const branchName = `kb/${id.toLowerCase()}`;
|
||||
const branchName = `fusion/${id.toLowerCase()}`;
|
||||
|
||||
// Build PR title
|
||||
let title: string;
|
||||
|
||||
Reference in New Issue
Block a user