agkan 3.15.0 → 3.15.1

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.
Files changed (99) hide show
  1. package/dist/board/boardRoutes.d.ts +1 -0
  2. package/dist/board/boardRoutes.d.ts.map +1 -1
  3. package/dist/board/boardRoutes.js +17 -1
  4. package/dist/board/boardRoutes.js.map +1 -1
  5. package/dist/board/server.js +1 -1
  6. package/dist/board/server.js.map +1 -1
  7. package/dist/cli/commands/block/add.js +10 -10
  8. package/dist/cli/commands/block/add.js.map +1 -1
  9. package/dist/cli/commands/block/list.js +4 -4
  10. package/dist/cli/commands/block/list.js.map +1 -1
  11. package/dist/cli/commands/block/remove.js +9 -9
  12. package/dist/cli/commands/block/remove.js.map +1 -1
  13. package/dist/cli/commands/comment/add.js +2 -2
  14. package/dist/cli/commands/comment/add.js.map +1 -1
  15. package/dist/cli/commands/comment/delete.js +2 -2
  16. package/dist/cli/commands/comment/delete.js.map +1 -1
  17. package/dist/cli/commands/comment/list.js +2 -2
  18. package/dist/cli/commands/comment/list.js.map +1 -1
  19. package/dist/cli/commands/config/get.js +2 -2
  20. package/dist/cli/commands/config/get.js.map +1 -1
  21. package/dist/cli/commands/meta/delete.js +3 -3
  22. package/dist/cli/commands/meta/delete.js.map +1 -1
  23. package/dist/cli/commands/meta/get.js +3 -3
  24. package/dist/cli/commands/meta/get.js.map +1 -1
  25. package/dist/cli/commands/meta/list.js +2 -2
  26. package/dist/cli/commands/meta/list.js.map +1 -1
  27. package/dist/cli/commands/meta/set.js +4 -4
  28. package/dist/cli/commands/meta/set.js.map +1 -1
  29. package/dist/cli/commands/tag/add.js +7 -7
  30. package/dist/cli/commands/tag/add.js.map +1 -1
  31. package/dist/cli/commands/tag/attach.js +7 -7
  32. package/dist/cli/commands/tag/attach.js.map +1 -1
  33. package/dist/cli/commands/tag/delete.js +5 -5
  34. package/dist/cli/commands/tag/delete.js.map +1 -1
  35. package/dist/cli/commands/tag/detach.js +7 -7
  36. package/dist/cli/commands/tag/detach.js.map +1 -1
  37. package/dist/cli/commands/tag/list.js +2 -2
  38. package/dist/cli/commands/tag/list.js.map +1 -1
  39. package/dist/cli/commands/tag/rename.js +8 -8
  40. package/dist/cli/commands/tag/rename.js.map +1 -1
  41. package/dist/cli/commands/tag/show.js +3 -3
  42. package/dist/cli/commands/tag/show.js.map +1 -1
  43. package/dist/cli/commands/task/add.js +11 -11
  44. package/dist/cli/commands/task/add.js.map +1 -1
  45. package/dist/cli/commands/task/archive.js +3 -3
  46. package/dist/cli/commands/task/archive.js.map +1 -1
  47. package/dist/cli/commands/task/copy.js +5 -5
  48. package/dist/cli/commands/task/copy.js.map +1 -1
  49. package/dist/cli/commands/task/count.js +4 -4
  50. package/dist/cli/commands/task/count.js.map +1 -1
  51. package/dist/cli/commands/task/delete.js +3 -3
  52. package/dist/cli/commands/task/delete.js.map +1 -1
  53. package/dist/cli/commands/task/find.js +2 -2
  54. package/dist/cli/commands/task/find.js.map +1 -1
  55. package/dist/cli/commands/task/get.js +3 -3
  56. package/dist/cli/commands/task/get.js.map +1 -1
  57. package/dist/cli/commands/task/list.d.ts.map +1 -1
  58. package/dist/cli/commands/task/list.js +163 -160
  59. package/dist/cli/commands/task/list.js.map +1 -1
  60. package/dist/cli/commands/task/purge.js +3 -3
  61. package/dist/cli/commands/task/purge.js.map +1 -1
  62. package/dist/cli/commands/task/unarchive.js +3 -3
  63. package/dist/cli/commands/task/unarchive.js.map +1 -1
  64. package/dist/cli/commands/task/update-helpers.js +11 -11
  65. package/dist/cli/commands/task/update-helpers.js.map +1 -1
  66. package/dist/cli/commands/task/update-parent.js +8 -8
  67. package/dist/cli/commands/task/update-parent.js.map +1 -1
  68. package/dist/cli/commands/task/update.js +3 -3
  69. package/dist/cli/commands/task/update.js.map +1 -1
  70. package/dist/cli/utils/error-handler.js +4 -4
  71. package/dist/cli/utils/error-handler.js.map +1 -1
  72. package/dist/cli/utils/output-formatter.js +2 -2
  73. package/dist/cli/utils/output-formatter.js.map +1 -1
  74. package/dist/hooks/hook-stop.mjs +107 -42
  75. package/dist/services/index.d.ts +1 -2
  76. package/dist/services/index.d.ts.map +1 -1
  77. package/dist/services/index.js +1 -3
  78. package/dist/services/index.js.map +1 -1
  79. package/dist/services/types.d.ts +28 -0
  80. package/dist/services/types.d.ts.map +1 -0
  81. package/dist/services/types.js +6 -0
  82. package/dist/services/types.js.map +1 -0
  83. package/dist/terminal/PtySessionService.d.ts +1 -1
  84. package/dist/terminal/PtySessionService.d.ts.map +1 -1
  85. package/dist/terminal/PtySessionService.js +2 -7
  86. package/dist/terminal/PtySessionService.js.map +1 -1
  87. package/dist/terminal/buildHookEnv.d.ts +8 -0
  88. package/dist/terminal/buildHookEnv.d.ts.map +1 -0
  89. package/dist/terminal/buildHookEnv.js +24 -0
  90. package/dist/terminal/buildHookEnv.js.map +1 -0
  91. package/dist/utils/runTargetStatus.d.ts +9 -0
  92. package/dist/utils/runTargetStatus.d.ts.map +1 -0
  93. package/dist/utils/runTargetStatus.js +17 -0
  94. package/dist/utils/runTargetStatus.js.map +1 -0
  95. package/package.json +1 -1
  96. package/dist/services/ClaudeProcessService.d.ts +0 -110
  97. package/dist/services/ClaudeProcessService.d.ts.map +0 -1
  98. package/dist/services/ClaudeProcessService.js +0 -312
  99. package/dist/services/ClaudeProcessService.js.map +0 -1
