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:
5
.changeset/add-depends-flag.md
Normal file
5
.changeset/add-depends-flag.md
Normal 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.
|
||||
@@ -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":
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user