@taiga-ai/mcp-server 0.26.0-dev.487 → 0.26.0-dev.488
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 +189 -182
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -124,6 +124,14 @@ function parseErrorBody(body) {
|
|
|
124
124
|
*/
|
|
125
125
|
var TaigaClient = class {
|
|
126
126
|
baseUrl;
|
|
127
|
+
/** The API this client talks to, as its host: the environment a reader is in (PRO-2823). */
|
|
128
|
+
get apiHost() {
|
|
129
|
+
try {
|
|
130
|
+
return new URL(this.baseUrl).host;
|
|
131
|
+
} catch {
|
|
132
|
+
return this.baseUrl;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
127
135
|
apiKey;
|
|
128
136
|
introspection = null;
|
|
129
137
|
constructor(opts) {
|
|
@@ -269,6 +277,9 @@ var TaigaClient = class {
|
|
|
269
277
|
if (opts?.status) params.set("status", opts.status);
|
|
270
278
|
if (opts?.planningStatus) params.set("planningStatus", opts.planningStatus);
|
|
271
279
|
if (opts?.category) params.set("category", opts.category);
|
|
280
|
+
if (opts?.state) params.set("state", opts.state);
|
|
281
|
+
if (opts?.open) params.set("open", "true");
|
|
282
|
+
if (opts?.order) params.set("order", opts.order);
|
|
272
283
|
if (opts?.includeArchived) params.set("includeArchived", "true");
|
|
273
284
|
if (opts?.limit) params.set("limit", String(opts.limit));
|
|
274
285
|
if (opts?.cursor) params.set("cursor", opts.cursor);
|
|
@@ -870,9 +881,6 @@ const PRODUCT_DOCUMENT_DISPLAY_NAMES = {
|
|
|
870
881
|
const PRODUCT_DOCUMENT_ORIGINS = ["plan", "reality"];
|
|
871
882
|
//#endregion
|
|
872
883
|
//#region ../../packages/types/dist/backlog.js
|
|
873
|
-
/** Dispositions that end an item's pipeline life: settled work neither blocks
|
|
874
|
-
* dependants nor counts toward completion denominators. */
|
|
875
|
-
const SETTLED_BACKLOG_STATUSES = ["done", "canceled"];
|
|
876
884
|
/**
|
|
877
885
|
* The initiative's STORED state on the line board (PRO-1305, phase 1).
|
|
878
886
|
*
|
|
@@ -922,11 +930,6 @@ const LINE_STATES = [
|
|
|
922
930
|
"plan",
|
|
923
931
|
"build"
|
|
924
932
|
];
|
|
925
|
-
/** Finished work: settled by a person or a merge, or archived. The one
|
|
926
|
-
* predicate every "can this still move / plan / build" guard reads. */
|
|
927
|
-
function isSettledOrArchived(item) {
|
|
928
|
-
return item.archivedAt != null || SETTLED_BACKLOG_STATUSES.includes(item.status);
|
|
929
|
-
}
|
|
930
933
|
/**
|
|
931
934
|
* The columns a person can drop an initiative into by hand.
|
|
932
935
|
*
|
|
@@ -2245,7 +2248,7 @@ const DOCUMENT_INPUTS = Object.freeze({
|
|
|
2245
2248
|
* reads as deleted) and fields they explain would simply be missing. So it
|
|
2246
2249
|
* refuses to start, and says what to do.
|
|
2247
2250
|
*/
|
|
2248
|
-
function apiLevelProblem(introspection, apiUrl, required =
|
|
2251
|
+
function apiLevelProblem(introspection, apiUrl, required = 2, version = SERVER_VERSION) {
|
|
2249
2252
|
const offered = introspection.mcpApiLevel ?? 0;
|
|
2250
2253
|
if (offered >= required) return null;
|
|
2251
2254
|
return `The Taiga API at ${apiUrl} is older than @taiga-ai/mcp-server ${version} needs (API level ${offered}, needs ${required}). Run the version published for it, or wait for that API to be updated; an earlier release of this package works against it.`;
|
|
@@ -2502,7 +2505,7 @@ const BY_ARGUMENT = {
|
|
|
2502
2505
|
/** Where an argument means something else on one tool, or the tool's 404 needs its own word. */
|
|
2503
2506
|
const BY_TOOL = {
|
|
2504
2507
|
taiga_resolve_action: () => "taiga_list_action_required lists the items with their IDs.",
|
|
2505
|
-
taiga_get_plan: (said) => /\bplan\b/i.test(said) ? "taiga_get_initiative says whether one is being
|
|
2508
|
+
taiga_get_plan: (said) => /\bplan\b/i.test(said) ? "taiga_get_initiative's situation says whether one is being planned or failed, and taiga_plan_again asks for a new one." : ITEM_ID_HINT
|
|
2506
2509
|
};
|
|
2507
2510
|
/**
|
|
2508
2511
|
* What the API calls the thing each ID names, as its 404 says it ("Environment
|
|
@@ -4033,7 +4036,7 @@ const FAILED_REASON_MEANS = {
|
|
|
4033
4036
|
* text out: its first sentence is in the outcome, and taiga_get_run has it all.
|
|
4034
4037
|
*/
|
|
4035
4038
|
function readableRun(run, opts = {}) {
|
|
4036
|
-
const { prDraft, error, reviewEscalation, runtime: _runtime, ...rest } = run;
|
|
4039
|
+
const { prDraft, error, reviewEscalation, runtime: _runtime, origin, ...rest } = run;
|
|
4037
4040
|
const said = runOutcome(run);
|
|
4038
4041
|
const means = FAILED_REASON_MEANS[String(run["failedReason"])];
|
|
4039
4042
|
const hardening = run["hardeningResult"];
|
|
@@ -4042,13 +4045,14 @@ function readableRun(run, opts = {}) {
|
|
|
4042
4045
|
const scan = scanSaid(hardening);
|
|
4043
4046
|
return {
|
|
4044
4047
|
...rest,
|
|
4048
|
+
...origin === void 0 ? {} : { origin: origin === "backlog" ? "initiative" : origin },
|
|
4045
4049
|
...means ? { failedReasonMeans: means } : {},
|
|
4046
4050
|
...scan && scan.gaps + (scan.verdict === "not_scanned" ? 1 : 0) > 0 ? { scanCoverage: scan.said } : {},
|
|
4047
4051
|
...blocking > 0 ? { hardeningNote: hardeningSaid(blocking, named) } : {},
|
|
4048
4052
|
...opts.row || error === void 0 ? {} : { error },
|
|
4049
4053
|
...opts.row || reviewEscalation === void 0 ? {} : { reviewEscalation },
|
|
4050
4054
|
...typeof rest["prNumber"] === "number" || rest["prUrl"] ? { prDraft } : {},
|
|
4051
|
-
...said
|
|
4055
|
+
...said ? { outcome: said.outcome } : {}
|
|
4052
4056
|
};
|
|
4053
4057
|
}
|
|
4054
4058
|
/** Distinct initiatives a run list reads to say which moved on; past this a waiting row says it was not checked. */
|
|
@@ -4100,7 +4104,8 @@ const INITIATIVE_ENDED = {
|
|
|
4100
4104
|
/** A readable run row that also says when its initiative ended while its pull request waited. */
|
|
4101
4105
|
function withInitiativeState(row, states) {
|
|
4102
4106
|
const state = states.get(String(row["backlogItemId"]));
|
|
4103
|
-
|
|
4107
|
+
const status = String(row["status"]);
|
|
4108
|
+
if (!(BUILD_RUN_STATUSES.includes(status) && ["gated", "unmerged"].includes(landedDisposition(status, row["prMerged"] === true)))) return row;
|
|
4104
4109
|
if (state === "unchecked") return {
|
|
4105
4110
|
...row,
|
|
4106
4111
|
outcome: `${String(row["outcome"])} Whether its initiative still wants it was not checked.`
|
|
@@ -4111,7 +4116,6 @@ function withInitiativeState(row, states) {
|
|
|
4111
4116
|
const open = row["prState"] === "open" ? ` ${pr} is still open on the forge.` : "";
|
|
4112
4117
|
return {
|
|
4113
4118
|
...row,
|
|
4114
|
-
disposition: "abandoned",
|
|
4115
4119
|
actions: [],
|
|
4116
4120
|
outcome: `Its initiative ${ended}, so nobody is waiting on this run.${open}`
|
|
4117
4121
|
};
|
|
@@ -4220,19 +4224,20 @@ function registerBuildTools(server, client) {
|
|
|
4220
4224
|
if (!client.hasCapability("products:read")) return;
|
|
4221
4225
|
server.registerTool("taiga_list_runs", {
|
|
4222
4226
|
title: "List runs",
|
|
4223
|
-
description: "List coding agent runs, newest first: branch, pull request, step progress, outcome
|
|
4227
|
+
description: "List coding agent runs, newest first: status, branch, pull request, step progress, actions and outcome, one sentence saying how it stands: running, waiting for a person to review and merge, merged, failed, stopped, its pull request closed unmerged, or nobody waiting on it because its initiative ended. Without productId, every product's runs; active: true keeps running ones. taiga_get_run reads one; carry work on from the initiative (taiga_resume_initiative).",
|
|
4224
4228
|
inputSchema: z.object({
|
|
4225
4229
|
productId: productId.optional(),
|
|
4226
|
-
initiativeId: idInput("initiative", "taiga_list_initiatives (query finds one by title) gives it").optional().describe("Only this initiative's runs -- get from taiga_list_initiatives
|
|
4230
|
+
initiativeId: idInput("initiative", "taiga_list_initiatives (query finds one by title) gives it").optional().describe("Only this initiative's runs -- get from taiga_list_initiatives"),
|
|
4227
4231
|
active: z.boolean().optional().describe("Only runs the agent is executing now (cloning through reviewing; a queued pending run is not yet executing and is left out); for the org-wide list"),
|
|
4228
4232
|
...pagination
|
|
4229
4233
|
}),
|
|
4230
4234
|
annotations: READ_ONLY
|
|
4231
4235
|
}, (params) => runTool(async () => {
|
|
4232
|
-
if (
|
|
4233
|
-
if (params.productId && params.
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
+
if (params.active && (params.productId || params.initiativeId)) return toolError("VALIDATION_ERROR", "active narrows the org-wide list only: omit productId and initiativeId, or read the product's runs and judge each by its status.");
|
|
4237
|
+
if (!params.productId && params.initiativeId && !client.hasCapability("backlog:read")) return toolError("VALIDATION_ERROR", "This key cannot read initiatives, so it cannot find the initiative's product: pass productId as well.");
|
|
4238
|
+
const productIdOf = params.productId ?? (params.initiativeId ? (await client.getBacklogItem(params.initiativeId)).data.productId : void 0);
|
|
4239
|
+
if (productIdOf) {
|
|
4240
|
+
const result = await client.listBuildRuns(productIdOf, {
|
|
4236
4241
|
backlogItemId: params.initiativeId,
|
|
4237
4242
|
limit: params.limit,
|
|
4238
4243
|
cursor: params.cursor
|
|
@@ -4245,7 +4250,7 @@ function registerBuildTools(server, client) {
|
|
|
4245
4250
|
}));
|
|
4246
4251
|
server.registerTool("taiga_get_run", {
|
|
4247
4252
|
title: "Get run",
|
|
4248
|
-
description: "Read one run with its steps:
|
|
4253
|
+
description: "Read one run with its steps: status, outcome, pull request, hardening (scanCoverage names the security scanners that did not run, and why; hardeningResult.verdict is passed only when every one ran), and per step its title, status, commit, files changed, verification and headline (first paragraph of the agent's report). verification is Taiga's own check after the step (tests, lint, build), so a docs-only step can fail on the repository's failing tests (verificationNote says when one failed throughout). A failed or stopped run has stoppedIn, lastProgressAt and failedReasonMeans (what its failedReason means). full: true adds complete instructions and reports. actions: resume and retry_from_failed_step are taiga_resume_initiative; webOnlyActions (start_over) are in the web; to stop it, move the initiative out of Build. A maintenance run has no initiative and is retried from its finding.",
|
|
4249
4254
|
inputSchema: z.object({
|
|
4250
4255
|
runId,
|
|
4251
4256
|
full: z.boolean().optional().describe("Carry each step's complete instructions and report (default false: a summary)")
|
|
@@ -4372,9 +4377,7 @@ async function queueView(client, product, options = { name: "all" }) {
|
|
|
4372
4377
|
if (item) {
|
|
4373
4378
|
Object.assign(row, {
|
|
4374
4379
|
title: item.title,
|
|
4375
|
-
|
|
4376
|
-
planningStatus: item.planningStatus,
|
|
4377
|
-
needsHumanCause: item.needsHumanCause,
|
|
4380
|
+
...standing(item),
|
|
4378
4381
|
criteria: criteriaCounts(item),
|
|
4379
4382
|
lifecycle: lifecycle ? {
|
|
4380
4383
|
turn: lifecycle.turn,
|
|
@@ -4406,7 +4409,7 @@ async function queueView(client, product, options = { name: "all" }) {
|
|
|
4406
4409
|
const line = typeof paused === "string" ? `The line is paused (since ${paused}): nothing is planned or built until a person resumes it.` : null;
|
|
4407
4410
|
return {
|
|
4408
4411
|
linePaused: paused === null ? null : typeof paused === "string",
|
|
4409
|
-
entries: rows,
|
|
4412
|
+
entries: rows.map(({ lifecycle: _lifecycle, runId: _runId, ...row }) => row),
|
|
4410
4413
|
waitingOn: line ?? waitingOn(rows),
|
|
4411
4414
|
...note ? { note } : {}
|
|
4412
4415
|
};
|
|
@@ -4452,18 +4455,21 @@ function registerQueueTools(server, client) {
|
|
|
4452
4455
|
}
|
|
4453
4456
|
//#endregion
|
|
4454
4457
|
//#region src/tools/backlogs.ts
|
|
4455
|
-
/**
|
|
4458
|
+
/**
|
|
4459
|
+
* What a list row needs to say for the model to pick an item to read in full.
|
|
4460
|
+
* One vocabulary for where an initiative stands (PRO-2828): its board column,
|
|
4461
|
+
* whose turn it is and the situation sentence. The API's own fields for it
|
|
4462
|
+
* (status, planningStatus, needsHumanCause, lifecycle) said the same thing
|
|
4463
|
+
* four more ways, and a reviewing agent had to reconcile them; they are in
|
|
4464
|
+
* taiga_get_initiative with full: true.
|
|
4465
|
+
*/
|
|
4456
4466
|
const BACKLOG_SUMMARY_FIELDS = [
|
|
4457
4467
|
"id",
|
|
4458
4468
|
"title",
|
|
4459
4469
|
"category",
|
|
4460
|
-
"status",
|
|
4461
|
-
"state",
|
|
4462
|
-
"planningStatus",
|
|
4463
4470
|
"priority",
|
|
4464
4471
|
"estimate",
|
|
4465
4472
|
"origin",
|
|
4466
|
-
"needsHumanCause",
|
|
4467
4473
|
"archivedAt",
|
|
4468
4474
|
"createdAt",
|
|
4469
4475
|
"updatedAt"
|
|
@@ -4477,21 +4483,44 @@ const ITEM_OUTCOME_FIELDS = [
|
|
|
4477
4483
|
"id",
|
|
4478
4484
|
"productId",
|
|
4479
4485
|
"title",
|
|
4480
|
-
"status",
|
|
4481
|
-
"state",
|
|
4482
|
-
"planningStatus",
|
|
4483
|
-
"needsHumanCause",
|
|
4484
|
-
"needsHumanDetail",
|
|
4485
4486
|
"autoBuild",
|
|
4486
|
-
"archivedAt"
|
|
4487
|
-
|
|
4487
|
+
"archivedAt"
|
|
4488
|
+
];
|
|
4489
|
+
/** The fields of an initiative's summary read: its row, plus what it is about. */
|
|
4490
|
+
const ITEM_SUMMARY_FIELDS = [
|
|
4491
|
+
...BACKLOG_SUMMARY_FIELDS,
|
|
4492
|
+
"productId",
|
|
4493
|
+
"description",
|
|
4494
|
+
"autoBuild",
|
|
4495
|
+
"dependencies"
|
|
4488
4496
|
];
|
|
4497
|
+
/** How much of a description a summary read carries; full: true has the rest. */
|
|
4498
|
+
const DESCRIPTION_IN_SUMMARY = 600;
|
|
4499
|
+
/**
|
|
4500
|
+
* Where an initiative stands, in the one vocabulary every read uses
|
|
4501
|
+
* (PRO-2828): its board column, whose turn it is, the situation sentence, and
|
|
4502
|
+
* its latest run to open with taiga_get_run.
|
|
4503
|
+
*/
|
|
4504
|
+
function standing(item) {
|
|
4505
|
+
const life = item["lifecycle"];
|
|
4506
|
+
const run = life?.latestBuild;
|
|
4507
|
+
const situation = situationOf(item);
|
|
4508
|
+
return {
|
|
4509
|
+
column: typeof item["state"] === "string" ? item["state"] : null,
|
|
4510
|
+
turn: typeof life?.turn === "string" ? life.turn : null,
|
|
4511
|
+
...situation ? { situation } : {},
|
|
4512
|
+
latestRun: run && typeof run.id === "string" ? {
|
|
4513
|
+
id: run.id,
|
|
4514
|
+
...typeof run.prNumber === "number" ? { prNumber: run.prNumber } : {}
|
|
4515
|
+
} : null
|
|
4516
|
+
};
|
|
4517
|
+
}
|
|
4489
4518
|
/**
|
|
4490
4519
|
* What the lifecycle on an initiative says, and the call each next step is.
|
|
4491
4520
|
* needsHumanCause is only the line's flag; a plan that failed outside the line
|
|
4492
4521
|
* leaves it null, and the lifecycle is what says why and what moves it on.
|
|
4493
4522
|
*/
|
|
4494
|
-
const LIFECYCLE_MEANING = "situation says in a sentence where it stands and
|
|
4523
|
+
const LIFECYCLE_MEANING = "situation says in a sentence where it stands and names the next call; turn says whose move it is (you, agent, or idle: nobody owes one).";
|
|
4495
4524
|
/** The move each call to action is, in the tools' words. */
|
|
4496
4525
|
const CTA_MOVE = {
|
|
4497
4526
|
start_plan: "taiga_queue_initiative puts it on the line to be planned and built (paid work: ask first)",
|
|
@@ -4504,8 +4533,8 @@ const CTA_MOVE = {
|
|
|
4504
4533
|
};
|
|
4505
4534
|
/** The move for an initiative off the line whose last run did not land: there is no line to take it off (PRO-2840). */
|
|
4506
4535
|
const OFF_THE_LINE_MOVE = "taiga_queue_initiative puts it back on the line (paid work: ask first)";
|
|
4507
|
-
/** What happens to a queued initiative
|
|
4508
|
-
const REQUEUED_MOVE = "the line
|
|
4536
|
+
/** What happens to a queued initiative: the line takes it up when its turn comes, replanning a run that did not land (PRO-2840). */
|
|
4537
|
+
const REQUEUED_MOVE = "the line takes it up when its turn comes";
|
|
4509
4538
|
/** How a run stands, as the end of "its latest run ...", by disposition. */
|
|
4510
4539
|
const RUN_STANDS = {
|
|
4511
4540
|
in_flight: "is running",
|
|
@@ -4542,7 +4571,10 @@ function situationOf(item) {
|
|
|
4542
4571
|
const column = typeof item["state"] === "string" ? item["state"] : null;
|
|
4543
4572
|
const offTheLine = column !== null && !LINE_STATES.includes(column);
|
|
4544
4573
|
const stalled = life.stage === "building" && life.turn !== "agent";
|
|
4545
|
-
const requeued =
|
|
4574
|
+
const requeued = column === "queue" && (stalled || life.turn === "idle" && life.cta == null);
|
|
4575
|
+
const offTheLineStart = offTheLine && (life.cta === "start_build" || life.cta === "start_plan");
|
|
4576
|
+
if (requeued && !stalled) stage = life.stage === "backlog" ? "Queued" : `Queued, ${stage.charAt(0).toLowerCase()}${stage.slice(1)}`;
|
|
4577
|
+
else if (offTheLineStart && life.stage === "planned") stage = "Planned, off the line";
|
|
4546
4578
|
if (stalled) stage = requeued ? "Queued" : offTheLine ? "Off the line" : column === null ? "Its build stopped" : "On the line, but its build stopped";
|
|
4547
4579
|
const build = life.latestBuild;
|
|
4548
4580
|
if (build && typeof build.status === "string" && life.stage !== "done" && life.stage !== "canceled") {
|
|
@@ -4563,7 +4595,7 @@ function situationOf(item) {
|
|
|
4563
4595
|
}
|
|
4564
4596
|
const turn = life.turn === "you" ? "A person's turn" : life.turn === "agent" ? "The agent's turn" : null;
|
|
4565
4597
|
const handedBack = handbackSaid(build?.reviewEscalation);
|
|
4566
|
-
const move = life.cta === "review_pr" && handedBack ? `a person should ${handbackNextStep(build.reviewEscalation)}` : stalled && offTheLine ? OFF_THE_LINE_MOVE : typeof life.cta === "string" ? CTA_MOVE[life.cta] : void 0;
|
|
4598
|
+
const move = life.cta === "review_pr" && handedBack ? `a person should ${handbackNextStep(build.reviewEscalation)}` : stalled && offTheLine || offTheLineStart && life.stage !== "backlog" ? OFF_THE_LINE_MOVE : typeof life.cta === "string" ? CTA_MOVE[life.cta] : void 0;
|
|
4567
4599
|
if (requeued) parts.push(`Nothing to do: ${REQUEUED_MOVE}.`);
|
|
4568
4600
|
else if (life.cta === "view_pr") parts.push(`Nothing to do: ${move}.`);
|
|
4569
4601
|
else if (turn && move) parts.push(`${turn}: ${move}.`);
|
|
@@ -4591,15 +4623,14 @@ function endingSaid(item) {
|
|
|
4591
4623
|
if (next && typeof next.id === "string") said.push(`; superseded by "${String(next.title)}" (${next.id})`);
|
|
4592
4624
|
return said.join("");
|
|
4593
4625
|
}
|
|
4594
|
-
/** A row or item with
|
|
4626
|
+
/** A row or item with where it stands added (`standing`). */
|
|
4595
4627
|
function withSituation(item, row = item) {
|
|
4596
|
-
|
|
4597
|
-
return situation ? {
|
|
4628
|
+
return {
|
|
4598
4629
|
...row,
|
|
4599
|
-
|
|
4600
|
-
}
|
|
4630
|
+
...standing(item)
|
|
4631
|
+
};
|
|
4601
4632
|
}
|
|
4602
|
-
/** List rows, each with
|
|
4633
|
+
/** List rows, each with where it stands. */
|
|
4603
4634
|
function situatedRows(items) {
|
|
4604
4635
|
return itemRows(items, BACKLOG_SUMMARY_FIELDS).map((row, index) => withSituation(items[index], row));
|
|
4605
4636
|
}
|
|
@@ -4676,36 +4707,6 @@ const outcomeOf = (item) => withSituation(item, {
|
|
|
4676
4707
|
criteria: criteriaCounts(item)
|
|
4677
4708
|
});
|
|
4678
4709
|
const outcome = (item) => toolResult(outcomeOf(item));
|
|
4679
|
-
/** The statuses an initiative in each board column can have: the column decides it (statusForState, PRO-2828). */
|
|
4680
|
-
const COLUMN_STATUSES = {
|
|
4681
|
-
backlog: [
|
|
4682
|
-
"all",
|
|
4683
|
-
"open",
|
|
4684
|
-
"backlog"
|
|
4685
|
-
],
|
|
4686
|
-
todo: [
|
|
4687
|
-
"all",
|
|
4688
|
-
"open",
|
|
4689
|
-
"backlog"
|
|
4690
|
-
],
|
|
4691
|
-
queue: [
|
|
4692
|
-
"all",
|
|
4693
|
-
"open",
|
|
4694
|
-
"backlog"
|
|
4695
|
-
],
|
|
4696
|
-
plan: [
|
|
4697
|
-
"all",
|
|
4698
|
-
"open",
|
|
4699
|
-
"backlog"
|
|
4700
|
-
],
|
|
4701
|
-
build: [
|
|
4702
|
-
"all",
|
|
4703
|
-
"open",
|
|
4704
|
-
"in_progress"
|
|
4705
|
-
],
|
|
4706
|
-
done: ["all", "done"],
|
|
4707
|
-
canceled: ["all", "canceled"]
|
|
4708
|
-
};
|
|
4709
4710
|
/**
|
|
4710
4711
|
* Each tool is registered under the capability its route requires, so a
|
|
4711
4712
|
* key sees exactly what it may call: reads on backlog:read; the board move on
|
|
@@ -4738,25 +4739,15 @@ function registerBacklogTools(server, client) {
|
|
|
4738
4739
|
function registerBacklogReads(server, client) {
|
|
4739
4740
|
server.registerTool("taiga_list_initiatives", {
|
|
4740
4741
|
title: "List initiatives",
|
|
4741
|
-
description: "List a product's open
|
|
4742
|
+
description: "List a product's initiatives, newest first: open ones (not done or canceled) unless column says otherwise. Each row has its title, category, column (backlog, todo, queue, plan, build, done, canceled), turn (you, agent or idle), situation (where it stands and what moves it on, in a sentence), latestRun and criteria (acceptance criteria met; null before planning). query finds one by title; taiga_get_initiative reads one. Archived ones are left out unless asked for.",
|
|
4742
4743
|
inputSchema: z.object({
|
|
4743
4744
|
productId,
|
|
4744
4745
|
query: z.string().trim().min(1).max(200).optional().describe("Part of the title, any case: only the items that contain it"),
|
|
4745
|
-
|
|
4746
|
+
column: z.enum([
|
|
4746
4747
|
"open",
|
|
4747
4748
|
"all",
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
"done",
|
|
4751
|
-
"canceled"
|
|
4752
|
-
]).optional().describe("open (default without state): not done or canceled; all: every one; in_progress is the Build column, backlog every other open one"),
|
|
4753
|
-
planningStatus: z.enum([
|
|
4754
|
-
"unplanned",
|
|
4755
|
-
"planning",
|
|
4756
|
-
"planned",
|
|
4757
|
-
"failed"
|
|
4758
|
-
]).optional().describe("Only items whose plan is in this state"),
|
|
4759
|
-
state: z.enum(INITIATIVE_STATES).optional().describe("Only items in this column of the board: backlog, todo, queue, plan, build, done or canceled (status groups them coarser)"),
|
|
4749
|
+
...INITIATIVE_STATES
|
|
4750
|
+
]).optional().describe("open (default): not done or canceled; all: every one; or one column of the board"),
|
|
4760
4751
|
category: z.enum([
|
|
4761
4752
|
"feature",
|
|
4762
4753
|
"bug",
|
|
@@ -4768,56 +4759,66 @@ function registerBacklogReads(server, client) {
|
|
|
4768
4759
|
}),
|
|
4769
4760
|
annotations: READ_ONLY
|
|
4770
4761
|
}, (params) => runTool(async () => {
|
|
4771
|
-
const { query,
|
|
4772
|
-
if (state !== void 0 && !COLUMN_STATUSES[state].includes(status)) return toolError("VALIDATION_ERROR", `No initiative is in the ${state} column with status ${status}: pass state alone, or a status it can have (${COLUMN_STATUSES[state].join(", ")}).`);
|
|
4762
|
+
const { query, column = "open", productId: id, ...filters } = params;
|
|
4773
4763
|
const apiFilters = {
|
|
4774
4764
|
...filters,
|
|
4775
|
-
|
|
4765
|
+
order: "newest",
|
|
4766
|
+
...column === "open" ? { open: true } : column === "all" ? {} : { state: column }
|
|
4776
4767
|
};
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
const result = await client.listBacklogItems(params.productId, apiFilters);
|
|
4768
|
+
if (query === void 0) {
|
|
4769
|
+
const result = await client.listBacklogItems(id, apiFilters);
|
|
4780
4770
|
return toolResult(page(situatedRows(result.data), result.nextCursor));
|
|
4781
4771
|
}
|
|
4782
|
-
if (params.cursor !== void 0) return toolError("VALIDATION_ERROR", "
|
|
4772
|
+
if (params.cursor !== void 0) return toolError("VALIDATION_ERROR", "A title search answers one page; narrow the title, or leave query out to page through a column.");
|
|
4783
4773
|
let closedSeen = 0;
|
|
4784
|
-
const needle =
|
|
4785
|
-
const found = await searchPages((cursor) => client.listBacklogItems(
|
|
4774
|
+
const needle = query.toLowerCase();
|
|
4775
|
+
const found = await searchPages((cursor) => client.listBacklogItems(id, {
|
|
4786
4776
|
...apiFilters,
|
|
4777
|
+
...column === "open" ? { open: void 0 } : {},
|
|
4787
4778
|
limit: 100,
|
|
4788
4779
|
cursor
|
|
4789
|
-
}), query
|
|
4790
|
-
if (
|
|
4791
|
-
if (
|
|
4792
|
-
if (query !== void 0 && String(row.title).toLowerCase().includes(needle)) closedSeen += 1;
|
|
4780
|
+
}), query, (row) => [row.title], params.limit ?? 25, (row) => {
|
|
4781
|
+
if (column !== "open" || !["done", "canceled"].includes(String(row.state))) return true;
|
|
4782
|
+
if (String(row.title).toLowerCase().includes(needle)) closedSeen += 1;
|
|
4793
4783
|
return false;
|
|
4794
4784
|
});
|
|
4795
|
-
const hidden =
|
|
4785
|
+
const hidden = column === "open" && !found.more ? closedSeen : 0;
|
|
4796
4786
|
return toolResult({
|
|
4797
4787
|
...page(situatedRows(found.rows), null),
|
|
4798
|
-
...hidden > 0 ? { closedMatches: `${hidden} done or canceled initiative${hidden === 1 ? " also matches" : "s also match"};
|
|
4799
|
-
...found.more ? { more: "There may be more than the limit carries: narrow
|
|
4788
|
+
...hidden > 0 ? { closedMatches: `${hidden} done or canceled initiative${hidden === 1 ? " also matches" : "s also match"}; column: 'all' lists them.` } : {},
|
|
4789
|
+
...found.more ? { more: "There may be more than the limit carries: narrow the title, or pick one column." } : {}
|
|
4800
4790
|
});
|
|
4801
4791
|
}));
|
|
4802
4792
|
server.registerTool("taiga_get_initiative", {
|
|
4803
4793
|
title: "Get initiative",
|
|
4804
|
-
description: `Read one initiative
|
|
4805
|
-
inputSchema: z.object({
|
|
4794
|
+
description: `Read one initiative: where it stands (column, turn, situation, latestRun), its acceptance criteria count and the start of its description. full: true reads it whole before implementing: the whole description, each acceptance criterion and whether it is met, technical context, source documents, and the API's own state fields. A failed plan carries planFailure (cause, meaning, when, by whom). ${LIFECYCLE_MEANING}`,
|
|
4795
|
+
inputSchema: z.object({
|
|
4796
|
+
initiativeId,
|
|
4797
|
+
full: z.boolean().optional().describe("Read it whole: every criterion, technical context and the raw state fields (default false)")
|
|
4798
|
+
}),
|
|
4806
4799
|
annotations: READ_ONLY
|
|
4807
4800
|
}, (params) => runTool(async () => {
|
|
4808
4801
|
const item = (await client.getBacklogItem(params.initiativeId)).data;
|
|
4809
4802
|
const row = item;
|
|
4810
|
-
|
|
4811
|
-
const
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
...
|
|
4815
|
-
|
|
4816
|
-
|
|
4803
|
+
const planFailure = item.planningStatus === "failed" ? await planFailureOf(client, params.initiativeId, item.lifecycle?.planFailureCause) : null;
|
|
4804
|
+
const failure = planFailure ? { planFailure } : {};
|
|
4805
|
+
if (params.full) return toolResult({
|
|
4806
|
+
...withSituation(row),
|
|
4807
|
+
...failure
|
|
4808
|
+
});
|
|
4809
|
+
const summary = itemRows([item], ITEM_SUMMARY_FIELDS)[0];
|
|
4810
|
+
const description = String(summary["description"] ?? "");
|
|
4811
|
+
return toolResult({
|
|
4812
|
+
...withSituation(row, {
|
|
4813
|
+
...summary,
|
|
4814
|
+
description: description.length > DESCRIPTION_IN_SUMMARY ? `${description.slice(0, DESCRIPTION_IN_SUMMARY).trimEnd()}... (full: true has the rest)` : description
|
|
4815
|
+
}),
|
|
4816
|
+
...failure
|
|
4817
|
+
});
|
|
4817
4818
|
}));
|
|
4818
4819
|
server.registerTool("taiga_get_plan", {
|
|
4819
4820
|
title: "Get the active plan",
|
|
4820
|
-
description: "Read an initiative's active plan: its steps in order, each with title, layer, dependencies (the orderIndex of the steps it builds on), files written and read, and resources created. Enough to approve or reject it (taiga_approve_plan, taiga_reject_plan); full: true adds each step's complete instructions (thousands of tokens). NOT_FOUND when there is no active plan yet;
|
|
4821
|
+
description: "Read an initiative's active plan: its steps in order, each with title, layer, dependencies (the orderIndex of the steps it builds on), files written and read, and resources created. Enough to approve or reject it (taiga_approve_plan, taiga_reject_plan); full: true adds each step's complete instructions (thousands of tokens). NOT_FOUND when there is no active plan yet; taiga_get_initiative's situation says whether one is being generated.",
|
|
4821
4822
|
inputSchema: z.object({
|
|
4822
4823
|
initiativeId,
|
|
4823
4824
|
full: z.boolean().optional().describe("Carry each step's complete instructions (default false: a summary)")
|
|
@@ -4980,7 +4981,7 @@ function registerReviewGate(server, client) {
|
|
|
4980
4981
|
function registerResume(server, client) {
|
|
4981
4982
|
server.registerTool("taiga_resume_initiative", {
|
|
4982
4983
|
title: "Resume initiative",
|
|
4983
|
-
description: "Carry on an initiative the line stopped on (
|
|
4984
|
+
description: "Carry on an initiative the line stopped on (its situation says the line stopped on it): a failed or cancelled plan is planned again, a stopped or failed run resumes from its first unfinished step (committed steps are kept), a refused run start is cleared and retried. Refused (CONFLICT) when nothing is flagged or the cause has no resume; a plan that failed off the line (its situation names taiga_plan_again, and planFailure says why) needs taiga_plan_again.",
|
|
4984
4985
|
inputSchema: z.object({ initiativeId }),
|
|
4985
4986
|
annotations: OPERATE
|
|
4986
4987
|
}, (params) => runTool(async () => outcome((await client.resumeInitiative(params.initiativeId)).data)));
|
|
@@ -5405,6 +5406,28 @@ function readWith(row, backlogRead) {
|
|
|
5405
5406
|
default: return;
|
|
5406
5407
|
}
|
|
5407
5408
|
}
|
|
5409
|
+
/**
|
|
5410
|
+
* The glossary's words for what the API names after its tables (PRO-2823): a reviewer met kind review_backlog,
|
|
5411
|
+
* subjectType backlog_item and inbox_task beside a glossary that says initiative and run, and had to infer they
|
|
5412
|
+
* were the same things.
|
|
5413
|
+
*/
|
|
5414
|
+
const KIND_SAID = { review_backlog: "review_initiatives" };
|
|
5415
|
+
const SUBJECT_SAID = {
|
|
5416
|
+
backlog_item: "initiative",
|
|
5417
|
+
build_run: "run",
|
|
5418
|
+
inbox_task: "setup_task"
|
|
5419
|
+
};
|
|
5420
|
+
const kindSaid = (kind) => KIND_SAID[String(kind)] ?? kind;
|
|
5421
|
+
/** A setup task row in the glossary's words: the Action Required item it is, by the name taiga_resolve_action takes. */
|
|
5422
|
+
function setupTaskRow(row) {
|
|
5423
|
+
const { inboxItemId, inboxItemStatus, alsoFiledAs, ...rest } = row;
|
|
5424
|
+
return {
|
|
5425
|
+
...rest,
|
|
5426
|
+
...inboxItemId !== void 0 ? { actionId: inboxItemId } : {},
|
|
5427
|
+
...inboxItemStatus !== void 0 ? { actionStatus: inboxItemStatus } : {},
|
|
5428
|
+
...Array.isArray(alsoFiledAs) ? { alsoFiledAs: alsoFiledAs.map(setupTaskRow) } : {}
|
|
5429
|
+
};
|
|
5430
|
+
}
|
|
5408
5431
|
/** A summary past its first sentence: review_doc rows quoted two documents, 800 to 1,200 characters each. */
|
|
5409
5432
|
const SUMMARY_LIMIT = 200;
|
|
5410
5433
|
function firstSentence(text) {
|
|
@@ -5422,9 +5445,12 @@ function itemRow(row, now, full, backlogRead) {
|
|
|
5422
5445
|
const created = typeof row["createdAt"] === "string" ? Date.parse(row["createdAt"]) : NaN;
|
|
5423
5446
|
const { summary, status, blocking, ...lean } = rest;
|
|
5424
5447
|
const read = readWith(row, backlogRead);
|
|
5448
|
+
const saysNothing = row["kind"] === "setup_manual" && String(summary).trim().toLowerCase() === "manual config";
|
|
5425
5449
|
return {
|
|
5426
5450
|
...lean,
|
|
5427
|
-
|
|
5451
|
+
kind: kindSaid(lean["kind"]),
|
|
5452
|
+
...typeof lean["subjectType"] === "string" ? { subjectType: SUBJECT_SAID[lean["subjectType"]] ?? lean["subjectType"] } : {},
|
|
5453
|
+
...saysNothing ? {} : full ? { summary } : row["kind"] === "review_pr" ? {} : { summary: firstSentence(summary) },
|
|
5428
5454
|
...full || status !== "open" ? { status } : {},
|
|
5429
5455
|
...full || blocking === true ? { blocking } : {},
|
|
5430
5456
|
...read ? { readWith: read } : {},
|
|
@@ -5456,7 +5482,8 @@ function summaryOf(rows, stale) {
|
|
|
5456
5482
|
if (stale.has(String(row["id"]))) entry.stale = (entry.stale ?? 0) + 1;
|
|
5457
5483
|
else {
|
|
5458
5484
|
entry.open += 1;
|
|
5459
|
-
|
|
5485
|
+
const kind = String(kindSaid(row["kind"]));
|
|
5486
|
+
entry.byKind[kind] = (entry.byKind[kind] ?? 0) + 1;
|
|
5460
5487
|
}
|
|
5461
5488
|
byProduct.set(key, entry);
|
|
5462
5489
|
}
|
|
@@ -5512,15 +5539,10 @@ async function staleBacklogReviews(client, rows, out) {
|
|
|
5512
5539
|
const empty = /* @__PURE__ */ new Set();
|
|
5513
5540
|
await Promise.all(checked.map(async (productId) => {
|
|
5514
5541
|
try {
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
limit:
|
|
5518
|
-
})));
|
|
5519
|
-
if (pages.some((page) => page.data.some((item) => item.state === "backlog" || item.state === "todo"))) return;
|
|
5520
|
-
if (pages.some((page) => page.nextCursor)) {
|
|
5521
|
-
unchecked += asks.filter((row) => row["productId"] === productId).length;
|
|
5522
|
-
return;
|
|
5523
|
-
}
|
|
5542
|
+
if ((await Promise.all(["backlog", "todo"].map((state) => client.listBacklogItems(productId, {
|
|
5543
|
+
state,
|
|
5544
|
+
limit: 1
|
|
5545
|
+
})))).some((page) => page.data.length > 0)) return;
|
|
5524
5546
|
empty.add(productId);
|
|
5525
5547
|
} catch (error) {
|
|
5526
5548
|
if (!(error instanceof TaigaApiError || isNetworkFailure(error))) throw error;
|
|
@@ -5603,6 +5625,7 @@ function sameKeyOnce(rows) {
|
|
|
5603
5625
|
id: from["id"],
|
|
5604
5626
|
category: from["category"],
|
|
5605
5627
|
title: from["title"],
|
|
5628
|
+
inboxItemId: from["inboxItemId"],
|
|
5606
5629
|
inboxItemStatus: from["inboxItemStatus"]
|
|
5607
5630
|
});
|
|
5608
5631
|
if (!isOpen(kept) && isOpen(row)) {
|
|
@@ -5702,7 +5725,7 @@ function registerInboxTools(server, client) {
|
|
|
5702
5725
|
...params.cursor === void 0 ? { withTotal: true } : {}
|
|
5703
5726
|
});
|
|
5704
5727
|
return toolResult({
|
|
5705
|
-
...folded(sameKeyOnce(pickFields(result.data, SETUP_TASK_FIELDS)), result.nextCursor, result.data.length),
|
|
5728
|
+
...folded(sameKeyOnce(pickFields(result.data, SETUP_TASK_FIELDS)).map(setupTaskRow), result.nextCursor, result.data.length),
|
|
5706
5729
|
...totalOf(result)
|
|
5707
5730
|
});
|
|
5708
5731
|
}));
|
|
@@ -5955,42 +5978,31 @@ async function policiesPart(client) {
|
|
|
5955
5978
|
sections: Object.keys(sectionsOf(row["data"] ?? {}) ?? {}).length
|
|
5956
5979
|
}));
|
|
5957
5980
|
}
|
|
5958
|
-
/** The statuses an item is open in; the API's filter takes one at a time. */
|
|
5959
|
-
const OPEN_BACKLOG_STATUSES = ["backlog", "in_progress"];
|
|
5960
5981
|
/**
|
|
5961
|
-
* The items still open: one
|
|
5962
|
-
*
|
|
5963
|
-
* reports. The list is oldest first and the open items are the newest, so a
|
|
5964
|
-
* single page would miss exactly the ones that matter. Rows carrying a
|
|
5965
|
-
* retired status (readable for a rolling deploy) match neither filter;
|
|
5966
|
-
* the settled check below keeps the shape honest for whatever comes back.
|
|
5982
|
+
* The items still open, newest first (PRO-2843): one read of the open ones,
|
|
5983
|
+
* paged up to a cap the result reports. The API leaves archived items out.
|
|
5967
5984
|
*/
|
|
5968
5985
|
async function backlogPart(client, productId) {
|
|
5969
|
-
const
|
|
5986
|
+
const rows = [];
|
|
5970
5987
|
let truncated = false;
|
|
5971
5988
|
let pageCapped = false;
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
break;
|
|
5979
|
-
}
|
|
5980
|
-
const result = await client.listBacklogItems(productId, {
|
|
5981
|
-
status,
|
|
5982
|
-
limit: 100,
|
|
5983
|
-
cursor
|
|
5984
|
-
});
|
|
5985
|
-
for (const row of result.data) if (!isSettledOrArchived({
|
|
5986
|
-
status: String(row.status),
|
|
5987
|
-
archivedAt: row.archivedAt ?? null
|
|
5988
|
-
})) open.set(String(row.id), row);
|
|
5989
|
-
if (!result.nextCursor) break;
|
|
5990
|
-
cursor = result.nextCursor;
|
|
5989
|
+
let cursor;
|
|
5990
|
+
for (let pageIndex = 0;; pageIndex += 1) {
|
|
5991
|
+
if (pageIndex === BACKLOG_PAGE_LIMIT) {
|
|
5992
|
+
truncated = true;
|
|
5993
|
+
pageCapped = true;
|
|
5994
|
+
break;
|
|
5991
5995
|
}
|
|
5996
|
+
const result = await client.listBacklogItems(productId, {
|
|
5997
|
+
open: true,
|
|
5998
|
+
order: "newest",
|
|
5999
|
+
limit: 100,
|
|
6000
|
+
cursor
|
|
6001
|
+
});
|
|
6002
|
+
rows.push(...result.data);
|
|
6003
|
+
if (!result.nextCursor) break;
|
|
6004
|
+
cursor = result.nextCursor;
|
|
5992
6005
|
}
|
|
5993
|
-
const rows = [...open.values()].sort((a, b) => String(a.id) < String(b.id) ? -1 : String(a.id) > String(b.id) ? 1 : 0);
|
|
5994
6006
|
if (rows.length > BACKLOG_IN_CONTEXT) truncated = true;
|
|
5995
6007
|
const byColumn = {
|
|
5996
6008
|
backlog: 0,
|
|
@@ -6002,9 +6014,9 @@ async function backlogPart(client, productId) {
|
|
|
6002
6014
|
for (const row of rows) if (row.state) byColumn[row.state] = (byColumn[row.state] ?? 0) + 1;
|
|
6003
6015
|
return {
|
|
6004
6016
|
byColumn,
|
|
6005
|
-
items: situatedRows(rows.slice(
|
|
6017
|
+
items: situatedRows(rows.slice(0, BACKLOG_IN_CONTEXT)),
|
|
6006
6018
|
truncated,
|
|
6007
|
-
...pageCapped ? { note: "More than 1000 open items
|
|
6019
|
+
...pageCapped ? { note: "More than 1000 open items: byColumn counts the newest 1000; taiga_list_initiatives pages the rest." } : {}
|
|
6008
6020
|
};
|
|
6009
6021
|
}
|
|
6010
6022
|
/**
|
|
@@ -6029,7 +6041,7 @@ const CONTEXT_RUN_SAID = [
|
|
|
6029
6041
|
"runNumber",
|
|
6030
6042
|
"backlogItemId",
|
|
6031
6043
|
"targetTitle",
|
|
6032
|
-
"
|
|
6044
|
+
"status",
|
|
6033
6045
|
"outcome",
|
|
6034
6046
|
"failedReasonMeans",
|
|
6035
6047
|
"hardeningNote",
|
|
@@ -6124,15 +6136,15 @@ async function productContext(client, product, options) {
|
|
|
6124
6136
|
}), null, 2).length;
|
|
6125
6137
|
}
|
|
6126
6138
|
return toolResult({
|
|
6127
|
-
|
|
6128
|
-
|
|
6139
|
+
characters,
|
|
6140
|
+
...result
|
|
6129
6141
|
});
|
|
6130
6142
|
}
|
|
6131
6143
|
function registerContextTools(server, client) {
|
|
6132
6144
|
if (!client.hasCapability("products:read")) return;
|
|
6133
6145
|
server.registerTool("taiga_get_product_context", {
|
|
6134
6146
|
title: "Get product context",
|
|
6135
|
-
description: `Read where a product stands in one call; the first read for any product question. Name it by ID, slug or git remote URL. Returns product (in setup, readiness and next move), factory with its instructions, specification and architecture outlines (section ids, sizes), policies with section counts, open initiatives with a count per board column (byColumn; taiga_list_initiatives
|
|
6147
|
+
description: `Read where a product stands in one call; the first read for any product question. Name it by ID, slug or git remote URL. Returns product (in setup, readiness and next move), factory with its instructions, specification and architecture outlines (section ids, sizes), policies with section counts, open initiatives with a count per board column (byColumn; taiga_list_initiatives column: 'all' has the done and canceled), repositories, the latest five runs (taiga_list_runs has more), and counts for Action Required, open findings and policy violations. A part that failed is under unavailable, one the key cannot read under notGranted; neither shows as empty. documents: budget returns whole sections up to about ${DOCUMENT_BUDGET / 1e3}k characters each, full returns all.`,
|
|
6136
6148
|
inputSchema: z.object({
|
|
6137
6149
|
productId: productId.optional(),
|
|
6138
6150
|
slug: slug.optional(),
|
|
@@ -22209,29 +22221,19 @@ function judgeItem(until, gate) {
|
|
|
22209
22221
|
/** The initiative as a wait reports it: where it is, whose turn, its latest run and its criteria. */
|
|
22210
22222
|
function itemReport(item) {
|
|
22211
22223
|
const run = latestBuildOf(item);
|
|
22212
|
-
const situation = situationOf(item);
|
|
22213
22224
|
return {
|
|
22214
|
-
...situation ? { situation } : {},
|
|
22215
22225
|
...pickFields([item], [
|
|
22216
22226
|
"id",
|
|
22217
22227
|
"title",
|
|
22218
|
-
"state",
|
|
22219
|
-
"planningStatus",
|
|
22220
|
-
"needsHumanCause",
|
|
22221
|
-
"needsHumanDetail",
|
|
22222
22228
|
"autoBuild"
|
|
22223
22229
|
])[0],
|
|
22224
|
-
|
|
22225
|
-
|
|
22226
|
-
"stage",
|
|
22227
|
-
"cta",
|
|
22228
|
-
"planFailureCause"
|
|
22229
|
-
])[0],
|
|
22230
|
-
latestBuild: run ? pickFields([run], [
|
|
22230
|
+
...standing(item),
|
|
22231
|
+
latestRun: run ? pickFields([run], [
|
|
22231
22232
|
"id",
|
|
22232
22233
|
"status",
|
|
22233
22234
|
"completedSteps",
|
|
22234
22235
|
"totalSteps",
|
|
22236
|
+
"prNumber",
|
|
22235
22237
|
"prUrl",
|
|
22236
22238
|
"prState",
|
|
22237
22239
|
"prMerged",
|
|
@@ -22415,6 +22417,10 @@ function withRecoveryHints(tool, schema, inputs, isRead) {
|
|
|
22415
22417
|
message: `${withStop(issue.message)} ${pointers.join(" ")}`
|
|
22416
22418
|
};
|
|
22417
22419
|
}
|
|
22420
|
+
if (issue.code === "invalid_value") return {
|
|
22421
|
+
...issue,
|
|
22422
|
+
message: `${withStop(issue.message)} Pass one of those values exactly.`
|
|
22423
|
+
};
|
|
22418
22424
|
const argument = issue.path?.length === 1 ? String(issue.path[0]) : void 0;
|
|
22419
22425
|
const source = argument ? idSource(argument) : void 0;
|
|
22420
22426
|
if (source && /received undefined/.test(issue.message)) return {
|
|
@@ -22563,7 +22569,7 @@ function registerAllTools(server, client, options = {}) {
|
|
|
22563
22569
|
function registerWhoami(server, client, registered) {
|
|
22564
22570
|
server.registerTool("taiga_whoami", {
|
|
22565
22571
|
title: "Who am I",
|
|
22566
|
-
description: "Read who this API key acts as: the person's email and organization role, the key's scopes (null means every capability the person has), its capabilities, and its tools split into reads, writes, and ask-first writes (paid agent work, changes others see). A tool the key cannot use is not offered at all.",
|
|
22572
|
+
description: "Read who this API key acts as: the API it talks to (api, which says the environment), the person's email and organization role, the key's scopes (null means every capability the person has), its capabilities, and its tools split into reads, writes, and ask-first writes (paid agent work, changes others see). A tool the key cannot use is not offered at all.",
|
|
22567
22573
|
inputSchema: z.object({}),
|
|
22568
22574
|
annotations: READ_ONLY
|
|
22569
22575
|
}, () => runTool(async () => {
|
|
@@ -22571,6 +22577,7 @@ function registerWhoami(server, client, registered) {
|
|
|
22571
22577
|
const tools = [...registered.entries()].filter(([name]) => name !== "taiga_whoami");
|
|
22572
22578
|
const named = (pick) => tools.filter(([, notes]) => pick(notes)).map(([name]) => name);
|
|
22573
22579
|
return toolResult({
|
|
22580
|
+
api: client.apiHost,
|
|
22574
22581
|
email: me?.email ?? null,
|
|
22575
22582
|
role: me?.role ?? null,
|
|
22576
22583
|
scopes: me?.scopes ?? null,
|
|
@@ -22613,8 +22620,8 @@ const SERVER_INSTRUCTIONS = [
|
|
|
22613
22620
|
"conversation that specifies a new product. A factory owns products.",
|
|
22614
22621
|
"",
|
|
22615
22622
|
"**IDs** are UUIDs; the product, factory and initiative lists take query to find one by name. A NOT_FOUND names",
|
|
22616
|
-
"the tool that lists the right ones. Lists page with nextCursor; taiga_list_initiatives lists open ones
|
|
22617
|
-
"
|
|
22623
|
+
"the tool that lists the right ones. Lists page with nextCursor; taiga_list_initiatives lists open ones, newest",
|
|
22624
|
+
"first. outcome, situation and nextStep are one-sentence summaries; setup writes return readiness.",
|
|
22618
22625
|
"Product documents have two timelines: plan (intent) and reality (from the code).",
|
|
22619
22626
|
"",
|
|
22620
22627
|
"**Writing.** A tool that writes opens with \"Writes.\", \"Ask first (paid agent work).\" or \"Ask first (others",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taiga-ai/mcp-server",
|
|
3
|
-
"version": "0.26.0-dev.
|
|
3
|
+
"version": "0.26.0-dev.488",
|
|
4
4
|
"description": "Taiga for AI coding assistants: read products, policies, initiatives and runs, and operate the line, over MCP",
|
|
5
5
|
"mcpName": "ga.tai/mcp-server",
|
|
6
6
|
"type": "module",
|