agkan 3.15.0 → 3.15.2

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 (110) 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 +33 -1
  4. package/dist/board/boardRoutes.js.map +1 -1
  5. package/dist/board/client/main.js +103 -183
  6. package/dist/board/server.js +1 -1
  7. package/dist/board/server.js.map +1 -1
  8. package/dist/cli/commands/block/add.js +10 -10
  9. package/dist/cli/commands/block/add.js.map +1 -1
  10. package/dist/cli/commands/block/list.js +4 -4
  11. package/dist/cli/commands/block/list.js.map +1 -1
  12. package/dist/cli/commands/block/remove.js +9 -9
  13. package/dist/cli/commands/block/remove.js.map +1 -1
  14. package/dist/cli/commands/comment/add.js +2 -2
  15. package/dist/cli/commands/comment/add.js.map +1 -1
  16. package/dist/cli/commands/comment/delete.js +2 -2
  17. package/dist/cli/commands/comment/delete.js.map +1 -1
  18. package/dist/cli/commands/comment/list.js +2 -2
  19. package/dist/cli/commands/comment/list.js.map +1 -1
  20. package/dist/cli/commands/config/get.js +2 -2
  21. package/dist/cli/commands/config/get.js.map +1 -1
  22. package/dist/cli/commands/meta/delete.js +3 -3
  23. package/dist/cli/commands/meta/delete.js.map +1 -1
  24. package/dist/cli/commands/meta/get.js +3 -3
  25. package/dist/cli/commands/meta/get.js.map +1 -1
  26. package/dist/cli/commands/meta/list.js +2 -2
  27. package/dist/cli/commands/meta/list.js.map +1 -1
  28. package/dist/cli/commands/meta/set.js +4 -4
  29. package/dist/cli/commands/meta/set.js.map +1 -1
  30. package/dist/cli/commands/tag/add.js +7 -7
  31. package/dist/cli/commands/tag/add.js.map +1 -1
  32. package/dist/cli/commands/tag/attach.js +7 -7
  33. package/dist/cli/commands/tag/attach.js.map +1 -1
  34. package/dist/cli/commands/tag/delete.js +5 -5
  35. package/dist/cli/commands/tag/delete.js.map +1 -1
  36. package/dist/cli/commands/tag/detach.js +7 -7
  37. package/dist/cli/commands/tag/detach.js.map +1 -1
  38. package/dist/cli/commands/tag/list.js +2 -2
  39. package/dist/cli/commands/tag/list.js.map +1 -1
  40. package/dist/cli/commands/tag/rename.js +8 -8
  41. package/dist/cli/commands/tag/rename.js.map +1 -1
  42. package/dist/cli/commands/tag/show.js +3 -3
  43. package/dist/cli/commands/tag/show.js.map +1 -1
  44. package/dist/cli/commands/task/add.js +11 -11
  45. package/dist/cli/commands/task/add.js.map +1 -1
  46. package/dist/cli/commands/task/archive.js +3 -3
  47. package/dist/cli/commands/task/archive.js.map +1 -1
  48. package/dist/cli/commands/task/copy.js +5 -5
  49. package/dist/cli/commands/task/copy.js.map +1 -1
  50. package/dist/cli/commands/task/count.js +4 -4
  51. package/dist/cli/commands/task/count.js.map +1 -1
  52. package/dist/cli/commands/task/delete.js +3 -3
  53. package/dist/cli/commands/task/delete.js.map +1 -1
  54. package/dist/cli/commands/task/find.js +2 -2
  55. package/dist/cli/commands/task/find.js.map +1 -1
  56. package/dist/cli/commands/task/get.js +3 -3
  57. package/dist/cli/commands/task/get.js.map +1 -1
  58. package/dist/cli/commands/task/list.d.ts.map +1 -1
  59. package/dist/cli/commands/task/list.js +195 -162
  60. package/dist/cli/commands/task/list.js.map +1 -1
  61. package/dist/cli/commands/task/purge.js +3 -3
  62. package/dist/cli/commands/task/purge.js.map +1 -1
  63. package/dist/cli/commands/task/unarchive.js +3 -3
  64. package/dist/cli/commands/task/unarchive.js.map +1 -1
  65. package/dist/cli/commands/task/update-helpers.js +11 -11
  66. package/dist/cli/commands/task/update-helpers.js.map +1 -1
  67. package/dist/cli/commands/task/update-parent.js +8 -8
  68. package/dist/cli/commands/task/update-parent.js.map +1 -1
  69. package/dist/cli/commands/task/update.js +3 -3
  70. package/dist/cli/commands/task/update.js.map +1 -1
  71. package/dist/cli/utils/error-handler.js +4 -4
  72. package/dist/cli/utils/error-handler.js.map +1 -1
  73. package/dist/cli/utils/output-formatter.js +2 -2
  74. package/dist/cli/utils/output-formatter.js.map +1 -1
  75. package/dist/db/adapters/sqlite-storage-backend.js +4 -4
  76. package/dist/db/adapters/sqlite-storage-backend.js.map +1 -1
  77. package/dist/hooks/hook-stop.mjs +107 -42
  78. package/dist/services/ExportImportService.d.ts +11 -0
  79. package/dist/services/ExportImportService.d.ts.map +1 -1
  80. package/dist/services/ExportImportService.js +44 -30
  81. package/dist/services/ExportImportService.js.map +1 -1
  82. package/dist/services/index.d.ts +1 -2
  83. package/dist/services/index.d.ts.map +1 -1
  84. package/dist/services/index.js +1 -3
  85. package/dist/services/index.js.map +1 -1
  86. package/dist/services/types.d.ts +28 -0
  87. package/dist/services/types.d.ts.map +1 -0
  88. package/dist/services/types.js +6 -0
  89. package/dist/services/types.js.map +1 -0
  90. package/dist/terminal/PtySessionService.d.ts +1 -1
  91. package/dist/terminal/PtySessionService.d.ts.map +1 -1
  92. package/dist/terminal/PtySessionService.js +24 -18
  93. package/dist/terminal/PtySessionService.js.map +1 -1
  94. package/dist/terminal/buildHookEnv.d.ts +8 -0
  95. package/dist/terminal/buildHookEnv.d.ts.map +1 -0
  96. package/dist/terminal/buildHookEnv.js +24 -0
  97. package/dist/terminal/buildHookEnv.js.map +1 -0
  98. package/dist/terminal/ensureSpawnHelperExecutable.d.ts +31 -0
  99. package/dist/terminal/ensureSpawnHelperExecutable.d.ts.map +1 -0
  100. package/dist/terminal/ensureSpawnHelperExecutable.js +89 -0
  101. package/dist/terminal/ensureSpawnHelperExecutable.js.map +1 -0
  102. package/dist/utils/runTargetStatus.d.ts +9 -0
  103. package/dist/utils/runTargetStatus.d.ts.map +1 -0
  104. package/dist/utils/runTargetStatus.js +17 -0
  105. package/dist/utils/runTargetStatus.js.map +1 -0
  106. package/package.json +3 -2
  107. package/dist/services/ClaudeProcessService.d.ts +0 -110
  108. package/dist/services/ClaudeProcessService.d.ts.map +0 -1
  109. package/dist/services/ClaudeProcessService.js +0 -312
  110. 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.
