feat(KB-127): add --depends flag to task create CLI command

- Add --depends flag to task create command in bin.ts and task.ts
- Support setting task dependencies at creation time via CLI
- Add tests for --depends flag including single and multiple dependencies
- Include changeset for the new CLI feature
This commit is contained in:
Dustin Byrne
2026-03-27 01:53:30 -04:00
parent 1f78c75ccb
commit f3c7f7db89
4 changed files with 81 additions and 4 deletions

View File

@@ -0,0 +1,5 @@
---
"@dustinbyrne/kb": patch
---
CLI `task create` now supports a `--depends <id>` flag (repeatable) to declare task dependencies at creation time.

View File

@@ -46,7 +46,7 @@ kb — AI-orchestrated task board
Usage:
kb dashboard Start the board web UI
kb task create [desc] [--attach f] Create a new task (goes to triage)
kb task create [desc] [opts] Create a new task (goes to triage)
kb task list List all tasks
kb task show <id> Show task details, steps, log
kb task move <id> <col> Move a task to a column
@@ -60,6 +60,7 @@ Usage:
Options:
--port, -p <port> Dashboard port (default: 4040)
--attach <file> Attach file(s) on task create (repeatable)
--depends <id> Declare dependency on task create (repeatable)
--help, -h Show this help
Columns: triage, todo, in-progress, in-review, done
@@ -97,17 +98,21 @@ async function main() {
case "create": {
const createArgs = args.slice(2);
const attachFiles: string[] = [];
const dependsIds: string[] = [];
const descParts: string[] = [];
for (let i = 0; i < createArgs.length; i++) {
if (createArgs[i] === "--attach" && i + 1 < createArgs.length) {
attachFiles.push(createArgs[i + 1]);
i++; // skip the value
} else if (createArgs[i] === "--depends" && i + 1 < createArgs.length) {
dependsIds.push(createArgs[i + 1]);
i++; // skip the value
} else {
descParts.push(createArgs[i]);
}
}
const title = descParts.join(" ");
await runTaskCreate(title || undefined, attachFiles.length > 0 ? attachFiles : undefined);
await runTaskCreate(title || undefined, attachFiles.length > 0 ? attachFiles : undefined, dependsIds.length > 0 ? dependsIds : undefined);
break;
}
case "list":

View File

@@ -206,3 +206,67 @@ describe("runTaskCreate with --attach", () => {
expect(mockAddAttachment).not.toHaveBeenCalled();
});
});
describe("runTaskCreate with --depends", () => {
let logSpy: ReturnType<typeof vi.spyOn>;
let mockCreateTask: ReturnType<typeof vi.fn>;
beforeEach(() => {
logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
mockCreateTask = vi.fn().mockImplementation((input: { description: string; dependencies?: string[] }) => ({
id: "KB-003",
description: input.description,
column: "triage",
dependencies: input.dependencies || [],
steps: [],
currentStep: 0,
log: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}));
(TaskStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(() => ({
init: vi.fn(),
createTask: mockCreateTask,
}));
});
afterEach(() => {
vi.restoreAllMocks();
});
it("passes dependencies to store.createTask when depends provided", async () => {
await runTaskCreate("test task", undefined, ["KB-124"]);
expect(mockCreateTask).toHaveBeenCalledWith({
description: "test task",
dependencies: ["KB-124"],
});
});
it("passes multiple dependencies correctly", async () => {
await runTaskCreate("test task", undefined, ["KB-124", "KB-100"]);
expect(mockCreateTask).toHaveBeenCalledWith({
description: "test task",
dependencies: ["KB-124", "KB-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");
});
it("works without dependencies (backward compatible)", async () => {
await runTaskCreate("test task");
expect(mockCreateTask).toHaveBeenCalledWith({
description: "test task",
dependencies: undefined,
});
});
});

View File

@@ -10,7 +10,7 @@ async function getStore(): Promise<TaskStore> {
return store;
}
export async function runTaskCreate(descriptionArg?: string, attachFiles?: string[]) {
export async function runTaskCreate(descriptionArg?: string, attachFiles?: string[], depends?: string[]) {
let description = descriptionArg;
if (!description) {
@@ -25,7 +25,7 @@ export async function runTaskCreate(descriptionArg?: string, attachFiles?: strin
}
const store = await getStore();
const task = await store.createTask({ description: description.trim() });
const task = await store.createTask({ description: description.trim(), dependencies: depends });
const label = task.description.length > 60
? task.description.slice(0, 60) + "…"
@@ -34,6 +34,9 @@ export async function runTaskCreate(descriptionArg?: string, attachFiles?: strin
console.log();
console.log(` ✓ Created ${task.id}: ${label}`);
console.log(` Column: triage`);
if (task.dependencies.length > 0) {
console.log(` Dependencies: ${task.dependencies.join(", ")}`);
}
console.log(` Path: .kb/tasks/${task.id}/`);
if (attachFiles && attachFiles.length > 0) {