@@ -16,10 +16,68 @@ const validators_1 = require("../../utils/validators");
16
16
  const tag_resolver_1 = require("../../utils/tag-resolver");
17
17
  const format_1 = require("../../../utils/format");
18
18
  const output_formatter_1 = require("../../utils/output-formatter");
19
+ const errors_1 = require("../../../errors");
19
20
  /**
20
- * Recursive function to display tasks in tree structure.
21
+ * Map the common set of task fields (shared by tree/list/dep-tree node builders)
22
+ * plus resolved tags and metadata for a task.
23
+ */
24
+ function mapTaskFields(task, allTaskTags, allTasksMetadata) {
25
+ const tags = allTaskTags.get(task.id);
26
+ const metadata = allTasksMetadata.get(task.id);
27
+ return {
28
+ id: task.id,
29
+ title: task.title,
30
+ body: task.body,
31
+ author: task.author,
32
+ assignees: task.assignees,
33
+ status: task.status,
34
+ priority: task.priority,
35
+ parent_id: task.parent_id,
36
+ created_at: task.created_at,
37
+ updated_at: task.updated_at,
38
+ tags: tags ? tags.map((tag) => ({ id: tag.id, name: tag.name })) : [],
39
+ metadata: metadata ? metadata.map((m) => ({ key: m.key, value: m.value })) : [],
40
+ };
41
+ }
42
+ /**
43
+ * Build a Map of task id -> task (including archived, unfiltered) and a Map of
44
+ * parent id -> non-archived children, both from a single bulk query. This
45
+ * replaces the N+1 getChildTasks()/getTask() calls previously made per node.
46
+ *
47
+ * getChildTasks()/getTask() ignore the list command's status/tag/priority filters
48
+ * and getChildTasks() always excludes archived tasks, so the maps here are built
49
+ * from the full (unfiltered) task set to preserve that exact behavior.
50
+ */
51
+ function buildTaskMaps(taskService) {
52
+ const allTasks = taskService.listTasks({ includeArchived: true }, 'created_at', 'asc');
53
+ const taskById = new Map();
54
+ const childrenByParentId = new Map();
55
+ for (const task of allTasks) {
56
+ taskById.set(task.id, task);
57
+ if (task.is_archived || task.parent_id == null) {
58
+ continue;
59
+ }
60
+ const existing = childrenByParentId.get(task.parent_id);
61
+ if (existing) {
62
+ existing.push(task);
63
+ }
64
+ else {
65
+ childrenByParentId.set(task.parent_id, [task]);
66
+ }
67
+ }
68
+ return { taskById, childrenByParentId };
69
+ }
70
+ /**
71
+ * Print a single tree/dep-tree node line (connector, id, title, status),
72
+ * followed by its priority and metadata lines.
21
73
  */