21
23
  */
22
- function printTreeTaskDetails(task, childPrefix, allTasksMetadata) {
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.
73
+ */
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,50 +87,65 @@ 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
  }
114
+ /**
115
+ * Determine the root tasks to render for tree view output. A task is a root
116
+ * if none of its ancestors (walked via the full, unfiltered taskById map) are
117
+ * present in the filtered result set (pseudo-root). Without this, a filter
118
+ * (e.g. `-s in_progress`) that only matches child tasks would report a
119
+ * non-zero count while rendering nothing, since the real root would be
120
+ * filtered out.
121
+ *
122
+ * Checking the entire ancestor chain (not just the immediate parent) avoids
123
+ * rendering a task twice: if some ancestor is in the display set, that
124
+ * ancestor will itself become a root (or a pseudo-root) and its full,
125
+ * unfiltered subtree — which already includes this task — gets printed.
126
+ */
127
+ function getTreeRootTasks(displayTasks, taskById) {
128
+ const displayTaskIds = new Set(displayTasks.map((task) => task.id));
129
+ return displayTasks.filter((task) => {
130
+ let current = task;
131
+ while (current.parent_id != null) {
132
+ if (displayTaskIds.has(current.parent_id)) {
133
+ return false;
134
+ }
135
+ const parent = taskById.get(current.parent_id);
136
+ if (!parent) {
137
+ break;
138
+ }
139
+ current = parent;
140
+ }
141
+ return true;
142
+ });
143
+ }
71
144
  /**
72
145
  * Build the JSON output object for the tree view.
73
146
  */
