Add safe task dependency mutation
This commit is contained in:
@@ -14,6 +14,7 @@ const commandMocks = vi.hoisted(() => ({
|
||||
runTaskMove: vi.fn(),
|
||||
runTaskMerge: vi.fn(),
|
||||
runTaskUpdate: vi.fn(),
|
||||
runTaskDeps: vi.fn(),
|
||||
runTaskLog: vi.fn(),
|
||||
runTaskLogs: vi.fn(),
|
||||
runTaskShow: vi.fn(),
|
||||
@@ -122,6 +123,7 @@ vi.mock("../commands/task.js", () => ({
|
||||
runTaskMove: commandMocks.runTaskMove,
|
||||
runTaskMerge: commandMocks.runTaskMerge,
|
||||
runTaskUpdate: commandMocks.runTaskUpdate,
|
||||
runTaskDeps: commandMocks.runTaskDeps,
|
||||
runTaskLog: commandMocks.runTaskLog,
|
||||
runTaskLogs: commandMocks.runTaskLogs,
|
||||
runTaskShow: commandMocks.runTaskShow,
|
||||
@@ -401,6 +403,32 @@ describe("bin command routing and fallbacks", () => {
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it("routes task dependency replacement", async () => {
|
||||
await runBin(["task", "deps", "replace", "FN-155", "FN-191", "FN-195", "--project", "atlas-notes"]);
|
||||
expect(commandMocks.runTaskDeps).toHaveBeenCalledWith(
|
||||
"replace",
|
||||
"FN-155",
|
||||
["FN-191", "FN-195"],
|
||||
"atlas-notes",
|
||||
);
|
||||
});
|
||||
|
||||
it("routes task dependency add", async () => {
|
||||
await runBin(["task", "deps", "add", "FN-155", "FN-191", "--project", "atlas-notes"]);
|
||||
expect(commandMocks.runTaskDeps).toHaveBeenCalledWith(
|
||||
"add",
|
||||
"FN-155",
|
||||
["FN-191"],
|
||||
"atlas-notes",
|
||||
);
|
||||
});
|
||||
|
||||
it("errors for task deps missing operation", async () => {
|
||||
await expect(runBin(["task", "deps"])).rejects.toThrow("process.exit:1");
|
||||
expect(errorSpy).toHaveBeenCalledWith("Usage: fn task deps add <id> <dependency>");
|
||||
});
|
||||
|
||||
it("errors for task move missing arguments", async () => {
|
||||
await expect(runBin(["task", "move"])).rejects.toThrow("process.exit:1");
|
||||
expect(errorSpy).toHaveBeenCalledWith("Usage: fn task move <id> <column>");
|
||||
|
||||
@@ -119,7 +119,7 @@ async function loadCommandHandlers() {
|
||||
const { runServe } = await import("./commands/serve.js");
|
||||
const { runDaemon } = await import("./commands/daemon.js");
|
||||
const { runDesktop } = await import("./commands/desktop.js");
|
||||
const { runTaskCreate, runTaskList, runTaskMove, runTaskMerge, runTaskUpdate, runTaskLog, runTaskLogs, runTaskShow, runTaskAttach, runTaskPause, runTaskUnpause, runTaskImportFromGitHub, runTaskDuplicate, runTaskArchive, runTaskUnarchive, runTaskRefine, runTaskPlan, runTaskDelete, runTaskRetry, runTaskComment, runTaskComments, runTaskSteer, runTaskSetNode, runTaskClearNode, runTaskPrCreate } = await import("./commands/task.js");
|
||||
const { runTaskCreate, runTaskList, runTaskMove, runTaskMerge, runTaskUpdate, runTaskDeps, runTaskLog, runTaskLogs, runTaskShow, runTaskAttach, runTaskPause, runTaskUnpause, runTaskImportFromGitHub, runTaskDuplicate, runTaskArchive, runTaskUnarchive, runTaskRefine, runTaskPlan, runTaskDelete, runTaskRetry, runTaskComment, runTaskComments, runTaskSteer, runTaskSetNode, runTaskClearNode, runTaskPrCreate } = await import("./commands/task.js");
|
||||
const { runSettingsShow, runSettingsSet } = await import("./commands/settings.js");
|
||||
const { runSettingsExport } = await import("./commands/settings-export.js");
|
||||
const { runSettingsImport } = await import("./commands/settings-import.js");
|
||||
@@ -153,6 +153,7 @@ async function loadCommandHandlers() {
|
||||
runTaskMove,
|
||||
runTaskMerge,
|
||||
runTaskUpdate,
|
||||
runTaskDeps,
|
||||
runTaskLog,
|
||||
runTaskLogs,
|
||||
runTaskShow,
|
||||
@@ -274,6 +275,7 @@ Usage:
|
||||
Show task agent execution logs
|
||||
fn task move <id> <col> Move a task to a column
|
||||
fn task update <id> <step> <status> Update step status (pending|in-progress|done|skipped)
|
||||
fn task deps <op> <id> ... Add/remove/replace/set task dependencies
|
||||
fn task log <id> <message> Add a log entry
|
||||
fn task merge <id> Merge an in-review task and close it
|
||||
fn task duplicate <id> Duplicate a task (creates copy in triage)
|
||||
@@ -562,6 +564,7 @@ async function main() {
|
||||
runTaskMove,
|
||||
runTaskMerge,
|
||||
runTaskUpdate,
|
||||
runTaskDeps,
|
||||
runTaskLog,
|
||||
runTaskLogs,
|
||||
runTaskShow,
|
||||
@@ -1088,6 +1091,20 @@ async function main() {
|
||||
await runTaskUpdate(id, step, status, projectName);
|
||||
break;
|
||||
}
|
||||
case "deps": {
|
||||
const operation = args[2];
|
||||
const id = args[3];
|
||||
const dependencyArgs = args.slice(4);
|
||||
if (!operation || !id || !["add", "remove", "replace", "set"].includes(operation)) {
|
||||
console.error("Usage: fn task deps add <id> <dependency>");
|
||||
console.error(" fn task deps remove <id> <dependency>");
|
||||
console.error(" fn task deps replace <id> <old> <new>");
|
||||
console.error(" fn task deps set <id> [dependency ...]");
|
||||
process.exit(1);
|
||||
}
|
||||
await runTaskDeps(operation as "add" | "remove" | "replace" | "set", id, dependencyArgs, projectName);
|
||||
break;
|
||||
}
|
||||
case "log": {
|
||||
const id = args[2], message = args.slice(3).join(" ");
|
||||
if (!id || !message) { console.error("Usage: fn task log <id> <message>"); process.exit(1); }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TaskStore, COLUMNS, COLUMN_LABELS, CentralCore, buildManualRetryResetPatch, extractIntentSignature, findNearDuplicates, getTaskDuplicateLineage, reconcileDeterministicDuplicate, runDeterministicDuplicateGuard, type Settings, type Column, type StepStatus, type AgentLogType, type AgentLogEntry, type IntentSignature, type NearDuplicateCandidate, type NearDuplicateMatch } from "@fusion/core";
|
||||
import { TaskStore, COLUMNS, COLUMN_LABELS, CentralCore, buildManualRetryResetPatch, extractIntentSignature, findNearDuplicates, getTaskDuplicateLineage, reconcileDeterministicDuplicate, runDeterministicDuplicateGuard, type Settings, type Column, type StepStatus, type AgentLogType, type AgentLogEntry, type IntentSignature, type NearDuplicateCandidate, type NearDuplicateMatch, type TaskDependencyMutation } from "@fusion/core";
|
||||
import { aiMergeTask } from "@fusion/engine";
|
||||
import { createInterface } from "node:readline/promises";
|
||||
import type { PlanningQuestion, PlanningSummary } from "@fusion/core";
|
||||
@@ -519,6 +519,43 @@ export async function runTaskUpdate(id: string, stepStr: string, status: string,
|
||||
console.log();
|
||||
}
|
||||
|
||||
|
||||
export async function runTaskDeps(
|
||||
operation: "add" | "remove" | "replace" | "set",
|
||||
id: string,
|
||||
dependencyArgs: string[],
|
||||
projectName?: string,
|
||||
) {
|
||||
const store = await getStore(projectName);
|
||||
let mutation: TaskDependencyMutation;
|
||||
switch (operation) {
|
||||
case "add":
|
||||
if (dependencyArgs.length !== 1) throw new Error("Usage: fn task deps add <task> <dependency>");
|
||||
mutation = { operation, dependency: dependencyArgs[0] };
|
||||
break;
|
||||
case "remove":
|
||||
if (dependencyArgs.length !== 1) throw new Error("Usage: fn task deps remove <task> <dependency>");
|
||||
mutation = { operation, dependency: dependencyArgs[0] };
|
||||
break;
|
||||
case "replace":
|
||||
if (dependencyArgs.length !== 2) throw new Error("Usage: fn task deps replace <task> <old> <new>");
|
||||
mutation = { operation, from: dependencyArgs[0], to: dependencyArgs[1] };
|
||||
break;
|
||||
case "set":
|
||||
mutation = { operation, dependencies: dependencyArgs };
|
||||
break;
|
||||
}
|
||||
const task = await store.updateTaskDependencies(id, mutation);
|
||||
console.log();
|
||||
console.log(` ✓ ${task.id}: dependencies → ${task.dependencies.length ? task.dependencies.join(", ") : "none"}`);
|
||||
if (task.blockedBy) {
|
||||
console.log(` Blocked by: ${task.blockedBy}`);
|
||||
} else {
|
||||
console.log(" Blocked by: none");
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
export async function runTaskLog(id: string, message: string, outcome?: string, projectName?: string) {
|
||||
const store = await getStore(projectName);
|
||||
await store.logEntry(id, message, outcome);
|
||||
@@ -601,6 +638,7 @@ function filterEntries(entries: AgentLogEntry[], options: LogsOptions): AgentLog
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
export async function runTaskLogs(id: string, options: LogsOptions = {}, projectName?: string) {
|
||||
const projectContext = await getProjectContext(projectName);
|
||||
const store = projectContext?.store ?? await getStore(projectName);
|
||||
|
||||
Reference in New Issue
Block a user