@taiga-ai/mcp-server 0.17.0-dev.441 → 0.18.0-dev.443

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 CHANGED
@@ -284,6 +284,10 @@ var TaigaClient = class {
284
284
  async getBacklogItem(itemId, signal) {
285
285
  return this.request("GET", `/backlog/${seg(itemId)}`, void 0, { signal });
286
286
  }
287
+ /** The initiative's history, oldest first: every turn event and audited move, each with who did it. */
288
+ async listBacklogItemActivity(itemId) {
289
+ return this.request("GET", `/backlog/${seg(itemId)}/activity`);
290
+ }
287
291
  /** A person's move on the line board: the column dropped into, and what to follow there. */
288
292
  async transitionInitiative(itemId, body) {
289
293
  return this.request("PUT", `/backlog/${seg(itemId)}/state`, body);
@@ -2178,7 +2182,7 @@ const BY_ARGUMENT = {
2178
2182
  factoryId: "taiga_list_factories lists the factories the key can see.",
2179
2183
  findingId: "taiga_list_findings lists a product's findings.",
2180
2184
  violationId: "taiga_get_policy_standing lists a product's policy violations.",
2181
- environmentId: "taiga_get_deployments lists the product's environments with their IDs; a product ID is not one.",
2185
+ environmentId: "taiga_list_environments lists the product's environments with their IDs; a product ID is not one.",
2182
2186
  knowledgeId: "taiga_list_knowledge lists the knowledge files.",
2183
2187
  productId: "taiga_list_products lists the products the key can see; a name is not an ID (taiga_get_product takes a slug)."
2184
2188
  };
@@ -2473,17 +2477,23 @@ function budgetSections(doc, budget) {
2473
2477
  return cutTo(doc, sections, Object.fromEntries(kept));
2474
2478
  }
2475
2479
  /**
2480
+ * The `sections` input of a document read: some sections by id, none for the
2481
+ * outline (every id with its size and no text), or omitted for the whole.
2482
+ */
2483
+ const sectionsInput = z.array(z.string().min(1).max(200)).max(50).optional().describe("Only these sections, by id; [] for the outline (every section id and its size, no text); omit for the whole document");
2484
+ /**
2476
2485
  * The answer to a read that names sections: the ones the document has, the
2477
2486
  * rest listed with their sizes, and the asked-for ids it lacks under
2478
- * unknownSections. One wrong id does not throw away the right ones (the
2479
- * model retries only the miss); none right is a miss that lists the real ids.
2487
+ * unknownSections. One shape whatever matched (PRO-2778): a wrong id does not
2488
+ * throw away the right ones, and an answer with none right still lists every
2489
+ * real id with its size, so the retry needs nothing else. No ids is the
2490
+ * outline.
2480
2491
  */
2481
2492
  function sectionReadResult(doc, ids, label) {
2482
2493
  const cut = pickSections(doc, new Set(ids));
2483
2494
  if (!cut) return toolError("VALIDATION_ERROR", `The ${label} is not made of sections; read it without sections.`);
2484
2495
  const all = Object.keys(sectionsOf(doc.data) ?? {});
2485
2496
  const unknownSections = ids.filter((id) => !all.includes(id));
2486
- if (unknownSections.length === ids.length) return toolError("NOT_FOUND", `The ${label} has no section ${ids.join(", ")}; its sections are ${all.join(", ")}.`);
2487
2497
  return toolResult({
2488
2498
  ...cut.doc,
2489
2499
  omittedSections: cut.omittedSections,
@@ -2847,7 +2857,7 @@ function registerProductTools(server, client) {
2847
2857
  }));
2848
2858
  server.registerTool("taiga_list_product_documents", {
2849
2859
  title: "List product documents",
2850
- description: `List a product's documents on both timelines, as summaries (type, name, version, draft state, review and update times), and say which timeline is the product's own (defaultOrigin). Two timelines: ${ORIGIN_MEANING.plan} ${ORIGIN_MEANING.reality} An empty side comes with a note saying why; a side the API could not list is null, with a note. Use taiga_get_product_document with the type and origin to read one in full.`,
2860
+ description: `List a product's documents on both timelines, as summaries (type, name, version, draft state, review and update times), and say which timeline is the product's own (defaultOrigin). Two timelines: ${ORIGIN_MEANING.plan} ${ORIGIN_MEANING.reality} An empty side comes with a note saying why; a side the API could not list is null, with a note. Use taiga_get_product_document with the type and origin to read one in full, or with sections: [] for its outline (section ids and sizes) first.`,
2851
2861
  inputSchema: z.object({ productId }),
2852
2862
  annotations: READ_ONLY
2853
2863
  }, (params) => runTool(async () => {
@@ -2856,12 +2866,12 @@ function registerProductTools(server, client) {
2856
2866
  }));
2857
2867
  server.registerTool("taiga_get_product_document", {
2858
2868
  title: "Get product document",
2859
- description: "Get the full content of one product document from one timeline. Read the plan to learn what to build: specification (requirements), architecture (design), threat_model (security risks), technology_decisions. Read reality, when the product has it, to learn what the code actually does before changing it. Omit origin for the product's own timeline. A large document runs to tens of thousands of characters: name the sections you need (the ids taiga_get_product_context lists) and the read carries only those, listing the rest with their sizes and any id the document lacks (unknownSections). A read that finds nothing says which documents and timelines the product does have.",
2869
+ description: "Get the full content of one product document from one timeline. Read the plan to learn what to build: specification (requirements), architecture (design), threat_model (security risks), technology_decisions. Read reality, when the product has it, to learn what the code actually does before changing it. Omit origin for the product's own timeline. A large document runs to tens of thousands of characters: sections: [] reads its outline (every section id with its size, no text) on either timeline, then name the sections you need and the read carries only those, listing the rest with their sizes and any id the document lacks (unknownSections). A read that finds nothing says which documents and timelines the product does have.",
2860
2870
  inputSchema: z.object({
2861
2871
  productId,
2862
2872
  type: z.enum(PRODUCT_DOCUMENT_TYPES).describe("Document type -- taiga_list_product_documents shows which ones the product has"),
2863
2873
  origin: originInput,
2864
- sections: z.array(z.string().min(1).max(200)).min(1).max(50).optional().describe("Only these sections, by id; omit for the whole document")
2874
+ sections: sectionsInput
2865
2875
  }),
2866
2876
  annotations: READ_ONLY
2867
2877
  }, (params) => runTool(async () => {
@@ -2993,10 +3003,10 @@ function registerDocumentTools(server, client) {
2993
3003
  }));
2994
3004
  server.registerTool("taiga_get_document", {
2995
3005
  title: "Get company document",
2996
- description: "Get the full content of one of the organization's documents, sections included. Use this when you need the security requirements, data handling rules, architecture standards or compliance controls that apply to your code, or the goals, standards and voice the organization has set down. A policy runs to thousands of tokens: name the sections you need (the ids taiga_get_product_context lists for every policy) and the read carries only those, listing the rest with their sizes and any id the document lacks (unknownSections). A read that finds nothing says which documents are published and whether a draft exists.",
3006
+ description: "Get the full content of one of the organization's documents, sections included. Use this when you need the security requirements, data handling rules, architecture standards or compliance controls that apply to your code, or the goals, standards and voice the organization has set down. A policy runs to thousands of tokens: name the sections you need (the ids taiga_get_product_context lists for every policy, or sections: [] for the outline) and the read carries only those, listing the rest with their sizes and any id the document lacks (unknownSections). A read that finds no document says which documents are published and whether a draft exists.",
2997
3007
  inputSchema: z.object({
2998
3008
  type: tenantDocumentType,
2999
- sections: z.array(z.string().min(1).max(200)).min(1).max(50).optional().describe("Only these sections, by id; omit for the whole document")
3009
+ sections: sectionsInput
3000
3010
  }),
3001
3011
  annotations: READ_ONLY
3002
3012
  }, (params) => runTool(async () => {
@@ -3399,6 +3409,51 @@ const ITEM_OUTCOME_FIELDS = [
3399
3409
  "archivedAt",
3400
3410
  "lifecycle"
3401
3411
  ];
3412
+ /** What each plan failure cause means for whoever reads it next (PLAN_FAILURE_CAUSES in @taiga/types). */
3413
+ const PLAN_FAILURE_MEANING = {
3414
+ agent_error: "The planning agent failed. Planning again is the usual next step.",
3415
+ worker_lost: "Nobody was running the planning any more (the worker stopped). Planning again is the usual next step.",
3416
+ cancelled: "A person stopped the planning (by says who). Ask before planning it again."
3417
+ };
3418
+ /**
3419
+ * Who or what ended a failed plan, and when, from the initiative's history
3420
+ * (PRO-2780): planFailureCause alone said "cancelled" with nobody behind it.
3421
+ * Best effort: a history the API will not give leaves the item as it was.
3422
+ */
3423
+ async function planFailureOf(client, itemId, itemCause) {
3424
+ let events;
3425
+ try {
3426
+ events = (await client.listBacklogItemActivity(itemId)).data;
3427
+ } catch (error) {
3428
+ if (error instanceof TaigaApiError || isNetworkFailure(error)) return null;
3429
+ throw error;
3430
+ }
3431
+ let failed;
3432
+ for (const event of [...events].reverse()) {
3433
+ if (event.kind !== "turn") continue;
3434
+ if (event.trigger === "plan_started") break;
3435
+ if (event.trigger === "plan_failed") {
3436
+ failed = event;
3437
+ break;
3438
+ }
3439
+ }
3440
+ if (!failed) return null;
3441
+ const actor = failed.actor ?? {};
3442
+ const data = failed.data ?? {};
3443
+ const cause = typeof data.cause === "string" ? data.cause : null;
3444
+ if (typeof itemCause === "string" && cause !== itemCause) return {
3445
+ cause: itemCause,
3446
+ meaning: PLAN_FAILURE_MEANING[itemCause] ?? "Planning ended without a plan.",
3447
+ at: null,
3448
+ by: null
3449
+ };
3450
+ return {
3451
+ cause,
3452
+ meaning: (cause && PLAN_FAILURE_MEANING[cause]) ?? "Planning ended without a plan.",
3453
+ at: failed.occurredAt ?? null,
3454
+ by: typeof actor.name === "string" ? actor.name : actor.type ?? null
3455
+ };
3456
+ }
3402
3457
  const planId = idInput("plan", "taiga_get_plan gives the initiative's active plan and its ID");
3403
3458
  const itemId = idInput("backlog item", "taiga_list_backlog_items (query finds one by title) gives it");
3404
3459
  /** The plan without its steps' prose: enough to approve it or say what a run will do. */
@@ -3505,11 +3560,17 @@ function registerBacklogReads(server, client) {
3505
3560
  }));
3506
3561
  server.registerTool("taiga_get_backlog_item", {
3507
3562
  title: "Get backlog item",
3508
- description: "Get one backlog item in full: description, acceptance criteria (and which are met), technical context (patterns, constraints, and the assumptions to verify before starting), dependencies and source documents. Read this before implementing an item, and read its runs with taiga_list_builds to see what the coding agent already did. lifecycle says whose turn it is and what moves it on: cta start_plan is taiga_queue_backlog_item, retry_plan taiga_plan_again (planFailureCause says why the plan stopped), start_build taiga_approve_plan, resume_build taiga_resume_backlog_item, retry_build a fresh run (start over, in the web), review_pr and view_pr the pull request. needsHumanCause is set only when the line stopped on it.",
3563
+ description: "Get one backlog item in full: description, acceptance criteria (and which are met), technical context (patterns, constraints, and the assumptions to verify before starting), dependencies and source documents. Read this before implementing an item, and read its runs with taiga_list_builds to see what the coding agent already did. A failed plan carries planFailure: the cause, what it means, when, and by whom. lifecycle says whose turn it is and what moves it on: cta start_plan is taiga_queue_backlog_item, retry_plan taiga_plan_again (planFailureCause says why the plan stopped), start_build taiga_approve_plan, resume_build taiga_resume_backlog_item, retry_build a fresh run (start over, in the web), review_pr and view_pr the pull request. needsHumanCause is set only when the line stopped on it.",
3509
3564
  inputSchema: z.object({ itemId }),
3510
3565
  annotations: READ_ONLY
3511
3566
  }, (params) => runTool(async () => {
3512
- return toolResult((await client.getBacklogItem(params.itemId)).data);
3567
+ const item = (await client.getBacklogItem(params.itemId)).data;
3568
+ if (item.planningStatus !== "failed") return toolResult(item);
3569
+ const planFailure = await planFailureOf(client, params.itemId, item.planFailureCause);
3570
+ return toolResult(planFailure ? {
3571
+ ...item,
3572
+ planFailure
3573
+ } : item);
3513
3574
  }));
3514
3575
  server.registerTool("taiga_get_plan", {
3515
3576
  title: "Get the active plan",
@@ -3572,7 +3633,7 @@ const BUILD_STATION = /* @__PURE__ */ new Set(["plan", "build"]);
3572
3633
  function registerPlanAgain(server, client) {
3573
3634
  server.registerTool("taiga_plan_again", {
3574
3635
  title: "Plan an initiative again",
3575
- description: "Supersede the plan of the initiative at the Build station and ask the planner for a new one: for a plan that failed, a plan you do not want built, or a run whose direction was wrong once it has stopped. The earlier plan is kept, and every run records which plan version it built; a pull request still open stays open. Offered only when the turn is yours: refused (CONFLICT) while an agent holds it (planning, building, reviewing). Also refused while a dependency is still in flight or a UI direction has no chosen concept, unless force says to plan anyway, which only the person decides. Anywhere off the Build station, put the initiative in the queue instead (taiga_queue_backlog_item): the queue is the only way to start work. To send a plan back with direction, taiga_reject_plan. Starts a planner run; ask the person first.",
3636
+ description: "Supersede the plan of the initiative at the Build station and ask the planner for a new one: for a plan that failed, a plan you do not want built, or a run whose direction was wrong once it has stopped. The earlier plan is kept, and every run records which plan version it built; a pull request still open stays open. Offered only when the turn is yours: refused (CONFLICT) while an agent holds it (planning, building, reviewing). Also refused while a dependency is still in flight or a UI direction has no chosen concept, unless force says to plan anyway, which only the person decides. Anywhere off the Build station, put the initiative in the queue instead (taiga_queue_backlog_item): the queue is the only way to start work. To send a plan back with direction, taiga_reject_plan. Starts a planner run.",
3576
3637
  inputSchema: z.object({
3577
3638
  itemId,
3578
3639
  force: z.boolean().optional().describe("Plan anyway past an unsettled dependency or an unchosen UI direction; only when the person says so")
@@ -3657,7 +3718,7 @@ function registerQueue(server, client) {
3657
3718
  function registerReviewGate(server, client) {
3658
3719
  server.registerTool("taiga_set_auto_build", {
3659
3720
  title: "Set an initiative's review gate",
3660
- description: "Set whether an initiative's plan is built as soon as it lands (true) or waits for a person to read it and approve or reject it (false); null follows the product's default. Standing: it applies to every plan the initiative gets, including one being written now. Opening the gate on a plan that is already waiting starts its build, so ask the person first. Returns the initiative with the gate that now applies.",
3721
+ description: "Set whether an initiative's plan is built as soon as it lands (true) or waits for a person to read it and approve or reject it (false); null follows the product's default. Standing: it applies to every plan the initiative gets, including one being written now. Opening the gate on a plan that is already waiting starts its build at once. Returns the initiative with the gate that now applies.",
3661
3722
  inputSchema: z.object({
3662
3723
  itemId,
3663
3724
  autoBuild: autoBuildInput
@@ -3676,7 +3737,7 @@ function registerReviewGate(server, client) {
3676
3737
  function registerResume(server, client) {
3677
3738
  server.registerTool("taiga_resume_backlog_item", {
3678
3739
  title: "Resume backlog item",
3679
- description: "Resume an initiative the line stopped on, which is when needsHumanCause is set (the line sets it): a failed or cancelled plan is planned again, a stopped or failed run resumes from its first unfinished part (the steps already committed are kept), a refused build start is cleared and retried. This is the way to carry work on: a run is the record of an attempt, and decisions about the work are made on the initiative. Refused (CONFLICT) when nothing is flagged or the cause has no resume: a plan that failed with needsHumanCause null (lifecycle.cta retry_plan, planFailureCause says why) is planned again with taiga_plan_again. Starts paid work; ask the person first.",
3740
+ description: "Resume an initiative the line stopped on, which is when needsHumanCause is set (the line sets it): a failed or cancelled plan is planned again, a stopped or failed run resumes from its first unfinished part (the steps already committed are kept), a refused build start is cleared and retried. This is the way to carry work on: a run is the record of an attempt, and decisions about the work are made on the initiative. Refused (CONFLICT) when nothing is flagged or the cause has no resume: a plan that failed with needsHumanCause null (lifecycle.cta retry_plan, planFailureCause says why) is planned again with taiga_plan_again. Starts paid work.",
3680
3741
  inputSchema: z.object({ itemId }),
3681
3742
  annotations: OPERATE
3682
3743
  }, (params) => runTool(async () => outcome((await client.resumeInitiative(params.itemId)).data)));
@@ -3684,13 +3745,13 @@ function registerResume(server, client) {
3684
3745
  function registerPlanVerdicts(server, client) {
3685
3746
  server.registerTool("taiga_approve_plan", {
3686
3747
  title: "Approve a plan",
3687
- description: "Approve an initiative's plan: its build starts now, as the approver, the same start the Start run button makes. Read the plan first with taiga_get_plan and name its ID; an approval of a plan that was replaced since is refused (CONFLICT), as is one while a new plan is being generated or a build is already running. Starts a coding run that pushes commits and opens a pull request; ask the person first.",
3748
+ description: "Approve an initiative's plan: its build starts now, as the approver, the same start the Start run button makes. Read the plan first with taiga_get_plan and name its ID; an approval of a plan that was replaced since is refused (CONFLICT), as is one while a new plan is being generated or a build is already running. Starts a coding run that pushes commits and opens a pull request.",
3688
3749
  inputSchema: z.object({ planId }),
3689
3750
  annotations: OPERATE
3690
3751
  }, (params) => runTool(async () => outcome((await client.approvePlan(params.planId)).data)));
3691
3752
  server.registerTool("taiga_reject_plan", {
3692
3753
  title: "Reject a plan",
3693
- description: "Send an initiative's plan back with direction: a new plan is generated with the comment as reviewer feedback and the rejected plan as its starting point, which supersedes it when the new one lands. Read the plan first with taiga_get_plan and name its ID. Starts a planner run; ask the person first.",
3754
+ description: "Send an initiative's plan back with direction: a new plan is generated with the comment as reviewer feedback and the rejected plan as its starting point, which supersedes it when the new one lands. Read the plan first with taiga_get_plan and name its ID. Starts a planner run.",
3694
3755
  inputSchema: z.object({
3695
3756
  planId,
3696
3757
  comment: z.string().trim().min(1).max(PLAN_REJECTION_COMMENT_MAX_CHARS).describe("What the next plan should do differently: a paragraph of direction")
@@ -3943,6 +4004,17 @@ function withoutCitations(doc) {
3943
4004
  dropped
3944
4005
  };
3945
4006
  }
4007
+ /**
4008
+ * How much of the two documents the bundle carries. The outline by default
4009
+ * (PRO-2778): the whole sections that fit a budget made the opening read run
4010
+ * to 43k characters for an active product, most of it prose a "where does it
4011
+ * stand" question never reads.
4012
+ */
4013
+ const DOCUMENT_DEPTHS = [
4014
+ "outline",
4015
+ "budget",
4016
+ "full"
4017
+ ];
3946
4018
  /** Pages of 100 the backlog part follows before it stops and says so. */
3947
4019
  const BACKLOG_PAGE_LIMIT = 10;
3948
4020
  /**
@@ -4005,7 +4077,7 @@ async function factoryPart(client, factoryId) {
4005
4077
  }
4006
4078
  /**
4007
4079
  * The specification and architecture from the product's own timeline: their
4008
- * whole sections up to a budget by default, in full or as section ids when
4080
+ * outline by default, the whole sections up to a budget or in full when
4009
4081
  * asked. Null, with a note, where unpublished.
4010
4082
  */
4011
4083
  async function documentsPart(client, product, depth) {
@@ -4039,11 +4111,16 @@ async function documentsPart(client, product, depth) {
4039
4111
  } else out[type] = cut.doc;
4040
4112
  } else {
4041
4113
  const sections = sectionsOf(doc.data ?? {});
4042
- if (sections) out[type] = {
4043
- version: doc.version,
4044
- sections: Object.keys(sections)
4045
- };
4046
- else {
4114
+ if (sections) {
4115
+ const outline = Object.keys(sections).map((id) => ({
4116
+ id,
4117
+ characters: sectionContent(sections, id)?.length ?? 0
4118
+ }));
4119
+ out[type] = {
4120
+ version: doc.version,
4121
+ sections: outline
4122
+ };
4123
+ } else {
4047
4124
  out[type] = {
4048
4125
  version: doc.version,
4049
4126
  sections: null
@@ -4160,7 +4237,7 @@ async function productContext(client, product, options) {
4160
4237
  ...bundle,
4161
4238
  notGranted: notGranted.map((part) => `${part} (needs ${PARTS[part]})`),
4162
4239
  unavailable,
4163
- readNext: "Read a policy with taiga_get_document (sections: the ids listed here, for only those), another product document with taiga_get_product_document, a backlog item with taiga_get_backlog_item (taiga_list_backlog_items pages a truncated backlog) and a run with taiga_get_build."
4240
+ readNext: "Read a policy with taiga_get_document (sections: the ids listed here, for only those), the specification's or architecture's sections or another product document with taiga_get_product_document, a backlog item with taiga_get_backlog_item (taiga_list_backlog_items pages a truncated backlog) and a run with taiga_get_build."
4164
4241
  };
4165
4242
  let characters = JSON.stringify(result, null, 2).length;
4166
4243
  for (let measured = -1; measured !== characters;) {
@@ -4179,12 +4256,12 @@ function registerContextTools(server, client) {
4179
4256
  if (!client.hasCapability("products:read")) return;
4180
4257
  server.registerTool("taiga_get_product_context", {
4181
4258
  title: "Get product context",
4182
- description: `Everything a coding session starts from, in one call: the product and which document timeline is its own, its factory with the instructions in full, the specification and architecture from that timeline, every published policy as its section ids, the open backlog items, the linked repositories and the latest runs. Name the product by ID, slug or the git remote URL of the checkout. A part the API refused or failed is named under unavailable with what it said; a part the key cannot read is named under notGranted; neither is shown as empty. The two documents bring as many whole sections as fit, about ${DOCUMENT_BUDGET / 1e3}k characters each, and name the sections left out (omittedSections) for taiga_get_product_document to read; fullDocuments: true carries them whole, tens of thousands of tokens on a large product and more than some clients accept, and false carries their section ids only. characters says how large the result is. Call this first, then read what you need.`,
4259
+ description: `Everything a coding session starts from, in one call: the product and which document timeline is its own, its factory with the instructions in full, the specification and architecture from that timeline, every published policy as its section ids, the open backlog items, the linked repositories and the latest runs. Name the product by ID, slug or the git remote URL of the checkout. A part the API refused or failed is named under unavailable with what it said; a part the key cannot read is named under notGranted; neither is shown as empty. The two documents come as their outline by default: each section id with its size, to read with taiga_get_product_document naming the sections. Before writing code, documents: budget brings as many whole sections as fit, about ${DOCUMENT_BUDGET / 1e3}k characters each, naming the ones left out; full brings them whole, tens of thousands of tokens on a large product and more than some clients accept. characters says how large the result is. Call this first, then read what you need.`,
4183
4260
  inputSchema: z.object({
4184
4261
  productId: productId.optional(),
4185
4262
  slug: slug.optional(),
4186
4263
  remoteUrl: remoteUrl.optional(),
4187
- fullDocuments: z.boolean().optional().describe("true carries the specification and architecture whole, false as section ids only; omit for the whole sections that fit a budget")
4264
+ documents: z.enum(DOCUMENT_DEPTHS).optional().describe("The specification and architecture as outline (section ids and sizes, the default), budget (the whole sections that fit) or full")
4188
4265
  }),
4189
4266
  annotations: READ_ONLY
4190
4267
  }, (params) => runTool(async () => {
@@ -4194,8 +4271,7 @@ function registerContextTools(server, client) {
4194
4271
  "remoteUrl"
4195
4272
  ]);
4196
4273
  if ("refusal" in resolved) return toolError(resolved.refusal.code, resolved.refusal.message);
4197
- const documents = params.fullDocuments === void 0 ? "budget" : params.fullDocuments ? "full" : "sectionIds";
4198
- return productContext(client, resolved.product, { documents });
4274
+ return productContext(client, resolved.product, { documents: params.documents ?? "outline" });
4199
4275
  }));
4200
4276
  }
4201
4277
  //#endregion
@@ -4203,7 +4279,7 @@ function registerContextTools(server, client) {
4203
4279
  function registerAutonomyTools(server, client) {
4204
4280
  if (client.hasCapability("products:write")) server.registerTool("taiga_set_product_autonomy", {
4205
4281
  title: "Set a product's autonomy defaults",
4206
- description: "Set the defaults a product's initiatives follow when they have not decided for themselves. autoBuild: false makes every landed plan wait for a person to approve or reject it; true builds it as soon as the product has room. autoMerge: false makes pull requests wait for a person to merge them; null follows the organization and factory above (autonomous merge needs the organization's switch on, and a factory can turn it off for everything under it; neither can be opened from here). Defaults, not rules: an initiative can decide otherwise for itself. Changes how work proceeds; ask the person first.",
4282
+ description: "Set the defaults a product's initiatives follow when they have not decided for themselves. autoBuild: false makes every landed plan wait for a person to approve or reject it; true builds it as soon as the product has room. autoMerge: false makes pull requests wait for a person to merge them; null follows the organization and factory above (autonomous merge needs the organization's switch on, and a factory can turn it off for everything under it; neither can be opened from here). Defaults, not rules: an initiative can decide otherwise for itself. Changes how work proceeds.",
4207
4283
  inputSchema: z.object({
4208
4284
  productId,
4209
4285
  autoBuild: z.boolean().optional().describe("Build landed plans without a review by default"),
@@ -4227,7 +4303,7 @@ function registerAutonomyTools(server, client) {
4227
4303
  }));
4228
4304
  if (client.hasCapability("backlog:write")) server.registerTool("taiga_set_auto_merge", {
4229
4305
  title: "Set whether an initiative's pull request merges on its own",
4230
- description: "An initiative's own switch for merging on its own: true lets its pull request merge itself once the repository's own rules pass (required checks, reviews, a code reviewer you made Taiga wait for); false makes it wait for a person; null follows the product's default. The final word within what the organization and factories allow: where either has turned autonomous merge off, true changes nothing. Taiga merges through your rules, never around them. Ask the person first.",
4306
+ description: "An initiative's own switch for merging on its own: true lets its pull request merge itself once the repository's own rules pass (required checks, reviews, a code reviewer you made Taiga wait for); false makes it wait for a person; null follows the product's default. The final word within what the organization and factories allow: where either has turned autonomous merge off, true changes nothing. Taiga merges through your rules, never around them.",
4231
4307
  inputSchema: z.object({
4232
4308
  itemId: idInput("backlog item", "taiga_list_backlog_items (query finds one by title) gives it"),
4233
4309
  autoMerge: z.boolean().nullable().describe("true merges on its own, false waits for a person, null follows the product")
@@ -4279,11 +4355,15 @@ const SETUP_TASK_FIELDS = [
4279
4355
  "inboxItemStatus",
4280
4356
  "createdAt"
4281
4357
  ];
4358
+ /** The API's count across every page, on the first page; nothing from an API that does not send one. */
4359
+ function totalOf(result) {
4360
+ return typeof result.totalCount === "number" ? { totalCount: result.totalCount } : {};
4361
+ }
4282
4362
  function registerInboxTools(server, client) {
4283
4363
  if (!client.hasCapability("products:read")) return;
4284
4364
  server.registerTool("taiga_list_action_required", {
4285
4365
  title: "List what needs a person",
4286
- description: "Read Action Required, where work comes back to a person: things to review (a plan, a set of initiatives, a document, a pull request, maintenance findings, new policy violations), each a decision an agent handed back rather than made, and things to set up that only a person can (a secret, an environment variable, an account, a package registry, a cloud permission). Across every product the key can open (each row names its product), or one product. category gate is the reviews, setup the setup items; resolved: true reads the archive of done and dismissed ones. The inbox is shared: it is the team's, not one person's. An item snoozed until later is left out until then, as the page leaves it out (snoozedHidden counts them). taiga_list_setup_tasks has the setup tasks with what exactly to set.",
4366
+ description: "Read Action Required, where work comes back to a person: things to review (a plan, a set of initiatives, a document, a pull request, maintenance findings, new policy violations), each a decision an agent handed back rather than made, and things to set up that only a person can (a secret, an environment variable, an account, a package registry, a cloud permission). Across every product the key can open (each row names its product), or one product. category gate is the reviews, setup the setup items; resolved: true reads the archive of done and dismissed ones. The inbox is shared: it is the team's, not one person's. An item snoozed until later is left out until then, as the page leaves it out (snoozedHidden counts them). The first page says how many there are on every page (totalCount, snoozed ones included). taiga_list_setup_tasks has the setup tasks with what exactly to set.",
4287
4367
  inputSchema: z.object({
4288
4368
  productId: productId.optional(),
4289
4369
  category: z.enum(["gate", "setup"]).optional().describe("gate: things to review; setup: things to set up"),
@@ -4295,7 +4375,8 @@ function registerInboxTools(server, client) {
4295
4375
  const { resolved, ...rest } = params;
4296
4376
  const result = await client.listInbox({
4297
4377
  ...rest,
4298
- ...resolved ? { resolved: true } : {}
4378
+ ...resolved ? { resolved: true } : {},
4379
+ ...params.cursor === void 0 ? { withTotal: true } : {}
4299
4380
  });
4300
4381
  const now = Date.now();
4301
4382
  const rows = result.data;
@@ -4303,12 +4384,13 @@ function registerInboxTools(server, client) {
4303
4384
  const hidden = rows.length - due.length;
4304
4385
  return toolResult({
4305
4386
  ...page(pickFields(due, ITEM_FIELDS), result.nextCursor),
4387
+ ...totalOf(result),
4306
4388
  ...hidden > 0 ? { snoozedHidden: hidden } : {}
4307
4389
  });
4308
4390
  }));
4309
4391
  server.registerTool("taiga_list_setup_tasks", {
4310
4392
  title: "List setup tasks",
4311
- description: "The setup tasks agents raised for work only a person can do, with what exactly to set and where: the name of an environment variable, secret or API key to create (key; never its value, which Taiga does not store or display), the provider page to do it on, and the phase that says when it blocks you (prereq: before the deploy, which fails without it; wiring: connecting generated values back to providers; followup: billing approvals, extra permissions and the like that can wait until after launch). A build does not wait for any of them. Across every product the key can open (each row names its product), or one. The tasks still to do, oldest first, the order they depend on each other; includeDone: true adds the done and dismissed ones.",
4393
+ description: "The setup tasks agents raised for work only a person can do, with what exactly to set and where: the name of an environment variable, secret or API key to create (key; never its value, which Taiga does not store or display), the provider page to do it on, and the phase that says when it blocks you (prereq: before the deploy, which fails without it; wiring: connecting generated values back to providers; followup: billing approvals, extra permissions and the like that can wait until after launch). A build does not wait for any of them. Across every product the key can open (each row names its product), or one. The tasks still to do, oldest first, the order they depend on each other; includeDone: true adds the done and dismissed ones. The first page says how many there are on every page (totalCount).",
4312
4394
  inputSchema: z.object({
4313
4395
  productId: productId.optional(),
4314
4396
  includeDone: z.boolean().optional().describe("Also the tasks already done or dismissed (default false)"),
@@ -4319,9 +4401,13 @@ function registerInboxTools(server, client) {
4319
4401
  const { includeDone, ...rest } = params;
4320
4402
  const result = await client.listSetupTasks({
4321
4403
  ...rest,
4322
- ...includeDone ? {} : { openOnly: true }
4404
+ ...includeDone ? {} : { openOnly: true },
4405
+ ...params.cursor === void 0 ? { withTotal: true } : {}
4406
+ });
4407
+ return toolResult({
4408
+ ...page(pickFields(result.data, SETUP_TASK_FIELDS), result.nextCursor),
4409
+ ...totalOf(result)
4323
4410
  });
4324
- return toolResult(page(pickFields(result.data, SETUP_TASK_FIELDS), result.nextCursor));
4325
4411
  }));
4326
4412
  if (!client.hasCapability("products:write")) return;
4327
4413
  server.registerTool("taiga_resolve_action", {
@@ -4477,7 +4563,7 @@ function registerKnowledgeTools(server, client) {
4477
4563
  if (!client.hasCapability("customDocuments:write")) return;
4478
4564
  server.registerTool("taiga_add_knowledge", {
4479
4565
  title: "Add knowledge",
4480
- description: "File a Markdown or plain-text reference document as knowledge, at the level it is relevant: a product (productId), a factory (factoryId), or the organization (neither; that takes an owner or admin, because every factory and product inherits it). Good candidates are what a new engineer would be handed to read: specifications and contracts to conform to, glossaries and code lists, third-party documentation, internal write-ups. A rule that applies every time belongs in instructions instead. The file is processed before agents can read it; taiga_list_knowledge says when it is ready. PDFs and spreadsheets are added in the web. The same content already filed at that level comes back as that file, with deduplicated: true: it was not created here, so do not delete it as if it were. Every agent at that level reads it from then on, and processing it is paid work; ask the person first.",
4566
+ description: "File a Markdown or plain-text reference document as knowledge, at the level it is relevant: a product (productId), a factory (factoryId), or the organization (neither; that takes an owner or admin, because every factory and product inherits it). Good candidates are what a new engineer would be handed to read: specifications and contracts to conform to, glossaries and code lists, third-party documentation, internal write-ups. A rule that applies every time belongs in instructions instead. The file is processed before agents can read it; taiga_list_knowledge says when it is ready. PDFs and spreadsheets are added in the web. The same content already filed at that level comes back as that file, with deduplicated: true: it was not created here, so do not delete it as if it were. Every agent at that level reads it from then on, and processing it is paid work.",
4481
4567
  inputSchema: z.object({
4482
4568
  ...scopeInputs,
4483
4569
  fileName: z.string().trim().min(1).max(200).regex(/\.(md|txt)$/i, "A Markdown (.md) or plain-text (.txt) file name").describe("The file name, ending in .md or .txt"),
@@ -4502,14 +4588,14 @@ function registerKnowledgeTools(server, client) {
4502
4588
  }));
4503
4589
  server.registerTool("taiga_retry_knowledge", {
4504
4590
  title: "Retry processing a knowledge file",
4505
- description: "Retry processing a knowledge file that failed (status failed): until it is processed, nothing reads it. Processing is paid work; ask the person first.",
4591
+ description: "Retry processing a knowledge file that failed (status failed): until it is processed, nothing reads it. Processing is paid work.",
4506
4592
  inputSchema: z.object({ knowledgeId }),
4507
4593
  annotations: OPERATE
4508
4594
  }, (params) => runTool(async () => toolResult(pickFields([(await client.retryKnowledge(params.knowledgeId)).data], KNOWLEDGE_FIELDS)[0])));
4509
4595
  if (!client.hasCapability("customDocuments:delete")) return;
4510
4596
  server.registerTool("taiga_delete_knowledge", {
4511
4597
  title: "Delete a knowledge file",
4512
- description: "Delete a knowledge file that is stale or wrong: stale reference material is worse than none, because it is confidently specific, and nothing expires on its own. Agents stop reading it at once. It cannot be undone; ask the person first. Organization knowledge takes an owner or admin.",
4598
+ description: "Delete a knowledge file that is stale or wrong: stale reference material is worse than none, because it is confidently specific, and nothing expires on its own. Agents stop reading it at once. It cannot be undone. Organization knowledge takes an owner or admin.",
4513
4599
  inputSchema: z.object({ knowledgeId }),
4514
4600
  annotations: OPERATE
4515
4601
  }, (params) => runTool(async () => {
@@ -4617,7 +4703,7 @@ function registerMaintenanceTools(server, client) {
4617
4703
  }, (params) => runTool(async () => toolResult((await client.getFinding(params.findingId)).data)));
4618
4704
  server.registerTool("taiga_get_policy_standing", {
4619
4705
  title: "Get a product's policy standing",
4620
- description: "Where a product stands against the organization's own published policies: the controls it violates, each with its violations (critical and high only, each citing the policy's sentence and the document passage that contradicts it), what is being done about each (an initiative raised, a fix in progress, awaiting verification), and when the documents were derived and assessed, led by a summary of the counts. A control with no violation is not listed, and it is not \"passing\" either: there is no such state. Pass full: true for every violation's evidence and the resolved ones. Fix a control with taiga_fix_policy_violation.",
4706
+ description: "Where a product stands against the organization's own published policies: the controls it violates, each with its violations (critical and high only, each citing the policy's sentence and the document passage that contradicts it), what is being done about each (an initiative raised, a fix in progress, awaiting verification), and when the documents were derived and assessed, led by a summary of the counts. A control breached by scanner findings counts them by tier (findingsByTier): a breach of routine findings only, such as secrets in test fixtures, is not an urgent one. A control with no violation is not listed, and it is not \"passing\" either: there is no such state. Pass full: true for every violation's evidence and the resolved ones. Fix a control with taiga_fix_policy_violation.",
4621
4707
  inputSchema: z.object({
4622
4708
  productId,
4623
4709
  full: z.boolean().optional().describe("Carry each violation's evidence, the policy excerpt and the resolved violations (default false)")
@@ -4657,6 +4743,7 @@ function registerMaintenanceTools(server, client) {
4657
4743
  "state",
4658
4744
  "divergenceCount",
4659
4745
  "findingCount",
4746
+ "findingsByTier",
4660
4747
  "plannedCount"
4661
4748
  ]),
4662
4749
  violations: pickFields(violations, VIOLATION_FIELDS)
@@ -4687,7 +4774,7 @@ function registerMaintenanceTools(server, client) {
4687
4774
  }));
4688
4775
  server.registerTool("taiga_fix_finding", {
4689
4776
  title: "Fix a finding",
4690
- description: "Ask for a finding's fix: Taiga raises an initiative and adds it to the end of the product's queue, where the line plans it in turn; whether the build then waits for a person is the initiative's review gate, as for any initiative. The fix is a normal run that ends in a pull request you review and merge. With wholeGroup: true it fixes the finding's whole group in one initiative, as the page does: for a dependency, every open advisory on that package at that installed version in one version bump; for code or infrastructure, every location the same rule flagged. Absent for a secret only in history, anything in a submodule, and a dependency with no fixed version (remediableBy says upstream_only); the API refuses those. Starts paid work; ask the person first.",
4777
+ description: "Ask for a finding's fix: Taiga raises an initiative and adds it to the end of the product's queue, where the line plans it in turn; whether the build then waits for a person is the initiative's review gate, as for any initiative. The fix is a normal run that ends in a pull request you review and merge. With wholeGroup: true it fixes the finding's whole group in one initiative, as the page does: for a dependency, every open advisory on that package at that installed version in one version bump; for code or infrastructure, every location the same rule flagged. Absent for a secret only in history, anything in a submodule, and a dependency with no fixed version (remediableBy says upstream_only); the API refuses those. Starts paid work.",
4691
4778
  inputSchema: z.object({
4692
4779
  productId,
4693
4780
  findingId,
@@ -4713,7 +4800,7 @@ function registerMaintenanceTools(server, client) {
4713
4800
  }));
4714
4801
  server.registerTool("taiga_fix_policy_violation", {
4715
4802
  title: "Fix policy violations",
4716
- description: "Ask for the fix of a product's policy violations: name a control to cover every violation recorded against it that has no initiative yet, or a single violation (violationId) when the control has more than one. Taiga raises one initiative and adds it to the end of the product's queue, exactly as a finding fix does. The violation stays listed while the work is under way: only the next assessment can record that the documents no longer show it. Starts paid work; ask the person first.",
4803
+ description: "Ask for the fix of a product's policy violations: name a control to cover every violation recorded against it that has no initiative yet, or a single violation (violationId) when the control has more than one. Taiga raises one initiative and adds it to the end of the product's queue, exactly as a finding fix does. The violation stays listed while the work is under way: only the next assessment can record that the documents no longer show it. Starts paid work.",
4717
4804
  inputSchema: z.object({
4718
4805
  productId,
4719
4806
  controlId: z.string().min(1).max(40).optional().describe("The control's identifier, e.g. SEC-3.4"),
@@ -4755,8 +4842,13 @@ const ENVIRONMENT_FIELDS = [
4755
4842
  "isProtected",
4756
4843
  "url"
4757
4844
  ];
4758
- const environmentId = idInput("environment", "taiga_get_deployments lists them; a product ID is not one");
4759
- const windowDays = (choices) => z.number().int().optional().describe(`Days to look back: ${choices} (default 30)`);
4845
+ const environmentId = idInput("environment", "taiga_list_environments lists them; a product ID is not one");
4846
+ /** The windows the pages offer, as the schema's own values rather than a number checked afterwards. */
4847
+ const windowDays = z.literal([
4848
+ 30,
4849
+ 60,
4850
+ 90
4851
+ ]).optional().describe("Days to look back: 30, 60 or 90 (default 30)");
4760
4852
  /** Whether a failure is the API's (refused, missing, unreachable) rather than a bug here. */
4761
4853
  const isApiFailure = (error) => error instanceof TaigaApiError || isNetworkFailure(error);
4762
4854
  /** A part that may fail on its own without failing the read: null and the reason, or the value. */
@@ -4828,22 +4920,30 @@ function registerObserveTools(server, client) {
4828
4920
  ...note ? { note } : {}
4829
4921
  });
4830
4922
  }));
4923
+ server.registerTool("taiga_list_environments", {
4924
+ title: "List a product's environments",
4925
+ description: "The places a product's software runs (development, staging, production or its own ladder), each with its ID, type, order and App URL. The environment IDs taiga_get_delivery_metrics, taiga_get_monitoring and taiga_update_environment take. taiga_get_deployments lists them too, beside what runs where.",
4926
+ inputSchema: z.object({ productId }),
4927
+ annotations: READ_ONLY
4928
+ }, (params) => runTool(async () => {
4929
+ const { rows, more } = await allEnvironments(client, params.productId);
4930
+ return toolResult({
4931
+ data: pickFields(rows, ENVIRONMENT_FIELDS),
4932
+ ...more ? { more: "More environments than this answer reads; the rest were left out." } : {},
4933
+ ...rows.length === 0 ? { note: "No environment is defined yet; taiga_define_environment adds one." } : {}
4934
+ });
4935
+ }));
4831
4936
  server.registerTool("taiga_get_delivery_metrics", {
4832
4937
  title: "Get delivery metrics",
4833
4938
  description: "DORA's five software delivery metrics for one environment (the one marked production, as the page reports them), computed from its deployment history over the last 30, 60 or 90 days: deployment frequency, lead time and failed deployment recovery time (throughput), change-failure rate and rework rate (instability), each with its trend against the window before, and a performance band for all but rework rate, which DORA has published none for. Failures and recoveries are read from deployments, not incidents. A trend built on fewer than ten deployments is approximate; a metric no deployment fed says so instead of showing a number. Previews and checks do not count.",
4834
4939
  inputSchema: z.object({
4835
4940
  productId,
4836
4941
  environmentId,
4837
- windowDays: windowDays("30, 60 or 90")
4942
+ windowDays
4838
4943
  }),
4839
4944
  annotations: READ_ONLY
4840
4945
  }, (params) => runTool(async () => {
4841
4946
  const days = params.windowDays ?? 30;
4842
- if (![
4843
- 30,
4844
- 60,
4845
- 90
4846
- ].includes(days)) return toolError("VALIDATION_ERROR", "windowDays is 30, 60 or 90.");
4847
4947
  return toolResult((await client.getDeliveryMetrics(params.productId, params.environmentId, days)).data);
4848
4948
  }));
4849
4949
  server.registerTool("taiga_get_monitoring", {
@@ -4852,7 +4952,7 @@ function registerObserveTools(server, client) {
4852
4952
  inputSchema: z.object({
4853
4953
  productId,
4854
4954
  environmentId: environmentId.optional(),
4855
- windowDays: windowDays("30, 60 or 90")
4955
+ windowDays
4856
4956
  }),
4857
4957
  annotations: READ_ONLY
4858
4958
  }, (params) => runTool(async () => {
@@ -4862,11 +4962,6 @@ function registerObserveTools(server, client) {
4862
4962
  ...health.data.length === 0 ? { note: "No environment is monitored: monitoring starts when an environment has its App URL (taiga_define_environment, taiga_update_environment). No health is not good health." } : {}
4863
4963
  });
4864
4964
  const days = params.windowDays ?? 30;
4865
- if (![
4866
- 30,
4867
- 60,
4868
- 90
4869
- ].includes(days)) return toolError("VALIDATION_ERROR", "windowDays is 30, 60 or 90, the windows the page offers.");
4870
4965
  const env = params.environmentId;
4871
4966
  const [uptime, experience, clientHealth] = await Promise.all([
4872
4967
  part(() => client.getMonitoringSignal(params.productId, "uptime", env, days)),
@@ -4892,7 +4987,7 @@ function registerObserveTools(server, client) {
4892
4987
  if (!client.hasCapability("products:write")) return;
4893
4988
  server.registerTool("taiga_define_environment", {
4894
4989
  title: "Define an environment",
4895
- description: "Add a place a product's software runs: development, staging, production or whatever ladder it uses. Deployments are recorded against an environment, monitoring is reported per environment, and plans read where the work will run. Describing one gives Taiga no access to it: the pipeline deploys, Taiga learns what happened from source control. Member-level work. Give it its App URL afterwards with taiga_update_environment, which is all monitoring needs to start checking. Changes the product's setup for everybody; ask the person first.",
4990
+ description: "Add a place a product's software runs: development, staging, production or whatever ladder it uses. Deployments are recorded against an environment, monitoring is reported per environment, and plans read where the work will run. Describing one gives Taiga no access to it: the pipeline deploys, Taiga learns what happened from source control. Member-level work. Give it its App URL afterwards with taiga_update_environment, which is all monitoring needs to start checking. Changes the product's setup for everybody.",
4896
4991
  inputSchema: z.object({
4897
4992
  productId,
4898
4993
  name: z.string().trim().min(1).max(100).describe("What the team calls it, e.g. staging"),
@@ -4908,7 +5003,7 @@ function registerObserveTools(server, client) {
4908
5003
  }));
4909
5004
  server.registerTool("taiga_update_environment", {
4910
5005
  title: "Update an environment",
4911
- description: "Rename an environment, change its kind, or set its public App URL (null clears it). The App URL is all monitoring needs: once it is saved, Taiga checks the app's availability every few minutes on its own. Marking an environment production makes it the one delivery metrics report on. Changes the product's setup for everybody; ask the person first.",
5006
+ description: "Rename an environment, change its kind, or set its public App URL (null clears it). The App URL is all monitoring needs: once it is saved, Taiga checks the app's availability every few minutes on its own. Marking an environment production makes it the one delivery metrics report on. Changes the product's setup for everybody.",
4912
5007
  inputSchema: z.object({
4913
5008
  environmentId,
4914
5009
  name: z.string().trim().min(1).max(100).optional().describe("A new name"),
@@ -4967,11 +5062,11 @@ function registerProductDocumentWrites(server, client) {
4967
5062
  productId,
4968
5063
  type: reviewableType
4969
5064
  }),
4970
- annotations: WRITE
5065
+ annotations: OPERATE
4971
5066
  }, (params) => runTool(async () => toolResult((await client.markProductDocumentReviewed(params.productId, params.type)).data)));
4972
5067
  if (canRead) server.registerTool("taiga_regenerate_product_document", {
4973
5068
  title: "Regenerate a product document",
4974
- description: "Ask an agent to generate one product document again from the documents it is derived from, for one marked outdated or one that needs another pass. The Specification comes from the Discovery conversation and the Look & Feel from its concepts, both in the web. The documents Discovery requires are regenerated while it is open only: a finished Discovery locks them, and reopening it is done in the web. The optional Service Blueprint can be generated whenever someone wants one. The result is published as the new version, and the documents generated from it are marked outdated in turn. Runs an agent in the background: this returns the job; taiga_wait_for_job says when it is done. Ask the person first.",
5069
+ description: "Ask an agent to generate one product document again from the documents it is derived from, for one marked outdated or one that needs another pass. The Specification comes from the Discovery conversation and the Look & Feel from its concepts, both in the web. The documents Discovery requires are regenerated while it is open only: a finished Discovery locks them, and reopening it is done in the web. The optional Service Blueprint can be generated whenever someone wants one. The result is published as the new version, and the documents generated from it are marked outdated in turn. Runs an agent in the background: this returns the job; taiga_wait_for_job says when it is done.",
4975
5070
  inputSchema: z.object({
4976
5071
  productId,
4977
5072
  type: generatedType
@@ -4990,13 +5085,13 @@ function registerProductDocumentWrites(server, client) {
4990
5085
  }));
4991
5086
  server.registerTool("taiga_generate_product_documents", {
4992
5087
  title: "Generate the remaining product documents",
4993
- description: "Generate remaining: every document the product still needs, and every one marked outdated, in the order they build on each other, one after another (each next one starts when the one before it is published). Refused (CONFLICT) while a document is already being generated. On an imported product, it queues every unpublished document. Runs agents in the background: this returns the jobs it started (jobIds); taiga_stop_generating_documents stops the run. Ask the person first.",
5088
+ description: "Generate remaining: every document the product still needs, and every one marked outdated, in the order they build on each other, one after another (each next one starts when the one before it is published). Refused (CONFLICT) while a document is already being generated. On an imported product, it queues every unpublished document. Runs agents in the background: this returns the jobs it started (jobIds); taiga_stop_generating_documents stops the run.",
4994
5089
  inputSchema: z.object({ productId }),
4995
5090
  annotations: OPERATE
4996
5091
  }, (params) => runTool(async () => toolResult((await client.generateRemainingDocuments(params.productId)).data)));
4997
5092
  server.registerTool("taiga_stop_generating_documents", {
4998
5093
  title: "Stop generating product documents",
4999
- description: "Stop a Generate remaining run: every document of it still being generated is cancelled and nothing after them starts; published documents stay. An empty list means no run was going. Interrupts agent work; ask the person first.",
5094
+ description: "Stop a Generate remaining run: every document of it still being generated is cancelled and nothing after them starts; published documents stay. An empty list means no run was going. Interrupts agent work.",
5000
5095
  inputSchema: z.object({ productId }),
5001
5096
  annotations: OPERATE
5002
5097
  }, (params) => runTool(async () => toolResult((await client.stopGeneratingDocuments(params.productId)).data)));
@@ -19738,6 +19833,13 @@ const GITHUB_REPOSITORY_FIELDS = [
19738
19833
  "language"
19739
19834
  ];
19740
19835
  const repositoryId = idInput("repository link", "taiga_list_product_repositories gives it; a GitHub repository id is not one");
19836
+ /** Round-robin over lists: the first of each, then the second of each, and so on. */
19837
+ function interleave(lists) {
19838
+ const out = [];
19839
+ const longest = Math.max(0, ...lists.map((list) => list.length));
19840
+ for (let index = 0; index < longest; index++) for (const list of lists) if (index < list.length) out.push(list[index]);
19841
+ return out;
19842
+ }
19741
19843
  function registerRepositoryTools(server, client) {
19742
19844
  if (client.hasCapability("products:read")) {
19743
19845
  server.registerTool("taiga_list_product_repositories", {
@@ -19746,7 +19848,11 @@ function registerRepositoryTools(server, client) {
19746
19848
  inputSchema: z.object({ productId }),
19747
19849
  annotations: READ_ONLY
19748
19850
  }, (params) => runTool(async () => {
19749
- return toolResult(pickFields((await client.listProductRepositories(params.productId)).data, LINK_FIELDS));
19851
+ const result = await client.listProductRepositories(params.productId);
19852
+ return toolResult({
19853
+ data: pickFields(result.data, LINK_FIELDS),
19854
+ truncated: result.nextCursor !== null
19855
+ });
19750
19856
  }));
19751
19857
  server.registerTool("taiga_list_repository_branches", {
19752
19858
  title: "List a repository's branches",
@@ -19759,16 +19865,17 @@ function registerRepositoryTools(server, client) {
19759
19865
  }, (params) => runTool(async () => {
19760
19866
  const result = await client.listRepositoryBranches(params.productId, params.repositoryId);
19761
19867
  return toolResult({
19762
- branches: result.data,
19868
+ data: result.data,
19763
19869
  truncated: result.truncated
19764
19870
  });
19765
19871
  }));
19766
19872
  }
19767
19873
  if (client.hasCapability("vcs:read")) server.registerTool("taiga_list_linkable_repositories", {
19768
19874
  title: "Find a repository to link",
19769
- description: "The repositories the organization's GitHub connection can reach, to link one to a product (taiga_link_repository takes its id). Searches by name with query; without installationId it reads every connected GitHub account. No connection means GitHub is not connected yet: that is done once, in the web, by someone who can install the app.",
19875
+ description: "The repositories the organization's GitHub connection can reach, to link one to a product (taiga_link_repository takes its id). Searches by name with query. Name the GitHub account (account: its login, the owner in owner/repo, or installationId) to read only its repositories; without one it reads every connected account and takes rows from each in turn, so one account cannot fill the page. No connection means GitHub is not connected yet: that is done once, in the web, by someone who can install the app.",
19770
19876
  inputSchema: z.object({
19771
19877
  query: z.string().trim().min(1).max(256).optional().describe("Part of the repository name"),
19878
+ account: z.string().trim().min(1).max(140).optional().describe("The GitHub account login (the owner in owner/repo), case-insensitive"),
19772
19879
  installationId: idInput("GitHub account", "this tool lists them").optional(),
19773
19880
  ...pagination
19774
19881
  }),
@@ -19777,10 +19884,11 @@ function registerRepositoryTools(server, client) {
19777
19884
  const installations = (await client.listGithubInstallations()).data;
19778
19885
  const accounts = pickFields(installations, INSTALLATION_FIELDS);
19779
19886
  if (installations.length === 0) return toolError("NOT_CONNECTED", "GitHub is not connected for this organization. It is connected once, in the web; then link.");
19780
- const chosen = params.installationId ? installations.filter((i) => i.id === params.installationId) : installations;
19887
+ const login = params.account?.split("/")[0].toLowerCase();
19888
+ const chosen = installations.filter((i) => (!params.installationId || i.id === params.installationId) && (!login || String(i.accountLogin ?? "").toLowerCase() === login));
19781
19889
  if (chosen.length === 0) return toolError("NOT_FOUND", "No such GitHub connection; these are the connected accounts.", { accounts });
19782
19890
  if (params.cursor !== void 0 && params.query !== void 0) return toolError("VALIDATION_ERROR", SEARCH_WITH_CURSOR);
19783
- if (chosen.length > 1 && params.cursor !== void 0) return toolError("VALIDATION_ERROR", "A cursor pages one account: name its installationId.", { accounts });
19891
+ if (chosen.length > 1 && params.cursor !== void 0) return toolError("VALIDATION_ERROR", "A cursor pages one account: name it (account or installationId).", { accounts });
19784
19892
  const max = params.limit ?? 25;
19785
19893
  const rows = (data) => pickFields(data, GITHUB_REPOSITORY_FIELDS);
19786
19894
  if (chosen.length === 1) {
@@ -19800,18 +19908,18 @@ function registerRepositoryTools(server, client) {
19800
19908
  q: params.query,
19801
19909
  limit: max
19802
19910
  })));
19803
- const found = pages.flatMap((p) => rows(p.data));
19911
+ const found = interleave(pages.map((p) => rows(p.data)));
19804
19912
  const cut = found.length > max || pages.some((p) => p.nextCursor);
19805
19913
  return toolResult({
19806
19914
  accounts,
19807
19915
  repositories: page(found.slice(0, max), null),
19808
- ...cut ? { more: "More than the limit across accounts: name an installationId, or narrow the query." } : {}
19916
+ ...cut ? { more: "More than the limit across accounts: name an account, or narrow the query." } : {}
19809
19917
  });
19810
19918
  }));
19811
19919
  if (!client.hasCapability("vcs:manage")) return;
19812
19920
  server.registerTool("taiga_link_repository", {
19813
19921
  title: "Link a repository to a product",
19814
- description: "Link a repository the GitHub connection can reach (taiga_list_linkable_repositories gives its id) to a product, or with replacesRepositoryId swap the one linked now for it in one step (naming the same repository there changes nothing; linking one already linked without it is refused). A link Taiga manages (isManaged) cannot be replaced or unlinked. Once linked, planning reads its code, runs open pull requests in it, sweeps scan it, and its deployment history is imported in the background. Ask the person first.",
19922
+ description: "Link a repository the GitHub connection can reach (taiga_list_linkable_repositories gives its id) to a product, or with replacesRepositoryId swap the one linked now for it in one step (naming the same repository there changes nothing; linking one already linked without it is refused). A link Taiga manages (isManaged) cannot be replaced or unlinked. Once linked, planning reads its code, runs open pull requests in it, sweeps scan it, and its deployment history is imported in the background.",
19815
19923
  inputSchema: z.object({
19816
19924
  productId,
19817
19925
  githubRepositoryId: idInput("GitHub repository", "taiga_list_linkable_repositories gives it"),
@@ -19826,7 +19934,7 @@ function registerRepositoryTools(server, client) {
19826
19934
  }));
19827
19935
  server.registerTool("taiga_set_repository_branch", {
19828
19936
  title: "Choose the branch Taiga works on",
19829
- description: "Choose the branch of a product's repository that Taiga branches from and opens its pull requests against (taiga_list_repository_branches lists them), or null to follow the repository's default again. A protected branch is not refused, but Taiga may not be able to merge into it: the answer says when it is protected. Optionally a label for the link. Ask the person first.",
19937
+ description: "Choose the branch of a product's repository that Taiga branches from and opens its pull requests against (taiga_list_repository_branches lists them), or null to follow the repository's default again. A protected branch is not refused, but Taiga may not be able to merge into it: the answer says when it is protected. Optionally a label for the link.",
19830
19938
  inputSchema: z.object({
19831
19939
  productId,
19832
19940
  repositoryId,
@@ -19847,7 +19955,7 @@ function registerRepositoryTools(server, client) {
19847
19955
  }));
19848
19956
  server.registerTool("taiga_unlink_repository", {
19849
19957
  title: "Unlink a repository from a product",
19850
- description: "Unlink a repository from a product. Without one, the product can still have documents and initiatives but no changes: nothing is planned against code, built or swept. The repository itself is untouched, and a repository Taiga created for the product cannot be unlinked. To swap it for another, use taiga_link_repository with replacesRepositoryId instead. Ask the person first.",
19958
+ description: "Unlink a repository from a product. Without one, the product can still have documents and initiatives but no changes: nothing is planned against code, built or swept. The repository itself is untouched, and a repository Taiga created for the product cannot be unlinked. To swap it for another, use taiga_link_repository with replacesRepositoryId instead.",
19851
19959
  inputSchema: z.object({
19852
19960
  productId,
19853
19961
  repositoryId
@@ -20063,7 +20171,7 @@ function registerSetupTools(server, client) {
20063
20171
  }, (params) => runTool(async () => toolResult(pickFields([(await client.createFactory({ name: params.name })).data], FACTORY_FIELDS)[0])));
20064
20172
  server.registerTool("taiga_rename_factory", {
20065
20173
  title: "Rename a factory",
20066
- description: "Rename a factory. Its products, members, knowledge and instructions stay as they are, but its slug follows the new name, so links to the factory change. Ask the person first.",
20174
+ description: "Rename a factory. Its products, members, knowledge and instructions stay as they are, but its slug follows the new name, so links to the factory change.",
20067
20175
  inputSchema: z.object({
20068
20176
  factoryId,
20069
20177
  name: z.string().trim().min(1).max(255).describe("The new name")
@@ -20081,7 +20189,7 @@ function registerSetupTools(server, client) {
20081
20189
  }, (params) => runTool(async () => toolResult((await client.previewProductMove(params.productId, params.factoryId)).data)));
20082
20190
  server.registerTool("taiga_move_product", {
20083
20191
  title: "Move a product to another factory",
20084
- description: "Move a product to another factory. Access follows the factory, so this changes who can reach the product (taiga_preview_product_move says who); the product's own knowledge files are re-filed onto the new factory, and factory knowledge and instructions stay with the factory that owns them. Refused (CONFLICT) while a plan or build is in flight: stopLine: true stops the line first and then moves, which interrupts that work. Ask the person first.",
20192
+ description: "Move a product to another factory. Access follows the factory, so this changes who can reach the product (taiga_preview_product_move says who); the product's own knowledge files are re-filed onto the new factory, and factory knowledge and instructions stay with the factory that owns them. Refused (CONFLICT) while a plan or build is in flight: stopLine: true stops the line first and then moves, which interrupts that work.",
20085
20193
  inputSchema: z.object({
20086
20194
  productId,
20087
20195
  factoryId: factoryId.describe("The factory to move it to"),
@@ -20105,7 +20213,7 @@ function registerSetupTools(server, client) {
20105
20213
  if (!client.hasCapability("products:write")) return;
20106
20214
  server.registerTool("taiga_create_product", {
20107
20215
  title: "Create a product",
20108
- description: "Create a product in a factory, the way New product does. It starts one of two ways, chosen now and not later: scratch, where Discovery is a conversation that writes the specification (held in the web), or import (projectType brownfield), where Taiga reads an existing repository and drafts the documents from the code: link the repository (taiga_link_repository; linking is for owners and admins, so a member asks one), then taiga_start_import. Also what Taiga builds for it: its infrastructure code and its CI/CD pipelines, each following the organization unless decided here; when one is off, deploymentNotes says in a sentence how that half is done today. Material the product should be built from is worth adding before Discovery starts (taiga_add_knowledge with the productId): what is added first shapes the specification.",
20216
+ description: "Create a product in a factory, the way New product does. It starts one of two ways, chosen now and not later: scratch (afterwards projectType greenfield), where Discovery is a conversation that writes the specification (held in the web), or import (projectType brownfield), where Taiga reads an existing repository and drafts the documents from the code: link the repository (taiga_link_repository; linking is for owners and admins, so a member asks one), then taiga_start_import. Also what Taiga builds for it: its infrastructure code and its CI/CD pipelines, each following the organization unless decided here; when one is off, deploymentNotes says in a sentence how that half is done today. Material the product should be built from is worth adding before Discovery starts (taiga_add_knowledge with the productId): what is added first shapes the specification.",
20109
20217
  inputSchema: z.object({
20110
20218
  factoryId,
20111
20219
  name: z.string().trim().min(1).max(255).describe("The product name"),
@@ -20129,7 +20237,7 @@ function registerSetupTools(server, client) {
20129
20237
  }));
20130
20238
  server.registerTool("taiga_update_product", {
20131
20239
  title: "Change a product's settings",
20132
- description: "Change a product's name (its slug follows the name, so links to the product change) or what Taiga builds for it: its infrastructure code and CI/CD pipelines (null returns one to the organization default), the sentence on how a half Taiga does not build is done today, and whether it inherits the organization's design standards (null follows the default for how the product started). Later plans read the new answer; work already planned is not redone. Autonomy is taiga_set_product_autonomy. Ask the person first.",
20240
+ description: "Change a product's name (its slug follows the name, so links to the product change) or what Taiga builds for it: its infrastructure code and CI/CD pipelines (null returns one to the organization default), the sentence on how a half Taiga does not build is done today, and whether it inherits the organization's design standards (null follows the default for how the product started). Later plans read the new answer; work already planned is not redone. Autonomy is taiga_set_product_autonomy.",
20133
20241
  inputSchema: z.object({
20134
20242
  productId,
20135
20243
  name: z.string().trim().min(1).max(255).optional().describe("A new name"),
@@ -20152,7 +20260,7 @@ function registerSetupTools(server, client) {
20152
20260
  }));
20153
20261
  server.registerTool("taiga_refresh_reality_documents", {
20154
20262
  title: "Refresh a product's documents from its code",
20155
- description: "Derive the reality documents again from the code on the branch Taiga works on (the link's effectiveBranch: the chosen branch, or the repository's default): all of them, or the ones named (with what they are derived from). For a document the import misread, refresh it rather than importing again; unchanged sections are not republished. Runs an agent in the background and returns the job; taiga_wait_for_job says when it is done. Ask the person first.",
20263
+ description: "Derive the reality documents again from the code on the branch Taiga works on (the link's effectiveBranch: the chosen branch, or the repository's default): all of them, or the ones named (with what they are derived from). For a document the import misread, refresh it rather than importing again; unchanged sections are not republished. Runs an agent in the background and returns the job; taiga_wait_for_job says when it is done.",
20156
20264
  inputSchema: z.object({
20157
20265
  productId,
20158
20266
  docTypes: z.array(z.enum(REALITY_DERIVED_DOCUMENT_TYPES)).min(1).max(REALITY_DERIVED_DOCUMENT_TYPES.length).optional().describe("The documents to refresh; omit for all")
@@ -20174,14 +20282,14 @@ function registerSetupTools(server, client) {
20174
20282
  }));
20175
20283
  server.registerTool("taiga_reopen_discovery", {
20176
20284
  title: "Reopen Discovery",
20177
- description: "Reopen a finished product's Discovery so its documents can be amended; finish it again afterwards. Nothing built from them is undone. Ask the person first.",
20285
+ description: "Reopen a finished product's Discovery so its documents can be amended; finish it again afterwards. Nothing built from them is undone.",
20178
20286
  inputSchema: z.object({ productId }),
20179
20287
  annotations: OPERATE
20180
20288
  }, (params) => runTool(async () => toolResult(await productRow(client, (await client.reopenSetup(params.productId)).data))));
20181
20289
  if (!client.hasCapability("products:read")) return;
20182
20290
  server.registerTool("taiga_start_import", {
20183
20291
  title: "Import a codebase into a product",
20184
- description: "Start the import of a product created to import a codebase: Taiga reads its linked repository and derives the full document set from the code, each document citing the files it came from. Importing is one decision; the rest runs by itself. The scanners then sweep the repository, the documents are assessed against the organization's policies, and the first initiatives are planned when Discovery is finished. Refused for a product started from scratch (the choice is made at creation), without a linked repository, or while an import runs. Runs agents in the background and returns the job; taiga_wait_for_job says when it is done (it takes a while). Ask the person first.",
20292
+ description: "Start the import of a product created to import a codebase: Taiga reads its linked repository and derives the full document set from the code, each document citing the files it came from. Importing is one decision; the rest runs by itself. The scanners then sweep the repository, the documents are assessed against the organization's policies, and the first initiatives are planned when Discovery is finished. Refused for a product started from scratch (the choice is made at creation), without a linked repository, or while an import runs. Runs agents in the background and returns the job; taiga_wait_for_job says when it is done (it takes a while).",
20185
20293
  inputSchema: z.object({ productId }),
20186
20294
  annotations: OPERATE
20187
20295
  }, (params) => runTool(async () => {
@@ -20545,14 +20653,29 @@ function withStrictInputs(server) {
20545
20653
  if (schema !== void 0 && !(schema instanceof z.ZodObject)) throw new Error(`Tool ${name}: inputSchema must be a z.object() so unknown arguments can be refused`);
20546
20654
  const handler = callback;
20547
20655
  const withHint = (args, ctx) => notFoundHints.run(notFoundHint(name, args), () => handler(args, ctx));
20548
- return register(name, schema ? {
20656
+ const described = {
20549
20657
  ...config,
20658
+ description: `${writeNotice(name, config.annotations)}${config.description ?? ""}`
20659
+ };
20660
+ return register(name, schema ? {
20661
+ ...described,
20550
20662
  inputSchema: schema.strict()
20551
- } : config, withHint);
20663
+ } : described, withHint);
20552
20664
  });
20553
20665
  return server;
20554
20666
  }
20555
20667
  /**
20668
+ * What a tool's annotations say, in its description's first words (PRO-2778).
20669
+ * Annotations are for the client: Claude Code does not show them to the
20670
+ * model, so an agent read 79 tools without knowing which ones write. A tool
20671
+ * with none is refused here, so no tool reaches the catalogue unclassified.
20672
+ */
20673
+ function writeNotice(name, annotations) {
20674
+ if (!annotations || annotations.readOnlyHint === void 0) throw new Error(`Tool ${name}: annotations must say whether it is read-only`);
20675
+ if (annotations.readOnlyHint) return "";
20676
+ return annotations.destructiveHint ? "Acts on paid or shared work: ask the person first. " : "Writes. ";
20677
+ }
20678
+ /**
20556
20679
  * Register all MCP tools, resources and prompts based on the API key's effective capabilities.
20557
20680
  * Only tools matching the key's scopes are registered -- the AI assistant
20558
20681
  * never sees tools it can't use.
@@ -20586,15 +20709,16 @@ const SERVER_INSTRUCTIONS = [
20586
20709
  "**Start here.** Call taiga_get_product_context first, naming the product by its git remote URL inside a",
20587
20710
  "checkout (git remote get-url origin), else by slug or ID (taiga_list_products finds one by name with",
20588
20711
  "query). It returns the product (and, while it is in setup, nextStep: what it waits for), its factory",
20589
- "instructions, its specification and architecture, every policy as its section ids, the open initiatives,",
20590
- "the repositories and the latest runs. Then read only what you need: the policy sections with",
20591
- "taiga_get_document, an initiative with taiga_get_backlog_item. Check Action Required",
20592
- "(taiga_list_action_required) for what waits on a person.",
20712
+ "instructions, the outline of its specification and architecture, every policy as its section ids, the",
20713
+ "open initiatives, the repositories and the latest runs. Then read only what you need: sections by id with",
20714
+ "taiga_get_document or taiga_get_product_document (sections: [] is any document's outline), an",
20715
+ "initiative with taiga_get_backlog_item. Check Action Required (taiga_list_action_required) for what",
20716
+ "waits on a person.",
20593
20717
  "",
20594
20718
  "**Before you write.** Tell the person what you are about to do and wait for their yes before a tool that",
20595
20719
  "starts paid agent work (queueing, approving, resuming, requesting, importing, generating), interrupts it",
20596
- "(stopping, pausing) or changes what others read (publishing, resolving, settings, repositories). A client",
20597
- "that prompts on destructive tools does part of that for you.",
20720
+ "(stopping, pausing) or changes what others read (publishing, resolving, settings, repositories). Such a",
20721
+ "tool says so first in its description; a client that prompts on destructive tools does part of it.",
20598
20722
  "",
20599
20723
  "**Answers.** A refusal carries the API reason: say it as it is. NOT_FOUND names the tool that lists the",
20600
20724
  "right IDs; an ID is a UUID, never a name. An empty result says why when it has a reason.",