@compilr-dev/cli 0.5.1 → 0.5.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.
- package/LICENSE +108 -0
- package/README.md +2 -2
- package/dist/.tsbuildinfo.app +1 -1
- package/dist/.tsbuildinfo.data +1 -1
- package/dist/.tsbuildinfo.domain +1 -1
- package/dist/.tsbuildinfo.foundation +1 -1
- package/dist/commands-v2/handlers/context.js +20 -0
- package/dist/commands-v2/handlers/project.js +52 -9
- package/dist/compilr-diff-companion.vsix +0 -0
- package/dist/db/repositories/document-repository.js +1 -0
- package/dist/db/schema.d.ts +1 -1
- package/dist/repl-helpers.js +2 -0
- package/dist/repl-v2.js +16 -5
- package/dist/tool-names.d.ts +5 -0
- package/dist/tool-names.js +12 -0
- package/dist/tools/db-tools.d.ts +6 -1
- package/dist/tools/db-tools.js +6 -2
- package/dist/tools/meta-tools.d.ts +1 -1
- package/dist/tools/platform-adapter.d.ts +6 -0
- package/dist/tools/platform-adapter.js +10 -0
- package/dist/tools.js +3 -1
- package/dist/ui/constants/labels.js +1 -0
- package/dist/ui/overlay/impl/workflow-overlay-v2.d.ts +1 -0
- package/dist/ui/overlay/impl/workflow-overlay-v2.js +5 -3
- package/dist/ui/tool-formatters.js +190 -6
- package/package.json +5 -4
- package/dist/tools/anchor-tools.d.ts +0 -31
- package/dist/tools/anchor-tools.js +0 -255
- package/dist/tools/artifact-tools.d.ts +0 -42
- package/dist/tools/artifact-tools.js +0 -328
- package/dist/tools/backlog-wrappers.d.ts +0 -56
- package/dist/tools/backlog-wrappers.js +0 -353
- package/dist/tools/document-db.d.ts +0 -43
- package/dist/tools/document-db.js +0 -220
- package/dist/tools/plan-tools.d.ts +0 -54
- package/dist/tools/plan-tools.js +0 -338
- package/dist/tools/recall-work-tool.d.ts +0 -18
- package/dist/tools/recall-work-tool.js +0 -82
- package/dist/tools/workitem-db.d.ts +0 -135
- package/dist/tools/workitem-db.js +0 -730
|
@@ -1,730 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Work Item Database Tools
|
|
3
|
-
*
|
|
4
|
-
* Tools for managing work items (backlog items, tasks, bugs) in the database.
|
|
5
|
-
*/
|
|
6
|
-
import { defineTool, createSuccessResult, createErrorResult } from '@compilr-dev/sdk';
|
|
7
|
-
import { workItemRepository } from '../db/repositories/index.js';
|
|
8
|
-
import { getActiveProject } from './project-db.js';
|
|
9
|
-
import { getNextStep, isValidTransition, getStepCriteria, formatStepDisplay, STEP_ORDER, } from '../workflow/index.js';
|
|
10
|
-
import { awardWorkItemCompletion } from '../games/coins.js';
|
|
11
|
-
import { getActiveSharedContext } from '../multi-agent/activity.js';
|
|
12
|
-
/**
|
|
13
|
-
* workitem_query - Query work items with filters
|
|
14
|
-
*/
|
|
15
|
-
export const workitemQueryTool = defineTool({
|
|
16
|
-
name: 'workitem_query',
|
|
17
|
-
description: 'Query PERSISTENT work items from the project backlog (stored in database). ' +
|
|
18
|
-
'For session todos shown in footer, use todo_read. ' +
|
|
19
|
-
'Supports filtering by status, type, priority, owner, and search.',
|
|
20
|
-
inputSchema: {
|
|
21
|
-
type: 'object',
|
|
22
|
-
properties: {
|
|
23
|
-
project_id: {
|
|
24
|
-
type: 'number',
|
|
25
|
-
description: 'Project ID (uses active project if not provided)',
|
|
26
|
-
},
|
|
27
|
-
status: {
|
|
28
|
-
type: 'string',
|
|
29
|
-
enum: ['backlog', 'in_progress', 'completed', 'skipped', 'all'],
|
|
30
|
-
description: 'Filter by status (default: all)',
|
|
31
|
-
},
|
|
32
|
-
type: {
|
|
33
|
-
type: 'string',
|
|
34
|
-
enum: ['feature', 'bug', 'tech-debt', 'chore', 'all'],
|
|
35
|
-
description: 'Filter by type (default: all)',
|
|
36
|
-
},
|
|
37
|
-
priority: {
|
|
38
|
-
type: 'string',
|
|
39
|
-
enum: ['critical', 'high', 'medium', 'low', 'all'],
|
|
40
|
-
description: 'Filter by priority (default: all)',
|
|
41
|
-
},
|
|
42
|
-
owner: {
|
|
43
|
-
type: 'string',
|
|
44
|
-
description: 'Filter by owner: agent ID, "self" for your own items, "unassigned" for unowned, or "all" (default: all)',
|
|
45
|
-
},
|
|
46
|
-
search: {
|
|
47
|
-
type: 'string',
|
|
48
|
-
description: 'Search in title and description',
|
|
49
|
-
},
|
|
50
|
-
limit: {
|
|
51
|
-
type: 'number',
|
|
52
|
-
description: 'Maximum items to return (default: 50)',
|
|
53
|
-
},
|
|
54
|
-
offset: {
|
|
55
|
-
type: 'number',
|
|
56
|
-
description: 'Offset for pagination (default: 0)',
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
required: [],
|
|
60
|
-
},
|
|
61
|
-
execute: async (input) => {
|
|
62
|
-
await Promise.resolve(); // Required for async type signature
|
|
63
|
-
try {
|
|
64
|
-
const projectId = input.project_id || getActiveProject()?.id;
|
|
65
|
-
if (!projectId) {
|
|
66
|
-
return createErrorResult('No project specified and no active project. Use project_get or /projects to select a project first.');
|
|
67
|
-
}
|
|
68
|
-
// Resolve "self" to the active agent ID
|
|
69
|
-
// TODO(Phase 3): This uses the FOREGROUND agent's ID from SharedContext.
|
|
70
|
-
// When background agents are implemented, each agent session needs its own
|
|
71
|
-
// identity. The tool execution context should include the caller's agent ID,
|
|
72
|
-
// so background agents can correctly resolve "self" to their own ID.
|
|
73
|
-
// See: background-agents-spec.md - Tool Execution Context section
|
|
74
|
-
let resolvedOwner = input.owner;
|
|
75
|
-
if (input.owner === 'self') {
|
|
76
|
-
const sharedContext = getActiveSharedContext();
|
|
77
|
-
const activeAgent = sharedContext?.getTeam().activeAgent;
|
|
78
|
-
if (activeAgent && activeAgent !== 'default') {
|
|
79
|
-
resolvedOwner = activeAgent;
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
// No active team agent, treat as "all" or return empty
|
|
83
|
-
resolvedOwner = undefined;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
const queryInput = {
|
|
87
|
-
project_id: projectId,
|
|
88
|
-
status: input.status,
|
|
89
|
-
type: input.type,
|
|
90
|
-
priority: input.priority,
|
|
91
|
-
owner: resolvedOwner,
|
|
92
|
-
search: input.search,
|
|
93
|
-
limit: input.limit || 50,
|
|
94
|
-
offset: input.offset || 0,
|
|
95
|
-
};
|
|
96
|
-
const result = workItemRepository.query(queryInput);
|
|
97
|
-
const items = result.items.map((item) => ({
|
|
98
|
-
id: item.id,
|
|
99
|
-
itemId: item.itemId,
|
|
100
|
-
type: item.type,
|
|
101
|
-
status: item.status,
|
|
102
|
-
priority: item.priority,
|
|
103
|
-
owner: item.owner,
|
|
104
|
-
guidedStep: item.guidedStep,
|
|
105
|
-
title: item.title,
|
|
106
|
-
description: item.description,
|
|
107
|
-
estimatedEffort: item.estimatedEffort,
|
|
108
|
-
completedAt: item.completedAt?.toISOString(),
|
|
109
|
-
commitHash: item.commitHash,
|
|
110
|
-
createdAt: item.createdAt.toISOString(),
|
|
111
|
-
}));
|
|
112
|
-
return createSuccessResult({
|
|
113
|
-
success: true,
|
|
114
|
-
items,
|
|
115
|
-
total: result.total,
|
|
116
|
-
hasMore: result.hasMore,
|
|
117
|
-
projectId,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
return createErrorResult(`Failed to query work items: ${error instanceof Error ? error.message : String(error)}`);
|
|
122
|
-
}
|
|
123
|
-
},
|
|
124
|
-
});
|
|
125
|
-
/**
|
|
126
|
-
* workitem_add - Add a new work item
|
|
127
|
-
*/
|
|
128
|
-
export const workitemAddTool = defineTool({
|
|
129
|
-
name: 'workitem_add',
|
|
130
|
-
description: 'Add a PERSISTENT work item to the project backlog (stored in database). ' +
|
|
131
|
-
'For session-level tasks shown in footer, use todo_write.',
|
|
132
|
-
inputSchema: {
|
|
133
|
-
type: 'object',
|
|
134
|
-
properties: {
|
|
135
|
-
project_id: {
|
|
136
|
-
type: 'number',
|
|
137
|
-
description: 'Project ID (uses active project if not provided)',
|
|
138
|
-
},
|
|
139
|
-
type: {
|
|
140
|
-
type: 'string',
|
|
141
|
-
enum: ['feature', 'bug', 'tech-debt', 'chore'],
|
|
142
|
-
description: 'Work item type',
|
|
143
|
-
},
|
|
144
|
-
title: {
|
|
145
|
-
type: 'string',
|
|
146
|
-
description: 'Title of the work item',
|
|
147
|
-
},
|
|
148
|
-
description: {
|
|
149
|
-
type: 'string',
|
|
150
|
-
description: 'Detailed description',
|
|
151
|
-
},
|
|
152
|
-
priority: {
|
|
153
|
-
type: 'string',
|
|
154
|
-
enum: ['critical', 'high', 'medium', 'low'],
|
|
155
|
-
description: 'Priority (default: medium)',
|
|
156
|
-
},
|
|
157
|
-
estimated_effort: {
|
|
158
|
-
type: 'string',
|
|
159
|
-
enum: ['low', 'medium', 'high'],
|
|
160
|
-
description: 'Estimated effort',
|
|
161
|
-
},
|
|
162
|
-
owner: {
|
|
163
|
-
type: 'string',
|
|
164
|
-
description: 'Owner agent ID (e.g., "dev", "pm", "arch"). Omit for unassigned.',
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
required: ['type', 'title'],
|
|
168
|
-
},
|
|
169
|
-
execute: async (input) => {
|
|
170
|
-
await Promise.resolve(); // Required for async type signature
|
|
171
|
-
try {
|
|
172
|
-
const projectId = input.project_id || getActiveProject()?.id;
|
|
173
|
-
if (!projectId) {
|
|
174
|
-
return createErrorResult('No project specified and no active project. Use project_get or /projects to select a project first.');
|
|
175
|
-
}
|
|
176
|
-
const createInput = {
|
|
177
|
-
project_id: projectId,
|
|
178
|
-
type: input.type,
|
|
179
|
-
title: input.title,
|
|
180
|
-
description: input.description,
|
|
181
|
-
priority: input.priority,
|
|
182
|
-
estimated_effort: input.estimated_effort,
|
|
183
|
-
owner: input.owner,
|
|
184
|
-
};
|
|
185
|
-
const item = workItemRepository.create(createInput);
|
|
186
|
-
return createSuccessResult({
|
|
187
|
-
success: true,
|
|
188
|
-
message: `Work item ${item.itemId} created: "${item.title}"${item.owner ? ` (owner: ${item.owner})` : ''}`,
|
|
189
|
-
item: {
|
|
190
|
-
id: item.id,
|
|
191
|
-
itemId: item.itemId,
|
|
192
|
-
type: item.type,
|
|
193
|
-
status: item.status,
|
|
194
|
-
priority: item.priority,
|
|
195
|
-
owner: item.owner,
|
|
196
|
-
title: item.title,
|
|
197
|
-
},
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
catch (error) {
|
|
201
|
-
return createErrorResult(`Failed to add work item: ${error instanceof Error ? error.message : String(error)}`);
|
|
202
|
-
}
|
|
203
|
-
},
|
|
204
|
-
});
|
|
205
|
-
/**
|
|
206
|
-
* workitem_update - Update a work item
|
|
207
|
-
*/
|
|
208
|
-
export const workitemUpdateTool = defineTool({
|
|
209
|
-
name: 'workitem_update',
|
|
210
|
-
description: 'Update a PERSISTENT work item in the project backlog (stored in database). ' +
|
|
211
|
-
'Can update status, priority, owner, title, description, and more.',
|
|
212
|
-
inputSchema: {
|
|
213
|
-
type: 'object',
|
|
214
|
-
properties: {
|
|
215
|
-
item_id: {
|
|
216
|
-
type: 'string',
|
|
217
|
-
description: 'Work item ID (e.g., "REQ-001", "BUG-002")',
|
|
218
|
-
},
|
|
219
|
-
project_id: {
|
|
220
|
-
type: 'number',
|
|
221
|
-
description: 'Project ID (uses active project if not provided)',
|
|
222
|
-
},
|
|
223
|
-
status: {
|
|
224
|
-
type: 'string',
|
|
225
|
-
enum: ['backlog', 'in_progress', 'completed', 'skipped'],
|
|
226
|
-
description: 'Work item status',
|
|
227
|
-
},
|
|
228
|
-
priority: {
|
|
229
|
-
type: 'string',
|
|
230
|
-
enum: ['critical', 'high', 'medium', 'low'],
|
|
231
|
-
description: 'Priority',
|
|
232
|
-
},
|
|
233
|
-
owner: {
|
|
234
|
-
type: 'string',
|
|
235
|
-
description: 'Owner agent ID. Set to empty string to unassign.',
|
|
236
|
-
},
|
|
237
|
-
guided_step: {
|
|
238
|
-
type: 'string',
|
|
239
|
-
enum: ['plan', 'implement', 'test', 'commit', 'review'],
|
|
240
|
-
description: 'Current step in guided mode workflow',
|
|
241
|
-
},
|
|
242
|
-
title: {
|
|
243
|
-
type: 'string',
|
|
244
|
-
description: 'Updated title',
|
|
245
|
-
},
|
|
246
|
-
description: {
|
|
247
|
-
type: 'string',
|
|
248
|
-
description: 'Updated description',
|
|
249
|
-
},
|
|
250
|
-
commit_hash: {
|
|
251
|
-
type: 'string',
|
|
252
|
-
description: 'Git commit hash when completing the item',
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
required: ['item_id'],
|
|
256
|
-
},
|
|
257
|
-
execute: async (input) => {
|
|
258
|
-
await Promise.resolve(); // Required for async type signature
|
|
259
|
-
try {
|
|
260
|
-
const projectId = input.project_id || getActiveProject()?.id;
|
|
261
|
-
if (!projectId) {
|
|
262
|
-
return createErrorResult('No project specified and no active project. Use project_get or /projects to select a project first.');
|
|
263
|
-
}
|
|
264
|
-
// Find the work item by item_id
|
|
265
|
-
const existingItem = workItemRepository.getByItemId(projectId, input.item_id);
|
|
266
|
-
if (!existingItem) {
|
|
267
|
-
return createErrorResult(`Work item "${input.item_id}" not found in current project`);
|
|
268
|
-
}
|
|
269
|
-
const updateInput = {};
|
|
270
|
-
if (input.status)
|
|
271
|
-
updateInput.status = input.status;
|
|
272
|
-
if (input.priority)
|
|
273
|
-
updateInput.priority = input.priority;
|
|
274
|
-
if (input.owner !== undefined) {
|
|
275
|
-
// Empty string means unassign
|
|
276
|
-
updateInput.owner = input.owner === '' ? null : input.owner;
|
|
277
|
-
}
|
|
278
|
-
if (input.guided_step !== undefined)
|
|
279
|
-
updateInput.guided_step = input.guided_step;
|
|
280
|
-
if (input.title)
|
|
281
|
-
updateInput.title = input.title;
|
|
282
|
-
if (input.description !== undefined)
|
|
283
|
-
updateInput.description = input.description;
|
|
284
|
-
if (input.commit_hash)
|
|
285
|
-
updateInput.commit_hash = input.commit_hash;
|
|
286
|
-
const item = workItemRepository.update(existingItem.id, updateInput);
|
|
287
|
-
if (!item) {
|
|
288
|
-
return createErrorResult(`Failed to update work item "${input.item_id}"`);
|
|
289
|
-
}
|
|
290
|
-
// Award coin if work item was just completed
|
|
291
|
-
if (input.status === 'completed' && existingItem.status !== 'completed') {
|
|
292
|
-
awardWorkItemCompletion();
|
|
293
|
-
}
|
|
294
|
-
return createSuccessResult({
|
|
295
|
-
success: true,
|
|
296
|
-
message: `Work item ${item.itemId} updated`,
|
|
297
|
-
item: {
|
|
298
|
-
id: item.id,
|
|
299
|
-
itemId: item.itemId,
|
|
300
|
-
type: item.type,
|
|
301
|
-
status: item.status,
|
|
302
|
-
priority: item.priority,
|
|
303
|
-
owner: item.owner,
|
|
304
|
-
guidedStep: item.guidedStep,
|
|
305
|
-
title: item.title,
|
|
306
|
-
completedAt: item.completedAt?.toISOString(),
|
|
307
|
-
commitHash: item.commitHash,
|
|
308
|
-
},
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
catch (error) {
|
|
312
|
-
return createErrorResult(`Failed to update work item: ${error instanceof Error ? error.message : String(error)}`);
|
|
313
|
-
}
|
|
314
|
-
},
|
|
315
|
-
});
|
|
316
|
-
/**
|
|
317
|
-
* workitem_next - Get the next work item to work on
|
|
318
|
-
*/
|
|
319
|
-
export const workitemNextTool = defineTool({
|
|
320
|
-
name: 'workitem_next',
|
|
321
|
-
description: 'Get the next work item to work on (highest priority backlog item). Useful for guided mode to pick the next task.',
|
|
322
|
-
inputSchema: {
|
|
323
|
-
type: 'object',
|
|
324
|
-
properties: {
|
|
325
|
-
project_id: {
|
|
326
|
-
type: 'number',
|
|
327
|
-
description: 'Project ID (uses active project if not provided)',
|
|
328
|
-
},
|
|
329
|
-
type: {
|
|
330
|
-
type: 'string',
|
|
331
|
-
enum: ['feature', 'bug', 'tech-debt', 'chore'],
|
|
332
|
-
description: 'Filter by type (optional)',
|
|
333
|
-
},
|
|
334
|
-
},
|
|
335
|
-
required: [],
|
|
336
|
-
},
|
|
337
|
-
execute: async (input) => {
|
|
338
|
-
await Promise.resolve(); // Required for async type signature
|
|
339
|
-
try {
|
|
340
|
-
const projectId = input.project_id || getActiveProject()?.id;
|
|
341
|
-
if (!projectId) {
|
|
342
|
-
return createErrorResult('No project specified and no active project. Use project_get or /projects to select a project first.');
|
|
343
|
-
}
|
|
344
|
-
const item = workItemRepository.getNext(projectId, input.type);
|
|
345
|
-
if (!item) {
|
|
346
|
-
return createSuccessResult({
|
|
347
|
-
success: true,
|
|
348
|
-
item: null,
|
|
349
|
-
message: 'No backlog items found. The backlog is empty or all items are completed.',
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
return createSuccessResult({
|
|
353
|
-
success: true,
|
|
354
|
-
item: {
|
|
355
|
-
id: item.id,
|
|
356
|
-
itemId: item.itemId,
|
|
357
|
-
type: item.type,
|
|
358
|
-
status: item.status,
|
|
359
|
-
priority: item.priority,
|
|
360
|
-
title: item.title,
|
|
361
|
-
description: item.description,
|
|
362
|
-
estimatedEffort: item.estimatedEffort,
|
|
363
|
-
},
|
|
364
|
-
message: `Next item: ${item.itemId} "${item.title}" (${item.priority} priority)`,
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
catch (error) {
|
|
368
|
-
return createErrorResult(`Failed to get next work item: ${error instanceof Error ? error.message : String(error)}`);
|
|
369
|
-
}
|
|
370
|
-
},
|
|
371
|
-
});
|
|
372
|
-
/**
|
|
373
|
-
* workitem_delete - Delete a work item
|
|
374
|
-
*/
|
|
375
|
-
export const workitemDeleteTool = defineTool({
|
|
376
|
-
name: 'workitem_delete',
|
|
377
|
-
description: 'Delete a work item from the backlog.',
|
|
378
|
-
inputSchema: {
|
|
379
|
-
type: 'object',
|
|
380
|
-
properties: {
|
|
381
|
-
item_id: {
|
|
382
|
-
type: 'string',
|
|
383
|
-
description: 'Work item ID (e.g., "REQ-001", "BUG-002")',
|
|
384
|
-
},
|
|
385
|
-
project_id: {
|
|
386
|
-
type: 'number',
|
|
387
|
-
description: 'Project ID (uses active project if not provided)',
|
|
388
|
-
},
|
|
389
|
-
},
|
|
390
|
-
required: ['item_id'],
|
|
391
|
-
},
|
|
392
|
-
execute: async (input) => {
|
|
393
|
-
await Promise.resolve(); // Required for async type signature
|
|
394
|
-
try {
|
|
395
|
-
const projectId = input.project_id || getActiveProject()?.id;
|
|
396
|
-
if (!projectId) {
|
|
397
|
-
return createErrorResult('No project specified and no active project. Use project_get or /projects to select a project first.');
|
|
398
|
-
}
|
|
399
|
-
// Find the work item by item_id
|
|
400
|
-
const existingItem = workItemRepository.getByItemId(projectId, input.item_id);
|
|
401
|
-
if (!existingItem) {
|
|
402
|
-
return createErrorResult(`Work item "${input.item_id}" not found in current project`);
|
|
403
|
-
}
|
|
404
|
-
const deleted = workItemRepository.delete(existingItem.id);
|
|
405
|
-
if (!deleted) {
|
|
406
|
-
return createErrorResult(`Failed to delete work item "${input.item_id}"`);
|
|
407
|
-
}
|
|
408
|
-
return createSuccessResult({
|
|
409
|
-
success: true,
|
|
410
|
-
message: `Work item ${input.item_id} deleted`,
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
catch (error) {
|
|
414
|
-
return createErrorResult(`Failed to delete work item: ${error instanceof Error ? error.message : String(error)}`);
|
|
415
|
-
}
|
|
416
|
-
},
|
|
417
|
-
});
|
|
418
|
-
/**
|
|
419
|
-
* workitem_status_counts - Get work item counts by status
|
|
420
|
-
*/
|
|
421
|
-
export const workitemStatusCountsTool = defineTool({
|
|
422
|
-
name: 'workitem_status_counts',
|
|
423
|
-
description: 'Get counts of work items by status for the current project. Useful for showing progress.',
|
|
424
|
-
inputSchema: {
|
|
425
|
-
type: 'object',
|
|
426
|
-
properties: {
|
|
427
|
-
project_id: {
|
|
428
|
-
type: 'number',
|
|
429
|
-
description: 'Project ID (uses active project if not provided)',
|
|
430
|
-
},
|
|
431
|
-
},
|
|
432
|
-
required: [],
|
|
433
|
-
},
|
|
434
|
-
execute: async (input) => {
|
|
435
|
-
await Promise.resolve(); // Required for async type signature
|
|
436
|
-
try {
|
|
437
|
-
const projectId = input.project_id || getActiveProject()?.id;
|
|
438
|
-
if (!projectId) {
|
|
439
|
-
return createErrorResult('No project specified and no active project. Use project_get or /projects to select a project first.');
|
|
440
|
-
}
|
|
441
|
-
const counts = workItemRepository.getStatusCounts(projectId);
|
|
442
|
-
const total = counts.backlog + counts.in_progress + counts.completed + counts.skipped;
|
|
443
|
-
const progress = total > 0 ? Math.round((counts.completed / total) * 100) : 0;
|
|
444
|
-
return createSuccessResult({
|
|
445
|
-
success: true,
|
|
446
|
-
counts,
|
|
447
|
-
total,
|
|
448
|
-
progress,
|
|
449
|
-
summary: `${String(counts.completed)}/${String(total)} completed (${String(progress)}%)`,
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
catch (error) {
|
|
453
|
-
return createErrorResult(`Failed to get status counts: ${error instanceof Error ? error.message : String(error)}`);
|
|
454
|
-
}
|
|
455
|
-
},
|
|
456
|
-
});
|
|
457
|
-
/**
|
|
458
|
-
* workitem_advance_step - Advance to the next guided workflow step
|
|
459
|
-
*/
|
|
460
|
-
export const workitemAdvanceStepTool = defineTool({
|
|
461
|
-
name: 'workitem_advance_step',
|
|
462
|
-
description: `Advance a work item to the next guided workflow step.
|
|
463
|
-
Steps progress in order: ${STEP_ORDER.join(' → ')} → complete.
|
|
464
|
-
Use this tool when the criteria for the current step are met.
|
|
465
|
-
The agent should explicitly call this to advance - it provides visibility and audit trail.`,
|
|
466
|
-
inputSchema: {
|
|
467
|
-
type: 'object',
|
|
468
|
-
properties: {
|
|
469
|
-
item_id: {
|
|
470
|
-
type: 'string',
|
|
471
|
-
description: 'Work item ID (e.g., "REQ-001", "BUG-002")',
|
|
472
|
-
},
|
|
473
|
-
reason: {
|
|
474
|
-
type: 'string',
|
|
475
|
-
description: 'Why the step criteria was met (shown to user for visibility)',
|
|
476
|
-
},
|
|
477
|
-
force: {
|
|
478
|
-
type: 'boolean',
|
|
479
|
-
description: 'Skip validation and force advance (e.g., user override after test failures)',
|
|
480
|
-
},
|
|
481
|
-
project_id: {
|
|
482
|
-
type: 'number',
|
|
483
|
-
description: 'Project ID (uses active project if not provided)',
|
|
484
|
-
},
|
|
485
|
-
},
|
|
486
|
-
required: ['item_id', 'reason'],
|
|
487
|
-
},
|
|
488
|
-
execute: async (input) => {
|
|
489
|
-
await Promise.resolve(); // Required for async type signature
|
|
490
|
-
try {
|
|
491
|
-
const projectId = input.project_id || getActiveProject()?.id;
|
|
492
|
-
if (!projectId) {
|
|
493
|
-
return createErrorResult('No project specified and no active project. Use project_get or /projects to select a project first.');
|
|
494
|
-
}
|
|
495
|
-
// Find the work item
|
|
496
|
-
const item = workItemRepository.getByItemId(projectId, input.item_id);
|
|
497
|
-
if (!item) {
|
|
498
|
-
return createErrorResult(`Work item "${input.item_id}" not found in current project`);
|
|
499
|
-
}
|
|
500
|
-
// Check if item is in progress
|
|
501
|
-
if (item.status !== 'in_progress') {
|
|
502
|
-
return createErrorResult(`Work item "${input.item_id}" is not in progress (status: ${item.status}). ` +
|
|
503
|
-
'Use workitem_update to set status to in_progress first.');
|
|
504
|
-
}
|
|
505
|
-
// Determine current and next step
|
|
506
|
-
const currentStep = item.guidedStep;
|
|
507
|
-
const nextStep = getNextStep(currentStep);
|
|
508
|
-
// Check for valid transition (unless forced)
|
|
509
|
-
if (!input.force && currentStep !== null) {
|
|
510
|
-
if (!isValidTransition(currentStep, nextStep)) {
|
|
511
|
-
return createErrorResult(`Cannot advance from ${currentStep} to ${nextStep}. ` +
|
|
512
|
-
`Valid next step: ${getNextStep(currentStep)}. ` +
|
|
513
|
-
'Use force=true to override.');
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
// Handle completion
|
|
517
|
-
if (nextStep === 'complete') {
|
|
518
|
-
// Mark item as completed
|
|
519
|
-
const updatedItem = workItemRepository.update(item.id, {
|
|
520
|
-
status: 'completed',
|
|
521
|
-
guided_step: null,
|
|
522
|
-
});
|
|
523
|
-
if (!updatedItem) {
|
|
524
|
-
return createErrorResult('Failed to update work item');
|
|
525
|
-
}
|
|
526
|
-
// Award coin for completing work item
|
|
527
|
-
awardWorkItemCompletion();
|
|
528
|
-
return createSuccessResult({
|
|
529
|
-
success: true,
|
|
530
|
-
item: {
|
|
531
|
-
id: updatedItem.id,
|
|
532
|
-
itemId: updatedItem.itemId,
|
|
533
|
-
type: updatedItem.type,
|
|
534
|
-
status: updatedItem.status,
|
|
535
|
-
priority: updatedItem.priority,
|
|
536
|
-
guidedStep: updatedItem.guidedStep,
|
|
537
|
-
title: updatedItem.title,
|
|
538
|
-
completedAt: updatedItem.completedAt?.toISOString(),
|
|
539
|
-
commitHash: updatedItem.commitHash,
|
|
540
|
-
},
|
|
541
|
-
previous_step: currentStep,
|
|
542
|
-
current_step: null,
|
|
543
|
-
message: currentStep
|
|
544
|
-
? `✓ ${input.item_id}: ${formatStepDisplay(currentStep)} → COMPLETED`
|
|
545
|
-
: `✓ ${input.item_id}: COMPLETED`,
|
|
546
|
-
completed: true,
|
|
547
|
-
reason: input.reason,
|
|
548
|
-
next_action: 'Work item complete! Use workitem_next to pick the next item from backlog.',
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
// Advance to next step (we know nextStep is not 'complete' at this point
|
|
552
|
-
// because 'complete' case is handled above and returns early)
|
|
553
|
-
const nextGuidedStep = nextStep;
|
|
554
|
-
const updatedItem = workItemRepository.update(item.id, {
|
|
555
|
-
guided_step: nextGuidedStep,
|
|
556
|
-
});
|
|
557
|
-
if (!updatedItem) {
|
|
558
|
-
return createErrorResult('Failed to update work item');
|
|
559
|
-
}
|
|
560
|
-
// Get hints for next step
|
|
561
|
-
const nextCriteria = getStepCriteria(nextGuidedStep);
|
|
562
|
-
return createSuccessResult({
|
|
563
|
-
success: true,
|
|
564
|
-
item: {
|
|
565
|
-
id: updatedItem.id,
|
|
566
|
-
itemId: updatedItem.itemId,
|
|
567
|
-
type: updatedItem.type,
|
|
568
|
-
status: updatedItem.status,
|
|
569
|
-
priority: updatedItem.priority,
|
|
570
|
-
guidedStep: updatedItem.guidedStep,
|
|
571
|
-
title: updatedItem.title,
|
|
572
|
-
},
|
|
573
|
-
previous_step: currentStep,
|
|
574
|
-
current_step: nextGuidedStep,
|
|
575
|
-
message: currentStep
|
|
576
|
-
? `✓ ${input.item_id}: ${formatStepDisplay(currentStep)} → ${formatStepDisplay(nextGuidedStep)}`
|
|
577
|
-
: `✓ ${input.item_id}: Starting ${formatStepDisplay(nextGuidedStep)}`,
|
|
578
|
-
completed: false,
|
|
579
|
-
reason: input.reason,
|
|
580
|
-
next_action: nextCriteria.nextActionHint,
|
|
581
|
-
exit_criteria: nextCriteria.exitCriteria,
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
catch (error) {
|
|
585
|
-
return createErrorResult(`Failed to advance step: ${error instanceof Error ? error.message : String(error)}`);
|
|
586
|
-
}
|
|
587
|
-
},
|
|
588
|
-
});
|
|
589
|
-
/**
|
|
590
|
-
* workitem_claim - Claim an unassigned work item
|
|
591
|
-
*/
|
|
592
|
-
export const workitemClaimTool = defineTool({
|
|
593
|
-
name: 'workitem_claim',
|
|
594
|
-
description: 'Claim an unassigned PERSISTENT work item from project backlog. ' +
|
|
595
|
-
'For session todos in footer, use todo_claim.',
|
|
596
|
-
inputSchema: {
|
|
597
|
-
type: 'object',
|
|
598
|
-
properties: {
|
|
599
|
-
item_id: {
|
|
600
|
-
type: 'string',
|
|
601
|
-
description: 'Work item ID (e.g., "REQ-001", "BUG-002")',
|
|
602
|
-
},
|
|
603
|
-
agent_id: {
|
|
604
|
-
type: 'string',
|
|
605
|
-
description: 'The agent ID claiming the work item',
|
|
606
|
-
},
|
|
607
|
-
project_id: {
|
|
608
|
-
type: 'number',
|
|
609
|
-
description: 'Project ID (uses active project if not provided)',
|
|
610
|
-
},
|
|
611
|
-
},
|
|
612
|
-
required: ['item_id', 'agent_id'],
|
|
613
|
-
},
|
|
614
|
-
execute: async (input) => {
|
|
615
|
-
await Promise.resolve();
|
|
616
|
-
try {
|
|
617
|
-
const projectId = input.project_id || getActiveProject()?.id;
|
|
618
|
-
if (!projectId) {
|
|
619
|
-
return createErrorResult('No project specified and no active project.');
|
|
620
|
-
}
|
|
621
|
-
const existingItem = workItemRepository.getByItemId(projectId, input.item_id);
|
|
622
|
-
if (!existingItem) {
|
|
623
|
-
return createErrorResult(`Work item "${input.item_id}" not found in current project`);
|
|
624
|
-
}
|
|
625
|
-
if (existingItem.owner) {
|
|
626
|
-
return createErrorResult(`Work item "${input.item_id}" is already owned by "${existingItem.owner}". Use workitem_handoff to transfer ownership.`);
|
|
627
|
-
}
|
|
628
|
-
const item = workItemRepository.update(existingItem.id, { owner: input.agent_id });
|
|
629
|
-
if (!item) {
|
|
630
|
-
return createErrorResult(`Failed to claim work item "${input.item_id}"`);
|
|
631
|
-
}
|
|
632
|
-
return createSuccessResult({
|
|
633
|
-
success: true,
|
|
634
|
-
message: `Work item ${item.itemId} claimed by ${input.agent_id}`,
|
|
635
|
-
item: {
|
|
636
|
-
id: item.id,
|
|
637
|
-
itemId: item.itemId,
|
|
638
|
-
type: item.type,
|
|
639
|
-
status: item.status,
|
|
640
|
-
priority: item.priority,
|
|
641
|
-
owner: item.owner,
|
|
642
|
-
title: item.title,
|
|
643
|
-
},
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
catch (error) {
|
|
647
|
-
return createErrorResult(`Failed to claim work item: ${error instanceof Error ? error.message : String(error)}`);
|
|
648
|
-
}
|
|
649
|
-
},
|
|
650
|
-
});
|
|
651
|
-
/**
|
|
652
|
-
* workitem_handoff - Hand off a work item to another agent
|
|
653
|
-
*/
|
|
654
|
-
export const workitemHandoffTool = defineTool({
|
|
655
|
-
name: 'workitem_handoff',
|
|
656
|
-
description: 'Hand off a PERSISTENT work item (from project backlog) to another agent. ' +
|
|
657
|
-
'For session todos in footer, use todo_handoff.',
|
|
658
|
-
inputSchema: {
|
|
659
|
-
type: 'object',
|
|
660
|
-
properties: {
|
|
661
|
-
item_id: {
|
|
662
|
-
type: 'string',
|
|
663
|
-
description: 'Work item ID (e.g., "REQ-001", "BUG-002")',
|
|
664
|
-
},
|
|
665
|
-
to_agent_id: {
|
|
666
|
-
type: 'string',
|
|
667
|
-
description: 'The agent ID to hand off to',
|
|
668
|
-
},
|
|
669
|
-
notes: {
|
|
670
|
-
type: 'string',
|
|
671
|
-
description: 'Optional notes about the handoff (context, status, blockers)',
|
|
672
|
-
},
|
|
673
|
-
project_id: {
|
|
674
|
-
type: 'number',
|
|
675
|
-
description: 'Project ID (uses active project if not provided)',
|
|
676
|
-
},
|
|
677
|
-
},
|
|
678
|
-
required: ['item_id', 'to_agent_id'],
|
|
679
|
-
},
|
|
680
|
-
execute: async (input) => {
|
|
681
|
-
await Promise.resolve();
|
|
682
|
-
try {
|
|
683
|
-
const projectId = input.project_id || getActiveProject()?.id;
|
|
684
|
-
if (!projectId) {
|
|
685
|
-
return createErrorResult('No project specified and no active project.');
|
|
686
|
-
}
|
|
687
|
-
const existingItem = workItemRepository.getByItemId(projectId, input.item_id);
|
|
688
|
-
if (!existingItem) {
|
|
689
|
-
return createErrorResult(`Work item "${input.item_id}" not found in current project`);
|
|
690
|
-
}
|
|
691
|
-
const previousOwner = existingItem.owner;
|
|
692
|
-
const item = workItemRepository.update(existingItem.id, { owner: input.to_agent_id });
|
|
693
|
-
if (!item) {
|
|
694
|
-
return createErrorResult(`Failed to hand off work item "${input.item_id}"`);
|
|
695
|
-
}
|
|
696
|
-
return createSuccessResult({
|
|
697
|
-
success: true,
|
|
698
|
-
message: `Work item ${item.itemId} handed off from "${previousOwner ?? 'unassigned'}" to "${input.to_agent_id}"`,
|
|
699
|
-
item: {
|
|
700
|
-
id: item.id,
|
|
701
|
-
itemId: item.itemId,
|
|
702
|
-
type: item.type,
|
|
703
|
-
status: item.status,
|
|
704
|
-
priority: item.priority,
|
|
705
|
-
owner: item.owner,
|
|
706
|
-
title: item.title,
|
|
707
|
-
},
|
|
708
|
-
previousOwner,
|
|
709
|
-
notes: input.notes,
|
|
710
|
-
});
|
|
711
|
-
}
|
|
712
|
-
catch (error) {
|
|
713
|
-
return createErrorResult(`Failed to hand off work item: ${error instanceof Error ? error.message : String(error)}`);
|
|
714
|
-
}
|
|
715
|
-
},
|
|
716
|
-
});
|
|
717
|
-
/**
|
|
718
|
-
* All work item tools
|
|
719
|
-
*/
|
|
720
|
-
export const workitemDbTools = [
|
|
721
|
-
workitemQueryTool,
|
|
722
|
-
workitemAddTool,
|
|
723
|
-
workitemUpdateTool,
|
|
724
|
-
workitemNextTool,
|
|
725
|
-
workitemDeleteTool,
|
|
726
|
-
workitemStatusCountsTool,
|
|
727
|
-
workitemAdvanceStepTool,
|
|
728
|
-
workitemClaimTool,
|
|
729
|
-
workitemHandoffTool,
|
|
730
|
-
];
|