feat(KB-016): add task duplicate command

- Add duplicateTask() method to core store with fresh state reset
- Add CLI command handler and register duplicate subcommand
- Add POST /tasks/:id/duplicate API endpoint to dashboard
- Add comprehensive tests for store, CLI, and API routes
- Add pi extension tool for task duplication
- Add changeset for minor release bump
This commit is contained in:
gsxdsm
2026-03-29 18:22:02 -07:00
parent 64fcb5d1b1
commit f3097c7023
11 changed files with 419 additions and 2 deletions

View File

@@ -213,6 +213,51 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
return task;
}
/**
* Duplicate an existing task, creating a fresh copy in triage.
* Copies title and description with source reference, but resets all
* execution state. The new task will be re-specified by the AI.
*/
async duplicateTask(id: string): Promise<Task> {
// Read the source task with its prompt
const sourceTask = await this.getTask(id);
// Allocate a new ID
const newId = await this.allocateId();
const now = new Date().toISOString();
// Create new task with copied title/description, but fresh state
const newTask: Task = {
id: newId,
title: sourceTask.title,
description: `${sourceTask.description}\n\n(Duplicated from ${id})`,
column: "triage",
dependencies: [], // Fresh task should have no dependencies
steps: [], // Reset execution state
currentStep: 0,
log: [{ timestamp: now, action: `Duplicated from ${id}` }],
columnMovedAt: now,
createdAt: now,
updatedAt: now,
// Explicitly NOT copied: worktree, status, blockedBy, paused, baseBranch,
// attachments, steeringComments, prInfo, agent logs, size, reviewLevel
};
const newDir = this.taskDir(newId);
await mkdir(newDir, { recursive: true });
await this.atomicWriteTaskJson(newDir, newTask);
// Copy source PROMPT.md content (the AI will re-specify it in triage)
const sourcePrompt = sourceTask.prompt;
await writeFile(join(newDir, "PROMPT.md"), sourcePrompt);
// Update cache if watcher is active
if (this.watcher) this.taskCache.set(newId, { ...newTask });
this.emit("task:created", newTask);
return newTask;
}
/**
* Read a task's JSON and prompt content.
*