22
- function printTreeTaskDetails(task, childPrefix, allTasksMetadata) {
74
+ function printTreeNode(task, allTasksMetadata, prefix, isLast, relationshipLabel) {
75
+ const statusColor = (0, format_1.getStatusColor)(task.status);
76
+ const connector = isLast ? '└── ' : '├── ';
77
+ const labelStr = relationshipLabel ? `${chalk_1.default.gray(relationshipLabel)} ` : '';
78
+ console.log(`${prefix}${connector}${labelStr}${chalk_1.default.bold.cyan(`[${task.id}]`)} ${chalk_1.default.bold(task.title)} ` +
79
+ `${chalk_1.default[statusColor](`(${task.status})`)}`);
80
+ const childPrefix = prefix + (isLast ? ' ' : '│ ');
23
81
  if (task.priority) {
24
82
  console.log(`${childPrefix}${formatPriority(task.priority)}`);
25
83
  }
@@ -29,49 +87,34 @@ function printTreeTaskDetails(task, childPrefix, allTasksMetadata) {
29
87
  console.log(`${childPrefix}${chalk_1.default.bold('Metadata:')} ${metadataStrings.join(', ')}`);
30
88
  }
31
89
  }
32
- function displayTaskTree(taskService, task, prefix, isLast, allTasksMetadata) {
33
- const statusColor = (0, format_1.getStatusColor)(task.status);
34
- const connector = isLast ? '└── ' : '├── ';
35
- console.log(`${prefix}${connector}${chalk_1.default.bold.cyan(`[${task.id}]`)} ${chalk_1.default.bold(task.title)} ` +
36
- `${chalk_1.default[statusColor](`(${task.status})`)}`);
37
- const childPrefix = prefix + (isLast ? ' ' : '│ ');
38
- printTreeTaskDetails(task, childPrefix, allTasksMetadata);
39
- const children = taskService.getChildTasks(task.id);
90
+ /**
91
+ * Recursive function to display tasks in tree structure.
92
+ */
93
+ function displayTaskTree(task, childrenByParentId, prefix, isLast, allTasksMetadata) {
94
+ printTreeNode(task, allTasksMetadata, prefix, isLast);
95
+ const children = childrenByParentId.get(task.id) || [];
40
96
  if (children.length > 0) {
41
97
  const newPrefix = prefix + (isLast ? ' ' : '│ ');
42
98
  children.forEach((child, index) => {
43
99
  const isChildLast = index === children.length - 1;
44
- displayTaskTree(taskService, child, newPrefix, isChildLast, allTasksMetadata);
100
+ displayTaskTree(child, childrenByParentId, newPrefix, isChildLast, allTasksMetadata);
45
101
  });
46
102
  }
47
103
  }
48
104
  /**
49
105
  * Build a tree node from a task, including children recursively.
50
106
  */
51
- function buildTreeNode(task, taskService, allTaskTags, allTasksMetadata) {
52
- const tags = allTaskTags.get(task.id);
53
- const metadata = allTasksMetadata.get(task.id);
54
- const children = taskService.getChildTasks(task.id);
107
+ function buildTreeNode(task, childrenByParentId, allTaskTags, allTasksMetadata) {
108
+ const children = childrenByParentId.get(task.id) || [];
55
109
  return {
56
- id: task.id,
57
- title: task.title,
58
- body: task.body,
59
- author: task.author,
60
- assignees: task.assignees,
61
- status: task.status,
62
- priority: task.priority,
63
- parent_id: task.parent_id,
64
- created_at: task.created_at,
65
- updated_at: task.updated_at,
66
- tags: tags ? tags.map((tag) => ({ id: tag.id, name: tag.name })) : [],
67
- metadata: metadata ? metadata.map((m) => ({ key: m.key, value: m.value })) : [],
68
- children: children.map((child) => buildTreeNode(child, taskService, allTaskTags, allTasksMetadata)),
110
+ ...mapTaskFields(task, allTaskTags, allTasksMetadata),
111
+ children: children.map((child) => buildTreeNode(child, childrenByParentId, allTaskTags, allTasksMetadata)),
69
112
  };
70
113
  }
71
114
  /**
72
115
  * Build the JSON output object for the tree view.
73
116
  */
74
- function buildTreeJsonOutput(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata) {
117
+ function buildTreeJsonOutput(displayTasks, options, tagIds, childrenByParentId, allTaskTags, allTasksMetadata) {
75
118
  const rootTasks = displayTasks.filter((task) => !task.parent_id);
76
119
  return {
77
120
  totalCount: displayTasks.length,
@@ -87,14 +130,14 @@ function buildTreeJsonOutput(displayTasks, options, tagIds, taskService, allTask
87
130
  },
88
131
  sort: options.sort || 'created_at',
89
132
  order: options.order || 'desc',
90
- tasks: rootTasks.map((task) => buildTreeNode(task, taskService, allTaskTags, allTasksMetadata)),
133
+ tasks: rootTasks.map((task) => buildTreeNode(task, childrenByParentId, allTaskTags, allTasksMetadata)),
91
134
  };
92
135
  }
93
136
  /**
94
137
  * Build the JSON output object for the normal (flat) list view.
95
138
  */
96
- function buildListJsonOutput(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata) {
97
- const tasksWithRelations = displayTasks.map((task) => buildTaskWithRelations(task, taskService, allTaskTags, allTasksMetadata));
139
+ function buildListJsonOutput(displayTasks, options, tagIds, taskById, allTaskTags, allTasksMetadata) {
140
+ const tasksWithRelations = displayTasks.map((task) => buildTaskWithRelations(task, taskById, allTaskTags, allTasksMetadata));
98
141
  return {
99
142
  totalCount: displayTasks.length,
100
143
  filters: {
@@ -113,24 +156,11 @@ function buildListJsonOutput(displayTasks, options, tagIds, taskService, allTask
113
156
  /**
114
157
  * Build a single task object with parent, tags and metadata relations.
115
158
  */
116
- function buildTaskWithRelations(task, taskService, allTaskTags, allTasksMetadata) {
117
- const tags = allTaskTags.get(task.id);
118
- const metadata = allTasksMetadata.get(task.id);
119
- const parent = task.parent_id ? taskService.getTask(task.parent_id) : null;
159
+ function buildTaskWithRelations(task, taskById, allTaskTags, allTasksMetadata) {
160
+ const parent = task.parent_id ? (taskById.get(task.parent_id) ?? null) : null;
120
161
  return {
121
- id: task.id,
122
- title: task.title,
123
- body: task.body,
124
- author: task.author,
125
- assignees: task.assignees,
126
- status: task.status,
127
- priority: task.priority,
128
- parent_id: task.parent_id,
129
- created_at: task.created_at,
130
- updated_at: task.updated_at,
162
+ ...mapTaskFields(task, allTaskTags, allTasksMetadata),
131
163
  parent: parent ? { id: parent.id, title: parent.title, status: parent.status } : null,
132
- tags: tags ? tags.map((tag) => ({ id: tag.id, name: tag.name })) : [],
133
- metadata: metadata ? metadata.map((m) => ({ key: m.key, value: m.value })) : [],
134
164
  };
135
165
  }
136
166
  /**
@@ -158,65 +188,35 @@ function collectAllBlockedIds(blockMap) {
158
188
  }
159
189
  return allBlockedIds;
160
190
  }
161
- /**
162
- * Display a single task node line with optional metadata and relationship label.
163
- */
164
- function printTreeNodeLine(task, allTasksMetadata, prefix, isLast, relationshipLabel) {
165
- const statusColor = (0, format_1.getStatusColor)(task.status);
166
- const connector = isLast ? '\u2514\u2500\u2500 ' : '\u251c\u2500\u2500 ';
167
- const labelStr = relationshipLabel ? `${chalk_1.default.gray(relationshipLabel)} ` : '';
168
- console.log(`${prefix}${connector}${labelStr}${chalk_1.default.bold.cyan(`[${task.id}]`)} ${chalk_1.default.bold(task.title)} ` +
169
- `${chalk_1.default[statusColor](`(${task.status})`)}`);
170
- const childPrefix = prefix + (isLast ? ' ' : '\u2502 ');
171
- if (task.priority) {
172
- console.log(`${childPrefix}${formatPriority(task.priority)}`);
173
- }
174
- const metadata = allTasksMetadata.get(task.id);
175
- if (metadata && metadata.length > 0) {
176
- const metadataStrings = metadata.map(formatMetadataEntry);
177
- console.log(`${childPrefix}${chalk_1.default.bold('Metadata:')} ${metadataStrings.join(', ')}`);
178
- }
179
- }
180
191
  /**
181
192
  * Recursively display dependency tree (blocker -> blocked only).
182
193
  */
183
- function displayDependencyTree(task, taskService, blockMap, allTasksMetadata, prefix, isLast, visited, relationshipLabel) {
184
- printTreeNodeLine(task, allTasksMetadata, prefix, isLast, relationshipLabel);
194
+ function displayDependencyTree(task, taskById, blockMap, allTasksMetadata, prefix, isLast, visited, relationshipLabel) {
195
+ printTreeNode(task, allTasksMetadata, prefix, isLast, relationshipLabel);
185
196
  if (visited.has(task.id)) {
186
197
  return;
187
198
  }
188
199
  visited.add(task.id);
189
200
  const blockedIds = blockMap.get(task.id) || [];
190
- const newPrefix = prefix + (isLast ? ' ' : '\u2502 ');
201
+ const newPrefix = prefix + (isLast ? ' ' : ' ');
191
202
  blockedIds.forEach((blockedId, index) => {
192
- const childTask = taskService.getTask(blockedId);
203
+ const childTask = taskById.get(blockedId);
193
204
  if (childTask && !visited.has(blockedId)) {
194
205
  const isChildLast = index === blockedIds.length - 1;
195
- displayDependencyTree(childTask, taskService, blockMap, allTasksMetadata, newPrefix, isChildLast, new Set(visited), '[blocks]');
206
+ displayDependencyTree(childTask, taskById, blockMap, allTasksMetadata, newPrefix, isChildLast, new Set(visited), '[blocks]');
196
207
  }
197
208
  });
198
209
  }
199
- /**
200
- * Extract tags and metadata for a task.
201
- */
202
- function extractTaskRelations(task, allTaskTags, allTasksMetadata) {
203
- const tags = allTaskTags.get(task.id);
204
- const metadata = allTasksMetadata.get(task.id);
205
- return {
206
- tags: tags ? tags.map((tag) => ({ id: tag.id, name: tag.name })) : [],
207
- metadata: metadata ? metadata.map((m) => ({ key: m.key, value: m.value })) : [],
208
- };
209
- }
210
210
  /**
211
211
  * Build blocked task nodes recursively.
212
212
  */
213
- function buildBlockedNodes(blockedIds, taskService, blockMap, allTaskTags, allTasksMetadata, visited) {
213
+ function buildBlockedNodes(blockedIds, taskById, blockMap, allTaskTags, allTasksMetadata, visited) {
214
214
  const blocks = [];
215
215
  for (const blockedId of blockedIds) {
216
216
  if (!visited.has(blockedId)) {
217
- const blockedTask = taskService.getTask(blockedId);
217
+ const blockedTask = taskById.get(blockedId);
218
218
  if (blockedTask) {
219
- blocks.push(buildDepTreeNode(blockedTask, taskService, blockMap, allTaskTags, allTasksMetadata, new Set(visited)));
219
+ blocks.push(buildDepTreeNode(blockedTask, taskById, blockMap, allTaskTags, allTasksMetadata, new Set(visited)));
220
220
  }
221
221
  }
222
222
  }
@@ -225,31 +225,19 @@ function buildBlockedNodes(blockedIds, taskService, blockMap, allTaskTags, allTa
225
225
  /**
226
226
  * Build a dependency tree node recursively (blocking relationships only).
227
227
  */
228
- function buildDepTreeNode(task, taskService, blockMap, allTaskTags, allTasksMetadata, visited) {
228
+ function buildDepTreeNode(task, taskById, blockMap, allTaskTags, allTasksMetadata, visited) {
229
229
  visited.add(task.id);
230
230
  const blockedIds = blockMap.get(task.id) || [];
231
- const { tags, metadata } = extractTaskRelations(task, allTaskTags, allTasksMetadata);
232
- const blocks = buildBlockedNodes(blockedIds, taskService, blockMap, allTaskTags, allTasksMetadata, visited);
231
+ const blocks = buildBlockedNodes(blockedIds, taskById, blockMap, allTaskTags, allTasksMetadata, visited);
233
232
  return {
234
- id: task.id,
235
- title: task.title,
236
- body: task.body,
237
- author: task.author,
238
- assignees: task.assignees,
239
- status: task.status,
240
- priority: task.priority,
241
- parent_id: task.parent_id,
242
- created_at: task.created_at,
243
- updated_at: task.updated_at,
244
- tags,
245
- metadata,
233
+ ...mapTaskFields(task, allTaskTags, allTasksMetadata),
246
234
  blocks,
247
235
  };
248
236
  }
249
237
  /**
250
238
  * Build the JSON output for dep-tree view.
251
239
  */
252
- function buildDepTreeJsonOutput(displayTasks, options, tagIds, taskService, blockMap, allTaskTags, allTasksMetadata) {
240
+ function buildDepTreeJsonOutput(displayTasks, options, tagIds, taskById, blockMap, allTaskTags, allTasksMetadata) {
253
241
  const allBlockedIds = collectAllBlockedIds(blockMap);
254
242
  const rootTasks = displayTasks.filter((task) => !allBlockedIds.has(task.id));
255
243
  return {
@@ -263,7 +251,7 @@ function buildDepTreeJsonOutput(displayTasks, options, tagIds, taskService, bloc
263
251
  all: options.all || false,
264
252
  priority: options.priority || null,
265
253
  },
266
- tasks: rootTasks.map((task) => buildDepTreeNode(task, taskService, blockMap, allTaskTags, allTasksMetadata, new Set())),
254
+ tasks: rootTasks.map((task) => buildDepTreeNode(task, taskById, blockMap, allTaskTags, allTasksMetadata, new Set())),
267
255
  };
268
256
  }
269
257
  /**
@@ -307,10 +295,10 @@ function printTaskPersonInfo(task) {
307
295
  /**
308
296
  * Print task parent reference.
309
297
  */
310
- function printTaskParent(task, taskService) {
298
+ function printTaskParent(task, taskById) {
311
299
  if (!task.parent_id)
312
300
  return;
313
- const parentTask = taskService.getTask(task.parent_id);
301
+ const parentTask = taskById.get(task.parent_id);
314
302
  if (parentTask) {
315
303
  console.log(` ${chalk_1.default.bold('Parent:')} ${chalk_1.default.cyan(`[${parentTask.id}]`)} ${parentTask.title}`);
316
304
  }
@@ -360,10 +348,10 @@ function printTaskSeparator(isLast) {
360
348
  /**
361
349
  * Print a single task row in the human-readable list view.
362
350
  */
363
- function printTaskRow(task, taskService, allTaskTags, allTasksMetadata, isLast) {
351
+ function printTaskRow(task, taskById, allTaskTags, allTasksMetadata, isLast) {
364
352
  printTaskHeader(task);
365
353
  printTaskPersonInfo(task);
366
- printTaskParent(task, taskService);
354
+ printTaskParent(task, taskById);
367
355
  printTaskPriority(task);
368
356
  printTaskTags(task.id, allTaskTags);
369
357
  printTaskMetadata(task.id, allTasksMetadata);
@@ -379,17 +367,31 @@ function parseStatusFilter(statusStr) {
379
367
  .map((s) => s.trim())
380
368
  .filter((s) => s !== '');
381
369
  }
370
+ /**
371
+ * Validation error for task list command options.
372
+ * Carries the exact text-mode rendering previously passed to formatter.error,
373
+ * so the action's catch (setupTaskListCommand) can reproduce identical output
374
+ * while owning the single process.exit(1) call site.
375
+ */
376
+ class TaskListValidationError extends errors_1.ValidationError {
377
+ render;
378
+ constructor(message, render) {
379
+ super(message);
380
+ this.render = render;
381
+ this.name = 'TaskListValidationError';
382
+ Object.setPrototypeOf(this, new.target.prototype);
383
+ }
384
+ }
382
385
  /**
383
386
  * Validate status values.
384
387
  */
385
- function validateStatuses(statuses, formatter) {
388
+ function validateStatuses(statuses) {
386
389
  for (const s of statuses) {
387
390
  if (!(0, validators_1.validateTaskStatus)(s)) {
388
- formatter.error(`Invalid status: ${s}. Valid statuses: icebox, backlog, ready, in_progress, review, done, closed`, () => {
389
- console.log(chalk_1.default.red(`Invalid status: ${s}`));
390
- console.log('Valid statuses: icebox, backlog, ready, in_progress, review, done, closed');
391
+ throw new TaskListValidationError(`Invalid status: ${s}. Valid statuses: icebox, backlog, ready, in_progress, review, done, closed`, () => {
392
+ console.error(chalk_1.default.red(`Invalid status: ${s}`));
393
+ console.error('Valid statuses: icebox, backlog, ready, in_progress, review, done, closed');
391
394
  });
392
- process.exit(1);
393
395
  }
394
396
  }
395
397
  }
@@ -402,25 +404,23 @@ function normalizeStatusFilter(statusParts) {
402
404
  /**
403
405
  * Validate sort field.
404
406
  */
405
- function validateSortField(sortField, formatter) {
407
+ function validateSortField(sortField) {
406
408
  if (!TaskService_1.ALLOWED_SORT_FIELDS.includes(sortField)) {
407
- formatter.error(`Invalid sort field: ${sortField}. Valid fields: ${TaskService_1.ALLOWED_SORT_FIELDS.join(', ')}`, () => {
408
- console.log(chalk_1.default.red(`Invalid sort field: ${sortField}`));
409
- console.log(`Valid fields: ${TaskService_1.ALLOWED_SORT_FIELDS.join(', ')}`);
409
+ throw new TaskListValidationError(`Invalid sort field: ${sortField}. Valid fields: ${TaskService_1.ALLOWED_SORT_FIELDS.join(', ')}`, () => {
410
+ console.error(chalk_1.default.red(`Invalid sort field: ${sortField}`));
411
+ console.error(`Valid fields: ${TaskService_1.ALLOWED_SORT_FIELDS.join(', ')}`);
410
412
  });
411
- process.exit(1);
412
413
  }
413
414
  }
414
415
  /**
415
416
  * Validate sort order.
416
417
  */
417
- function validateSortOrder(sortOrder, formatter) {
418
+ function validateSortOrder(sortOrder) {
418
419
  if (!['asc', 'desc'].includes(sortOrder)) {
419
- formatter.error(`Invalid sort order: ${sortOrder}. Valid orders: asc, desc`, () => {
420
- console.log(chalk_1.default.red(`Invalid sort order: ${sortOrder}`));
421
- console.log('Valid orders: asc, desc');
420
+ throw new TaskListValidationError(`Invalid sort order: ${sortOrder}. Valid orders: asc, desc`, () => {
421
+ console.error(chalk_1.default.red(`Invalid sort order: ${sortOrder}`));
422
+ console.error('Valid orders: asc, desc');
422
423
  });
423
- process.exit(1);
424
424
  }
425
425
  }
426
426
  /**
@@ -435,14 +435,13 @@ function parsePriorityFilter(priorityStr) {
435
435
  /**
436
436
  * Validate priority values.
437
437
  */
438
- function validatePriorities(priorities, formatter) {
438
+ function validatePriorities(priorities) {
439
439
  for (const p of priorities) {
440
440
  if (!(0, models_1.isPriority)(p)) {
441
- formatter.error(`Invalid priority: ${p}. Valid priorities: ${models_1.PRIORITIES.join(', ')}`, () => {
442
- console.log(chalk_1.default.red(`Invalid priority: ${p}`));
443
- console.log(`Valid priorities: ${models_1.PRIORITIES.join(', ')}`);
441
+ throw new TaskListValidationError(`Invalid priority: ${p}. Valid priorities: ${models_1.PRIORITIES.join(', ')}`, () => {
442
+ console.error(chalk_1.default.red(`Invalid priority: ${p}`));
443
+ console.error(`Valid priorities: ${models_1.PRIORITIES.join(', ')}`);
444
444
  });
445
- process.exit(1);
446
445
  }
447
446
  }
448
447
  }
@@ -455,9 +454,9 @@ function normalizePriorityFilter(priorityParts) {
455
454
  /**
456
455
  * Resolve tag filter from options to an array of tag IDs.
457
456
  * Returns undefined if no tag filter is specified.
458
- * Exits with error if tag filter is invalid.
457
+ * Throws TaskListValidationError if the tag filter is invalid.
459
458
  */
460
- function resolveTagIds(tagOption, tagService, formatter) {
459
+ function resolveTagIds(tagOption, tagService) {
461
460
  if (!tagOption) {
462
461
  return undefined;
463
462
  }
@@ -466,20 +465,18 @@ function resolveTagIds(tagOption, tagService, formatter) {
466
465
  .map((s) => s.trim())
467
466
  .filter((s) => s !== '');
468
467
  if (parts.length === 0) {
469
- formatter.error('Invalid tag filter. Provide tag IDs or names.', () => {
470
- console.log(chalk_1.default.red('\nError: Invalid tag filter. Provide tag IDs or names.\n'));
468
+ throw new TaskListValidationError('Invalid tag filter. Provide tag IDs or names.', () => {
469
+ console.error(chalk_1.default.red('\nError: Invalid tag filter. Provide tag IDs or names.\n'));
471
470
  });
472
- process.exit(1);
473
471
  }
474
472
  const tagIds = [];
475
473
  for (const part of parts) {
476
474
  const { tag, byId } = (0, tag_resolver_1.resolveTag)(tagService, part);
477
475
  if (!tag) {
478
476
  const message = byId ? `Tag with ID "${part}" not found` : `Tag with name "${part}" not found`;
479
- formatter.error(message, () => {
480
- console.log(chalk_1.default.red(`\nError: ${message}\n`));
477
+ throw new TaskListValidationError(message, () => {
478
+ console.error(chalk_1.default.red(`\nError: ${message}\n`));
481
479
  });
482
- process.exit(1);
483
480
  }
484
481
  tagIds.push(tag.id);
485
482
  }
@@ -488,14 +485,14 @@ function resolveTagIds(tagOption, tagService, formatter) {
488
485
  /**
489
486
  * Handle tree view output.
490
487
  */
491
- function handleTreeView(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata, formatter) {
488
+ function handleTreeView(displayTasks, options, tagIds, childrenByParentId, allTaskTags, allTasksMetadata, formatter) {
492
489
  const rootTasks = displayTasks.filter((task) => !task.parent_id);
493
- formatter.output(() => buildTreeJsonOutput(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata), () => {
490
+ formatter.output(() => buildTreeJsonOutput(displayTasks, options, tagIds, childrenByParentId, allTaskTags, allTasksMetadata), () => {
494
491
  console.log(chalk_1.default.bold(`\nFound ${displayTasks.length} task(s) in tree view:\n`));
495
492
  console.log(chalk_1.default.bold('─'.repeat(80)));
496
493
  rootTasks.forEach((task, index) => {
497
494
  const isLast = index === rootTasks.length - 1;
498
- displayTaskTree(taskService, task, '', isLast, allTasksMetadata);
495
+ displayTaskTree(task, childrenByParentId, '', isLast, allTasksMetadata);
499
496
  });
500
497
  console.log('\n');
501
498
  });
@@ -503,16 +500,16 @@ function handleTreeView(displayTasks, options, tagIds, taskService, allTaskTags,
503
500
  /**
504
501
  * Handle dependency tree view output.
505
502
  */
506
- function handleDepTreeView(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata, taskBlockService, formatter) {
503
+ function handleDepTreeView(displayTasks, options, tagIds, taskById, allTaskTags, allTasksMetadata, taskBlockService, formatter) {
507
504
  const blockMap = buildBlockMap(taskBlockService);
508
505
  const allBlockedIds = collectAllBlockedIds(blockMap);
509
506
  const rootTasks = displayTasks.filter((task) => !allBlockedIds.has(task.id));
510
- formatter.output(() => buildDepTreeJsonOutput(displayTasks, options, tagIds, taskService, blockMap, allTaskTags, allTasksMetadata), () => {
507
+ formatter.output(() => buildDepTreeJsonOutput(displayTasks, options, tagIds, taskById, blockMap, allTaskTags, allTasksMetadata), () => {
511
508
  console.log(chalk_1.default.bold(`\nFound ${displayTasks.length} task(s) in dependency tree view:\n`));
512
- console.log(chalk_1.default.bold('\u2500'.repeat(80)));
509
+ console.log(chalk_1.default.bold(''.repeat(80)));
513
510
  rootTasks.forEach((task, index) => {
514
511
  const isLast = index === rootTasks.length - 1;
515
- displayDependencyTree(task, taskService, blockMap, allTasksMetadata, '', isLast, new Set());
512
+ displayDependencyTree(task, taskById, blockMap, allTasksMetadata, '', isLast, new Set());
516
513
  });
517
514
  console.log('\n');
518
515
  });
@@ -520,13 +517,13 @@ function handleDepTreeView(displayTasks, options, tagIds, taskService, allTaskTa
520
517
  /**
521
518
  * Handle normal list view output.
522
519
  */
523
- function handleListView(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata, formatter) {
524
- formatter.output(() => buildListJsonOutput(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata), () => {
520
+ function handleListView(displayTasks, options, tagIds, taskById, allTaskTags, allTasksMetadata, formatter) {
521
+ formatter.output(() => buildListJsonOutput(displayTasks, options, tagIds, taskById, allTaskTags, allTasksMetadata), () => {
525
522
  console.log(chalk_1.default.bold(`\nFound ${displayTasks.length} task(s):\n`));
526
523
  console.log(chalk_1.default.bold('─'.repeat(80)));
527
524
  displayTasks.forEach((task, index) => {
528
525
  const isLast = index === displayTasks.length - 1;
529
- printTaskRow(task, taskService, allTaskTags, allTasksMetadata, isLast);
526
+ printTaskRow(task, taskById, allTaskTags, allTasksMetadata, isLast);
530
527
  });
531
528
  console.log('\n');
532
529
  });
@@ -570,41 +567,44 @@ function handleEmptyResults(options, tagIds, tasks, formatter) {
570
567
  /**
571
568
  * Resolve all filter parameters from command options.
572
569
  */
573
- function resolveFilters(options, tagService, formatter) {
570
+ function resolveFilters(options, tagService) {
574
571
  // Validate and normalize status filter
575
572
  let statusFilter;
576
573
  if (options.status) {
577
574
  const statusParts = parseStatusFilter(options.status);
578
- validateStatuses(statusParts, formatter);
575
+ validateStatuses(statusParts);
579
576
  statusFilter = normalizeStatusFilter(statusParts);
580
577
  }
581
578
  // Validate sort field and order
582
579
  if (options.sort) {
583
- validateSortField(options.sort, formatter);
580
+ validateSortField(options.sort);
584
581
  }
585
582
  if (options.order) {
586
- validateSortOrder(options.order, formatter);
583
+ validateSortOrder(options.order);
587
584
  }
588
585
  // Parse and resolve tag filter
589
- const tagIds = resolveTagIds(options.tag, tagService, formatter);
586
+ const tagIds = resolveTagIds(options.tag, tagService);
590
587
  // Validate and normalize priority filter
591
588
  let priorityFilter;
592
589
  if (options.priority) {
593
590
  const priorityParts = parsePriorityFilter(options.priority);
594
- validatePriorities(priorityParts, formatter);
591
+ validatePriorities(priorityParts);
595
592
  priorityFilter = normalizePriorityFilter(priorityParts);
596
593
  }
597
594
  return { statusFilter, tagIds, priorityFilter };
598
595
  }
599
596
  /**
600
- * Fetch task data and relations (tags, metadata, blocks).
597
+ * Fetch task data and relations (tags, metadata, blocks, parent/children maps).
601
598
  */
602
- function fetchTaskRelations() {
599
+ function fetchTaskRelations(taskService) {
603
600
  const { taskTagService, metadataService, taskBlockService } = (0, service_container_1.getServiceContainer)();
601
+ const { taskById, childrenByParentId } = buildTaskMaps(taskService);
604
602
  return {
605
603
  allTaskTags: taskTagService.getAllTaskTags(),
606
604
  allTasksMetadata: metadataService.getAllTasksMetadata(),
607
605
  taskBlockService,
606
+ taskById,
607
+ childrenByParentId,
608
608
  };
609
609
  }
610
610
  /**
@@ -627,21 +627,21 @@ function queryAndFilterTasks(taskService, options, statusFilter, tagIds, priorit
627
627
  */
628
628
  async function executeListAction(options, formatter) {
629
629
  const { taskService, tagService } = (0, service_container_1.getServiceContainer)();
630
- const { statusFilter, tagIds, priorityFilter } = resolveFilters(options, tagService, formatter);
630
+ const { statusFilter, tagIds, priorityFilter } = resolveFilters(options, tagService);
631
631
  const { displayTasks, allTasks } = queryAndFilterTasks(taskService, options, statusFilter, tagIds, priorityFilter);
632
632
  if (displayTasks.length === 0) {
633
633
  handleEmptyResults(options, tagIds, allTasks, formatter);
634
634
  return;
635
635
  }
636
- const { allTaskTags, allTasksMetadata, taskBlockService } = fetchTaskRelations();
636
+ const { allTaskTags, allTasksMetadata, taskBlockService, taskById, childrenByParentId } = fetchTaskRelations(taskService);
637
637
  if (options.tree) {
638
- handleTreeView(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata, formatter);
638
+ handleTreeView(displayTasks, options, tagIds, childrenByParentId, allTaskTags, allTasksMetadata, formatter);
639
639
  }
640
640
  else if (options.depTree) {
641
- handleDepTreeView(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata, taskBlockService, formatter);
641
+ handleDepTreeView(displayTasks, options, tagIds, taskById, allTaskTags, allTasksMetadata, taskBlockService, formatter);
642
642
  }
643
643
  else {
644
- handleListView(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata, formatter);
644
+ handleListView(displayTasks, options, tagIds, taskById, allTaskTags, allTasksMetadata, formatter);
645
645
  }
646
646
  }
647
647
  function setupTaskListCommand(program) {
@@ -671,12 +671,15 @@ function setupTaskListCommand(program) {
671
671
  await executeListAction(options, formatter);
672
672
  }
673
673
  catch (error) {
674
- if (error instanceof Error) {
674
+ if (error instanceof TaskListValidationError) {
675
+ formatter.error(error.message, error.render);
676
+ }
677
+ else if (error instanceof Error) {
675
678
  (0, error_handler_1.handleError)(error, options);
676
679
  }
677
680
  else {
678
681
  formatter.error('An unknown error occurred', () => {
679
- console.log(chalk_1.default.red('\n✗ An unknown error occurred\n'));
682
+ console.error(chalk_1.default.red('\n✗ An unknown error occurred\n'));
680
683
  });
681
684
  }
682
685
  process.exit(1);