@taiga-ai/mcp-server 0.18.0-dev.450 → 0.19.0-dev.451
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/dist/index.js +1036 -628
- package/package.json +1 -1
- package/src/client.ts +11 -0
- package/src/modern.integration.test.ts +1 -1
- package/src/server.integration.test.ts +767 -232
- package/src/tools/autonomy.ts +2 -2
- package/src/tools/backlogs.ts +164 -51
- package/src/tools/builds.ts +134 -47
- package/src/tools/catalogue.test.ts +23 -17
- package/src/tools/context.ts +98 -30
- package/src/tools/documents.ts +4 -1
- package/src/tools/errors.ts +40 -2
- package/src/tools/hints.ts +34 -9
- package/src/tools/inbox.ts +151 -28
- package/src/tools/index.ts +15 -1
- package/src/tools/instructions.ts +32 -154
- package/src/tools/maintenance.ts +44 -18
- package/src/tools/product-documents.ts +1 -1
- package/src/tools/products.ts +7 -5
- package/src/tools/prompts.ts +7 -7
- package/src/tools/queue.ts +12 -12
- package/src/tools/readiness.ts +172 -0
- package/src/tools/repositories.ts +4 -1
- package/src/tools/resources.ts +6 -6
- package/src/tools/sections.ts +13 -1
- package/src/tools/setup.ts +30 -30
- package/src/tools/wait.ts +13 -7
- package/taiga-ai-mcp-server-0.19.0-dev.451.tgz +0 -0
- package/taiga-ai-mcp-server-0.18.0-dev.450.tgz +0 -0
package/src/tools/autonomy.ts
CHANGED
|
@@ -70,7 +70,7 @@ export function registerAutonomyTools(server: McpServer, client: TaigaClient) {
|
|
|
70
70
|
'organization and factories allow: where either has turned autonomous merge off, true changes nothing. ' +
|
|
71
71
|
'Taiga merges through your rules, never around them.',
|
|
72
72
|
inputSchema: z.object({
|
|
73
|
-
|
|
73
|
+
initiativeId: idInput('initiative', 'taiga_list_initiatives (query finds one by title) gives it'),
|
|
74
74
|
autoMerge: z
|
|
75
75
|
.boolean()
|
|
76
76
|
.nullable()
|
|
@@ -80,7 +80,7 @@ export function registerAutonomyTools(server: McpServer, client: TaigaClient) {
|
|
|
80
80
|
},
|
|
81
81
|
(params) =>
|
|
82
82
|
runTool(async () => {
|
|
83
|
-
const item = (await client.updateBacklogItem(params.
|
|
83
|
+
const item = (await client.updateBacklogItem(params.initiativeId, { autoMerge: params.autoMerge })).data;
|
|
84
84
|
return toolResult(pickFields([item], ['id', 'title', 'state', 'autoMerge', 'autoBuild'])[0]);
|
|
85
85
|
})
|
|
86
86
|
);
|
package/src/tools/backlogs.ts
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/server';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import
|
|
3
|
+
import {
|
|
4
|
+
type ActivityEvent,
|
|
5
|
+
type ActivityActor,
|
|
6
|
+
type BacklogItem,
|
|
7
|
+
BUILD_RUN_STATUSES,
|
|
8
|
+
PLAN_REJECTION_COMMENT_MAX_CHARS,
|
|
9
|
+
PR_CLOSING_STATES,
|
|
10
|
+
USER_DROP_STATES,
|
|
11
|
+
renderHalt,
|
|
12
|
+
resolveAutoBuild,
|
|
13
|
+
type BuildRunStatus,
|
|
14
|
+
type Halt,
|
|
15
|
+
} from '@taiga/types';
|
|
4
16
|
import { TaigaApiError, type TaigaClient, type WithoutTenant } from '../client.js';
|
|
5
|
-
import { PLAN_REJECTION_COMMENT_MAX_CHARS, PR_CLOSING_STATES, USER_DROP_STATES, resolveAutoBuild } from '@taiga/types';
|
|
6
17
|
import { OPERATE, OPERATE_SETTING, READ_ONLY } from './annotations.js';
|
|
7
18
|
import { idInput, pagination, productId } from './inputs.js';
|
|
8
19
|
import { isNetworkFailure, runTool, toolError, toolResult } from './errors.js';
|
|
9
20
|
import { jobReport } from './jobs.js';
|
|
10
21
|
import { requestPrompt } from './request-prompt.js';
|
|
11
22
|
import { queueView } from './queue.js';
|
|
23
|
+
import { landedDisposition } from './builds.js';
|
|
12
24
|
import {
|
|
13
25
|
SEARCH_NOTE,
|
|
14
26
|
SEARCH_PAGE_SIZE,
|
|
@@ -62,11 +74,106 @@ const ITEM_OUTCOME_FIELDS = [
|
|
|
62
74
|
* leaves it null, and the lifecycle is what says why and what moves it on.
|
|
63
75
|
*/
|
|
64
76
|
const LIFECYCLE_MEANING =
|
|
65
|
-
'
|
|
77
|
+
'situation says in one sentence where it stands, whose turn it is and the call that moves it on. The same ' +
|
|
78
|
+
'as fields: lifecycle says whose turn it is and what moves it on: cta start_plan is taiga_queue_initiative, ' +
|
|
66
79
|
'retry_plan taiga_plan_again (planFailureCause says why the plan stopped), start_build taiga_approve_plan, ' +
|
|
67
|
-
'resume_build
|
|
80
|
+
'resume_build taiga_resume_initiative, retry_build a fresh run (start over, in the web), review_pr and ' +
|
|
68
81
|
'view_pr the pull request. needsHumanCause is set only when the line stopped on it.';
|
|
69
82
|
|
|
83
|
+
/** The move each call to action is, in the tools' words. */
|
|
84
|
+
const CTA_MOVE: Record<string, string> = {
|
|
85
|
+
start_plan: 'taiga_queue_initiative puts it on the line to be planned and built (paid work: ask first)',
|
|
86
|
+
retry_plan: 'taiga_plan_again asks for a new plan (paid work: ask first)',
|
|
87
|
+
start_build:
|
|
88
|
+
'read the plan with taiga_get_plan, then taiga_approve_plan builds it or taiga_reject_plan sends it back',
|
|
89
|
+
resume_build: 'taiga_resume_initiative carries the run on (paid work: ask first)',
|
|
90
|
+
retry_build: 'a new run starts over from the web; taiga_move_initiative takes it off the line',
|
|
91
|
+
review_pr: 'a person reviews and merges the pull request',
|
|
92
|
+
view_pr: 'the line merges the pull request once its checks pass',
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/** How a run stands, as the end of "its latest run ...", by disposition. */
|
|
96
|
+
const RUN_STANDS: Record<string, string> = {
|
|
97
|
+
in_flight: 'is running',
|
|
98
|
+
gated: 'is in review',
|
|
99
|
+
succeeded: 'merged',
|
|
100
|
+
unmerged: 'settled unmerged',
|
|
101
|
+
failed: 'failed',
|
|
102
|
+
stopped: 'was stopped',
|
|
103
|
+
rejected: 'had its pull request closed without merging',
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/** Where an initiative is, in a phrase, by its lifecycle stage. */
|
|
107
|
+
const STAGE: Record<string, string> = {
|
|
108
|
+
backlog: 'In the backlog, not on the line yet',
|
|
109
|
+
planning: 'Being planned',
|
|
110
|
+
planned: 'Planned',
|
|
111
|
+
building: 'Being built',
|
|
112
|
+
in_review: 'Its pull request is in review',
|
|
113
|
+
done: 'Done',
|
|
114
|
+
canceled: 'Canceled',
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Where an initiative stands and what moves it on, in one sentence (PRO-2800).
|
|
119
|
+
* Five fields carried it (status, state, planningStatus, needsHumanCause,
|
|
120
|
+
* lifecycle) and a reviewing agent had to read the queue, the initiative and
|
|
121
|
+
* the run together to say what had happened; the lifecycle the API derives
|
|
122
|
+
* already decides it. Null for a row without a lifecycle.
|
|
123
|
+
*/
|
|
124
|
+
export function situationOf(item: Record<string, unknown>): string | null {
|
|
125
|
+
const life = item['lifecycle'] as
|
|
126
|
+
| { turn?: unknown; stage?: unknown; cta?: unknown; planFailureCause?: unknown; latestBuild?: unknown }
|
|
127
|
+
| null
|
|
128
|
+
| undefined;
|
|
129
|
+
if (!life || typeof life.stage !== 'string') return null;
|
|
130
|
+
const parts: string[] = [];
|
|
131
|
+
let stage = STAGE[life.stage] ?? life.stage;
|
|
132
|
+
if (life.stage === 'planning' && life.turn === 'you') {
|
|
133
|
+
stage = `Planning stopped${typeof life.planFailureCause === 'string' ? ` (${life.planFailureCause})` : ''}`;
|
|
134
|
+
}
|
|
135
|
+
const build = life.latestBuild as { status?: unknown; prNumber?: unknown; prMerged?: unknown } | null | undefined;
|
|
136
|
+
if (build && typeof build.status === 'string' && life.stage !== 'done' && life.stage !== 'canceled') {
|
|
137
|
+
const pr = typeof build.prNumber === 'number' ? ` (PR #${build.prNumber})` : '';
|
|
138
|
+
const said = (BUILD_RUN_STATUSES as readonly string[]).includes(build.status)
|
|
139
|
+
? RUN_STANDS[landedDisposition(build.status as BuildRunStatus, build.prMerged === true)]
|
|
140
|
+
: build.status;
|
|
141
|
+
stage += `; its latest run ${said}${pr}`;
|
|
142
|
+
}
|
|
143
|
+
parts.push(`${stage}.`);
|
|
144
|
+
if (typeof item['needsHumanCause'] === 'string') {
|
|
145
|
+
const halt = renderHalt({ cause: item['needsHumanCause'], params: {} } as unknown as Halt);
|
|
146
|
+
const detail = typeof item['needsHumanDetail'] === 'string' ? item['needsHumanDetail'] : '';
|
|
147
|
+
// The halt says what stopped the line; the detail is the run's own reason, kept beside it.
|
|
148
|
+
const why = [halt, detail]
|
|
149
|
+
.filter(Boolean)
|
|
150
|
+
.map((text) => text.replace(/\.$/, ''))
|
|
151
|
+
.join('. ');
|
|
152
|
+
if (why) parts.push(`The line stopped on it: ${why}.`);
|
|
153
|
+
}
|
|
154
|
+
const turn = life.turn === 'you' ? "A person's turn" : life.turn === 'agent' ? "The agent's turn" : null;
|
|
155
|
+
const move = typeof life.cta === 'string' ? CTA_MOVE[life.cta] : undefined;
|
|
156
|
+
if (life.cta === 'view_pr') parts.push(`Nothing to do: ${move}.`);
|
|
157
|
+
else if (turn && move) parts.push(`${turn}: ${move}.`);
|
|
158
|
+
else if (turn) parts.push(`${turn}.`);
|
|
159
|
+
else if (move) parts.push(`Next: ${move}.`);
|
|
160
|
+
return parts.join(' ');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** A row or item with its situation added when it has a lifecycle. */
|
|
164
|
+
export function withSituation(
|
|
165
|
+
item: Record<string, unknown>,
|
|
166
|
+
row: Record<string, unknown> = item
|
|
167
|
+
): Record<string, unknown> {
|
|
168
|
+
const situation = situationOf(item);
|
|
169
|
+
return situation ? { ...row, situation } : row;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** List rows, each with its situation. */
|
|
173
|
+
export function situatedRows(items: Record<string, unknown>[]): Record<string, unknown>[] {
|
|
174
|
+
return itemRows(items, BACKLOG_SUMMARY_FIELDS).map((row, index) => withSituation(items[index]!, row));
|
|
175
|
+
}
|
|
176
|
+
|
|
70
177
|
/** What each plan failure cause means for whoever reads it next (PLAN_FAILURE_CAUSES in @taiga/types). */
|
|
71
178
|
const PLAN_FAILURE_MEANING: Record<string, string> = {
|
|
72
179
|
agent_error: 'The planning agent failed. Planning again is the usual next step.',
|
|
@@ -81,12 +188,12 @@ const PLAN_FAILURE_MEANING: Record<string, string> = {
|
|
|
81
188
|
*/
|
|
82
189
|
async function planFailureOf(
|
|
83
190
|
client: TaigaClient,
|
|
84
|
-
|
|
191
|
+
initiativeId: string,
|
|
85
192
|
itemCause: unknown
|
|
86
193
|
): Promise<Record<string, unknown> | null> {
|
|
87
194
|
let events: WithoutTenant<ActivityEvent>[];
|
|
88
195
|
try {
|
|
89
|
-
events = (await client.listBacklogItemActivity(
|
|
196
|
+
events = (await client.listBacklogItemActivity(initiativeId)).data;
|
|
90
197
|
} catch (error) {
|
|
91
198
|
if (error instanceof TaigaApiError || isNetworkFailure(error)) return null;
|
|
92
199
|
throw error;
|
|
@@ -126,7 +233,7 @@ async function planFailureOf(
|
|
|
126
233
|
|
|
127
234
|
const planId = idInput('plan', "taiga_get_plan gives the initiative's active plan and its ID");
|
|
128
235
|
|
|
129
|
-
const
|
|
236
|
+
const initiativeId = idInput('initiative', 'taiga_list_initiatives (query finds one by title) gives it');
|
|
130
237
|
|
|
131
238
|
/** The plan without its steps' prose: enough to approve it or say what a run will do. */
|
|
132
239
|
const PLAN_SUMMARY_FIELDS = ['id', 'backlogItemId', 'version', 'status', 'createdAt'] as const;
|
|
@@ -143,8 +250,12 @@ const PLAN_STEP_SUMMARY_FIELDS = [
|
|
|
143
250
|
|
|
144
251
|
type Item = WithoutTenant<BacklogItem>;
|
|
145
252
|
|
|
146
|
-
/** The outcome fields of an initiative, with how many of its criteria are met. */
|
|
147
|
-
const outcomeOf = (item: Item) =>
|
|
253
|
+
/** The outcome fields of an initiative, with how many of its criteria are met, and where it stands. */
|
|
254
|
+
const outcomeOf = (item: Item) =>
|
|
255
|
+
withSituation(item as unknown as Record<string, unknown>, {
|
|
256
|
+
...pickFields([item], ITEM_OUTCOME_FIELDS)[0],
|
|
257
|
+
criteria: criteriaCounts(item),
|
|
258
|
+
});
|
|
148
259
|
|
|
149
260
|
const outcome = (item: Item) => toolResult(outcomeOf(item));
|
|
150
261
|
|
|
@@ -180,15 +291,16 @@ export function registerBacklogTools(server: McpServer, client: TaigaClient) {
|
|
|
180
291
|
|
|
181
292
|
function registerBacklogReads(server: McpServer, client: TaigaClient) {
|
|
182
293
|
server.registerTool(
|
|
183
|
-
'
|
|
294
|
+
'taiga_list_initiatives',
|
|
184
295
|
{
|
|
185
|
-
title: 'List
|
|
296
|
+
title: 'List initiatives',
|
|
186
297
|
description:
|
|
187
|
-
"List a product's
|
|
188
|
-
'line (backlog, todo, queue, plan, build, done)
|
|
298
|
+
"List a product's initiatives, its backlog: titles, category, status, where each one is on the " +
|
|
299
|
+
'line (backlog, todo, queue, plan, build, done), situation (where it stands and what moves it on, in a ' +
|
|
300
|
+
'sentence) and criteria, how many of its acceptance criteria are met ' +
|
|
189
301
|
'(null before it is planned; met counts the criteria the agent confirmed with evidence or a person checked ' +
|
|
190
302
|
'off, so a done item can show fewer met than it has). Use this to find the item you are working on (query ' +
|
|
191
|
-
'finds one by title), then read its acceptance criteria and technical context with
|
|
303
|
+
'finds one by title), then read its acceptance criteria and technical context with taiga_get_initiative. ' +
|
|
192
304
|
'Archived items are left out unless asked for.',
|
|
193
305
|
inputSchema: z.object({
|
|
194
306
|
productId,
|
|
@@ -218,7 +330,7 @@ function registerBacklogReads(server: McpServer, client: TaigaClient) {
|
|
|
218
330
|
const { query, ...filters } = params;
|
|
219
331
|
if (query === undefined) {
|
|
220
332
|
const result = await client.listBacklogItems(params.productId, filters);
|
|
221
|
-
return toolResult(page(
|
|
333
|
+
return toolResult(page(situatedRows(result.data as Record<string, unknown>[]), result.nextCursor));
|
|
222
334
|
}
|
|
223
335
|
if (params.cursor !== undefined) {
|
|
224
336
|
return toolError('VALIDATION_ERROR', SEARCH_WITH_CURSOR);
|
|
@@ -230,36 +342,37 @@ function registerBacklogReads(server: McpServer, client: TaigaClient) {
|
|
|
230
342
|
params.limit ?? 25
|
|
231
343
|
);
|
|
232
344
|
return toolResult({
|
|
233
|
-
...page(
|
|
345
|
+
...page(situatedRows(found.rows), null),
|
|
234
346
|
...(found.more ? { more: SEARCH_NOTE } : {}),
|
|
235
347
|
});
|
|
236
348
|
})
|
|
237
349
|
);
|
|
238
350
|
|
|
239
351
|
server.registerTool(
|
|
240
|
-
'
|
|
352
|
+
'taiga_get_initiative',
|
|
241
353
|
{
|
|
242
|
-
title: 'Get
|
|
354
|
+
title: 'Get initiative',
|
|
243
355
|
description:
|
|
244
|
-
'Get one
|
|
356
|
+
'Get one initiative in full: description, acceptance criteria (and which are met), technical ' +
|
|
245
357
|
'context (patterns, constraints, and the assumptions to verify before starting), dependencies and source documents. Read this before ' +
|
|
246
|
-
'implementing an item, and read its runs with
|
|
358
|
+
'implementing an item, and read its runs with taiga_list_runs to see what the coding agent already did. ' +
|
|
247
359
|
'A failed plan carries planFailure: the cause, what it means, when, and by whom. ' +
|
|
248
360
|
LIFECYCLE_MEANING,
|
|
249
361
|
inputSchema: z.object({
|
|
250
|
-
|
|
362
|
+
initiativeId,
|
|
251
363
|
}),
|
|
252
364
|
annotations: READ_ONLY,
|
|
253
365
|
},
|
|
254
366
|
(params) =>
|
|
255
367
|
runTool(async () => {
|
|
256
|
-
const item = (await client.getBacklogItem(params.
|
|
257
|
-
|
|
368
|
+
const item = (await client.getBacklogItem(params.initiativeId)).data;
|
|
369
|
+
const row = item as unknown as Record<string, unknown>;
|
|
370
|
+
if (item.planningStatus !== 'failed') return toolResult(withSituation(row));
|
|
258
371
|
// Always undefined: the API sends the cause only under `lifecycle`, so
|
|
259
372
|
// planFailureOf's cross-check never fires (PRO-2799). Kept as it was.
|
|
260
373
|
const ownCause = (item as { planFailureCause?: unknown }).planFailureCause;
|
|
261
|
-
const planFailure = await planFailureOf(client, params.
|
|
262
|
-
return toolResult(planFailure ? { ...item, planFailure } : item);
|
|
374
|
+
const planFailure = await planFailureOf(client, params.initiativeId, ownCause);
|
|
375
|
+
return toolResult(withSituation(row, planFailure ? { ...item, planFailure } : item));
|
|
263
376
|
})
|
|
264
377
|
);
|
|
265
378
|
|
|
@@ -275,14 +388,14 @@ function registerBacklogReads(server: McpServer, client: TaigaClient) {
|
|
|
275
388
|
"full: true for each step's complete instructions, several thousand tokens on a large plan. NOT_FOUND " +
|
|
276
389
|
'when the initiative has no active plan yet (planningStatus says whether one is being generated).',
|
|
277
390
|
inputSchema: z.object({
|
|
278
|
-
|
|
391
|
+
initiativeId,
|
|
279
392
|
full: z.boolean().optional().describe("Carry each step's complete instructions (default false: a summary)"),
|
|
280
393
|
}),
|
|
281
394
|
annotations: READ_ONLY,
|
|
282
395
|
},
|
|
283
396
|
(params) =>
|
|
284
397
|
runTool(async () => {
|
|
285
|
-
const plan = (await client.getActivePlan(params.
|
|
398
|
+
const plan = (await client.getActivePlan(params.initiativeId)).data;
|
|
286
399
|
if (params.full) return toolResult(plan);
|
|
287
400
|
const steps = Array.isArray(plan.steps) ? plan.steps : [];
|
|
288
401
|
return toolResult({
|
|
@@ -340,12 +453,12 @@ function registerRequest(server: McpServer, client: TaigaClient) {
|
|
|
340
453
|
|
|
341
454
|
function registerMove(server: McpServer, client: TaigaClient) {
|
|
342
455
|
server.registerTool(
|
|
343
|
-
'
|
|
456
|
+
'taiga_move_initiative',
|
|
344
457
|
{
|
|
345
|
-
title: 'Move
|
|
458
|
+
title: 'Move initiative on the board',
|
|
346
459
|
description:
|
|
347
460
|
'Move an initiative on the board, or order it within a column: backlog and todo are yours and the line ' +
|
|
348
|
-
'never moves an initiative out of them; queue puts it in the line (
|
|
461
|
+
'never moves an initiative out of them; queue puts it in the line (taiga_queue_initiative says more); ' +
|
|
349
462
|
'done and canceled finish it by hand. The queue is the only way to start work: there is no "plan this ' +
|
|
350
463
|
'now", and nothing is moved into Build by a person. Moving an initiative out of Build stops the plan ' +
|
|
351
464
|
'being written or the run in flight: that is how work is stopped, and it throws that work away. A move to ' +
|
|
@@ -356,9 +469,9 @@ function registerMove(server: McpServer, client: TaigaClient) {
|
|
|
356
469
|
'archived: true files the initiative where it stands (pass its current column as state; never from the Build station, plan or build) ' +
|
|
357
470
|
'and archived: false brings it back; archived work is left out of lists unless includeArchived asks for it.',
|
|
358
471
|
inputSchema: z.object({
|
|
359
|
-
|
|
472
|
+
initiativeId,
|
|
360
473
|
state: z.enum(USER_DROP_STATES).describe('The column to move into'),
|
|
361
|
-
afterId: idInput('
|
|
474
|
+
afterId: idInput('initiative', 'taiga_list_initiatives gives it')
|
|
362
475
|
.nullable()
|
|
363
476
|
.optional()
|
|
364
477
|
.describe('The initiative to follow in that column; null for the head; omit to keep rank order'),
|
|
@@ -371,7 +484,7 @@ function registerMove(server: McpServer, client: TaigaClient) {
|
|
|
371
484
|
},
|
|
372
485
|
(params) =>
|
|
373
486
|
runTool(async () => {
|
|
374
|
-
const {
|
|
487
|
+
const { initiativeId: id, ...body } = params;
|
|
375
488
|
// What the route checks on top of backlog:write for a drop into the
|
|
376
489
|
// queue; refused here so the model learns which capability, not just
|
|
377
490
|
// that it was forbidden.
|
|
@@ -401,10 +514,10 @@ function registerPlanAgain(server: McpServer, client: TaigaClient) {
|
|
|
401
514
|
'stays open. Offered only when the turn is yours: refused (CONFLICT) while an agent holds it (planning, ' +
|
|
402
515
|
'building, reviewing). Also refused while a dependency is still in flight or a UI direction has no ' +
|
|
403
516
|
'chosen concept, unless force says to plan anyway, which only the person decides. Anywhere off the ' +
|
|
404
|
-
'Build station, put the initiative in the queue instead (
|
|
517
|
+
'Build station, put the initiative in the queue instead (taiga_queue_initiative): the queue is the ' +
|
|
405
518
|
'only way to start work. To send a plan back with direction, taiga_reject_plan. Starts a planner run.',
|
|
406
519
|
inputSchema: z.object({
|
|
407
|
-
|
|
520
|
+
initiativeId,
|
|
408
521
|
force: z
|
|
409
522
|
.boolean()
|
|
410
523
|
.optional()
|
|
@@ -416,16 +529,16 @@ function registerPlanAgain(server: McpServer, client: TaigaClient) {
|
|
|
416
529
|
},
|
|
417
530
|
(params) =>
|
|
418
531
|
runTool(async () => {
|
|
419
|
-
const item = (await client.getBacklogItem(params.
|
|
532
|
+
const item = (await client.getBacklogItem(params.initiativeId)).data;
|
|
420
533
|
if (!BUILD_STATION.has(String(item.state))) {
|
|
421
534
|
return toolError(
|
|
422
535
|
'CONFLICT',
|
|
423
536
|
`This initiative is in ${String(item.state)}, not at the Build station. The queue is the only way to ` +
|
|
424
|
-
'start work: put it in the queue with
|
|
537
|
+
'start work: put it in the queue with taiga_queue_initiative and it is planned when its turn comes.'
|
|
425
538
|
);
|
|
426
539
|
}
|
|
427
540
|
const body = { state: 'plan', ...(params.force ? { force: true } : {}) };
|
|
428
|
-
return outcome((await client.transitionInitiative(params.
|
|
541
|
+
return outcome((await client.transitionInitiative(params.initiativeId, body)).data);
|
|
429
542
|
})
|
|
430
543
|
);
|
|
431
544
|
}
|
|
@@ -481,9 +594,9 @@ const autoBuildInput = z
|
|
|
481
594
|
|
|
482
595
|
function registerQueue(server: McpServer, client: TaigaClient) {
|
|
483
596
|
server.registerTool(
|
|
484
|
-
'
|
|
597
|
+
'taiga_queue_initiative',
|
|
485
598
|
{
|
|
486
|
-
title: 'Queue
|
|
599
|
+
title: 'Queue initiative',
|
|
487
600
|
description:
|
|
488
601
|
'Add an initiative to the product queue, the line the coding agent works: queued initiatives are ' +
|
|
489
602
|
'planned and built one at a time, each advancing when the previous pull request merges. This starts ' +
|
|
@@ -491,12 +604,12 @@ function registerQueue(server: McpServer, client: TaigaClient) {
|
|
|
491
604
|
"repository. Whether the plan is built at once or waits for a person is the initiative's review gate: " +
|
|
492
605
|
"pass autoBuild: false to read the plan before anything is built (the product's default applies " +
|
|
493
606
|
'otherwise, and the outcome says which it is); the gate is set first and stays set if the move is then ' +
|
|
494
|
-
'refused.
|
|
607
|
+
'refused. An initiative already ' +
|
|
495
608
|
'in the queue stays where it is unless afterId says where to go; one being planned or built cannot move ' +
|
|
496
609
|
'(CONFLICT). Returns the initiative with its new state, its review gate and its place in the queue.',
|
|
497
610
|
inputSchema: z.object({
|
|
498
|
-
|
|
499
|
-
afterId: idInput('
|
|
611
|
+
initiativeId,
|
|
612
|
+
afterId: idInput('initiative', 'taiga_list_initiatives gives it')
|
|
500
613
|
.nullable()
|
|
501
614
|
.optional()
|
|
502
615
|
.describe(
|
|
@@ -513,7 +626,7 @@ function registerQueue(server: McpServer, client: TaigaClient) {
|
|
|
513
626
|
},
|
|
514
627
|
(params) =>
|
|
515
628
|
runTool(async () => {
|
|
516
|
-
const {
|
|
629
|
+
const { initiativeId: id, autoBuild, ...placement } = params;
|
|
517
630
|
// The gate first: set after the move, a plan landing in between
|
|
518
631
|
// would be built under the old answer.
|
|
519
632
|
if (autoBuild !== undefined) await client.setAutoBuild(id, autoBuild);
|
|
@@ -527,7 +640,7 @@ function registerQueue(server: McpServer, client: TaigaClient) {
|
|
|
527
640
|
queue,
|
|
528
641
|
...(gate && queue
|
|
529
642
|
? {}
|
|
530
|
-
: { note: 'Part of the outcome could not be read;
|
|
643
|
+
: { note: 'Part of the outcome could not be read; taiga_get_line and taiga_get_product say the rest.' }),
|
|
531
644
|
});
|
|
532
645
|
})
|
|
533
646
|
);
|
|
@@ -543,12 +656,12 @@ function registerReviewGate(server: McpServer, client: TaigaClient) {
|
|
|
543
656
|
"approve or reject it (false); null follows the product's default. Standing: it applies to every plan the " +
|
|
544
657
|
'initiative gets, including one being written now. Opening the gate on a plan that is already waiting ' +
|
|
545
658
|
'starts its build at once. Returns the initiative with the gate that now applies.',
|
|
546
|
-
inputSchema: z.object({
|
|
659
|
+
inputSchema: z.object({ initiativeId, autoBuild: autoBuildInput }),
|
|
547
660
|
annotations: OPERATE_SETTING,
|
|
548
661
|
},
|
|
549
662
|
(params) =>
|
|
550
663
|
runTool(async () => {
|
|
551
|
-
const item = (await client.setAutoBuild(params.
|
|
664
|
+
const item = (await client.setAutoBuild(params.initiativeId, params.autoBuild)).data;
|
|
552
665
|
const gate = await reviewGate(client, item);
|
|
553
666
|
return toolResult({
|
|
554
667
|
...outcomeOf(item),
|
|
@@ -561,9 +674,9 @@ function registerReviewGate(server: McpServer, client: TaigaClient) {
|
|
|
561
674
|
|
|
562
675
|
function registerResume(server: McpServer, client: TaigaClient) {
|
|
563
676
|
server.registerTool(
|
|
564
|
-
'
|
|
677
|
+
'taiga_resume_initiative',
|
|
565
678
|
{
|
|
566
|
-
title: 'Resume
|
|
679
|
+
title: 'Resume initiative',
|
|
567
680
|
description:
|
|
568
681
|
'Resume an initiative the line stopped on, which is when needsHumanCause is set (the line sets it): a ' +
|
|
569
682
|
'failed or cancelled plan is planned again, a stopped or failed run resumes from its first unfinished ' +
|
|
@@ -572,10 +685,10 @@ function registerResume(server: McpServer, client: TaigaClient) {
|
|
|
572
685
|
'initiative. Refused (CONFLICT) when nothing is flagged or the cause has no resume: a plan that failed ' +
|
|
573
686
|
'with needsHumanCause null (lifecycle.cta retry_plan, planFailureCause says why) is planned again with ' +
|
|
574
687
|
'taiga_plan_again. Starts paid work.',
|
|
575
|
-
inputSchema: z.object({
|
|
688
|
+
inputSchema: z.object({ initiativeId }),
|
|
576
689
|
annotations: OPERATE,
|
|
577
690
|
},
|
|
578
|
-
(params) => runTool(async () => outcome((await client.resumeInitiative(params.
|
|
691
|
+
(params) => runTool(async () => outcome((await client.resumeInitiative(params.initiativeId)).data))
|
|
579
692
|
);
|
|
580
693
|
}
|
|
581
694
|
|