agent-tasks 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -15
- package/dist/domain/agent-bridge.d.ts.map +1 -1
- package/dist/domain/agent-bridge.js +22 -2
- package/dist/domain/agent-bridge.js.map +1 -1
- package/dist/domain/approvals.d.ts.map +1 -1
- package/dist/domain/approvals.js +4 -1
- package/dist/domain/approvals.js.map +1 -1
- package/dist/domain/cleanup.d.ts.map +1 -1
- package/dist/domain/cleanup.js +8 -3
- package/dist/domain/cleanup.js.map +1 -1
- package/dist/domain/rules.js +11 -10
- package/dist/domain/rules.js.map +1 -1
- package/dist/domain/task-validator.d.ts +9 -0
- package/dist/domain/task-validator.d.ts.map +1 -0
- package/dist/domain/task-validator.js +70 -0
- package/dist/domain/task-validator.js.map +1 -0
- package/dist/domain/tasks.d.ts +14 -9
- package/dist/domain/tasks.d.ts.map +1 -1
- package/dist/domain/tasks.js +176 -109
- package/dist/domain/tasks.js.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/storage/database.d.ts.map +1 -1
- package/dist/storage/database.js +4 -2
- package/dist/storage/database.js.map +1 -1
- package/dist/transport/mcp-handlers.d.ts +30 -0
- package/dist/transport/mcp-handlers.d.ts.map +1 -0
- package/dist/transport/mcp-handlers.js +408 -0
- package/dist/transport/mcp-handlers.js.map +1 -0
- package/dist/transport/mcp.d.ts.map +1 -1
- package/dist/transport/mcp.js +196 -656
- package/dist/transport/mcp.js.map +1 -1
- package/dist/transport/rest.d.ts.map +1 -1
- package/dist/transport/rest.js +8 -2
- package/dist/transport/rest.js.map +1 -1
- package/dist/transport/ws.d.ts.map +1 -1
- package/dist/transport/ws.js +7 -4
- package/dist/transport/ws.js.map +1 -1
- package/dist/ui/app.js +188 -1554
- package/dist/ui/board.js +401 -0
- package/dist/ui/drag.js +143 -0
- package/dist/ui/index.html +5 -0
- package/dist/ui/inline-edit.js +242 -0
- package/dist/ui/panel.js +574 -0
- package/dist/ui/styles.css +200 -0
- package/dist/ui/ui-utils.js +323 -0
- package/package.json +1 -1
- package/dist/db.d.ts +0 -10
- package/dist/db.d.ts.map +0 -1
- package/dist/db.js +0 -112
- package/dist/db.js.map +0 -1
- package/dist/event-bus.d.ts +0 -10
- package/dist/event-bus.d.ts.map +0 -1
- package/dist/event-bus.js +0 -38
- package/dist/event-bus.js.map +0 -1
- package/dist/session.d.ts +0 -7
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js +0 -11
- package/dist/session.js.map +0 -1
- package/dist/tasks.d.ts +0 -32
- package/dist/tasks.d.ts.map +0 -1
- package/dist/tasks.js +0 -410
- package/dist/tasks.js.map +0 -1
package/dist/transport/mcp.js
CHANGED
|
@@ -4,61 +4,79 @@
|
|
|
4
4
|
// Maps MCP tool calls to the TaskService. Thin adapter — validation lives
|
|
5
5
|
// in the domain layer, not here.
|
|
6
6
|
// =============================================================================
|
|
7
|
-
import { writeFileSync, mkdirSync } from 'fs';
|
|
8
|
-
import { join } from 'path';
|
|
9
|
-
import { homedir } from 'os';
|
|
10
7
|
import { ValidationError } from '../types.js';
|
|
11
|
-
import {
|
|
8
|
+
import { handlers } from './mcp-handlers.js';
|
|
12
9
|
// ---------------------------------------------------------------------------
|
|
13
|
-
// Tool definitions
|
|
10
|
+
// Tool definitions (13 tools)
|
|
14
11
|
// ---------------------------------------------------------------------------
|
|
15
12
|
export const tools = [
|
|
16
13
|
{
|
|
17
14
|
name: 'task_create',
|
|
18
|
-
description: 'Create a pipeline task
|
|
15
|
+
description: 'Create a pipeline task. Tasks start in "backlog" and move through stages: backlog → spec → plan → implement → test → review → done. Use parent_id to create subtasks under an existing task.',
|
|
19
16
|
inputSchema: {
|
|
20
17
|
type: 'object',
|
|
21
18
|
properties: {
|
|
22
19
|
title: { type: 'string', description: 'Task title (max 500 chars)' },
|
|
23
20
|
description: { type: 'string', description: 'Detailed instructions (max 50K chars)' },
|
|
24
21
|
assign_to: { type: 'string', description: 'Agent name to assign to' },
|
|
25
|
-
stage: {
|
|
22
|
+
stage: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'Initial pipeline stage (default: backlog)',
|
|
25
|
+
},
|
|
26
26
|
priority: {
|
|
27
27
|
type: 'number',
|
|
28
|
-
description: 'Priority
|
|
28
|
+
description: 'Priority — higher number = more important (default: 0)',
|
|
29
29
|
},
|
|
30
30
|
project: { type: 'string', description: 'Project name for grouping' },
|
|
31
31
|
tags: { type: 'array', items: { type: 'string' }, description: 'Tags for categorization' },
|
|
32
|
-
parent_id: {
|
|
32
|
+
parent_id: {
|
|
33
|
+
type: 'number',
|
|
34
|
+
description: 'Parent task ID — creates a subtask that inherits project and priority',
|
|
35
|
+
},
|
|
33
36
|
},
|
|
34
37
|
required: ['title'],
|
|
35
38
|
},
|
|
36
39
|
},
|
|
37
40
|
{
|
|
38
41
|
name: 'task_list',
|
|
39
|
-
description: 'List tasks
|
|
42
|
+
description: 'List, search, or pick tasks. Without params: returns all tasks. With filters: narrow by status/stage/project/assignee. With "query": full-text search across titles and descriptions. With "next": true: returns the single highest-priority unassigned task with all dependencies met (uses affinity scoring when agent is provided).',
|
|
40
43
|
inputSchema: {
|
|
41
44
|
type: 'object',
|
|
42
45
|
properties: {
|
|
46
|
+
query: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
description: 'Full-text search across task titles and descriptions (FTS5)',
|
|
49
|
+
},
|
|
50
|
+
next: {
|
|
51
|
+
type: 'boolean',
|
|
52
|
+
description: 'Return the single best available task — highest priority, unassigned, all dependencies met. Uses affinity scoring when "agent" is provided.',
|
|
53
|
+
},
|
|
54
|
+
agent: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
description: 'Agent name for affinity scoring (only with next: true) — prefers tasks where the agent worked on the parent, a dependency, or the same project',
|
|
57
|
+
},
|
|
43
58
|
status: {
|
|
44
59
|
type: 'string',
|
|
45
60
|
enum: ['pending', 'in_progress', 'completed', 'failed', 'cancelled'],
|
|
46
61
|
description: 'Filter by status',
|
|
47
62
|
},
|
|
48
|
-
|
|
63
|
+
assign_to: { type: 'string', description: 'Filter by assigned agent name' },
|
|
49
64
|
stage: { type: 'string', description: 'Filter by pipeline stage' },
|
|
50
65
|
project: { type: 'string', description: 'Filter by project' },
|
|
51
66
|
collaborator: { type: 'string', description: 'Filter tasks where agent is a collaborator' },
|
|
52
67
|
root_only: { type: 'boolean', description: 'Only show top-level tasks (no subtasks)' },
|
|
53
68
|
parent_id: { type: 'number', description: 'Filter subtasks of a specific parent' },
|
|
54
|
-
limit: {
|
|
69
|
+
limit: {
|
|
70
|
+
type: 'number',
|
|
71
|
+
description: 'Max results (default: 500 for list, 50 for search)',
|
|
72
|
+
},
|
|
55
73
|
offset: { type: 'number', description: 'Skip first N results (for pagination)' },
|
|
56
74
|
},
|
|
57
75
|
},
|
|
58
76
|
},
|
|
59
77
|
{
|
|
60
78
|
name: 'task_claim',
|
|
61
|
-
description: 'Claim a pending task
|
|
79
|
+
description: 'Claim a pending task — assigns it to you and advances from backlog to spec. This is the standard way to start working on a task.',
|
|
62
80
|
inputSchema: {
|
|
63
81
|
type: 'object',
|
|
64
82
|
properties: {
|
|
@@ -72,189 +90,170 @@ export const tools = [
|
|
|
72
90
|
},
|
|
73
91
|
},
|
|
74
92
|
{
|
|
75
|
-
name: '
|
|
76
|
-
description: '
|
|
93
|
+
name: 'task_update',
|
|
94
|
+
description: 'Update task metadata — title, description, priority, tags, project, or assignment. Does not change stage or status (use task_stage for that).',
|
|
77
95
|
inputSchema: {
|
|
78
96
|
type: 'object',
|
|
79
97
|
properties: {
|
|
80
98
|
task_id: { type: 'number', description: 'Task ID' },
|
|
81
|
-
|
|
99
|
+
title: { type: 'string', description: 'New title' },
|
|
100
|
+
description: { type: 'string', description: 'New description' },
|
|
101
|
+
priority: { type: 'number', description: 'New priority' },
|
|
102
|
+
project: { type: 'string', description: 'New project' },
|
|
103
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'New tags' },
|
|
104
|
+
assign_to: {
|
|
105
|
+
type: 'string',
|
|
106
|
+
description: 'New assignee (empty string to unassign)',
|
|
107
|
+
},
|
|
82
108
|
},
|
|
83
|
-
required: ['task_id'
|
|
109
|
+
required: ['task_id'],
|
|
84
110
|
},
|
|
85
111
|
},
|
|
86
112
|
{
|
|
87
|
-
name: '
|
|
88
|
-
description: '
|
|
113
|
+
name: 'task_delete',
|
|
114
|
+
description: 'Delete a task and all its artifacts, comments, and dependencies (cascading delete). Cannot be undone.',
|
|
89
115
|
inputSchema: {
|
|
90
116
|
type: 'object',
|
|
91
117
|
properties: {
|
|
92
|
-
task_id: { type: 'number', description: 'Task ID' },
|
|
93
|
-
result: { type: 'string', description: 'Error description' },
|
|
118
|
+
task_id: { type: 'number', description: 'Task ID to delete' },
|
|
94
119
|
},
|
|
95
|
-
required: ['task_id'
|
|
120
|
+
required: ['task_id'],
|
|
96
121
|
},
|
|
97
122
|
},
|
|
98
123
|
{
|
|
99
|
-
name: '
|
|
100
|
-
description: '
|
|
124
|
+
name: 'task_comment',
|
|
125
|
+
description: 'Add a comment to a task — used for async discussion between agents. Supports threading via parent_comment_id. Comments also satisfy stage-gate "require_comment" checks.',
|
|
101
126
|
inputSchema: {
|
|
102
127
|
type: 'object',
|
|
103
128
|
properties: {
|
|
104
129
|
task_id: { type: 'number', description: 'Task ID' },
|
|
105
|
-
|
|
130
|
+
content: { type: 'string', description: 'Comment text' },
|
|
131
|
+
parent_comment_id: { type: 'number', description: 'Reply to this comment (threading)' },
|
|
106
132
|
},
|
|
107
|
-
required: ['task_id', '
|
|
133
|
+
required: ['task_id', 'content'],
|
|
108
134
|
},
|
|
109
135
|
},
|
|
110
136
|
{
|
|
111
|
-
name: '
|
|
112
|
-
description: '
|
|
137
|
+
name: 'task_stage',
|
|
138
|
+
description: 'Move a task through its lifecycle. Actions: "advance" → next stage (or jump to a specific one), "regress" → earlier stage (requires reason), "complete" → marks done with result, "fail" → marks failed with error, "cancel" → cancels with reason.',
|
|
113
139
|
inputSchema: {
|
|
114
140
|
type: 'object',
|
|
115
141
|
properties: {
|
|
142
|
+
action: {
|
|
143
|
+
type: 'string',
|
|
144
|
+
enum: ['advance', 'regress', 'complete', 'fail', 'cancel'],
|
|
145
|
+
description: 'Lifecycle action to perform',
|
|
146
|
+
},
|
|
116
147
|
task_id: { type: 'number', description: 'Task ID' },
|
|
117
|
-
stage: {
|
|
148
|
+
stage: {
|
|
149
|
+
type: 'string',
|
|
150
|
+
description: 'Target stage (advance: optional — advances to next if omitted; regress: required)',
|
|
151
|
+
},
|
|
118
152
|
comment: {
|
|
119
153
|
type: 'string',
|
|
120
|
-
description: '
|
|
154
|
+
description: 'Comment (advance: optional, also satisfies stage-gate require_comment check)',
|
|
121
155
|
},
|
|
122
|
-
},
|
|
123
|
-
required: ['task_id'],
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
name: 'task_regress',
|
|
128
|
-
description: 'Send a task back to an earlier stage (e.g. review rejection).',
|
|
129
|
-
inputSchema: {
|
|
130
|
-
type: 'object',
|
|
131
|
-
properties: {
|
|
132
|
-
task_id: { type: 'number', description: 'Task ID' },
|
|
133
|
-
stage: { type: 'string', description: 'Target stage to regress to' },
|
|
134
156
|
reason: {
|
|
135
157
|
type: 'string',
|
|
136
|
-
description: 'Reason for regression
|
|
158
|
+
description: 'Reason for regression, failure, or cancellation',
|
|
137
159
|
},
|
|
138
|
-
|
|
139
|
-
required: ['task_id', 'stage'],
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
name: 'task_update',
|
|
144
|
-
description: 'Update task metadata (title, description, priority, tags, assignment).',
|
|
145
|
-
inputSchema: {
|
|
146
|
-
type: 'object',
|
|
147
|
-
properties: {
|
|
148
|
-
task_id: { type: 'number', description: 'Task ID' },
|
|
149
|
-
title: { type: 'string', description: 'New title' },
|
|
150
|
-
description: { type: 'string', description: 'New description' },
|
|
151
|
-
priority: { type: 'number', description: 'New priority' },
|
|
152
|
-
project: { type: 'string', description: 'New project' },
|
|
153
|
-
tags: { type: 'array', items: { type: 'string' }, description: 'New tags' },
|
|
154
|
-
assign_to: {
|
|
160
|
+
result: {
|
|
155
161
|
type: 'string',
|
|
156
|
-
description: '
|
|
162
|
+
description: 'Result summary (complete) or error description (fail)',
|
|
157
163
|
},
|
|
158
164
|
},
|
|
159
|
-
required: ['task_id'],
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
name: 'task_next',
|
|
164
|
-
description: 'Get the highest-priority unassigned task with all dependencies met. Returns null if none.',
|
|
165
|
-
inputSchema: {
|
|
166
|
-
type: 'object',
|
|
167
|
-
properties: {
|
|
168
|
-
project: { type: 'string', description: 'Filter by project' },
|
|
169
|
-
stage: { type: 'string', description: 'Filter by stage' },
|
|
170
|
-
},
|
|
165
|
+
required: ['action', 'task_id'],
|
|
171
166
|
},
|
|
172
167
|
},
|
|
173
168
|
{
|
|
174
|
-
name: '
|
|
175
|
-
description: '
|
|
169
|
+
name: 'task_query',
|
|
170
|
+
description: 'Read task-related data. Types: "subtasks" → child tasks, "artifacts" → attached documents/decisions/learnings (filterable by stage), "comments" → discussion threads.',
|
|
176
171
|
inputSchema: {
|
|
177
172
|
type: 'object',
|
|
178
173
|
properties: {
|
|
179
|
-
|
|
180
|
-
depends_on: {
|
|
181
|
-
type: 'number',
|
|
182
|
-
description: 'Task that must complete first (for blocks) or related task',
|
|
183
|
-
},
|
|
184
|
-
relationship: {
|
|
174
|
+
type: {
|
|
185
175
|
type: 'string',
|
|
186
|
-
enum: ['
|
|
187
|
-
description: '
|
|
176
|
+
enum: ['subtasks', 'artifacts', 'comments'],
|
|
177
|
+
description: 'What to query',
|
|
188
178
|
},
|
|
189
|
-
},
|
|
190
|
-
required: ['task_id', 'depends_on'],
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
name: 'task_remove_dependency',
|
|
195
|
-
description: 'Remove a dependency between tasks.',
|
|
196
|
-
inputSchema: {
|
|
197
|
-
type: 'object',
|
|
198
|
-
properties: {
|
|
199
179
|
task_id: { type: 'number', description: 'Task ID' },
|
|
200
|
-
|
|
180
|
+
stage: {
|
|
181
|
+
type: 'string',
|
|
182
|
+
description: 'Filter artifacts by stage (only used with type: "artifacts")',
|
|
183
|
+
},
|
|
184
|
+
limit: {
|
|
185
|
+
type: 'number',
|
|
186
|
+
description: 'Max comments to return (only used with type: "comments", default: 100)',
|
|
187
|
+
},
|
|
201
188
|
},
|
|
202
|
-
required: ['
|
|
189
|
+
required: ['type', 'task_id'],
|
|
203
190
|
},
|
|
204
191
|
},
|
|
205
192
|
{
|
|
206
|
-
name: '
|
|
207
|
-
description: 'Attach
|
|
193
|
+
name: 'task_artifact',
|
|
194
|
+
description: 'Attach artifacts to a task. Types: "general" → document (spec, test-results, review-notes), "decision" → structured decision record (chose/over/because), "learning" → insight that auto-propagates to parent and sibling tasks on completion.',
|
|
208
195
|
inputSchema: {
|
|
209
196
|
type: 'object',
|
|
210
197
|
properties: {
|
|
198
|
+
type: {
|
|
199
|
+
type: 'string',
|
|
200
|
+
enum: ['general', 'decision', 'learning'],
|
|
201
|
+
description: 'Artifact type',
|
|
202
|
+
},
|
|
211
203
|
task_id: { type: 'number', description: 'Task ID' },
|
|
212
204
|
name: {
|
|
213
205
|
type: 'string',
|
|
214
|
-
description: 'Artifact name (e.g. "spec", "test-results", "review-notes")',
|
|
206
|
+
description: 'Artifact name (type: "general" only, e.g. "spec", "test-results", "review-notes")',
|
|
215
207
|
},
|
|
216
208
|
content: {
|
|
217
209
|
type: 'string',
|
|
218
|
-
description: 'Artifact content (text
|
|
210
|
+
description: 'Artifact content (type: "general": text/markdown/JSON, max 100K; type: "learning": the insight)',
|
|
219
211
|
},
|
|
220
212
|
stage: {
|
|
221
213
|
type: 'string',
|
|
222
|
-
description: 'Stage to attach to (defaults to
|
|
214
|
+
description: 'Stage to attach to (type: "general" only, defaults to current stage)',
|
|
215
|
+
},
|
|
216
|
+
chose: { type: 'string', description: 'What was chosen (type: "decision" only)' },
|
|
217
|
+
over: {
|
|
218
|
+
type: 'string',
|
|
219
|
+
description: 'What alternatives were considered (type: "decision" only)',
|
|
220
|
+
},
|
|
221
|
+
because: {
|
|
222
|
+
type: 'string',
|
|
223
|
+
description: 'Rationale for the decision (type: "decision" only)',
|
|
224
|
+
},
|
|
225
|
+
category: {
|
|
226
|
+
type: 'string',
|
|
227
|
+
enum: ['technique', 'pitfall', 'decision', 'pattern'],
|
|
228
|
+
description: 'Learning category (type: "learning" only, default: technique)',
|
|
223
229
|
},
|
|
224
230
|
},
|
|
225
|
-
required: ['
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
name: 'task_get_artifacts',
|
|
230
|
-
description: 'Get artifacts attached to a task.',
|
|
231
|
-
inputSchema: {
|
|
232
|
-
type: 'object',
|
|
233
|
-
properties: {
|
|
234
|
-
task_id: { type: 'number', description: 'Task ID' },
|
|
235
|
-
stage: { type: 'string', description: 'Filter by stage' },
|
|
236
|
-
},
|
|
237
|
-
required: ['task_id'],
|
|
231
|
+
required: ['type', 'task_id'],
|
|
238
232
|
},
|
|
239
233
|
},
|
|
240
234
|
{
|
|
241
|
-
name: '
|
|
242
|
-
description: '
|
|
235
|
+
name: 'task_config',
|
|
236
|
+
description: 'Configuration and admin. Actions: "session" → set agent identity (call this first), "pipeline" → get/set pipeline stages and gate config for a project, "cleanup" → purge old completed tasks, "rules" → generate IDE rule files (.mdc or CLAUDE.md).',
|
|
243
237
|
inputSchema: {
|
|
244
238
|
type: 'object',
|
|
245
239
|
properties: {
|
|
240
|
+
action: {
|
|
241
|
+
type: 'string',
|
|
242
|
+
enum: ['pipeline', 'session', 'cleanup', 'rules'],
|
|
243
|
+
description: 'Config action to perform',
|
|
244
|
+
},
|
|
246
245
|
project: {
|
|
247
246
|
type: 'string',
|
|
248
|
-
description: 'Project name (
|
|
247
|
+
description: 'Project name (pipeline: scope, rules: project-specific rules)',
|
|
249
248
|
},
|
|
250
249
|
stages: {
|
|
251
250
|
type: 'array',
|
|
252
251
|
items: { type: 'string' },
|
|
253
|
-
description: 'Stage names in order (set mode)',
|
|
252
|
+
description: 'Stage names in order (pipeline set mode)',
|
|
254
253
|
},
|
|
255
254
|
gate_config: {
|
|
256
255
|
type: 'object',
|
|
257
|
-
description: 'Stage-gate enforcement config. Example: { "require_comment": true, "require_artifact": false, "exempt_stages": ["backlog"] }',
|
|
256
|
+
description: 'Stage-gate enforcement config (pipeline only). Example: { "require_comment": true, "require_artifact": false, "exempt_stages": ["backlog"] }',
|
|
258
257
|
properties: {
|
|
259
258
|
require_comment: {
|
|
260
259
|
type: 'boolean',
|
|
@@ -297,588 +296,129 @@ export const tools = [
|
|
|
297
296
|
},
|
|
298
297
|
},
|
|
299
298
|
},
|
|
299
|
+
id: { type: 'string', description: 'Session ID (session only)' },
|
|
300
|
+
name: { type: 'string', description: 'Session name (session only)' },
|
|
301
|
+
mode: {
|
|
302
|
+
type: 'string',
|
|
303
|
+
enum: ['retention', 'stale_agents', 'all'],
|
|
304
|
+
description: 'Cleanup mode (cleanup only, default: retention)',
|
|
305
|
+
},
|
|
306
|
+
timeout_minutes: {
|
|
307
|
+
type: 'number',
|
|
308
|
+
description: 'Heartbeat timeout in minutes for stale agent detection (cleanup only, default: 30)',
|
|
309
|
+
},
|
|
310
|
+
format: {
|
|
311
|
+
type: 'string',
|
|
312
|
+
enum: ['mdc', 'claude_md'],
|
|
313
|
+
description: 'Output format for rules: mdc (Cursor) or claude_md (Claude Code)',
|
|
314
|
+
},
|
|
300
315
|
},
|
|
316
|
+
required: ['action'],
|
|
301
317
|
},
|
|
302
318
|
},
|
|
303
319
|
{
|
|
304
|
-
name: '
|
|
305
|
-
description: '
|
|
306
|
-
inputSchema: {
|
|
307
|
-
type: 'object',
|
|
308
|
-
properties: {
|
|
309
|
-
id: { type: 'string', description: 'Session ID' },
|
|
310
|
-
name: { type: 'string', description: 'Session name' },
|
|
311
|
-
},
|
|
312
|
-
required: ['id', 'name'],
|
|
313
|
-
},
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
name: 'task_delete',
|
|
317
|
-
description: 'Delete a task and all its artifacts and dependencies (cascading delete).',
|
|
318
|
-
inputSchema: {
|
|
319
|
-
type: 'object',
|
|
320
|
-
properties: {
|
|
321
|
-
task_id: { type: 'number', description: 'Task ID to delete' },
|
|
322
|
-
},
|
|
323
|
-
required: ['task_id'],
|
|
324
|
-
},
|
|
325
|
-
},
|
|
326
|
-
{
|
|
327
|
-
name: 'task_comment',
|
|
328
|
-
description: 'Add a comment to a task for async discussion between agents.',
|
|
329
|
-
inputSchema: {
|
|
330
|
-
type: 'object',
|
|
331
|
-
properties: {
|
|
332
|
-
task_id: { type: 'number', description: 'Task ID' },
|
|
333
|
-
content: { type: 'string', description: 'Comment text' },
|
|
334
|
-
parent_comment_id: { type: 'number', description: 'Reply to this comment (threading)' },
|
|
335
|
-
},
|
|
336
|
-
required: ['task_id', 'content'],
|
|
337
|
-
},
|
|
338
|
-
},
|
|
339
|
-
{
|
|
340
|
-
name: 'task_get_comments',
|
|
341
|
-
description: 'Get comments on a task.',
|
|
320
|
+
name: 'task_dependency',
|
|
321
|
+
description: 'Manage task dependencies. "add" → create a blocks/related/duplicate relationship between two tasks (blocks advancement until resolved). "remove" → delete a dependency.',
|
|
342
322
|
inputSchema: {
|
|
343
323
|
type: 'object',
|
|
344
324
|
properties: {
|
|
345
|
-
|
|
346
|
-
|
|
325
|
+
action: {
|
|
326
|
+
type: 'string',
|
|
327
|
+
enum: ['add', 'remove'],
|
|
328
|
+
description: 'Action to perform',
|
|
329
|
+
},
|
|
330
|
+
task_id: { type: 'number', description: 'Task that depends on another' },
|
|
331
|
+
depends_on: {
|
|
332
|
+
type: 'number',
|
|
333
|
+
description: 'Task that must complete first (for blocks) or related task',
|
|
334
|
+
},
|
|
335
|
+
relationship: {
|
|
336
|
+
type: 'string',
|
|
337
|
+
enum: ['blocks', 'related', 'duplicate'],
|
|
338
|
+
description: 'Relationship type (default: blocks, only used with "add")',
|
|
339
|
+
},
|
|
347
340
|
},
|
|
348
|
-
required: ['task_id'],
|
|
341
|
+
required: ['action', 'task_id', 'depends_on'],
|
|
349
342
|
},
|
|
350
343
|
},
|
|
351
344
|
{
|
|
352
|
-
name: '
|
|
353
|
-
description: '
|
|
345
|
+
name: 'task_collaborator',
|
|
346
|
+
description: 'Manage task collaborators. "add" → assign an agent as collaborator (can work on it), reviewer (reviews artifacts), or watcher (gets notifications). "remove" → unassign an agent.',
|
|
354
347
|
inputSchema: {
|
|
355
348
|
type: 'object',
|
|
356
349
|
properties: {
|
|
350
|
+
action: {
|
|
351
|
+
type: 'string',
|
|
352
|
+
enum: ['add', 'remove'],
|
|
353
|
+
description: 'Action to perform',
|
|
354
|
+
},
|
|
357
355
|
task_id: { type: 'number', description: 'Task ID' },
|
|
358
356
|
agent_id: { type: 'string', description: 'Agent name or ID' },
|
|
359
357
|
role: {
|
|
360
358
|
type: 'string',
|
|
361
359
|
enum: ['collaborator', 'reviewer', 'watcher'],
|
|
362
|
-
description: 'Role (default: collaborator)',
|
|
360
|
+
description: 'Role (default: collaborator, only used with "add")',
|
|
363
361
|
},
|
|
364
362
|
},
|
|
365
|
-
required: ['task_id', 'agent_id'],
|
|
366
|
-
},
|
|
367
|
-
},
|
|
368
|
-
{
|
|
369
|
-
name: 'task_remove_collaborator',
|
|
370
|
-
description: 'Remove a collaborator from a task.',
|
|
371
|
-
inputSchema: {
|
|
372
|
-
type: 'object',
|
|
373
|
-
properties: {
|
|
374
|
-
task_id: { type: 'number', description: 'Task ID' },
|
|
375
|
-
agent_id: { type: 'string', description: 'Agent name or ID' },
|
|
376
|
-
},
|
|
377
|
-
required: ['task_id', 'agent_id'],
|
|
378
|
-
},
|
|
379
|
-
},
|
|
380
|
-
{
|
|
381
|
-
name: 'task_search',
|
|
382
|
-
description: 'Full-text search across task titles and descriptions.',
|
|
383
|
-
inputSchema: {
|
|
384
|
-
type: 'object',
|
|
385
|
-
properties: {
|
|
386
|
-
query: { type: 'string', description: 'Search query' },
|
|
387
|
-
project: { type: 'string', description: 'Filter by project' },
|
|
388
|
-
limit: { type: 'number', description: 'Max results (default: 50)' },
|
|
389
|
-
},
|
|
390
|
-
required: ['query'],
|
|
391
|
-
},
|
|
392
|
-
},
|
|
393
|
-
{
|
|
394
|
-
name: 'task_get_subtasks',
|
|
395
|
-
description: 'Get subtasks of a parent task.',
|
|
396
|
-
inputSchema: {
|
|
397
|
-
type: 'object',
|
|
398
|
-
properties: {
|
|
399
|
-
task_id: { type: 'number', description: 'Parent task ID' },
|
|
400
|
-
},
|
|
401
|
-
required: ['task_id'],
|
|
402
|
-
},
|
|
403
|
-
},
|
|
404
|
-
{
|
|
405
|
-
name: 'task_request_approval',
|
|
406
|
-
description: 'Request approval for a task at a specific stage.',
|
|
407
|
-
inputSchema: {
|
|
408
|
-
type: 'object',
|
|
409
|
-
properties: {
|
|
410
|
-
task_id: { type: 'number', description: 'Task ID' },
|
|
411
|
-
stage: { type: 'string', description: 'Stage requiring approval (defaults to current)' },
|
|
412
|
-
reviewer: { type: 'string', description: 'Specific reviewer to assign' },
|
|
413
|
-
},
|
|
414
|
-
required: ['task_id'],
|
|
415
|
-
},
|
|
416
|
-
},
|
|
417
|
-
{
|
|
418
|
-
name: 'task_approve',
|
|
419
|
-
description: 'Approve a pending approval request.',
|
|
420
|
-
inputSchema: {
|
|
421
|
-
type: 'object',
|
|
422
|
-
properties: {
|
|
423
|
-
approval_id: { type: 'number', description: 'Approval ID' },
|
|
424
|
-
comment: { type: 'string', description: 'Approval comment' },
|
|
425
|
-
},
|
|
426
|
-
required: ['approval_id'],
|
|
427
|
-
},
|
|
428
|
-
},
|
|
429
|
-
{
|
|
430
|
-
name: 'task_reject',
|
|
431
|
-
description: 'Reject a pending approval and optionally regress the task.',
|
|
432
|
-
inputSchema: {
|
|
433
|
-
type: 'object',
|
|
434
|
-
properties: {
|
|
435
|
-
approval_id: { type: 'number', description: 'Approval ID' },
|
|
436
|
-
comment: { type: 'string', description: 'Rejection reason (required)' },
|
|
437
|
-
regress_to: { type: 'string', description: 'Stage to regress task to' },
|
|
438
|
-
},
|
|
439
|
-
required: ['approval_id', 'comment'],
|
|
363
|
+
required: ['action', 'task_id', 'agent_id'],
|
|
440
364
|
},
|
|
441
365
|
},
|
|
442
366
|
{
|
|
443
|
-
name: '
|
|
444
|
-
description: '
|
|
367
|
+
name: 'task_approval',
|
|
368
|
+
description: 'Manage approval workflows for stage gates. Actions: "request" → create approval request at current stage, "approve"/"reject" → decide on a pending approval, "list" → show pending approvals, "review" → convenience action that approves+advances or rejects+regresses in one call.',
|
|
445
369
|
inputSchema: {
|
|
446
370
|
type: 'object',
|
|
447
371
|
properties: {
|
|
448
|
-
reviewer: { type: 'string', description: 'Filter by reviewer' },
|
|
449
|
-
},
|
|
450
|
-
},
|
|
451
|
-
},
|
|
452
|
-
{
|
|
453
|
-
name: 'task_review_cycle',
|
|
454
|
-
description: 'Review a task: approve (advance to next stage) or reject (regress with reason). Convenience wrapper for the maker-checker pattern.',
|
|
455
|
-
inputSchema: {
|
|
456
|
-
type: 'object',
|
|
457
|
-
properties: {
|
|
458
|
-
task_id: { type: 'number', description: 'Task ID to review' },
|
|
459
372
|
action: {
|
|
460
373
|
type: 'string',
|
|
461
|
-
enum: ['approve', 'reject'],
|
|
462
|
-
description: '
|
|
374
|
+
enum: ['request', 'approve', 'reject', 'list', 'review'],
|
|
375
|
+
description: 'Action to perform',
|
|
463
376
|
},
|
|
464
|
-
|
|
465
|
-
|
|
377
|
+
task_id: {
|
|
378
|
+
type: 'number',
|
|
379
|
+
description: 'Task ID (required for request and review)',
|
|
380
|
+
},
|
|
381
|
+
approval_id: {
|
|
382
|
+
type: 'number',
|
|
383
|
+
description: 'Approval ID (required for approve and reject)',
|
|
384
|
+
},
|
|
385
|
+
stage: {
|
|
466
386
|
type: 'string',
|
|
467
|
-
description: 'Stage
|
|
387
|
+
description: 'Stage requiring approval (request only, defaults to current)',
|
|
468
388
|
},
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
},
|
|
473
|
-
{
|
|
474
|
-
name: 'task_expand',
|
|
475
|
-
description: 'Break a task into subtasks. Creates subtasks with parent_id pointing to the given task, inheriting project and priority.',
|
|
476
|
-
inputSchema: {
|
|
477
|
-
type: 'object',
|
|
478
|
-
properties: {
|
|
479
|
-
task_id: { type: 'number', description: 'Parent task ID to expand' },
|
|
480
|
-
subtasks: {
|
|
481
|
-
type: 'array',
|
|
482
|
-
items: {
|
|
483
|
-
type: 'object',
|
|
484
|
-
properties: {
|
|
485
|
-
title: { type: 'string', description: 'Subtask title' },
|
|
486
|
-
description: { type: 'string', description: 'Subtask description' },
|
|
487
|
-
priority: {
|
|
488
|
-
type: 'number',
|
|
489
|
-
description: 'Priority override (inherits from parent if omitted)',
|
|
490
|
-
},
|
|
491
|
-
},
|
|
492
|
-
required: ['title'],
|
|
493
|
-
},
|
|
494
|
-
description: 'Array of subtasks to create',
|
|
389
|
+
reviewer: {
|
|
390
|
+
type: 'string',
|
|
391
|
+
description: 'Reviewer to assign (request) or filter by (list)',
|
|
495
392
|
},
|
|
496
|
-
|
|
497
|
-
required: ['task_id', 'subtasks'],
|
|
498
|
-
},
|
|
499
|
-
},
|
|
500
|
-
{
|
|
501
|
-
name: 'task_cleanup',
|
|
502
|
-
description: 'Run data cleanup. Modes: "retention" (default) purges old completed/cancelled tasks, "stale_agents" checks agent heartbeats and fails tasks from dead agents, "all" runs both.',
|
|
503
|
-
inputSchema: {
|
|
504
|
-
type: 'object',
|
|
505
|
-
properties: {
|
|
506
|
-
mode: {
|
|
393
|
+
comment: {
|
|
507
394
|
type: 'string',
|
|
508
|
-
|
|
509
|
-
description: 'Cleanup mode (default: retention)',
|
|
395
|
+
description: 'Comment (optional for approve, required for reject)',
|
|
510
396
|
},
|
|
511
|
-
|
|
512
|
-
type: '
|
|
513
|
-
|
|
397
|
+
decision: {
|
|
398
|
+
type: 'string',
|
|
399
|
+
enum: ['approve', 'reject'],
|
|
400
|
+
description: 'Decision for review action',
|
|
514
401
|
},
|
|
515
|
-
|
|
516
|
-
},
|
|
517
|
-
},
|
|
518
|
-
{
|
|
519
|
-
name: 'task_generate_rules',
|
|
520
|
-
description: 'Generate IDE-specific rule files that instruct agents to use the pipeline. Supports Cursor (.mdc) and Claude Code (CLAUDE.md) formats.',
|
|
521
|
-
inputSchema: {
|
|
522
|
-
type: 'object',
|
|
523
|
-
properties: {
|
|
524
|
-
format: {
|
|
402
|
+
reason: {
|
|
525
403
|
type: 'string',
|
|
526
|
-
|
|
527
|
-
|
|
404
|
+
description: 'Rejection reason (required for review+reject)',
|
|
405
|
+
},
|
|
406
|
+
regress_to: {
|
|
407
|
+
type: 'string',
|
|
408
|
+
description: 'Stage to regress to on rejection (reject/review, default: implement)',
|
|
528
409
|
},
|
|
529
|
-
project: { type: 'string', description: 'Project name for project-specific rules' },
|
|
530
|
-
},
|
|
531
|
-
required: ['format'],
|
|
532
|
-
},
|
|
533
|
-
},
|
|
534
|
-
{
|
|
535
|
-
name: 'task_decision',
|
|
536
|
-
description: 'Record an architectural or design decision as a structured artifact on a task. Stored at the current stage.',
|
|
537
|
-
inputSchema: {
|
|
538
|
-
type: 'object',
|
|
539
|
-
properties: {
|
|
540
|
-
task_id: { type: 'number', description: 'Task ID to record the decision on' },
|
|
541
|
-
chose: { type: 'string', description: 'What was chosen' },
|
|
542
|
-
over: { type: 'string', description: 'What alternatives were considered' },
|
|
543
|
-
because: { type: 'string', description: 'Rationale for the decision' },
|
|
544
410
|
},
|
|
545
|
-
required: ['
|
|
411
|
+
required: ['action'],
|
|
546
412
|
},
|
|
547
413
|
},
|
|
548
414
|
];
|
|
549
|
-
// ---------------------------------------------------------------------------
|
|
550
|
-
// Input validation helpers
|
|
551
|
-
// ---------------------------------------------------------------------------
|
|
552
|
-
function requireString(args, key) {
|
|
553
|
-
const val = args[key];
|
|
554
|
-
if (typeof val !== 'string' || !val.trim()) {
|
|
555
|
-
throw new ValidationError(`"${key}" must be a non-empty string.`);
|
|
556
|
-
}
|
|
557
|
-
return val;
|
|
558
|
-
}
|
|
559
|
-
function optString(args, key) {
|
|
560
|
-
const val = args[key];
|
|
561
|
-
if (val === undefined || val === null)
|
|
562
|
-
return undefined;
|
|
563
|
-
if (typeof val !== 'string')
|
|
564
|
-
throw new ValidationError(`"${key}" must be a string.`);
|
|
565
|
-
return val;
|
|
566
|
-
}
|
|
567
|
-
function requireNumber(args, key) {
|
|
568
|
-
const val = args[key];
|
|
569
|
-
if (typeof val !== 'number') {
|
|
570
|
-
throw new ValidationError(`"${key}" is required and must be a number.`);
|
|
571
|
-
}
|
|
572
|
-
return val;
|
|
573
|
-
}
|
|
574
|
-
function optNumber(args, key) {
|
|
575
|
-
const val = args[key];
|
|
576
|
-
if (val === undefined || val === null)
|
|
577
|
-
return undefined;
|
|
578
|
-
if (typeof val !== 'number')
|
|
579
|
-
throw new ValidationError(`"${key}" must be a number.`);
|
|
580
|
-
return val;
|
|
581
|
-
}
|
|
582
|
-
function optBoolean(args, key) {
|
|
583
|
-
const val = args[key];
|
|
584
|
-
if (val === undefined || val === null)
|
|
585
|
-
return undefined;
|
|
586
|
-
if (typeof val !== 'boolean')
|
|
587
|
-
throw new ValidationError(`"${key}" must be a boolean.`);
|
|
588
|
-
return val;
|
|
589
|
-
}
|
|
590
|
-
function optStringArray(args, key) {
|
|
591
|
-
const val = args[key];
|
|
592
|
-
if (val === undefined || val === null)
|
|
593
|
-
return undefined;
|
|
594
|
-
if (!Array.isArray(val) || !val.every((v) => typeof v === 'string')) {
|
|
595
|
-
throw new ValidationError(`"${key}" must be an array of strings.`);
|
|
596
|
-
}
|
|
597
|
-
return val;
|
|
598
|
-
}
|
|
599
415
|
export function createToolHandler(ctx) {
|
|
600
|
-
|
|
601
|
-
function sessionName() {
|
|
602
|
-
return currentSession?.name ?? 'system';
|
|
603
|
-
}
|
|
416
|
+
const session = { current: null };
|
|
604
417
|
return function handleTool(name, args) {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
currentSession = { id, name: sName };
|
|
610
|
-
writeSessionFile(id, sName);
|
|
611
|
-
return { success: true, id, name: sName };
|
|
612
|
-
}
|
|
613
|
-
case 'task_create': {
|
|
614
|
-
return ctx.tasks.create({
|
|
615
|
-
title: requireString(args, 'title'),
|
|
616
|
-
description: optString(args, 'description'),
|
|
617
|
-
assign_to: optString(args, 'assign_to'),
|
|
618
|
-
stage: optString(args, 'stage'),
|
|
619
|
-
priority: optNumber(args, 'priority'),
|
|
620
|
-
project: optString(args, 'project'),
|
|
621
|
-
tags: optStringArray(args, 'tags'),
|
|
622
|
-
parent_id: optNumber(args, 'parent_id'),
|
|
623
|
-
}, sessionName());
|
|
624
|
-
}
|
|
625
|
-
case 'task_list': {
|
|
626
|
-
return ctx.tasks.list({
|
|
627
|
-
status: optString(args, 'status'),
|
|
628
|
-
assigned_to: optString(args, 'assigned_to'),
|
|
629
|
-
stage: optString(args, 'stage'),
|
|
630
|
-
project: optString(args, 'project'),
|
|
631
|
-
parent_id: optNumber(args, 'parent_id'),
|
|
632
|
-
root_only: optBoolean(args, 'root_only'),
|
|
633
|
-
collaborator: optString(args, 'collaborator'),
|
|
634
|
-
limit: optNumber(args, 'limit'),
|
|
635
|
-
offset: optNumber(args, 'offset'),
|
|
636
|
-
});
|
|
637
|
-
}
|
|
638
|
-
case 'task_claim': {
|
|
639
|
-
const claimer = optString(args, 'claimer') ?? sessionName();
|
|
640
|
-
return ctx.tasks.claim(requireNumber(args, 'task_id'), claimer);
|
|
641
|
-
}
|
|
642
|
-
case 'task_complete':
|
|
643
|
-
return ctx.tasks.complete(requireNumber(args, 'task_id'), requireString(args, 'result'));
|
|
644
|
-
case 'task_fail':
|
|
645
|
-
return ctx.tasks.fail(requireNumber(args, 'task_id'), requireString(args, 'result'));
|
|
646
|
-
case 'task_cancel':
|
|
647
|
-
return ctx.tasks.cancel(requireNumber(args, 'task_id'), requireString(args, 'reason'));
|
|
648
|
-
case 'task_advance': {
|
|
649
|
-
const advanceTaskId = requireNumber(args, 'task_id');
|
|
650
|
-
const advanceComment = optString(args, 'comment');
|
|
651
|
-
const advanced = ctx.tasks.advance(advanceTaskId, optString(args, 'stage'), advanceComment);
|
|
652
|
-
if (advanceComment) {
|
|
653
|
-
ctx.comments.add(advanceTaskId, sessionName(), advanceComment);
|
|
654
|
-
}
|
|
655
|
-
return advanced;
|
|
656
|
-
}
|
|
657
|
-
case 'task_regress':
|
|
658
|
-
return ctx.tasks.regress(requireNumber(args, 'task_id'), requireString(args, 'stage'), optString(args, 'reason'));
|
|
659
|
-
case 'task_update':
|
|
660
|
-
return ctx.tasks.update(requireNumber(args, 'task_id'), {
|
|
661
|
-
title: optString(args, 'title'),
|
|
662
|
-
description: optString(args, 'description'),
|
|
663
|
-
priority: optNumber(args, 'priority'),
|
|
664
|
-
project: optString(args, 'project'),
|
|
665
|
-
tags: optStringArray(args, 'tags'),
|
|
666
|
-
assigned_to: optString(args, 'assign_to'),
|
|
667
|
-
});
|
|
668
|
-
case 'task_next': {
|
|
669
|
-
return (ctx.tasks.next(optString(args, 'project'), optString(args, 'stage')) ?? {
|
|
670
|
-
message: 'No available tasks.',
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
case 'task_add_dependency': {
|
|
674
|
-
const relationship = (optString(args, 'relationship') ?? 'blocks');
|
|
675
|
-
ctx.tasks.addDependency(requireNumber(args, 'task_id'), requireNumber(args, 'depends_on'), relationship);
|
|
676
|
-
return { success: true, task_id: args.task_id, depends_on: args.depends_on, relationship };
|
|
677
|
-
}
|
|
678
|
-
case 'task_remove_dependency': {
|
|
679
|
-
ctx.tasks.removeDependency(requireNumber(args, 'task_id'), requireNumber(args, 'depends_on'));
|
|
680
|
-
return { success: true };
|
|
681
|
-
}
|
|
682
|
-
case 'task_add_artifact': {
|
|
683
|
-
return ctx.tasks.addArtifact(requireNumber(args, 'task_id'), requireString(args, 'name'), requireString(args, 'content'), sessionName(), optString(args, 'stage'));
|
|
684
|
-
}
|
|
685
|
-
case 'task_get_artifacts':
|
|
686
|
-
return ctx.tasks.getArtifacts(requireNumber(args, 'task_id'), optString(args, 'stage'));
|
|
687
|
-
case 'task_pipeline_config': {
|
|
688
|
-
const stages = optStringArray(args, 'stages');
|
|
689
|
-
const gateConfig = args.gate_config;
|
|
690
|
-
const project = optString(args, 'project') || 'default';
|
|
691
|
-
if (stages) {
|
|
692
|
-
ctx.tasks.setPipelineConfig(project, stages);
|
|
693
|
-
}
|
|
694
|
-
if (gateConfig && typeof gateConfig === 'object') {
|
|
695
|
-
const parsedGates = {};
|
|
696
|
-
if (gateConfig.gates && typeof gateConfig.gates === 'object') {
|
|
697
|
-
for (const [stageName, stageRule] of Object.entries(gateConfig.gates)) {
|
|
698
|
-
parsedGates[stageName] = {
|
|
699
|
-
require_artifacts: Array.isArray(stageRule.require_artifacts)
|
|
700
|
-
? stageRule.require_artifacts
|
|
701
|
-
: undefined,
|
|
702
|
-
require_min_artifacts: typeof stageRule.require_min_artifacts === 'number'
|
|
703
|
-
? stageRule.require_min_artifacts
|
|
704
|
-
: undefined,
|
|
705
|
-
require_comment: stageRule.require_comment === true ? true : undefined,
|
|
706
|
-
require_approval: stageRule.require_approval === true ? true : undefined,
|
|
707
|
-
};
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
ctx.tasks.setGateConfig(project, {
|
|
711
|
-
require_comment: gateConfig.require_comment === true,
|
|
712
|
-
require_artifact: gateConfig.require_artifact === true,
|
|
713
|
-
exempt_stages: Array.isArray(gateConfig.exempt_stages)
|
|
714
|
-
? gateConfig.exempt_stages
|
|
715
|
-
: undefined,
|
|
716
|
-
gates: Object.keys(parsedGates).length > 0 ? parsedGates : undefined,
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
if (stages || gateConfig) {
|
|
720
|
-
const config = ctx.tasks.getGateConfig(project);
|
|
721
|
-
return {
|
|
722
|
-
stages: ctx.tasks.getPipelineStages(project),
|
|
723
|
-
gate_config: config ?? { require_comment: false },
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
|
-
return {
|
|
727
|
-
stages: ctx.tasks.getPipelineStages(optString(args, 'project')),
|
|
728
|
-
gate_config: ctx.tasks.getGateConfig(optString(args, 'project')) ?? {
|
|
729
|
-
require_comment: false,
|
|
730
|
-
},
|
|
731
|
-
};
|
|
732
|
-
}
|
|
733
|
-
case 'task_delete': {
|
|
734
|
-
ctx.tasks.delete(requireNumber(args, 'task_id'));
|
|
735
|
-
return { success: true };
|
|
736
|
-
}
|
|
737
|
-
case 'task_comment': {
|
|
738
|
-
return ctx.comments.add(requireNumber(args, 'task_id'), sessionName(), requireString(args, 'content'), optNumber(args, 'parent_comment_id'));
|
|
739
|
-
}
|
|
740
|
-
case 'task_get_comments':
|
|
741
|
-
return ctx.comments.list(requireNumber(args, 'task_id'), optNumber(args, 'limit'));
|
|
742
|
-
case 'task_add_collaborator': {
|
|
743
|
-
return ctx.collaborators.add(requireNumber(args, 'task_id'), requireString(args, 'agent_id'), (optString(args, 'role') ?? 'collaborator'));
|
|
744
|
-
}
|
|
745
|
-
case 'task_remove_collaborator': {
|
|
746
|
-
ctx.collaborators.remove(requireNumber(args, 'task_id'), requireString(args, 'agent_id'));
|
|
747
|
-
return { success: true };
|
|
748
|
-
}
|
|
749
|
-
case 'task_search':
|
|
750
|
-
return ctx.tasks.search(requireString(args, 'query'), {
|
|
751
|
-
project: optString(args, 'project'),
|
|
752
|
-
limit: optNumber(args, 'limit'),
|
|
753
|
-
});
|
|
754
|
-
case 'task_get_subtasks':
|
|
755
|
-
return ctx.tasks.getSubtasks(requireNumber(args, 'task_id'));
|
|
756
|
-
case 'task_request_approval': {
|
|
757
|
-
const taskId = requireNumber(args, 'task_id');
|
|
758
|
-
const task = ctx.tasks.getById(taskId);
|
|
759
|
-
if (!task)
|
|
760
|
-
throw new ValidationError(`Task ${taskId} not found.`);
|
|
761
|
-
const stage = optString(args, 'stage') ?? task.stage;
|
|
762
|
-
return ctx.approvals.request(taskId, stage, optString(args, 'reviewer'));
|
|
763
|
-
}
|
|
764
|
-
case 'task_approve':
|
|
765
|
-
return ctx.approvals.approve(requireNumber(args, 'approval_id'), sessionName(), optString(args, 'comment'));
|
|
766
|
-
case 'task_reject': {
|
|
767
|
-
const approval = ctx.approvals.reject(requireNumber(args, 'approval_id'), sessionName(), requireString(args, 'comment'));
|
|
768
|
-
const regressTo = optString(args, 'regress_to');
|
|
769
|
-
if (regressTo) {
|
|
770
|
-
ctx.tasks.regress(approval.task_id, regressTo, requireString(args, 'comment'));
|
|
771
|
-
}
|
|
772
|
-
return approval;
|
|
773
|
-
}
|
|
774
|
-
case 'task_pending_approvals':
|
|
775
|
-
return ctx.approvals.getPending(optString(args, 'reviewer'));
|
|
776
|
-
case 'task_review_cycle': {
|
|
777
|
-
const taskId = requireNumber(args, 'task_id');
|
|
778
|
-
const action = requireString(args, 'action');
|
|
779
|
-
const task = ctx.tasks.getById(taskId);
|
|
780
|
-
if (!task)
|
|
781
|
-
throw new ValidationError(`Task ${taskId} not found.`);
|
|
782
|
-
if (action === 'approve') {
|
|
783
|
-
ctx.tasks.advance(taskId);
|
|
784
|
-
return { success: true, action: 'approved', task: ctx.tasks.getById(taskId) };
|
|
785
|
-
}
|
|
786
|
-
else if (action === 'reject') {
|
|
787
|
-
const reason = requireString(args, 'reason');
|
|
788
|
-
const regressTo = optString(args, 'regress_to') ?? 'implement';
|
|
789
|
-
ctx.tasks.regress(taskId, regressTo, reason);
|
|
790
|
-
return { success: true, action: 'rejected', task: ctx.tasks.getById(taskId) };
|
|
791
|
-
}
|
|
792
|
-
else {
|
|
793
|
-
throw new ValidationError(`Invalid action: ${action}. Use "approve" or "reject".`);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
case 'task_expand': {
|
|
797
|
-
const parentId = requireNumber(args, 'task_id');
|
|
798
|
-
const parent = ctx.tasks.getById(parentId);
|
|
799
|
-
if (!parent)
|
|
800
|
-
throw new ValidationError(`Task ${parentId} not found.`);
|
|
801
|
-
const subtaskDefs = args.subtasks;
|
|
802
|
-
if (!Array.isArray(subtaskDefs) || subtaskDefs.length === 0) {
|
|
803
|
-
throw new ValidationError('"subtasks" must be a non-empty array.');
|
|
804
|
-
}
|
|
805
|
-
const created = [];
|
|
806
|
-
for (const sub of subtaskDefs) {
|
|
807
|
-
if (typeof sub !== 'object' ||
|
|
808
|
-
sub === null ||
|
|
809
|
-
typeof sub.title !== 'string') {
|
|
810
|
-
throw new ValidationError('Each subtask must have a "title" string.');
|
|
811
|
-
}
|
|
812
|
-
const s = sub;
|
|
813
|
-
created.push(ctx.tasks.create({
|
|
814
|
-
title: s.title,
|
|
815
|
-
description: s.description ?? undefined,
|
|
816
|
-
priority: typeof s.priority === 'number' ? s.priority : parent.priority,
|
|
817
|
-
project: parent.project ?? undefined,
|
|
818
|
-
parent_id: parentId,
|
|
819
|
-
}, sessionName()));
|
|
820
|
-
}
|
|
821
|
-
return created;
|
|
822
|
-
}
|
|
823
|
-
case 'task_cleanup': {
|
|
824
|
-
const cleanupMode = (optString(args, 'mode') ?? 'retention');
|
|
825
|
-
const timeoutMinutes = optNumber(args, 'timeout_minutes');
|
|
826
|
-
if (cleanupMode === 'stale_agents') {
|
|
827
|
-
return ctx.cleanup.failStaleAgentTasks(timeoutMinutes).then((stale) => ({
|
|
828
|
-
stale_agents: stale,
|
|
829
|
-
}));
|
|
830
|
-
}
|
|
831
|
-
if (cleanupMode === 'all') {
|
|
832
|
-
const retention = ctx.cleanup.run();
|
|
833
|
-
return ctx.cleanup.failStaleAgentTasks(timeoutMinutes).then((stale) => ({
|
|
834
|
-
retention,
|
|
835
|
-
stale_agents: stale,
|
|
836
|
-
}));
|
|
837
|
-
}
|
|
838
|
-
return ctx.cleanup.run();
|
|
839
|
-
}
|
|
840
|
-
case 'task_generate_rules': {
|
|
841
|
-
const format = requireString(args, 'format');
|
|
842
|
-
if (format !== 'mdc' && format !== 'claude_md') {
|
|
843
|
-
throw new ValidationError('Format must be "mdc" or "claude_md".');
|
|
844
|
-
}
|
|
845
|
-
const project = optString(args, 'project');
|
|
846
|
-
const stages = ctx.tasks.getPipelineStages(project);
|
|
847
|
-
return { rules: generateRules(format, stages, project) };
|
|
848
|
-
}
|
|
849
|
-
case 'task_decision': {
|
|
850
|
-
const taskId = requireNumber(args, 'task_id');
|
|
851
|
-
const chose = requireString(args, 'chose');
|
|
852
|
-
const over = requireString(args, 'over');
|
|
853
|
-
const because = requireString(args, 'because');
|
|
854
|
-
const task = ctx.tasks.getById(taskId);
|
|
855
|
-
if (!task)
|
|
856
|
-
throw new ValidationError(`Task ${taskId} not found.`);
|
|
857
|
-
const decisionStage = task.stage;
|
|
858
|
-
const decisionContent = [
|
|
859
|
-
'## Decision',
|
|
860
|
-
`**Chose:** ${chose}`,
|
|
861
|
-
`**Over:** ${over}`,
|
|
862
|
-
`**Because:** ${because}`,
|
|
863
|
-
'',
|
|
864
|
-
`_Recorded at stage: ${decisionStage}_`,
|
|
865
|
-
].join('\n');
|
|
866
|
-
return ctx.tasks.addArtifact(taskId, decisionStage, 'decision', decisionContent);
|
|
867
|
-
}
|
|
868
|
-
default:
|
|
869
|
-
throw new ValidationError(`Unknown tool: ${name}`);
|
|
870
|
-
}
|
|
418
|
+
const handler = handlers[name];
|
|
419
|
+
if (!handler)
|
|
420
|
+
throw new ValidationError(`Unknown tool: ${name}`);
|
|
421
|
+
return handler(ctx, args, session);
|
|
871
422
|
};
|
|
872
423
|
}
|
|
873
|
-
function writeSessionFile(id, name) {
|
|
874
|
-
try {
|
|
875
|
-
const claudeDir = join(homedir(), '.claude');
|
|
876
|
-
mkdirSync(claudeDir, { recursive: true });
|
|
877
|
-
const filePath = join(claudeDir, `hub-session.${id}.json`);
|
|
878
|
-
writeFileSync(filePath, JSON.stringify({ pid: process.pid, name, id, timestamp: new Date().toISOString() }));
|
|
879
|
-
}
|
|
880
|
-
catch {
|
|
881
|
-
/* non-critical */
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
424
|
//# sourceMappingURL=mcp.js.map
|