Skip to content

Commit ef2a682

Browse files
committed
refactor(api): split task done/undone into separate operations
Create markTaskDoneOp and markTaskUndoneOp as distinct operations. CLI task done/undone commands now derive descriptions from their respective operations. Zero hardcoded descriptions remain outside parent grouping labels.
1 parent a3bece5 commit ef2a682

4 files changed

Lines changed: 70 additions & 9 deletions

File tree

src/cli/commands/task.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import type { CommandDef } from "../define-command.js";
33
import { loadDocument, saveDocument } from "../../io.js";
44
import {
55
addPlanTaskOp,
6-
updatePlanTaskOp,
6+
markTaskDoneOp,
7+
markTaskUndoneOp,
78
taskListOp,
89
} from "../../operations/index.js";
910

@@ -107,8 +108,8 @@ const doneOpts = z.object({});
107108

108109
const doneSubcommand: CommandDef<typeof doneArgs, typeof doneOpts> = {
109110
name: "done",
110-
// TODO: derive description from updatePlanTaskOp (conditional on done: true)
111-
description: "Mark a task as done",
111+
description: markTaskDoneOp.def.description,
112+
apiLink: markTaskDoneOp.def.name,
112113
args: doneArgs,
113114
opts: doneOpts,
114115
action(args) {
@@ -121,11 +122,10 @@ const doneSubcommand: CommandDef<typeof doneArgs, typeof doneOpts> = {
121122
}
122123

123124
try {
124-
const newDoc = updatePlanTaskOp({
125+
const newDoc = markTaskDoneOp({
125126
doc,
126127
changeId: args.changeId,
127128
taskIndex,
128-
done: true,
129129
});
130130
saveDocument(newDoc, format, path);
131131
console.log(`Marked task ${String(taskIndex)} done on ${args.changeId}`);
@@ -146,8 +146,8 @@ const undoneOpts = z.object({});
146146

147147
const undoneSubcommand: CommandDef<typeof undoneArgs, typeof undoneOpts> = {
148148
name: "undone",
149-
// TODO: derive description from updatePlanTaskOp (conditional on done: false)
150-
description: "Mark a task as undone",
149+
description: markTaskUndoneOp.def.description,
150+
apiLink: markTaskUndoneOp.def.name,
151151
args: undoneArgs,
152152
opts: undoneOpts,
153153
action(args) {
@@ -160,11 +160,10 @@ const undoneSubcommand: CommandDef<typeof undoneArgs, typeof undoneOpts> = {
160160
}
161161

162162
try {
163-
const newDoc = updatePlanTaskOp({
163+
const newDoc = markTaskUndoneOp({
164164
doc,
165165
changeId: args.changeId,
166166
taskIndex,
167-
done: false,
168167
});
169168
saveDocument(newDoc, format, path);
170169
console.log(

src/operations/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export { nextIdOp } from "./next-id.js";
1515
export { initDocumentOp } from "./init-document.js";
1616
export { addPlanTaskOp } from "./add-plan-task.js";
1717
export { updatePlanTaskOp } from "./update-plan-task.js";
18+
export { markTaskDoneOp } from "./mark-task-done.js";
19+
export { markTaskUndoneOp } from "./mark-task-undone.js";
1820
export { taskListOp } from "./task-list.js";
1921
export { planInitOp } from "./plan-init.js";
2022
export { planAddTaskOp } from "./plan-add-task.js";

src/operations/mark-task-done.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import * as z from "zod";
2+
import { defineOperation } from "./define-operation.js";
3+
import { SysProMDocument } from "../schema.js";
4+
5+
export const markTaskDoneOp = defineOperation({
6+
name: "markTaskDone",
7+
description: "Mark a task as done",
8+
input: z.object({
9+
doc: SysProMDocument,
10+
changeId: z.string().describe("ID of the change node"),
11+
taskIndex: z.number().describe("Zero-based index of the task in the plan"),
12+
}),
13+
output: SysProMDocument,
14+
fn: (input) => {
15+
const node = input.doc.nodes.find((n) => n.id === input.changeId);
16+
if (!node) throw new Error(`Node not found: ${input.changeId}`);
17+
const plan = node.plan ?? [];
18+
if (input.taskIndex < 0 || input.taskIndex >= plan.length) {
19+
throw new Error(
20+
`Task index ${String(input.taskIndex)} out of range (plan has ${String(plan.length)} task(s))`,
21+
);
22+
}
23+
const newPlan = [...plan];
24+
newPlan[input.taskIndex] = { ...newPlan[input.taskIndex], done: true };
25+
const newNodes = input.doc.nodes.map((n) =>
26+
n.id === input.changeId ? { ...n, plan: newPlan } : n,
27+
);
28+
return { ...input.doc, nodes: newNodes };
29+
},
30+
});

src/operations/mark-task-undone.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import * as z from "zod";
2+
import { defineOperation } from "./define-operation.js";
3+
import { SysProMDocument } from "../schema.js";
4+
5+
export const markTaskUndoneOp = defineOperation({
6+
name: "markTaskUndone",
7+
description: "Mark a task as undone",
8+
input: z.object({
9+
doc: SysProMDocument,
10+
changeId: z.string().describe("ID of the change node"),
11+
taskIndex: z.number().describe("Zero-based index of the task in the plan"),
12+
}),
13+
output: SysProMDocument,
14+
fn: (input) => {
15+
const node = input.doc.nodes.find((n) => n.id === input.changeId);
16+
if (!node) throw new Error(`Node not found: ${input.changeId}`);
17+
const plan = node.plan ?? [];
18+
if (input.taskIndex < 0 || input.taskIndex >= plan.length) {
19+
throw new Error(
20+
`Task index ${String(input.taskIndex)} out of range (plan has ${String(plan.length)} task(s))`,
21+
);
22+
}
23+
const newPlan = [...plan];
24+
newPlan[input.taskIndex] = { ...newPlan[input.taskIndex], done: false };
25+
const newNodes = input.doc.nodes.map((n) =>
26+
n.id === input.changeId ? { ...n, plan: newPlan } : n,
27+
);
28+
return { ...input.doc, nodes: newNodes };
29+
},
30+
});

0 commit comments

Comments
 (0)