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