74
- function buildTreeJsonOutput(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata) {
75
- const rootTasks = displayTasks.filter((task) => !task.parent_id);
147
+ function buildTreeJsonOutput(displayTasks, options, tagIds, childrenByParentId, taskById, allTaskTags, allTasksMetadata) {
148
+ const rootTasks = getTreeRootTasks(displayTasks, taskById);
76
149
  return {
77
150
  totalCount: displayTasks.length,
78
151
  viewMode: 'tree',
@@ -87,14 +160,14 @@ function buildTreeJsonOutput(displayTasks, options, tagIds, taskService, allTask
87
160
  },
88
161
  sort: options.sort || 'created_at',
89
162
  order: options.order || 'desc',
90
- tasks: rootTasks.map((task) => buildTreeNode(task, taskService, allTaskTags, allTasksMetadata)),
163
+ tasks: rootTasks.map((task) => buildTreeNode(task, childrenByParentId, allTaskTags, allTasksMetadata)),
91
164
  };
92
165
  }
93
166
  /**
94
167
  * Build the JSON output object for the normal (flat) list view.
95
168
  */
96
- function buildListJsonOutput(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata) {
97
- const tasksWithRelations = displayTasks.map((task) => buildTaskWithRelations(task, taskService, allTaskTags, allTasksMetadata));
169
+ function buildListJsonOutput(displayTasks, options, tagIds, taskById, allTaskTags, allTasksMetadata) {
170
+ const tasksWithRelations = displayTasks.map((task) => buildTaskWithRelations(task, taskById, allTaskTags, allTasksMetadata));
98
171
  return {
99
172
  totalCount: displayTasks.length,
100
173
  filters: {
@@ -113,24 +186,11 @@ function buildListJsonOutput(displayTasks, options, tagIds, taskService, allTask
113
186
  /**
114
187
  * Build a single task object with parent, tags and metadata relations.
115
188
  */
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;
189
+ function buildTaskWithRelations(task, taskById, allTaskTags, allTasksMetadata) {
190
+ const parent = task.parent_id ? (taskById.get(task.parent_id) ?? null) : null;
120
191
  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,
192
+ ...mapTaskFields(task, allTaskTags, allTasksMetadata),
131
193
  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
194
  };
135
195
  }
136
196
  /**
@@ -158,65 +218,35 @@ function collectAllBlockedIds(blockMap) {
158
218
  }
159
219
  return allBlockedIds;
160
220
  }
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
221
  /**
181
222
  * Recursively display dependency tree (blocker -> blocked only).
182
223
  */
183
- function displayDependencyTree(task, taskService, blockMap, allTasksMetadata, prefix, isLast, visited, relationshipLabel) {
184
- printTreeNodeLine(task, allTasksMetadata, prefix, isLast, relationshipLabel);
224
+ function displayDependencyTree(task, taskById, blockMap, allTasksMetadata, prefix, isLast, visited, relationshipLabel) {
225
+ printTreeNode(task, allTasksMetadata, prefix, isLast, relationshipLabel);
185
226
  if (visited.has(task.id)) {
186
227
  return;
187
228
  }
188
229
  visited.add(task.id);
189
230
  const blockedIds = blockMap.get(task.id) || [];
190
- const newPrefix = prefix + (isLast ? ' ' : '\u2502 ');
231
+ const newPrefix = prefix + (isLast ? ' ' : ' ');
191
232
  blockedIds.forEach((blockedId, index) => {
192
- const childTask = taskService.getTask(blockedId);
233
+ const childTask = taskById.get(blockedId);
193
234
  if (childTask && !visited.has(blockedId)) {
194
235
  const isChildLast = index === blockedIds.length - 1;
195
- displayDependencyTree(childTask, taskService, blockMap, allTasksMetadata, newPrefix, isChildLast, new Set(visited), '[blocks]');
236
+ displayDependencyTree(childTask, taskById, blockMap, allTasksMetadata, newPrefix, isChildLast, new Set(visited), '[blocks]');
196
237
  }
197
238
  });
198
239
  }
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
240
  /**
211
241
  * Build blocked task nodes recursively.
212
242
  */
213
- function buildBlockedNodes(blockedIds, taskService, blockMap, allTaskTags, allTasksMetadata, visited) {
243
+ function buildBlockedNodes(blockedIds, taskById, blockMap, allTaskTags, allTasksMetadata, visited) {
214
244
  const blocks = [];
215
245
  for (const blockedId of blockedIds) {
216
246
  if (!visited.has(blockedId)) {
217
- const blockedTask = taskService.getTask(blockedId);
247
+ const blockedTask = taskById.get(blockedId);
218
248
  if (blockedTask) {
219
- blocks.push(buildDepTreeNode(blockedTask, taskService, blockMap, allTaskTags, allTasksMetadata, new Set(visited)));
249
+ blocks.push(buildDepTreeNode(blockedTask, taskById, blockMap, allTaskTags, allTasksMetadata, new Set(visited)));
220
250
  }
221
251
  }
222
252
  }
@@ -225,31 +255,19 @@ function buildBlockedNodes(blockedIds, taskService, blockMap, allTaskTags, allTa
225
255
  /**
226
256
  * Build a dependency tree node recursively (blocking relationships only).
227
257
  */
228
- function buildDepTreeNode(task, taskService, blockMap, allTaskTags, allTasksMetadata, visited) {
258
+ function buildDepTreeNode(task, taskById, blockMap, allTaskTags, allTasksMetadata, visited) {
229
259
  visited.add(task.id);
230
260
  const blockedIds = blockMap.get(task.id) || [];
231
- const { tags, metadata } = extractTaskRelations(task, allTaskTags, allTasksMetadata);
232
- const blocks = buildBlockedNodes(blockedIds, taskService, blockMap, allTaskTags, allTasksMetadata, visited);
261
+ const blocks = buildBlockedNodes(blockedIds, taskById, blockMap, allTaskTags, allTasksMetadata, visited);
233
262
  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,
263
+ ...mapTaskFields(task, allTaskTags, allTasksMetadata),
246
264
  blocks,
247
265
  };
248
266
  }
249
267
  /**
250
268
  * Build the JSON output for dep-tree view.
251
269
  */
252
- function buildDepTreeJsonOutput(displayTasks, options, tagIds, taskService, blockMap, allTaskTags, allTasksMetadata) {
270
+ function buildDepTreeJsonOutput(displayTasks, options, tagIds, taskById, blockMap, allTaskTags, allTasksMetadata) {
253
271
  const allBlockedIds = collectAllBlockedIds(blockMap);
254
272
  const rootTasks = displayTasks.filter((task) => !allBlockedIds.has(task.id));
255
273
  return {
@@ -263,7 +281,7 @@ function buildDepTreeJsonOutput(displayTasks, options, tagIds, taskService, bloc
263
281
  all: options.all || false,
264
282
  priority: options.priority || null,
265
283
  },
266
- tasks: rootTasks.map((task) => buildDepTreeNode(task, taskService, blockMap, allTaskTags, allTasksMetadata, new Set())),
284
+ tasks: rootTasks.map((task) => buildDepTreeNode(task, taskById, blockMap, allTaskTags, allTasksMetadata, new Set())),
267
285
  };
268
286
  }
269
287
  /**
@@ -307,10 +325,10 @@ function printTaskPersonInfo(task) {
307
325
  /**
308
326
  * Print task parent reference.
309
327
  */
310
- function printTaskParent(task, taskService) {
328
+ function printTaskParent(task, taskById) {
311
329
  if (!task.parent_id)
312
330
  return;
313
- const parentTask = taskService.getTask(task.parent_id);
331
+ const parentTask = taskById.get(task.parent_id);
314
332
  if (parentTask) {
315
333
  console.log(` ${chalk_1.default.bold('Parent:')} ${chalk_1.default.cyan(`[${parentTask.id}]`)} ${parentTask.title}`);
316
334
  }
@@ -360,10 +378,10 @@ function printTaskSeparator(isLast) {
360
378
  /**
361
379
  * Print a single task row in the human-readable list view.
362
380
  */
363
- function printTaskRow(task, taskService, allTaskTags, allTasksMetadata, isLast) {
381
+ function printTaskRow(task, taskById, allTaskTags, allTasksMetadata, isLast) {
364
382
  printTaskHeader(task);
365
383
  printTaskPersonInfo(task);
366
- printTaskParent(task, taskService);
384
+ printTaskParent(task, taskById);
367
385
  printTaskPriority(task);
368
386
  printTaskTags(task.id, allTaskTags);
369
387
  printTaskMetadata(task.id, allTasksMetadata);
@@ -379,17 +397,31 @@ function parseStatusFilter(statusStr) {
379
397
  .map((s) => s.trim())
380
398
  .filter((s) => s !== '');
381
399
  }
400
+ /**
401
+ * Validation error for task list command options.
402
+ * Carries the exact text-mode rendering previously passed to formatter.error,
403
+ * so the action's catch (setupTaskListCommand) can reproduce identical output
404
+ * while owning the single process.exit(1) call site.
405
+ */
406
+ class TaskListValidationError extends errors_1.ValidationError {
407
+ render;
408
+ constructor(message, render) {
409
+ super(message);
410
+ this.render = render;
411
+ this.name = 'TaskListValidationError';
412
+ Object.setPrototypeOf(this, new.target.prototype);
413
+ }
414
+ }
382
415
  /**
383
416
  * Validate status values.
384
417
  */
385
- function validateStatuses(statuses, formatter) {
418
+ function validateStatuses(statuses) {
386
419
  for (const s of statuses) {
387
420
  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');
421
+ throw new TaskListValidationError(`Invalid status: ${s}. Valid statuses: icebox, backlog, ready, in_progress, review, done, closed`, () => {
422
+ console.error(chalk_1.default.red(`Invalid status: ${s}`));
423
+ console.error('Valid statuses: icebox, backlog, ready, in_progress, review, done, closed');
391
424
  });
392
- process.exit(1);
393
425
  }
394
426
  }
395
427
  }
@@ -402,25 +434,23 @@ function normalizeStatusFilter(statusParts) {
402
434
  /**
403
435
  * Validate sort field.
404
436
  */
405
- function validateSortField(sortField, formatter) {
437
+ function validateSortField(sortField) {
406
438
  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(', ')}`);
439
+ throw new TaskListValidationError(`Invalid sort field: ${sortField}. Valid fields: ${TaskService_1.ALLOWED_SORT_FIELDS.join(', ')}`, () => {
440
+ console.error(chalk_1.default.red(`Invalid sort field: ${sortField}`));
441
+ console.error(`Valid fields: ${TaskService_1.ALLOWED_SORT_FIELDS.join(', ')}`);
410
442
  });
411
- process.exit(1);
412
443
  }
413
444
  }
414
445
  /**
415
446
  * Validate sort order.
416
447
  */
417
- function validateSortOrder(sortOrder, formatter) {
448
+ function validateSortOrder(sortOrder) {
418
449
  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');
450
+ throw new TaskListValidationError(`Invalid sort order: ${sortOrder}. Valid orders: asc, desc`, () => {
451
+ console.error(chalk_1.default.red(`Invalid sort order: ${sortOrder}`));
452
+ console.error('Valid orders: asc, desc');
422
453
  });
423
- process.exit(1);
424
454
  }
425
455
  }
426
456
  /**
@@ -435,14 +465,13 @@ function parsePriorityFilter(priorityStr) {
435
465
  /**
436
466
  * Validate priority values.
437
467
  */
438
- function validatePriorities(priorities, formatter) {
468
+ function validatePriorities(priorities) {
439
469
  for (const p of priorities) {
440
470
  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(', ')}`);
471
+ throw new TaskListValidationError(`Invalid priority: ${p}. Valid priorities: ${models_1.PRIORITIES.join(', ')}`, () => {
472
+ console.error(chalk_1.default.red(`Invalid priority: ${p}`));
473
+ console.error(`Valid priorities: ${models_1.PRIORITIES.join(', ')}`);
444
474
  });
445
- process.exit(1);
446
475
  }
447
476
  }
448
477
  }
@@ -455,9 +484,9 @@ function normalizePriorityFilter(priorityParts) {
455
484
  /**
456
485
  * Resolve tag filter from options to an array of tag IDs.
457
486
  * Returns undefined if no tag filter is specified.
458
- * Exits with error if tag filter is invalid.
487
+ * Throws TaskListValidationError if the tag filter is invalid.
459
488
  */
460
- function resolveTagIds(tagOption, tagService, formatter) {
489
+ function resolveTagIds(tagOption, tagService) {
461
490
  if (!tagOption) {
462
491
  return undefined;
463
492
  }
@@ -466,20 +495,18 @@ function resolveTagIds(tagOption, tagService, formatter) {
466
495
  .map((s) => s.trim())
467
496
  .filter((s) => s !== '');
468
497
  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'));
498
+ throw new TaskListValidationError('Invalid tag filter. Provide tag IDs or names.', () => {
499
+ console.error(chalk_1.default.red('\nError: Invalid tag filter. Provide tag IDs or names.\n'));
471
500
  });
472
- process.exit(1);
473
501
  }
474
502
  const tagIds = [];
475
503
  for (const part of parts) {
476
504
  const { tag, byId } = (0, tag_resolver_1.resolveTag)(tagService, part);
477
505
  if (!tag) {
478
506
  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`));
507
+ throw new TaskListValidationError(message, () => {
508
+ console.error(chalk_1.default.red(`\nError: ${message}\n`));
481
509
  });
482
- process.exit(1);
483
510
  }
484
511
  tagIds.push(tag.id);
485
512
  }
@@ -488,14 +515,14 @@ function resolveTagIds(tagOption, tagService, formatter) {
488
515
  /**
489
516
  * Handle tree view output.
490
517
  */
491
- function handleTreeView(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata, formatter) {
492
- const rootTasks = displayTasks.filter((task) => !task.parent_id);
493
- formatter.output(() => buildTreeJsonOutput(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata), () => {
518
+ function handleTreeView(displayTasks, options, tagIds, childrenByParentId, taskById, allTaskTags, allTasksMetadata, formatter) {
519
+ const rootTasks = getTreeRootTasks(displayTasks, taskById);
520
+ formatter.output(() => buildTreeJsonOutput(displayTasks, options, tagIds, childrenByParentId, taskById, allTaskTags, allTasksMetadata), () => {
494
521
  console.log(chalk_1.default.bold(`\nFound ${displayTasks.length} task(s) in tree view:\n`));
495
522
  console.log(chalk_1.default.bold('─'.repeat(80)));
496
523
  rootTasks.forEach((task, index) => {
497
524
  const isLast = index === rootTasks.length - 1;
498
- displayTaskTree(taskService, task, '', isLast, allTasksMetadata);
525
+ displayTaskTree(task, childrenByParentId, '', isLast, allTasksMetadata);
499
526
  });
500
527
  console.log('\n');
501
528
  });
@@ -503,16 +530,16 @@ function handleTreeView(displayTasks, options, tagIds, taskService, allTaskTags,
503
530
  /**
504
531
  * Handle dependency tree view output.
505
532
  */
506
- function handleDepTreeView(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata, taskBlockService, formatter) {
533
+ function handleDepTreeView(displayTasks, options, tagIds, taskById, allTaskTags, allTasksMetadata, taskBlockService, formatter) {
507
534
  const blockMap = buildBlockMap(taskBlockService);
508
535
  const allBlockedIds = collectAllBlockedIds(blockMap);
509
536
  const rootTasks = displayTasks.filter((task) => !allBlockedIds.has(task.id));
510
- formatter.output(() => buildDepTreeJsonOutput(displayTasks, options, tagIds, taskService, blockMap, allTaskTags, allTasksMetadata), () => {
537
+ formatter.output(() => buildDepTreeJsonOutput(displayTasks, options, tagIds, taskById, blockMap, allTaskTags, allTasksMetadata), () => {
511
538
  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)));
539
+ console.log(chalk_1.default.bold(''.repeat(80)));
513
540
  rootTasks.forEach((task, index) => {
514
541
  const isLast = index === rootTasks.length - 1;
515
- displayDependencyTree(task, taskService, blockMap, allTasksMetadata, '', isLast, new Set());
542
+ displayDependencyTree(task, taskById, blockMap, allTasksMetadata, '', isLast, new Set());
516
543
  });
517
544
  console.log('\n');
518
545
  });
@@ -520,13 +547,13 @@ function handleDepTreeView(displayTasks, options, tagIds, taskService, allTaskTa
520
547
  /**
521
548
  * Handle normal list view output.
522
549
  */
523
- function handleListView(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata, formatter) {
524
- formatter.output(() => buildListJsonOutput(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata), () => {
550
+ function handleListView(displayTasks, options, tagIds, taskById, allTaskTags, allTasksMetadata, formatter) {
551
+ formatter.output(() => buildListJsonOutput(displayTasks, options, tagIds, taskById, allTaskTags, allTasksMetadata), () => {
525
552
  console.log(chalk_1.default.bold(`\nFound ${displayTasks.length} task(s):\n`));
526
553
  console.log(chalk_1.default.bold('─'.repeat(80)));
527
554
  displayTasks.forEach((task, index) => {
528
555
  const isLast = index === displayTasks.length - 1;
529
- printTaskRow(task, taskService, allTaskTags, allTasksMetadata, isLast);
556
+ printTaskRow(task, taskById, allTaskTags, allTasksMetadata, isLast);
530
557
  });
531
558
  console.log('\n');
532
559
  });
@@ -570,41 +597,44 @@ function handleEmptyResults(options, tagIds, tasks, formatter) {
570
597
  /**
571
598
  * Resolve all filter parameters from command options.
572
599
  */
573
- function resolveFilters(options, tagService, formatter) {
600
+ function resolveFilters(options, tagService) {
574
601
  // Validate and normalize status filter
575
602
  let statusFilter;
576
603
  if (options.status) {
577
604
  const statusParts = parseStatusFilter(options.status);
578
- validateStatuses(statusParts, formatter);
605
+ validateStatuses(statusParts);
579
606
  statusFilter = normalizeStatusFilter(statusParts);
580
607
  }
581
608
  // Validate sort field and order
582
609
  if (options.sort) {
583
- validateSortField(options.sort, formatter);
610
+ validateSortField(options.sort);
584
611
  }
585
612
  if (options.order) {
586
- validateSortOrder(options.order, formatter);
613
+ validateSortOrder(options.order);
587
614
  }
588
615
  // Parse and resolve tag filter
589
- const tagIds = resolveTagIds(options.tag, tagService, formatter);
616
+ const tagIds = resolveTagIds(options.tag, tagService);
590
617
  // Validate and normalize priority filter
591
618
  let priorityFilter;
592
619
  if (options.priority) {
593
620
  const priorityParts = parsePriorityFilter(options.priority);
594
- validatePriorities(priorityParts, formatter);
621
+ validatePriorities(priorityParts);
595
622
  priorityFilter = normalizePriorityFilter(priorityParts);
596
623
  }
597
624
  return { statusFilter, tagIds, priorityFilter };
598
625
  }
599
626
  /**
600
- * Fetch task data and relations (tags, metadata, blocks).
627
+ * Fetch task data and relations (tags, metadata, blocks, parent/children maps).
601
628
  */
602
- function fetchTaskRelations() {
629
+ function fetchTaskRelations(taskService) {
603
630
  const { taskTagService, metadataService, taskBlockService } = (0, service_container_1.getServiceContainer)();
631
+ const { taskById, childrenByParentId } = buildTaskMaps(taskService);
604
632
  return {
605
633
  allTaskTags: taskTagService.getAllTaskTags(),
606
634
  allTasksMetadata: metadataService.getAllTasksMetadata(),
607
635
  taskBlockService,
636
+ taskById,
637
+ childrenByParentId,
608
638
  };
609
639
  }
610
640
  /**
@@ -627,21 +657,21 @@ function queryAndFilterTasks(taskService, options, statusFilter, tagIds, priorit
627
657
  */
628
658
  async function executeListAction(options, formatter) {
629
659
  const { taskService, tagService } = (0, service_container_1.getServiceContainer)();
630
- const { statusFilter, tagIds, priorityFilter } = resolveFilters(options, tagService, formatter);
660
+ const { statusFilter, tagIds, priorityFilter } = resolveFilters(options, tagService);
631
661
  const { displayTasks, allTasks } = queryAndFilterTasks(taskService, options, statusFilter, tagIds, priorityFilter);
632
662
  if (displayTasks.length === 0) {
633
663
  handleEmptyResults(options, tagIds, allTasks, formatter);
634
664
  return;
635
665
  }
636
- const { allTaskTags, allTasksMetadata, taskBlockService } = fetchTaskRelations();
666
+ const { allTaskTags, allTasksMetadata, taskBlockService, taskById, childrenByParentId } = fetchTaskRelations(taskService);
637
667
  if (options.tree) {
638
- handleTreeView(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata, formatter);
668
+ handleTreeView(displayTasks, options, tagIds, childrenByParentId, taskById, allTaskTags, allTasksMetadata, formatter);
639
669
  }
640
670
  else if (options.depTree) {
641
- handleDepTreeView(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata, taskBlockService, formatter);
671
+ handleDepTreeView(displayTasks, options, tagIds, taskById, allTaskTags, allTasksMetadata, taskBlockService, formatter);
642
672
  }
643
673
  else {
644
- handleListView(displayTasks, options, tagIds, taskService, allTaskTags, allTasksMetadata, formatter);
674
+ handleListView(displayTasks, options, tagIds, taskById, allTaskTags, allTasksMetadata, formatter);
645
675
  }
646
676
  }
647
677
  function setupTaskListCommand(program) {
@@ -671,12 +701,15 @@ function setupTaskListCommand(program) {
671
701
  await executeListAction(options, formatter);
672
702
  }
673
703
  catch (error) {
674
- if (error instanceof Error) {
704
+ if (error instanceof TaskListValidationError) {
705
+ formatter.error(error.message, error.render);
706
+ }
707
+ else if (error instanceof Error) {
675
708
  (0, error_handler_1.handleError)(error, options);
676
709
  }
677
710
  else {
678
711
  formatter.error('An unknown error occurred', () => {
679
- console.log(chalk_1.default.red('\n✗ An unknown error occurred\n'));
712
+ console.error(chalk_1.default.red('\n✗ An unknown error occurred\n'));
680
713
  });
681
714
  }
682
715
  process.exit(1);