@pi-archimedes/todo 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/tool.test.ts CHANGED
@@ -19,6 +19,7 @@ const VALIDATION_ERROR_TEXT = [
19
19
  // tell apart the status colour, the glyph, and the label.
20
20
  const theme = {
21
21
  fg: (token: unknown, text: unknown) => `[${token}]${text}`,
22
+ strikethrough: (text: unknown) => `[s]${text}`,
22
23
  } as unknown as Theme;
23
24
 
24
25
  const tool = createManageTodoListTool(new TodoStateManager(), () => {});
@@ -72,6 +73,40 @@ describe("renderResult — harness error shapes", () => {
72
73
  });
73
74
  });
74
75
 
76
+ describe("tool schema", () => {
77
+ const params = tool.parameters as unknown as {
78
+ properties: Record<string, { type?: string; enum?: string[]; items?: unknown }>;
79
+ required?: string[];
80
+ };
81
+ const itemSchema = params.properties.todoList?.items as unknown as {
82
+ properties: Record<string, { type?: string; enum?: string[] }>;
83
+ required?: string[];
84
+ };
85
+
86
+ it("declares content + canonical status enum, with no id/title and optional description", () => {
87
+ const props = itemSchema.properties;
88
+ expect(props.id).toBeUndefined();
89
+ expect(props.title).toBeUndefined();
90
+ expect(props.content?.type).toBe("string");
91
+ expect(props.status?.enum).toEqual(["pending", "in_progress", "completed"]);
92
+ expect(itemSchema.required).toContain("content");
93
+ expect(itemSchema.required).toContain("status");
94
+ expect(itemSchema.required).not.toContain("description");
95
+ });
96
+
97
+ it("keeps the manage_todo_list tool name, op and prepareArguments wiring", () => {
98
+ expect(tool.name).toBe("manage_todo_list");
99
+ expect(typeof tool.prepareArguments).toBe("function");
100
+ expect(params.properties.operation?.enum).toEqual(["write", "read"]);
101
+ });
102
+
103
+ it("model-facing description uses canonical status tokens only", () => {
104
+ expect(tool.description).not.toMatch(/not-started|in-progress/);
105
+ expect(tool.description).toContain("in_progress");
106
+ expect(tool.description).toContain("\"content\": \"Fix the auth middleware\"");
107
+ });
108
+ });
109
+
75
110
  describe("renderResult — successful results unchanged", () => {
76
111
  it("renders the completed counter from details.todos", () => {
77
112
  const result = {
@@ -79,13 +114,35 @@ describe("renderResult — successful results unchanged", () => {
79
114
  details: {
80
115
  operation: "write" as const,
81
116
  todos: [
82
- { id: 1, title: "a", description: "a", status: "completed" as const },
83
- { id: 2, title: "b", description: "b", status: "completed" as const },
84
- { id: 3, title: "c", description: "c", status: "completed" as const },
117
+ { content: "a", status: "completed" as const },
118
+ { content: "b", status: "completed" as const },
119
+ { content: "c", status: "completed" as const },
85
120
  ],
86
121
  },
87
122
  } as unknown as AgentToolResult<TodoDetails>;
88
123
  const [line] = renderLines(result, false).split("\n");
89
124
  expect(line).toContain("3/3 completed");
90
125
  });
126
+
127
+ it("numbers items by array position and renders content", () => {
128
+ const result = {
129
+ content: [{ type: "text" as const, text: "1/3 completed." }],
130
+ details: {
131
+ operation: "write" as const,
132
+ todos: [
133
+ { content: "First", status: "completed" as const },
134
+ { content: "Second", status: "in_progress" as const },
135
+ { content: "Third", status: "pending" as const },
136
+ ],
137
+ },
138
+ } as unknown as AgentToolResult<TodoDetails>;
139
+ const lines = renderLines(result, true);
140
+ expect(lines).toContain("[accent]1.");
141
+ expect(lines).toContain("[accent]2.");
142
+ expect(lines).toContain("[accent]3.");
143
+ expect(lines).toContain("[s]First");
144
+ expect(lines).toContain("[warning]Second");
145
+ expect(lines).toContain("[muted]Third");
146
+ expect(lines).not.toContain("undefined");
147
+ });
91
148
  });
package/src/tool.ts CHANGED
@@ -9,14 +9,15 @@ import { STATUS_ICONS } from "./types.js";
9
9
  import type { TodoDetails } from "./types.js";
10
10
 
11
11
  const TodoItemSchema = Type.Object({
12
- id: Type.Number({ description: "Unique identifier for the todo. Use sequential numbers starting from 1." }),
13
- title: Type.String({ description: "Concise action-oriented todo label (3-7 words). Displayed in UI." }),
14
- description: Type.String({
15
- description: "Detailed context, requirements, or implementation notes. Include file paths, specific methods, or acceptance criteria.",
12
+ content: Type.String({
13
+ description: "Short imperative label for the task (3-10 words). Displayed in UI. Example: \"Fix the auth middleware\".",
16
14
  }),
17
- status: StringEnum(["not-started", "in-progress", "completed"] as const, {
18
- description: "not-started: Not begun | in-progress: Currently working (multiple allowed for parallel work) | completed: Fully finished with no blockers",
15
+ status: StringEnum(["pending", "in_progress", "completed"] as const, {
16
+ description: "pending: Not begun | in_progress: Currently working (multiple allowed for parallel work/subagents) | completed: Fully finished with no blockers",
19
17
  }),
18
+ description: Type.Optional(Type.String({
19
+ description: "Optional detailed context: file paths, specific methods, or acceptance criteria.",
20
+ })),
20
21
  });
21
22
 
22
23
  export const ManageTodoListParams = Type.Object({
@@ -38,7 +39,7 @@ When to use this tool:
38
39
  - Complex multi-step work requiring planning and tracking
39
40
  - When user provides multiple tasks or requests (numbered, comma-separated)
40
41
  - After receiving new instructions that require multiple steps
41
- - BEFORE starting work on any todo (mark as in-progress)
42
+ - BEFORE starting work on any todo (mark as in_progress)
42
43
  - IMMEDIATELY after completing each todo (mark completed individually)
43
44
  - When breaking down larger tasks into smaller actionable steps
44
45
  - To give users visibility into your progress and planning
@@ -50,14 +51,19 @@ When NOT to use:
50
51
 
51
52
  CRITICAL workflow:
52
53
  1. Plan tasks by writing todo list with specific, actionable items
53
- 2. Mark todo(s) as in-progress before starting work
54
+ 2. Mark todo(s) as in_progress before starting work
54
55
  3. Complete the work for that specific todo
55
56
  4. Mark that todo as completed IMMEDIATELY
56
57
  5. Move to next todo and repeat
57
58
 
59
+ Todo item shape:
60
+ {"content": "Fix the auth middleware", "status": "pending", "description": "optional: file paths, acceptance criteria"}
61
+ - content: the short displayed label — do NOT assign ids (numbering is order)
62
+ - status: exactly one of pending | in_progress | completed
63
+
58
64
  Todo states:
59
- - not-started: Todo not yet begun
60
- - in-progress: Currently working (multiple allowed for parallel work/subagents)
65
+ - pending: Todo not yet begun
66
+ - in_progress: Currently working (multiple allowed for parallel work/subagents)
61
67
  - completed: Finished successfully
62
68
 
63
69
  IMPORTANT: Mark todos completed as soon as they are done. Do not batch completions.
@@ -188,21 +194,22 @@ export function createManageTodoListTool(state: TodoStateManager, onUpdate: () =
188
194
  let text = renderStatusLabel("success", label, theme);
189
195
 
190
196
  if (expanded) {
191
- for (const todo of todos) {
192
- const iconChar = STATUS_ICONS[todo.status] ?? "?";
197
+ for (let i = 0; i < todos.length; i++) {
198
+ const todo = todos[i]!;
199
+ const iconChar = (STATUS_ICONS[todo.status] ?? "?").trim();
193
200
  const icon =
194
201
  todo.status === "completed"
195
202
  ? theme.fg("success", iconChar)
196
- : todo.status === "in-progress"
203
+ : todo.status === "in_progress"
197
204
  ? theme.fg("warning", iconChar)
198
205
  : theme.fg("dim", iconChar);
199
206
  const title =
200
207
  todo.status === "completed"
201
- ? theme.fg("dim", theme.strikethrough(todo.title))
202
- : todo.status === "in-progress"
203
- ? theme.fg("warning", todo.title)
204
- : theme.fg("muted", todo.title);
205
- text += `\n ${icon} ${theme.fg("accent", `${todo.id}.`)} ${title}`;
208
+ ? theme.fg("dim", theme.strikethrough(todo.content ?? ""))
209
+ : todo.status === "in_progress"
210
+ ? theme.fg("warning", todo.content ?? "")
211
+ : theme.fg("muted", todo.content ?? "");
212
+ text += `\n ${icon} ${theme.fg("accent", `${i + 1}.`)} ${title}`;
206
213
  }
207
214
  }
208
215
 
package/src/types.ts CHANGED
@@ -2,17 +2,15 @@
2
2
  * Core types for the todo list extension.
3
3
  */
4
4
 
5
- /** Status of a single todo item */
6
- export type TodoStatus = "not-started" | "in-progress" | "completed";
5
+ /** Status of a single todo item (Claude Code aligned). */
6
+ export type TodoStatus = "pending" | "in_progress" | "completed";
7
7
 
8
- /** A single todo item */
8
+ /** A single todo item. No `id` — display numbering is array position. */
9
9
  export interface TodoItem {
10
- /** Sequential identifier starting from 1 */
11
- id: number;
12
- /** Concise action-oriented label (3-7 words). Displayed in UI. */
13
- title: string;
14
- /** Detailed context, requirements, or implementation notes. */
15
- description: string;
10
+ /** Short imperative label of the task (3-10 words). Displayed in UI. */
11
+ content: string;
12
+ /** Optional detailed context: file paths, methods, acceptance criteria. */
13
+ description?: string;
16
14
  /** Current status. */
17
15
  status: TodoStatus;
18
16
  }
@@ -29,7 +27,7 @@ export interface TodoStats {
29
27
  total: number;
30
28
  completed: number;
31
29
  inProgress: number;
32
- notStarted: number;
30
+ pending: number;
33
31
  }
34
32
 
35
33
  /** Validation result */
@@ -40,7 +38,7 @@ export interface ValidationResult {
40
38
 
41
39
  /** Status icons for each todo state */
42
40
  export const STATUS_ICONS: Record<TodoStatus, string> = {
43
- "completed": "✓",
44
- "in-progress": "◉ ",
45
- "not-started": "○",
41
+ completed: "✓",
42
+ in_progress: "◉ ",
43
+ pending: "○",
46
44
  };
@@ -52,7 +52,9 @@ export function updateWidget(
52
52
  for (const source of sortedSources) {
53
53
  const todos = currentSub.get(source)!;
54
54
  if (todos.length > 0) {
55
- const agentName = source.replace("subagent:", "");
55
+ // Per-child sources are subagent:<agent>:<uuid>; render only the agent
56
+ // name so the header stays `subagent (myagent)` regardless of suffix.
57
+ const agentName = source.replace("subagent:", "").split(":")[0];
56
58
  columns.push({ header: `subagent (${agentName})`, todos });
57
59
  }
58
60
  }
@@ -103,7 +105,7 @@ export function updateWidget(
103
105
  const todo = column.todos[todoIndex];
104
106
  if (todo) {
105
107
  const icon = getStatusIcon(todo.status, theme);
106
- const idStr = theme.fg("accent", `${todo.id}.`);
108
+ const idStr = theme.fg("accent", `${todoIndex + 1}.`);
107
109
  const title = formatTodoTitle(todo, theme);
108
110
  cellText = ` ${icon} ${idStr} ${title}`;
109
111
  } else {
@@ -142,18 +144,18 @@ export function updateWidget(
142
144
  function getStatusIcon(status: TodoItem["status"], theme: Theme): string {
143
145
  const icon = STATUS_ICONS[status] ?? "?";
144
146
  if (status === "completed") return theme.fg("success", icon);
145
- if (status === "in-progress") return theme.fg("warning", icon.trim());
147
+ if (status === "in_progress") return theme.fg("warning", icon.trim());
146
148
  return theme.fg("dim", icon);
147
149
  }
148
150
 
149
151
  function formatTodoTitle(todo: TodoItem, theme: Theme): string {
150
152
  if (todo.status === "completed") {
151
- return theme.fg("dim", theme.strikethrough(todo.title));
153
+ return theme.fg("dim", theme.strikethrough(todo.content ?? ""));
152
154
  }
153
- if (todo.status === "in-progress") {
154
- return theme.fg("warning", todo.title);
155
+ if (todo.status === "in_progress") {
156
+ return theme.fg("warning", todo.content ?? "");
155
157
  }
156
- return theme.fg("muted", todo.title);
158
+ return theme.fg("muted", todo.content ?? "");
157
159
  }
158
160
 
159
161
  /** Clear the widget */